From 48e084ac0f595df5b37587f663fb8176f99e275f Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Wed, 21 Jan 2026 18:35:26 -0500 Subject: [PATCH] Make adjustments for easier CGO integration --- age/age.go | 5 ++-- clip/arguments.go | 5 ++-- core/edit.go | 5 ++-- core/util.go | 5 ++-- crypt/rcw.go | 61 ++++++++++++++++++++++++++-------------------- go.mod | 4 +-- wiki/developers.md | 1 - 7 files changed, 49 insertions(+), 37 deletions(-) diff --git a/age/age.go b/age/age.go index c565cc2..78969ce 100644 --- a/age/age.go +++ b/age/age.go @@ -31,7 +31,8 @@ func Entry(vanityPath string, timestamp int64) error { // AllPasswordEntries adds age data for all un-aged entries containing passwords. // Each entry is aged with a random timestamp from within the last year to prevent // all entries having their passwords expire at the same time. -func AllPasswordEntries(forceReage bool) error { +// Leave rcwPassword nil to use RCW demonization. +func AllPasswordEntries(forceReage bool, rcwPassword []byte) error { allVanityPaths, _, err := synccommon.WalkEntryDir() if err != nil { return errors.New("unable to walk entry directory: " + err.Error()) @@ -48,7 +49,7 @@ func AllPasswordEntries(forceReage bool) error { } } - decSlice, err := crypt.DecryptFileToSlice(global.EntryRoot + vanityPath) + decSlice, err := crypt.DecryptFileToSlice(global.EntryRoot+vanityPath, rcwPassword) if err != nil { return err } diff --git a/clip/arguments.go b/clip/arguments.go index 49dae3c..db70050 100644 --- a/clip/arguments.go +++ b/clip/arguments.go @@ -13,7 +13,8 @@ import ( // CopyShortcut (given a path) decrypts an // entry and copies a field to the clipboard. -func CopyShortcut(realPath string, field int) error { +// Leave rcwPassword nil to use RCW demonization. +func CopyShortcut(realPath string, field int, rcwPassword []byte) error { // ensure realPath exists and is a file _, err := back.TargetIsFile(realPath, true) if err != nil { @@ -21,7 +22,7 @@ func CopyShortcut(realPath string, field int) error { } // decrypt entry - decSlice, err := crypt.DecryptFileToSlice(realPath) + decSlice, err := crypt.DecryptFileToSlice(realPath, rcwPassword) if err != nil { return errors.New("unable to decrypt entry: " + err.Error()) } diff --git a/core/edit.go b/core/edit.go index 4826b3d..8867c32 100644 --- a/core/edit.go +++ b/core/edit.go @@ -10,7 +10,8 @@ import ( // GetOldEntryData decrypts and returns old entry data (with all required lines present). // This is a wrapper around the DecryptFileToSlice function that ensures all required lines are present in the returned slice. // This makes it ideal for editing entries, as it guarantees at least a baseline slice length. -func GetOldEntryData(realPath string, field int) ([]string, error) { +// Leave rcwPassword nil to use RCW demonization. +func GetOldEntryData(realPath string, field int, rcwPassword []byte) ([]string, error) { // ensure realPath exists and is a file _, err := back.TargetIsFile(realPath, true) if err != nil { @@ -18,7 +19,7 @@ func GetOldEntryData(realPath string, field int) ([]string, error) { } // read old entry data - decryptedEntry, err := crypt.DecryptFileToSlice(realPath) + decryptedEntry, err := crypt.DecryptFileToSlice(realPath, rcwPassword) if err != nil { return nil, errors.New("unable to decrypt entry: " + err.Error()) } diff --git a/core/util.go b/core/util.go index ce0aa06..6ae9ea1 100644 --- a/core/util.go +++ b/core/util.go @@ -19,8 +19,9 @@ import ( // WriteEntry writes entryData to an encrypted file at realPath. // If the entry contains an updated password, an age file is also created. -func WriteEntry(realPath string, decSlice []string, passwordIsNew bool) error { - err := os.WriteFile(realPath, crypt.EncryptBytes([]byte(strings.Join(decSlice, "\n"))), 0600) +// Leave rcwPassword nil to use RCW demonization. +func WriteEntry(realPath string, decSlice []string, passwordIsNew bool, rcwPassword []byte) error { + err := os.WriteFile(realPath, crypt.EncryptBytes([]byte(strings.Join(decSlice, "\n")), rcwPassword), 0600) if err != nil { return errors.New("unable to write to file: " + err.Error()) } diff --git a/crypt/rcw.go b/crypt/rcw.go index d4d3ad7..67d8043 100644 --- a/crypt/rcw.go +++ b/crypt/rcw.go @@ -13,7 +13,6 @@ import ( "github.com/rwinkhart/rcw/wrappers" ) -var Daemonize = true var RetryPassword = true // RCWDArgument reads the password from stdin and caches it via an RCW daemon. @@ -25,23 +24,29 @@ func RCWDArgument() { daemon.Start([]byte(password)) } -// DecryptFileToSlice decrypts an RCW wrapped file and returns the contents as a slice of (trimmed) strings. -func DecryptFileToSlice(realPath string) ([]string, error) { +// DecryptFileToSlice decrypts an RCW wrapped file +// and returns the contents as a slice of (trimmed) strings. +// Leave rcwPassword nil to use RCW demonization. +func DecryptFileToSlice(realPath string, rcwPassword []byte) ([]string, error) { // read encrypted file encBytes, err := os.ReadFile(realPath) if err != nil { return nil, errors.New("unable to open \"" + realPath + "\" for decryption: " + err.Error()) } - // decrypt data using RCW daemon - password := launchRCWDProcess() - if password == nil { - // if daemon is already running, use it to decrypt the data - return strings.Split(string(daemon.GetDec(encBytes)), "\n"), nil + // if no password was provided, assume daemon mode + if rcwPassword == nil { + rcwPassword = launchRCWDProcess() + // if rcwPassword is still nil, the daemon is already running; + // use it to encrypt the data + if rcwPassword == nil { + return strings.Split(string(daemon.GetDec(encBytes)), "\n"), nil + } } - // if the daemon is not already running, use wrappers.Decrypt - // directly to avoid waiting for socket file creation - decBytes, err := wrappers.Decrypt(encBytes, password) + + // if the daemon is not being used/was not already running, + // use wrappers.Decrypt directly to avoid waiting for socket file creation + decBytes, err := wrappers.Decrypt(encBytes, rcwPassword) if err != nil { return nil, errors.New("unable to decrypt \"" + realPath + "\": " + err.Error()) } @@ -49,22 +54,28 @@ func DecryptFileToSlice(realPath string) ([]string, error) { } // EncryptBytes encrypts a byte slice using RCW and returns the encrypted data. -func EncryptBytes(decBytes []byte) []byte { - password := launchRCWDProcess() - if password == nil { - // if daemon is already running, use it to encrypt the data - return daemon.GetEnc(decBytes) +// Leave rcwPassword nil to use RCW demonization. +func EncryptBytes(decBytes, rcwPassword []byte) []byte { + // if no rcwPassword was provided, assume daemon mode + if rcwPassword == nil { + rcwPassword = launchRCWDProcess() + // if rcwPassword is still nil, the daemon is already running; + // use it to encrypt the data + if rcwPassword == nil { + return daemon.GetEnc(decBytes) + } } - // if the daemon is not already running, use wrappers.Encrypt - // directly to avoid waiting for socket file creation - return wrappers.Encrypt(decBytes, password) + + // if the daemon is not being used/was not already running, + // use wrappers.Encrypt directly to avoid waiting for socket file creation + return wrappers.Encrypt(decBytes, rcwPassword) } // launchRCWDProcess launches an RCW daemon to cache a password. // If the daemon is not already running OR if not running in daemonize mode, // it collects and returns the password (otherwise returns nil). func launchRCWDProcess() []byte { - if Daemonize && daemon.IsOpen() { + if daemon.IsOpen() { return nil } var password []byte @@ -81,12 +92,10 @@ func launchRCWDProcess() []byte { password = global.GetPassword("RCW Password:") } - if Daemonize { - cmd := exec.Command(os.Args[0], "startrcwd") - cmd.SysProcAttr = global.GetSysProcAttr() - _ = back.WriteToStdin(cmd, string(password)) - _ = cmd.Start() - } + cmd := exec.Command(os.Args[0], "startrcwd") + cmd.SysProcAttr = global.GetSysProcAttr() + _ = back.WriteToStdin(cmd, string(password)) + _ = cmd.Start() return password } diff --git a/go.mod b/go.mod index 43d60fa..e6697e5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/rwinkhart/libmutton -go 1.25.5 +go 1.25.6 require ( github.com/pkg/sftp v1.13.10 @@ -8,6 +8,7 @@ require ( github.com/rwinkhart/go-boilerplate v0.1.1 github.com/rwinkhart/rcw v0.2.4 golang.org/x/crypto v0.46.0 + golang.org/x/sys v0.40.0 ) require ( @@ -15,7 +16,6 @@ require ( github.com/boombuler/barcode v1.1.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/rwinkhart/peercred-mini v0.1.2 // indirect - golang.org/x/sys v0.40.0 // indirect ) replace golang.org/x/sys => github.com/rwinkhart/sys v0.40.0 diff --git a/wiki/developers.md b/wiki/developers.md index cd94fb9..71162e3 100644 --- a/wiki/developers.md +++ b/wiki/developers.md @@ -13,7 +13,6 @@ These are as follows: ## Required Global Variable Manipulation - `global.GetPassword` must be set to allow for different types of clients (CLI, GUI, TUI) to prompt for the password in the most appropriate way. -- `crypt.Daemonize`, true by default, determines whether to make use of the RCW daemon for password caching. This may be best to disable for interactive clients. - `crypt.RetryPassword`, true by default, determines whether the crypt package should verify user-typed passwords and re-prompt if needed. Turn this off to handle this uniquely in the client. ## Required Arguments