From e7293505bc7dedd28e032d3336be41423951c2ee Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Thu, 8 May 2025 19:27:09 -0400 Subject: [PATCH] Port to latest rcw development version --- core/1globals.go | 2 ++ core/copy.go | 4 +-- core/edit.go | 4 +-- core/rcw.go | 82 +++++++++++++++++++++++++++---------------- core/utilitiesMisc.go | 4 +-- go.mod | 8 ++--- go.sum | 12 +++---- wiki/developers.md | 5 +-- 8 files changed, 72 insertions(+), 49 deletions(-) diff --git a/core/1globals.go b/core/1globals.go index 2c30be5..807cc04 100644 --- a/core/1globals.go +++ b/core/1globals.go @@ -33,3 +33,5 @@ const ( ErrorClipboard = 110 ErrorOther = 111 ) + +var GetPassphrase func() []byte diff --git a/core/copy.go b/core/copy.go index a57cf50..421c116 100644 --- a/core/copy.go +++ b/core/copy.go @@ -11,10 +11,10 @@ import ( ) // CopyArgument copies a field from an entry to the clipboard. -func CopyArgument(targetLocation string, field int, passphrase []byte) { +func CopyArgument(targetLocation string, field int) { if isFile, _ := TargetIsFile(targetLocation, true, 2); isFile { - decryptedEntry := DecryptFileToSlice(targetLocation, passphrase) + decryptedEntry := DecryptFileToSlice(targetLocation) var copySubject string // will store data to be copied // ensure field exists in entry diff --git a/core/edit.go b/core/edit.go index 3cdac2e..9f3c863 100644 --- a/core/edit.go +++ b/core/edit.go @@ -1,12 +1,12 @@ package core // GetOldEntryData decrypts and returns old entry data (with all required lines present). -func GetOldEntryData(targetLocation string, field int, passphrase []byte) []string { +func GetOldEntryData(targetLocation string, field int) []string { // ensure targetLocation exists TargetIsFile(targetLocation, true, 2) // read old entry data - unencryptedEntry := DecryptFileToSlice(targetLocation, passphrase) + unencryptedEntry := DecryptFileToSlice(targetLocation) // return the old entry data with all required lines present if field > 0 { diff --git a/core/rcw.go b/core/rcw.go index 88bd4a4..bf083c2 100644 --- a/core/rcw.go +++ b/core/rcw.go @@ -1,48 +1,68 @@ package core import ( + "fmt" "os" "os/exec" "strings" + "time" "github.com/rwinkhart/rcw/daemon" "github.com/rwinkhart/rcw/wrappers" ) -// DecryptFileToSlice decrypts an RCW wrapped file and returns the contents as a slice of (trimmed) strings. -func DecryptFileToSlice(targetLocation string, passphrase []byte) []string { - encBytes, err := os.ReadFile(targetLocation) - if err != nil { - PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), ErrorDecryption, true) - } - decBytes, err := wrappers.Decrypt(encBytes, passphrase) - if err != nil { - PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), ErrorDecryption, true) - } - return strings.Split(string(decBytes), "\n") -} - -// EncryptBytes encrypts a byte slice using RCW and returns the encrypted data. -func EncryptBytes(decBytes []byte, passphrase []byte) []byte { - encBytes := wrappers.Encrypt(decBytes, passphrase) - return encBytes -} - -// LaunchRCWDProcess launches an RCW daemon to serve the given passphrase. -func LaunchRCWDProcess(passphrase string) { - cmd := exec.Command(os.Args[0], "startrcwd") - writeToStdin(cmd, passphrase) - err := cmd.Start() - if err != nil { - PrintError("Failed to launch RCW daemon - Does this libmutton implementation support the \"startrcwd\" argument?", ErrorOther, true) - } -} - -// RCWDArgument reads the passphrase from stdin and serves it via an RCW daemon. +// RCWDArgument reads the passphrase from stdin and caches it via an RCW daemon. func RCWDArgument() { passphrase := readFromStdin() if passphrase == "" { os.Exit(0) } - daemon.Start(string(passphrase)) + daemon.Start([]byte(passphrase)) +} + +// DecryptFileToSlice decrypts an RCW wrapped file and returns the contents as a slice of (trimmed) strings. +func DecryptFileToSlice(targetLocation string) []string { + // read encrypted file + encBytes, err := os.ReadFile(targetLocation) + if err != nil { + PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), ErrorDecryption, true) + } + + // decrypt data using RCW daemon + launchRCWDProcess() + return strings.Split(string(daemon.GetDec(encBytes)), "\n") +} + +// EncryptBytes encrypts a byte slice using RCW and returns the encrypted data. +func EncryptBytes(decBytes []byte) []byte { + launchRCWDProcess() + return daemon.GetEnc(decBytes) +} + +// launchRCWDProcess launches an RCW daemon to cache a passphrase. +// It returns immediately if the daemon appears to be already running. +func launchRCWDProcess() { + if daemon.IsOpen() { + return + } + var passphrase []byte + for { + passphrase = GetPassphrase() + err := wrappers.RunSanityCheck(ConfigDir+PathSeparator+"sanity.rcw", passphrase) + if err == nil { + break + } + fmt.Println(AnsiError + "Incorrect passphrase" + AnsiReset) + } + cmd := exec.Command(os.Args[0], "startrcwd") + writeToStdin(cmd, string(passphrase)) + cmd.Start() + + // block until socket file is created + for { + if daemon.IsOpen() { + break + } + time.Sleep(100 * time.Millisecond) + } } diff --git a/core/utilitiesMisc.go b/core/utilitiesMisc.go index f0e1c03..46c2fcb 100644 --- a/core/utilitiesMisc.go +++ b/core/utilitiesMisc.go @@ -37,8 +37,8 @@ func TargetIsFile(targetLocation string, errorOnFail bool, failCondition uint8) } // WriteEntry writes entryData to an encrypted file at targetLocation. -func WriteEntry(targetLocation string, entryData []byte, passphrase []byte) { - encryptedBytes := EncryptBytes(entryData, passphrase) +func WriteEntry(targetLocation string, entryData []byte) { + encryptedBytes := EncryptBytes(entryData) err := os.WriteFile(targetLocation, encryptedBytes, 0600) if err != nil { PrintError("Failed to write to file: "+err.Error(), ErrorWrite, true) diff --git a/go.mod b/go.mod index f26db6a..3ea33c9 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module github.com/rwinkhart/libmutton -go 1.24.2 +go 1.24.3 require ( github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727 github.com/pkg/sftp v1.13.9 github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481 - github.com/rwinkhart/rcw v0.0.0-20250505000323-5975c71b4f68 + github.com/rwinkhart/rcw v0.0.0-20250508202611-f447c8a28b43 golang.design/x/clipboard v0.7.0 // only for Android builds - golang.org/x/crypto v0.37.0 + golang.org/x/crypto v0.38.0 gopkg.in/ini.v1 v1.67.0 ) @@ -20,7 +20,7 @@ require ( golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect; only for Android builds golang.org/x/image v0.26.0 // indirect; only for Android builds golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 // indirect; only for Android builds - golang.org/x/sys v0.32.0 // indirect + golang.org/x/sys v0.33.0 // indirect ) replace golang.org/x/sys => github.com/rwinkhart/sys-freebsd-13-xucred v0.32.0 diff --git a/go.sum b/go.sum index fd3887c..cc51465 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,8 @@ github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e84 github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e8410/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/rwinkhart/peercred-mini v0.0.0-20250407033241-c09add2eceea h1:VE2ti/AE4Y3kgnyK+J5c5hpddE+dKHpsFch3K2R6puQ= github.com/rwinkhart/peercred-mini v0.0.0-20250407033241-c09add2eceea/go.mod h1:t+YkvAdnTKTrg4d469tw3K+GCUzX/Bja4h8yKjSIsGs= -github.com/rwinkhart/rcw v0.0.0-20250505000323-5975c71b4f68 h1:2+mAXmmlt28JyyCdKih3UN4QWE8aZu2hzos8jH8J8cU= -github.com/rwinkhart/rcw v0.0.0-20250505000323-5975c71b4f68/go.mod h1:giXrq9o5a7bwMSuMvz+5bb2+qbM+zV4pXaG/SceXfLM= +github.com/rwinkhart/rcw v0.0.0-20250508202611-f447c8a28b43 h1:TFHU8EkCP0AxAExfA/k5TLfFt7Nja2AmPA1DdVzgRX8= +github.com/rwinkhart/rcw v0.0.0-20250508202611-f447c8a28b43/go.mod h1:6/TBegxRkK+FbGEmcxqDs4entuCX45cqpYOYKUv2MI0= github.com/rwinkhart/sys-freebsd-13-xucred v0.32.0 h1:KRbqimv9Eexf3VB2FrRAQ4v2fGGu7gt3ayMgdT0FNao= github.com/rwinkhart/sys-freebsd-13-xucred v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -38,8 +38,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80= golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8= golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= @@ -75,8 +75,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= diff --git a/wiki/developers.md b/wiki/developers.md index ab4bdbf..bbe6c5c 100644 --- a/wiki/developers.md +++ b/wiki/developers.md @@ -18,8 +18,9 @@ These are as follows: ## Required Global Variable Manipulation libmutton provides a `PassphraseInputFunction` global variable that all clients must set to support passphrase-protected SSH identity files. This approach allows for different types of clients (CLI, GUI, TUI) to prompt for the passphrase in the most appropriate way. -## Required Argument (clipclear) -The `clipclear` argument should be accepted by all non-interactive CLI libmutton implementations (not required for interactive GUI/TUI implementations). In order to clear the clipboard on a timer, non-interactive libmutton-based password managers call another instance of their executable with the `clipclear` argument (e.g. `mutn clipclear`) with the intended clipboard contents provided via STDIN. If after 30 seconds the clipboard contents have not changed, they are cleared. Please accept a `clipclear` argument that calls `core.ClipClearArgument()`. +## Required Arguments +- `clipclear`: Should be accepted by all non-interactive CLI libmutton implementations (not required for interactive GUI/TUI implementations). In order to clear the clipboard on a timer, non-interactive libmutton-based password managers call another instance of their executable with the `clipclear` argument (e.g. `mutn clipclear`) with the intended clipboard contents provided via STDIN. If after 30 seconds the clipboard contents have not changed, they are cleared. Please accept a `clipclear` argument that calls `core.ClipClearArgument()`. +- `startrcwd`: Should be accepted by all libmutton implementations making use of the RCW daemon to cache passphrases. Please accept a `startrcwd` argument that calls `core.RCWDArgument()`. ## Configuration libmutton-based password manager clients should all share the same INI configuration file.