Encrypt restore token
This commit is contained in:
parent
abd9fd4773
commit
16dc47b570
11 changed files with 137 additions and 49 deletions
|
|
@ -29,6 +29,11 @@ modules:
|
||||||
tag: v0.37.0
|
tag: v0.37.0
|
||||||
commit: 99b3ae0643f9a2f9d820fcbba5f9e4c83b23bd48
|
commit: 99b3ae0643f9a2f9d820fcbba5f9e4c83b23bd48
|
||||||
dest: vendor/golang.org/x/net
|
dest: vendor/golang.org/x/net
|
||||||
|
- type: git
|
||||||
|
url: https://github.com/golang/crypto
|
||||||
|
tag: v0.36.0
|
||||||
|
commit: 49bf5b80c8108983f588ecabd7bf996e6e63a515
|
||||||
|
dest: vendor/golang.org/x/crypto
|
||||||
- type: file
|
- type: file
|
||||||
path: modules.txt
|
path: modules.txt
|
||||||
dest: vendor
|
dest: vendor
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ github.com/godbus/dbus/v5
|
||||||
github.com/skip2/go-qrcode
|
github.com/skip2/go-qrcode
|
||||||
github.com/skip2/go-qrcode/bitset
|
github.com/skip2/go-qrcode/bitset
|
||||||
github.com/skip2/go-qrcode/reedsolomon
|
github.com/skip2/go-qrcode/reedsolomon
|
||||||
|
# golang.org/x/crypto v0.36.0
|
||||||
|
## explicit; go 1.23.0
|
||||||
|
golang.org/x/crypto/hkdf
|
||||||
# golang.org/x/net v0.37.0
|
# golang.org/x/net v0.37.0
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.23.0
|
||||||
golang.org/x/net/websocket
|
golang.org/x/net/websocket
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -8,5 +8,6 @@ require (
|
||||||
github.com/bendahl/uinput v1.7.0
|
github.com/bendahl/uinput v1.7.0
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
|
golang.org/x/crypto v0.36.0
|
||||||
golang.org/x/net v0.37.0
|
golang.org/x/net v0.37.0
|
||||||
)
|
)
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -4,5 +4,7 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,14 @@ const (
|
||||||
|
|
||||||
type ControllerInfo struct {
|
type ControllerInfo struct {
|
||||||
Name string
|
Name string
|
||||||
Init func(bool) (Controller, error)
|
Init func() (Controller, error)
|
||||||
|
|
||||||
priority int
|
priority int
|
||||||
}
|
}
|
||||||
|
|
||||||
var Controllers []ControllerInfo
|
var Controllers []ControllerInfo
|
||||||
|
|
||||||
func RegisterController(name string, init func(bool) (Controller, error), priority int) {
|
func RegisterController(name string, init func() (Controller, error), priority int) {
|
||||||
Controllers = append(Controllers, ControllerInfo{name, init, priority})
|
Controllers = append(Controllers, ControllerInfo{name, init, priority})
|
||||||
sort.SliceStable(Controllers, func(i, j int) bool {
|
sort.SliceStable(Controllers, func(i, j int) bool {
|
||||||
return Controllers[i].priority < Controllers[j].priority
|
return Controllers[i].priority < Controllers[j].priority
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func init() {
|
||||||
RegisterController("null", InitNullController, 1000)
|
RegisterController("null", InitNullController, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitNullController(saveRestoreToken bool) (Controller, error) {
|
func InitNullController() (Controller, error) {
|
||||||
return &nullController{}, nil
|
return &nullController{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,20 @@
|
||||||
package inputcontrol
|
package inputcontrol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/godbus/dbus/v5"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -55,7 +63,7 @@ func init() {
|
||||||
RegisterController("RemoteDesktop portal", InitPortalController, 1)
|
RegisterController("RemoteDesktop portal", InitPortalController, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitPortalController(saveRestoreToken bool) (Controller, error) {
|
func InitPortalController() (Controller, error) {
|
||||||
bus, err := dbus.SessionBusPrivate()
|
bus, err := dbus.SessionBusPrivate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &UnsupportedPlatformError{err}
|
return nil, &UnsupportedPlatformError{err}
|
||||||
|
|
@ -81,25 +89,26 @@ func InitPortalController(saveRestoreToken bool) (Controller, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &UnsupportedPlatformError{err}
|
return nil, &UnsupportedPlatformError{err}
|
||||||
}
|
}
|
||||||
supportsRestoreTokens := version.Value().(uint32) >= 2
|
restoreTokenStore, err := func() (*secretStore, error) {
|
||||||
var restoreTokenFilePath string
|
if version.Value().(uint32) < 2 {
|
||||||
var cacheDirectory string
|
return nil, nil
|
||||||
var restoreToken string
|
|
||||||
if supportsRestoreTokens {
|
|
||||||
cacheDirectory, err = os.UserCacheDir()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Cannot get user cache directory: %s. Therefore cannot get restore token file path in order to read or save the restore token.\n", err)
|
|
||||||
} else {
|
|
||||||
restoreTokenFilePath = filepath.Join(cacheDirectory, "remote_touchpad_portals_restore_token")
|
|
||||||
restoreTokenBytes, err := os.ReadFile(restoreTokenFilePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to read restore token file: %s\n", err)
|
|
||||||
} else {
|
|
||||||
restoreToken = string(restoreTokenBytes)
|
|
||||||
}
|
}
|
||||||
|
cacheDirectory, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
if err := os.MkdirAll(cacheDirectory, 0700); err != nil {
|
||||||
log.Println("Portals implementation does not support restore tokens")
|
return nil, err
|
||||||
|
}
|
||||||
|
secret, err := retrieveSecret(bus)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newSecretStore(secret,
|
||||||
|
filepath.Join(cacheDirectory, "remote-touchpad.portal-restore-token.bin"))
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Skipping restore token: %v", err)
|
||||||
}
|
}
|
||||||
availableDeviceTypesV, err := remoteDesktop.GetProperty(
|
availableDeviceTypesV, err := remoteDesktop.GetProperty(
|
||||||
"org.freedesktop.portal.RemoteDesktop.AvailableDeviceTypes")
|
"org.freedesktop.portal.RemoteDesktop.AvailableDeviceTypes")
|
||||||
|
|
@ -148,14 +157,16 @@ func InitPortalController(saveRestoreToken bool) (Controller, error) {
|
||||||
sessionHandle := dbus.ObjectPath(sessionHandleS)
|
sessionHandle := dbus.ObjectPath(sessionHandleS)
|
||||||
inVardict = make(map[string]dbus.Variant)
|
inVardict = make(map[string]dbus.Variant)
|
||||||
inVardict["types"] = dbus.MakeVariant(deviceKeyboard | devicePointer)
|
inVardict["types"] = dbus.MakeVariant(deviceKeyboard | devicePointer)
|
||||||
if supportsRestoreTokens {
|
if restoreTokenStore != nil {
|
||||||
if restoreToken != "" {
|
if restoreToken, err := restoreTokenStore.Load(); err != nil {
|
||||||
inVardict["restore_token"] = dbus.MakeVariant(restoreToken)
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
log.Printf("Failed to load restore token: %v", err)
|
||||||
|
}
|
||||||
|
} else if len(restoreToken) > 0 {
|
||||||
|
inVardict["restore_token"] = dbus.MakeVariant(string(restoreToken))
|
||||||
}
|
}
|
||||||
if saveRestoreToken {
|
|
||||||
inVardict["persist_mode"] = dbus.MakeVariant(untilRevoked)
|
inVardict["persist_mode"] = dbus.MakeVariant(untilRevoked)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
result, outVardict, err = getResponse(bus, remoteDesktop,
|
result, outVardict, err = getResponse(bus, remoteDesktop,
|
||||||
"org.freedesktop.portal.RemoteDesktop.SelectDevices", 0, sessionHandle, inVardict)
|
"org.freedesktop.portal.RemoteDesktop.SelectDevices", 0, sessionHandle, inVardict)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -174,20 +185,9 @@ func InitPortalController(saveRestoreToken bool) (Controller, error) {
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
return nil, errors.New("keyboard or pointer access denied")
|
return nil, errors.New("keyboard or pointer access denied")
|
||||||
}
|
}
|
||||||
if supportsRestoreTokens && saveRestoreToken {
|
if restoreToken, _ := outVardict["restore_token"].Value().(string); restoreTokenStore != nil {
|
||||||
restoreToken, ok := outVardict["restore_token"].Value().(string)
|
if err := restoreTokenStore.Store([]byte(restoreToken)); err != nil {
|
||||||
if !ok {
|
log.Printf("Failed to store restore token: %v", err)
|
||||||
log.Println("Failed to get new restore token")
|
|
||||||
} else if restoreTokenFilePath != "" {
|
|
||||||
err := os.MkdirAll(cacheDirectory, 0700)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to create cache directory for restore token: %s\n", err)
|
|
||||||
} else {
|
|
||||||
err := os.WriteFile(restoreTokenFilePath, []byte(restoreToken), 0600)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to write restore token: %s\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
devicesV, ok := outVardict["devices"]
|
devicesV, ok := outVardict["devices"]
|
||||||
|
|
@ -208,6 +208,84 @@ func InitPortalController(saveRestoreToken bool) (Controller, error) {
|
||||||
sessionHandle: sessionHandle}, nil
|
sessionHandle: sessionHandle}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func retrieveSecret(bus *dbus.Conn) ([]byte, error) {
|
||||||
|
portalDesktop := bus.Object("org.freedesktop.portal.Desktop",
|
||||||
|
"/org/freedesktop/portal/desktop")
|
||||||
|
secretReader, secretWriter, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer secretReader.Close()
|
||||||
|
defer secretWriter.Close()
|
||||||
|
if result, _, err := getResponse(bus, portalDesktop,
|
||||||
|
"org.freedesktop.portal.Secret.RetrieveSecret", 0,
|
||||||
|
dbus.UnixFD(secretWriter.Fd()),
|
||||||
|
map[string]dbus.Variant{},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if result != 0 {
|
||||||
|
return nil, fmt.Errorf("calling 'RetrieveSecret' failed (%v)", result)
|
||||||
|
}
|
||||||
|
if err := secretWriter.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
secret, err := io.ReadAll(secretReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(secret) < 16 {
|
||||||
|
return nil, fmt.Errorf("'RetrieveSecret' returned too few bytes (%d)", len(secret))
|
||||||
|
}
|
||||||
|
return secret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type secretStore struct {
|
||||||
|
aesgcm cipher.AEAD
|
||||||
|
filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSecretStore(key []byte, filename string) (*secretStore, error) {
|
||||||
|
hkdf := hkdf.New(sha256.New, key, nil, nil)
|
||||||
|
derivedKey := make([]byte, 32)
|
||||||
|
if _, err := io.ReadFull(hkdf, derivedKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
block, err := aes.NewCipher(derivedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aesgcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &secretStore{
|
||||||
|
aesgcm: aesgcm,
|
||||||
|
filename: filename,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) Load() ([]byte, error) {
|
||||||
|
data, err := os.ReadFile(s.filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(data) < s.aesgcm.NonceSize() {
|
||||||
|
return nil, errors.New("invalid ciphertext")
|
||||||
|
}
|
||||||
|
nonce := data[:s.aesgcm.NonceSize()]
|
||||||
|
ciphertext := data[len(nonce):]
|
||||||
|
return s.aesgcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *secretStore) Store(data []byte) error {
|
||||||
|
nonce := make([]byte, s.aesgcm.NonceSize())
|
||||||
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ciphertext := s.aesgcm.Seal(nil, nonce, data, nil)
|
||||||
|
return os.WriteFile(s.filename, slices.Concat(nonce, ciphertext), 0600)
|
||||||
|
}
|
||||||
|
|
||||||
func getResponse(bus *dbus.Conn, object dbus.BusObject, method string,
|
func getResponse(bus *dbus.Conn, object dbus.BusObject, method string,
|
||||||
flags dbus.Flags, args ...interface{}) (uint32, map[string]dbus.Variant, error) {
|
flags dbus.Flags, args ...interface{}) (uint32, map[string]dbus.Variant, error) {
|
||||||
ch := make(chan *dbus.Signal, 512)
|
ch := make(chan *dbus.Signal, 512)
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ func init() {
|
||||||
RegisterController("uinput", InitUinputController, 10)
|
RegisterController("uinput", InitUinputController, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitUinputController(saveRestoreToken bool) (Controller, error) {
|
func InitUinputController() (Controller, error) {
|
||||||
keymapName, keymapSet := os.LookupEnv("REMOTE_TOUCHPAD_UINPUT_KEYMAP")
|
keymapName, keymapSet := os.LookupEnv("REMOTE_TOUCHPAD_UINPUT_KEYMAP")
|
||||||
if !keymapSet {
|
if !keymapSet {
|
||||||
keymapName = "defkeymap"
|
keymapName = "defkeymap"
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ func init() {
|
||||||
RegisterController("Windows", InitWindowsController, 0)
|
RegisterController("Windows", InitWindowsController, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitWindowsController(saveRestoreToken bool) (Controller, error) {
|
func InitWindowsController() (Controller, error) {
|
||||||
p := &windowsController{}
|
p := &windowsController{}
|
||||||
if err := sendInputProc.Find(); err != nil {
|
if err := sendInputProc.Find(); err != nil {
|
||||||
return nil, &UnsupportedPlatformError{err}
|
return nil, &UnsupportedPlatformError{err}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ func init() {
|
||||||
RegisterController("X11", InitX11Controller, 0)
|
RegisterController("X11", InitX11Controller, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitX11Controller(saveRestoreToken bool) (Controller, error) {
|
func InitX11Controller() (Controller, error) {
|
||||||
display := C.XOpenDisplay(nil)
|
display := C.XOpenDisplay(nil)
|
||||||
if display == nil {
|
if display == nil {
|
||||||
return nil, &UnsupportedPlatformError{
|
return nil, &UnsupportedPlatformError{
|
||||||
|
|
|
||||||
5
main.go
5
main.go
|
|
@ -152,7 +152,7 @@ func secureRandBase64(length int) string {
|
||||||
func main() {
|
func main() {
|
||||||
terminal.SetTitle(prettyAppName)
|
terminal.SetTitle(prettyAppName)
|
||||||
var bind, certFile, keyFile, secret string
|
var bind, certFile, keyFile, secret string
|
||||||
var showVersion, savePlaintextRestoreToken bool
|
var showVersion bool
|
||||||
var config config
|
var config config
|
||||||
flag.BoolVar(&showVersion, "version", false, "show program's version number and exit")
|
flag.BoolVar(&showVersion, "version", false, "show program's version number and exit")
|
||||||
flag.StringVar(&bind, "bind", defaultBind, "bind server to [HOSTNAME]:PORT")
|
flag.StringVar(&bind, "bind", defaultBind, "bind server to [HOSTNAME]:PORT")
|
||||||
|
|
@ -164,7 +164,6 @@ func main() {
|
||||||
flag.Float64Var(&config.ScrollSpeed, "scroll-speed", 1, "scroll speed multiplier")
|
flag.Float64Var(&config.ScrollSpeed, "scroll-speed", 1, "scroll speed multiplier")
|
||||||
flag.Float64Var(&config.MouseMoveSpeed, "mouse-move-speed", 1, "mouse move speed multiplier")
|
flag.Float64Var(&config.MouseMoveSpeed, "mouse-move-speed", 1, "mouse move speed multiplier")
|
||||||
flag.Float64Var(&config.MouseScrollSpeed, "mouse-scroll-speed", 1, "mouse scroll speed multiplier")
|
flag.Float64Var(&config.MouseScrollSpeed, "mouse-scroll-speed", 1, "mouse scroll speed multiplier")
|
||||||
flag.BoolVar(&savePlaintextRestoreToken, "save-plaintext-restore-token", false, "save a restore token in plaintext in order to avoid the confirmation dialogue that appears with some implementations of the portals backend on wayland on linux from appearing on the next run of remote-touchpad")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if showVersion {
|
if showVersion {
|
||||||
fmt.Println(version)
|
fmt.Println(version)
|
||||||
|
|
@ -189,7 +188,7 @@ func main() {
|
||||||
for _, controllerInfo := range inputcontrol.Controllers {
|
for _, controllerInfo := range inputcontrol.Controllers {
|
||||||
controllerName = controllerInfo.Name
|
controllerName = controllerInfo.Name
|
||||||
var err error
|
var err error
|
||||||
controller, err = controllerInfo.Init(savePlaintextRestoreToken)
|
controller, err = controllerInfo.Init()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue