From 420333e412c3c8ab959a0422c02701f70f174aac Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Sun, 8 Feb 2026 00:35:01 -0500 Subject: [PATCH] Allow custom priv key bytes on all platforms --- config/{write.go => cfg.go} | 13 +++++++++ config/load_GENERIC.go | 24 ----------------- config/load_IOS.go | 27 ------------------- go.mod | 4 +-- go.sum | 4 +-- privkey/all.go | 54 +++++++++++++++++++++++++++++++++++++ syncclient/client.go | 9 ++++--- 7 files changed, 76 insertions(+), 59 deletions(-) rename config/{write.go => cfg.go} (81%) delete mode 100644 config/load_GENERIC.go delete mode 100644 config/load_IOS.go create mode 100644 privkey/all.go diff --git a/config/write.go b/config/cfg.go similarity index 81% rename from config/write.go rename to config/cfg.go index c42801a..8286d50 100644 --- a/config/write.go +++ b/config/cfg.go @@ -24,6 +24,19 @@ type CfgT struct { ClientSpecific *map[string]any `json:"clientSpecific"` } +// Load loads libmuttoncfg.json and returns the configuration. +func Load() (*CfgT, error) { + cfgBytes, err := os.ReadFile(global.CfgPath) + if err != nil { + return nil, errors.New("unable to load libmuttoncfg.json: " + err.Error()) + } + var cfg CfgT + if err = json.Unmarshal(cfgBytes, &cfg); err != nil { + return nil, errors.New("unable to unmarshal libmuttoncfg.json: " + err.Error()) + } + return &cfg, nil +} + // Write writes cfg to libmuttoncfg.json. // If used in append mode, any nil values in the // input cfg will be substituted with the existing values. diff --git a/config/load_GENERIC.go b/config/load_GENERIC.go deleted file mode 100644 index 07d0911..0000000 --- a/config/load_GENERIC.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build !ios - -package config - -import ( - "encoding/json" - "errors" - "os" - - "github.com/rwinkhart/libmutton/global" -) - -// Load loads libmuttoncfg.json and returns the configuration. -func Load() (*CfgT, error) { - cfgBytes, err := os.ReadFile(global.CfgPath) - if err != nil { - return nil, errors.New("unable to load libmuttoncfg.json: " + err.Error()) - } - var cfg CfgT - if err = json.Unmarshal(cfgBytes, &cfg); err != nil { - return nil, errors.New("unable to unmarshal libmuttoncfg.json: " + err.Error()) - } - return &cfg, nil -} diff --git a/config/load_IOS.go b/config/load_IOS.go deleted file mode 100644 index 6c205ac..0000000 --- a/config/load_IOS.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build ios - -package config - -import ( - "encoding/json" - "errors" - "os" - - "github.com/rwinkhart/libmutton/global" -) - -// Load loads libmuttoncfg.json and returns the configuration. -func Load() (*CfgT, error) { - cfgBytes, err := os.ReadFile(global.CfgPath) - if err != nil { - return nil, errors.New("unable to load libmuttoncfg.json: " + err.Error()) - } - var cfg CfgT - if err = json.Unmarshal(cfgBytes, &cfg); err != nil { - return nil, errors.New("unable to unmarshal libmuttoncfg.json: " + err.Error()) - } - - // iOS stores a relative path to account for the ever-changing app UUID - cfg.Libmutton.SSHKeyPath = new(global.SSHDir + *cfg.Libmutton.SSHKeyPath) - return &cfg, nil -} diff --git a/go.mod b/go.mod index e1799e6..1aa50d8 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/rwinkhart/libmutton -go 1.26rc3 +go 1.25.7 require ( github.com/pkg/sftp v1.13.10 github.com/pquerna/otp v1.5.0 - github.com/rwinkhart/go-boilerplate v0.2.2 + github.com/rwinkhart/go-boilerplate v0.2.3-0.20260208035402-e1bb0012a781 github.com/rwinkhart/rcw v0.2.5 golang.org/x/crypto v0.47.0 golang.org/x/sys v0.40.0 diff --git a/go.sum b/go.sum index f570ac0..55ebdfa 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/rwinkhart/go-boilerplate v0.2.2 h1:SVHTAQU+HWFivtUnDBcfrgClJV5ZmHyFS7/uERh7NKU= -github.com/rwinkhart/go-boilerplate v0.2.2/go.mod h1:/NVRKGslU20E5xU5YOgXzWxA6aa94BMtv5MtHRTb5Ek= +github.com/rwinkhart/go-boilerplate v0.2.3-0.20260208035402-e1bb0012a781 h1:kMAM0fkWGPa8WK7A4a9rMX2oD2Lv198zJoD9LCUAzOA= +github.com/rwinkhart/go-boilerplate v0.2.3-0.20260208035402-e1bb0012a781/go.mod h1:QcUlr4SMgONRMKD965m8oTuCa6ZJVAc17w27Qopyr+s= github.com/rwinkhart/go-winio v0.1.0 h1:b72agLW+dETGmhR3VbcbwnStfgKfc5AfgJOXBJDkaHg= github.com/rwinkhart/go-winio v0.1.0/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= github.com/rwinkhart/peercred-mini v0.1.2 h1:4cGWDbv0whvLeVvbUdx84V/9p+2fS+DEXgrA1KxlRFo= diff --git a/privkey/all.go b/privkey/all.go new file mode 100644 index 0000000..2bc07b0 --- /dev/null +++ b/privkey/all.go @@ -0,0 +1,54 @@ +package privkey + +import ( + "errors" + "os" + "sync" +) + +var keyBytesProvider = func(sshKeyPath *string) ([]byte, error) { + if sshKeyPath != nil { + key, err := os.ReadFile(*sshKeyPath) + if err != nil { + return nil, errors.New("unable to read private key: " + *sshKeyPath) + } + return key, nil + } else { + return nil, errors.New("unable to identify private key location (nil config)") + } +} + +var providerMutex sync.RWMutex + +// GetBytes returns the SSH private key bytes. +// On iOS, it calls the provider function that was set by the iOS app +// using SetKeyBytesProvider or SetKeyBytes. +func GetBytes(sshKeyPath *string) ([]byte, error) { + providerMutex.RLock() + provider := keyBytesProvider + providerMutex.RUnlock() + + if provider == nil { + return nil, errors.New("key bytes provider not set - call SetKeyBytes first") + } + + keyBytes, err := provider(sshKeyPath) + if err != nil { + return nil, errors.New("unable to get key bytes from provider: " + err.Error()) + } + + if len(keyBytes) == 0 { + return nil, errors.New("provider returned empty key bytes") + } + + return keyBytes, nil +} + +// SetBytes allows the iOS app to directly set the SSH key bytes. +func SetBytes(keyBytes []byte) { + providerMutex.Lock() + defer providerMutex.Unlock() + keyBytesProvider = func(*string) ([]byte, error) { + return keyBytes, nil + } +} diff --git a/syncclient/client.go b/syncclient/client.go index a8298a4..c2d5482 100644 --- a/syncclient/client.go +++ b/syncclient/client.go @@ -13,6 +13,7 @@ import ( "github.com/rwinkhart/libmutton/age" "github.com/rwinkhart/libmutton/config" "github.com/rwinkhart/libmutton/global" + "github.com/rwinkhart/libmutton/privkey" "github.com/rwinkhart/libmutton/synccommon" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" @@ -41,10 +42,10 @@ func GetSSHClient() (*ssh.Client, bool, *bool, *string, *string, error) { return nil, true, nil, nil, nil, nil } - // read private key - key, err := os.ReadFile(*cfg.Libmutton.SSHKeyPath) + // get private key + key, err := privkey.GetBytes(cfg.Libmutton.SSHKeyPath) if err != nil { - return nil, false, nil, nil, nil, errors.New("unable to read private key: " + *cfg.Libmutton.SSHKeyPath) + return nil, false, nil, nil, nil, err } // parse private key @@ -55,7 +56,7 @@ func GetSSHClient() (*ssh.Client, bool, *bool, *string, *string, error) { parsedKey, err = ssh.ParsePrivateKeyWithPassphrase(key, global.GetPassword("Enter password for your SSH keyfile:")) } if err != nil { - return nil, false, nil, nil, nil, errors.New("unable to parse private key: " + *cfg.Libmutton.SSHKeyPath) + return nil, false, nil, nil, nil, errors.New("unable to parse private key: " + err.Error()) } // read known hosts file