Encrypt restore token

This commit is contained in:
Unrud 2025-03-18 09:18:15 +01:00
parent abd9fd4773
commit 16dc47b570
11 changed files with 137 additions and 49 deletions

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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

View file

@ -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
} }

View file

@ -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)
}
} }
} else { cacheDirectory, err := os.UserCacheDir()
log.Println("Portals implementation does not support restore tokens") if err != nil {
return nil, err
}
if err := os.MkdirAll(cacheDirectory, 0700); err != nil {
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,13 +157,15 @@ 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)
if saveRestoreToken { }
inVardict["persist_mode"] = dbus.MakeVariant(untilRevoked) } else if len(restoreToken) > 0 {
inVardict["restore_token"] = dbus.MakeVariant(string(restoreToken))
} }
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)
@ -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)

View file

@ -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"

View file

@ -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}

View file

@ -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{

View file

@ -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 {