From f9aeca5ba9a53ed850863a2a37357c12aed5913b Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Fri, 13 Feb 2026 18:23:38 -0500 Subject: [PATCH] Bump dependencies (rcw hardening) --- clip/arguments.go | 4 ++-- clip/clipClearProcess.go | 9 ++++---- clip/copy_UNIX.go | 6 ++--- clip/launchClipClearProcessCLI_GENERIC.go | 4 ++-- clip/totp.go | 2 +- config/cfg.go | 3 +-- core/init.go | 2 +- core/util.go | 6 ++--- crypt/rcw.go | 10 ++++---- go.mod | 16 ++++++------- go.sum | 28 +++++++++++------------ syncclient/oneOff.go | 4 ++-- 12 files changed, 46 insertions(+), 48 deletions(-) diff --git a/clip/arguments.go b/clip/arguments.go index bcb3456..0e14a96 100644 --- a/clip/arguments.go +++ b/clip/arguments.go @@ -43,7 +43,7 @@ func CopyShortcut(realPath string, field int, rcwPassword []byte) error { select {} // block indefinitely } else { // other // copy field to clipboard; launch clipboard clearing process - if err = CopyString(true, decSlice[field]); err != nil { + if err = CopyBytes(true, []byte(decSlice[field])); err != nil { return err } return nil @@ -56,7 +56,7 @@ func CopyShortcut(realPath string, field int, rcwPassword []byte) error { // ClearArgument reads the assigned clipboard contents from stdin and passes them to clipClearProcess. func ClearArgument() error { assignedContents := back.ReadFromStdin() - if assignedContents == "" { + if assignedContents == nil { os.Exit(0) // use os.Exit directly since this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) } return ClearProcess(assignedContents) diff --git a/clip/clipClearProcess.go b/clip/clipClearProcess.go index 3f5c6f4..eb38394 100644 --- a/clip/clipClearProcess.go +++ b/clip/clipClearProcess.go @@ -3,8 +3,8 @@ package clip import ( + "bytes" "errors" - "strings" "time" "github.com/rwinkhart/go-boilerplate/back" @@ -12,7 +12,7 @@ import ( // ClearProcess clears the clipboard after 30 seconds if the clipboard contents have not changed. // assignedContents can be omitted to clear the clipboard immediately and unconditionally. -func ClearProcess(assignedContents string) error { +func ClearProcess(assignedContents []byte) error { cmdPaste, cmdClear, err := getClipCommands() if err != nil { return errors.New("unable to determine clipboard platform: " + err.Error()) @@ -27,7 +27,7 @@ func ClearProcess(assignedContents string) error { } // if assignedContents is empty, clear the clipboard immediately and unconditionally - if assignedContents == "" { + if assignedContents == nil { if err := clearClipboard(); err != nil { return err } @@ -41,8 +41,7 @@ func ClearProcess(assignedContents string) error { if err != nil { return errors.New("unable to read clipboard contents") } - - if assignedContents == strings.TrimRight(string(newContents), "\r\n") { + if bytes.Equal(assignedContents, newContents) { if err = clearClipboard(); err != nil { return err } diff --git a/clip/copy_UNIX.go b/clip/copy_UNIX.go index 0aedcf0..236bc84 100644 --- a/clip/copy_UNIX.go +++ b/clip/copy_UNIX.go @@ -10,8 +10,8 @@ import ( "github.com/rwinkhart/go-boilerplate/back" ) -// CopyString copies a string to the clipboard. -func CopyString(clearClipboardAutomatically bool, copySubject string) error { +// CopyBytes copies a byte slice to the clipboard. +func CopyBytes(clearClipboardAutomatically bool, copySubject []byte) error { // determine whether to use wl-copy (Wayland) or xclip (X11) sessionIsWayland, err := isWayland() if err != nil { @@ -24,7 +24,7 @@ func CopyString(clearClipboardAutomatically bool, copySubject string) error { cmdCopy = exec.Command("xclip", "-sel", "c", "-t", "text/plain") } - _ = back.WriteToStdin(cmdCopy, copySubject) + _ = back.WriteToStdinAndZeroizeInput(cmdCopy, copySubject) if err = cmdCopy.Run(); err != nil { return errors.New("unable to copy to clipboard: " + err.Error()) } diff --git a/clip/launchClipClearProcessCLI_GENERIC.go b/clip/launchClipClearProcessCLI_GENERIC.go index d6ece12..12170f9 100644 --- a/clip/launchClipClearProcessCLI_GENERIC.go +++ b/clip/launchClipClearProcessCLI_GENERIC.go @@ -12,10 +12,10 @@ import ( // LaunchClearProcess launches the timed clipboard clearing process. // For non-interactive CLI implementations, an entirely separate process is created for this purpose. -func LaunchClearProcess(copySubject string) { +func LaunchClearProcess(copySubject []byte) { cmd := exec.Command(os.Args[0], "clipclear") cmd.SysProcAttr = global.GetSysProcAttr() - _ = back.WriteToStdin(cmd, copySubject) + _ = back.WriteToStdinAndZeroizeInput(cmd, copySubject) _ = cmd.Start() os.Exit(0) // use os.Exit directly since this version of this function is only meant for non-interactive CLI implementations } diff --git a/clip/totp.go b/clip/totp.go index 528e836..788c2ee 100644 --- a/clip/totp.go +++ b/clip/totp.go @@ -26,7 +26,7 @@ func TOTPCopier(secret string, errorChan chan<- error, done <-chan bool) { if firstRun && err != nil { errorChan <- err } - err = CopyString(false, token) + err = CopyBytes(false, []byte(token)) if firstRun { if err != nil { errorChan <- err diff --git a/config/cfg.go b/config/cfg.go index 8286d50..d97abb6 100644 --- a/config/cfg.go +++ b/config/cfg.go @@ -46,8 +46,7 @@ start: // check if any fields are nil var hasNilFields bool cfgValue := reflect.ValueOf(&cfg.Libmutton).Elem() - for i := 0; i < cfgValue.NumField(); i++ { - field := cfgValue.Field(i) + for _, field := range cfgValue.Fields() { if field.IsNil() { hasNilFields = true break diff --git a/core/init.go b/core/init.go index 8fc3ed4..1e73361 100644 --- a/core/init.go +++ b/core/init.go @@ -98,7 +98,7 @@ func LibmuttonInit(inputCB func(prompt string) string, rcwPassword []byte, appen // RCWSanityCheckGen generates the RCW sanity check file for libmutton. func RCWSanityCheckGen(password []byte) error { - if err := wrappers.GenSanityCheck(global.CfgDir+global.PathSeparator+"sanity.rcw", password); err != nil { + if err := wrappers.GenSanityCheck(global.CfgDir+global.PathSeparator+"sanity.rcw", password, true); err != nil { return errors.New("unable to generate sanity check file: " + err.Error()) } return nil diff --git a/core/util.go b/core/util.go index e3832ae..db5e9cc 100644 --- a/core/util.go +++ b/core/util.go @@ -78,7 +78,7 @@ func EntryRefresh(oldRCWPassword, newRCWPassword []byte, removeOldDir bool) erro if err != nil { return errors.New("unable to open \"" + realPath + "\" for decryption: " + err.Error()) } - decBytes, err := wrappers.Decrypt(encBytes, oldRCWPassword) + decBytes, err := wrappers.Decrypt(encBytes, oldRCWPassword, false) if err != nil { return err } @@ -87,7 +87,7 @@ func EntryRefresh(oldRCWPassword, newRCWPassword []byte, removeOldDir bool) erro decSlice := clampTrailingWhitespace(strings.Split(string(decBytes), "\n")) // re-encrypt the entry with the new password - encBytes = wrappers.Encrypt([]byte(strings.Join(decSlice, "\n")), newRCWPassword) + encBytes = wrappers.Encrypt([]byte(strings.Join(decSlice, "\n")), newRCWPassword, true, false) // write the entry to the new directory if err = os.WriteFile(global.EntryRoot+"-new"+strings.ReplaceAll(vanityPath, "/", global.PathSeparator), encBytes, 0600); err != nil { @@ -124,7 +124,7 @@ func VerifyEntries(rcwPassword []byte) error { if err != nil { return errors.New("unable to open \"" + realPath + "\" for decryption: " + err.Error()) } - decBytes, err := wrappers.Decrypt(encBytes, rcwPassword) + decBytes, err := wrappers.Decrypt(encBytes, rcwPassword, false) if err != nil { return errors.New("unable to verify \"" + vanityPath + "\" (decryption failure): " + err.Error()) } diff --git a/crypt/rcw.go b/crypt/rcw.go index 148e564..a99ab6f 100644 --- a/crypt/rcw.go +++ b/crypt/rcw.go @@ -18,7 +18,7 @@ var RetryPassword = true // RCWDArgument reads the password from stdin and caches it via an RCW daemon. func RCWDArgument() { password := back.ReadFromStdin() - if password == "" { + if password == nil { os.Exit(0) // use os.Exit directly since this function is only intended for non-interactive CLI clients } daemon.Start([]byte(password)) @@ -46,7 +46,7 @@ func DecryptFileToSlice(realPath string, rcwPassword []byte) ([]string, error) { // 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) + decBytes, err := wrappers.Decrypt(encBytes, rcwPassword, true) if err != nil { return nil, errors.New("unable to decrypt \"" + realPath + "\": " + err.Error()) } @@ -63,13 +63,13 @@ func EncryptBytes(decBytes, rcwPassword []byte) []byte { // if rcwPassword is still nil, the daemon is already running; // use it to encrypt the data if rcwPassword == nil { - return daemon.GetEnc(decBytes) + return daemon.GetEnc(decBytes, true) } } // 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) + return wrappers.Encrypt(decBytes, rcwPassword, true, true) } // launchRCWDProcess launches an RCW daemon to cache a password. @@ -95,7 +95,7 @@ func launchRCWDProcess() []byte { cmd := exec.Command(os.Args[0], "startrcwd") cmd.SysProcAttr = global.GetSysProcAttr() - _ = back.WriteToStdin(cmd, string(password)) + _ = back.WriteToStdinAndZeroizeInput(cmd, append([]byte{}, password...)) _ = cmd.Start() return password diff --git a/go.mod b/go.mod index 684ac30..39b404a 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,23 @@ module github.com/rwinkhart/libmutton -go 1.26rc3 +go 1.26.0 require ( github.com/pkg/sftp v1.13.10 github.com/pquerna/otp v1.5.0 - 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 + github.com/rwinkhart/go-boilerplate v0.3.0 + github.com/rwinkhart/rcw v0.3.0 + golang.org/x/crypto v0.48.0 + golang.org/x/sys v0.41.0 ) require ( github.com/Microsoft/go-winio v0.6.2 // indirect 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 + github.com/rwinkhart/peercred-mini v0.1.4 // indirect ) -replace golang.org/x/sys => github.com/rwinkhart/sys v0.40.0 +replace golang.org/x/sys => github.com/rwinkhart/sys v0.41.0 -replace github.com/Microsoft/go-winio => github.com/rwinkhart/go-winio v0.1.0 +replace github.com/Microsoft/go-winio => github.com/rwinkhart/go-winio v0.1.1 diff --git a/go.sum b/go.sum index 55ebdfa..60795c0 100644 --- a/go.sum +++ b/go.sum @@ -12,23 +12,23 @@ 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.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= -github.com/rwinkhart/peercred-mini v0.1.2/go.mod h1:LLHG7YshHEpbpJJP+Il9nx2dnGj5O3VGE32rWmflj0c= -github.com/rwinkhart/rcw v0.2.5 h1:3GJeii9sDZsZNL+HRkfHrnbgxnaB8a9kejiMIfPXnpE= -github.com/rwinkhart/rcw v0.2.5/go.mod h1:qVw8Yp/SKtsuVZYOzzluVEVkoRJ1G7UEh2aR1CXso/U= -github.com/rwinkhart/sys v0.40.0 h1:ZPBbXb+27vLL518sZhhNV89jXK1KbCnFGmYJ2juOQ/c= -github.com/rwinkhart/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +github.com/rwinkhart/go-boilerplate v0.3.0 h1:dwlm1mZya1xrATkvm2pHbTIFNb5WRjIc7A3a4Ep2aAM= +github.com/rwinkhart/go-boilerplate v0.3.0/go.mod h1:ES13A2r9fnCVfyezwMBgY/RgA4pOIudOUXz3Jk/ikes= +github.com/rwinkhart/go-winio v0.1.1 h1:kAJKiqneR7cUR01Wn5/doAAV4kOGTEGPug4oinXc5N4= +github.com/rwinkhart/go-winio v0.1.1/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= +github.com/rwinkhart/peercred-mini v0.1.4 h1:93+phjLknvJadEd2cu/ZPPWdfRSPOwFJzDBEn4ZtWVc= +github.com/rwinkhart/peercred-mini v0.1.4/go.mod h1:E8eApo/izzmq4nGGR+kpJ0ZLAXjofnVRMGCeVMPQ/Ik= +github.com/rwinkhart/rcw v0.3.0 h1:OdgA++IGcDrZqN6FzyYnzbz04fL8Q1MHvlgPjhtgJM4= +github.com/rwinkhart/rcw v0.3.0/go.mod h1:UUY7kSku6kV2X1RWPUcK0Re+rQ6RvngWI8DVZJ11cqk= +github.com/rwinkhart/sys v0.41.0 h1:pHB6HphVC132UXYZ6yeOk62abqgl+pyl5ZZnsk4iUKg= +github.com/rwinkhart/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/syncclient/oneOff.go b/syncclient/oneOff.go index 21f00d9..ce1caf6 100644 --- a/syncclient/oneOff.go +++ b/syncclient/oneOff.go @@ -10,7 +10,7 @@ import ( "time" "github.com/rwinkhart/go-boilerplate/back" - "github.com/rwinkhart/go-boilerplate/stringy" + "github.com/rwinkhart/go-boilerplate/security" "github.com/rwinkhart/libmutton/global" "github.com/rwinkhart/libmutton/synccommon" ) @@ -169,7 +169,7 @@ func GenDeviceID(oldDeviceID *string, prefix string) (string, string, bool, erro if prefix == "" { prefix, _ = os.Hostname() } - newDeviceID := prefix + "-" + stringy.StringGen(rand.Intn(32)+48, 0.2, 1) + "-" + strconv.FormatInt(time.Now().Unix(), 10) + newDeviceID := prefix + "-" + string(security.BytesGen(rand.Intn(32)+48, 0.2, 1)) + "-" + strconv.FormatInt(time.Now().Unix(), 10) // create new device ID file (locally) newDeviceIDPath := global.CfgDir + global.PathSeparator + "devices" + global.PathSeparator + newDeviceID