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
|
||||
commit: 99b3ae0643f9a2f9d820fcbba5f9e4c83b23bd48
|
||||
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
|
||||
path: modules.txt
|
||||
dest: vendor
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ github.com/godbus/dbus/v5
|
|||
github.com/skip2/go-qrcode
|
||||
github.com/skip2/go-qrcode/bitset
|
||||
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
|
||||
## explicit; go 1.23.0
|
||||
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/godbus/dbus/v5 v5.1.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
golang.org/x/crypto v0.36.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/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=
|
||||
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/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
|
|
|
|||
|
|
@ -55,14 +55,14 @@ const (
|
|||
|
||||
type ControllerInfo struct {
|
||||
Name string
|
||||
Init func(bool) (Controller, error)
|
||||
Init func() (Controller, error)
|
||||
|
||||
priority int
|
||||
}
|
||||
|
||||
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})
|
||||
sort.SliceStable(Controllers, func(i, j int) bool {
|
||||
return Controllers[i].priority < Controllers[j].priority
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func init() {
|
|||
RegisterController("null", InitNullController, 1000)
|
||||
}
|
||||
|
||||
func InitNullController(saveRestoreToken bool) (Controller, error) {
|
||||
func InitNullController() (Controller, error) {
|
||||
return &nullController{}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,20 @@
|
|||
package inputcontrol
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/godbus/dbus/v5"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -55,7 +63,7 @@ func init() {
|
|||
RegisterController("RemoteDesktop portal", InitPortalController, 1)
|
||||
}
|
||||
|
||||
func InitPortalController(saveRestoreToken bool) (Controller, error) {
|
||||
func InitPortalController() (Controller, error) {
|
||||
bus, err := dbus.SessionBusPrivate()
|
||||
if err != nil {
|
||||
return nil, &UnsupportedPlatformError{err}
|
||||
|
|
@ -81,25 +89,26 @@ func InitPortalController(saveRestoreToken bool) (Controller, error) {
|
|||
if err != nil {
|
||||
return nil, &UnsupportedPlatformError{err}
|
||||
}
|
||||
supportsRestoreTokens := version.Value().(uint32) >= 2
|
||||
var restoreTokenFilePath string
|
||||
var cacheDirectory string
|
||||
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)
|
||||
}
|
||||
restoreTokenStore, err := func() (*secretStore, error) {
|
||||
if version.Value().(uint32) < 2 {
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
log.Println("Portals implementation does not support restore tokens")
|
||||
cacheDirectory, err := os.UserCacheDir()
|
||||
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(
|
||||
"org.freedesktop.portal.RemoteDesktop.AvailableDeviceTypes")
|
||||
|
|
@ -148,13 +157,15 @@ func InitPortalController(saveRestoreToken bool) (Controller, error) {
|
|||
sessionHandle := dbus.ObjectPath(sessionHandleS)
|
||||
inVardict = make(map[string]dbus.Variant)
|
||||
inVardict["types"] = dbus.MakeVariant(deviceKeyboard | devicePointer)
|
||||
if supportsRestoreTokens {
|
||||
if restoreToken != "" {
|
||||
inVardict["restore_token"] = dbus.MakeVariant(restoreToken)
|
||||
}
|
||||
if saveRestoreToken {
|
||||
inVardict["persist_mode"] = dbus.MakeVariant(untilRevoked)
|
||||
if restoreTokenStore != nil {
|
||||
if restoreToken, err := restoreTokenStore.Load(); err != nil {
|
||||
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))
|
||||
}
|
||||
inVardict["persist_mode"] = dbus.MakeVariant(untilRevoked)
|
||||
}
|
||||
result, outVardict, err = getResponse(bus, remoteDesktop,
|
||||
"org.freedesktop.portal.RemoteDesktop.SelectDevices", 0, sessionHandle, inVardict)
|
||||
|
|
@ -174,20 +185,9 @@ func InitPortalController(saveRestoreToken bool) (Controller, error) {
|
|||
if result != 0 {
|
||||
return nil, errors.New("keyboard or pointer access denied")
|
||||
}
|
||||
if supportsRestoreTokens && saveRestoreToken {
|
||||
restoreToken, ok := outVardict["restore_token"].Value().(string)
|
||||
if !ok {
|
||||
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)
|
||||
}
|
||||
}
|
||||
if restoreToken, _ := outVardict["restore_token"].Value().(string); restoreTokenStore != nil {
|
||||
if err := restoreTokenStore.Store([]byte(restoreToken)); err != nil {
|
||||
log.Printf("Failed to store restore token: %v", err)
|
||||
}
|
||||
}
|
||||
devicesV, ok := outVardict["devices"]
|
||||
|
|
@ -208,6 +208,84 @@ func InitPortalController(saveRestoreToken bool) (Controller, error) {
|
|||
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,
|
||||
flags dbus.Flags, args ...interface{}) (uint32, map[string]dbus.Variant, error) {
|
||||
ch := make(chan *dbus.Signal, 512)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ func init() {
|
|||
RegisterController("uinput", InitUinputController, 10)
|
||||
}
|
||||
|
||||
func InitUinputController(saveRestoreToken bool) (Controller, error) {
|
||||
func InitUinputController() (Controller, error) {
|
||||
keymapName, keymapSet := os.LookupEnv("REMOTE_TOUCHPAD_UINPUT_KEYMAP")
|
||||
if !keymapSet {
|
||||
keymapName = "defkeymap"
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ func init() {
|
|||
RegisterController("Windows", InitWindowsController, 0)
|
||||
}
|
||||
|
||||
func InitWindowsController(saveRestoreToken bool) (Controller, error) {
|
||||
func InitWindowsController() (Controller, error) {
|
||||
p := &windowsController{}
|
||||
if err := sendInputProc.Find(); err != nil {
|
||||
return nil, &UnsupportedPlatformError{err}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func init() {
|
|||
RegisterController("X11", InitX11Controller, 0)
|
||||
}
|
||||
|
||||
func InitX11Controller(saveRestoreToken bool) (Controller, error) {
|
||||
func InitX11Controller() (Controller, error) {
|
||||
display := C.XOpenDisplay(nil)
|
||||
if display == nil {
|
||||
return nil, &UnsupportedPlatformError{
|
||||
|
|
|
|||
5
main.go
5
main.go
|
|
@ -152,7 +152,7 @@ func secureRandBase64(length int) string {
|
|||
func main() {
|
||||
terminal.SetTitle(prettyAppName)
|
||||
var bind, certFile, keyFile, secret string
|
||||
var showVersion, savePlaintextRestoreToken bool
|
||||
var showVersion bool
|
||||
var config config
|
||||
flag.BoolVar(&showVersion, "version", false, "show program's version number and exit")
|
||||
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.MouseMoveSpeed, "mouse-move-speed", 1, "mouse move 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()
|
||||
if showVersion {
|
||||
fmt.Println(version)
|
||||
|
|
@ -189,7 +188,7 @@ func main() {
|
|||
for _, controllerInfo := range inputcontrol.Controllers {
|
||||
controllerName = controllerInfo.Name
|
||||
var err error
|
||||
controller, err = controllerInfo.Init(savePlaintextRestoreToken)
|
||||
controller, err = controllerInfo.Init()
|
||||
if err == nil {
|
||||
break
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue