From e8ef243e87470605521d70ce94cf193788323c54 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Tue, 10 Feb 2026 23:34:30 -0500 Subject: [PATCH] Make input zeroization optional --- daemon/2server.go | 8 ++++---- daemon/2serverUNIXGeneric.go | 7 +++++-- daemon/2serverWindows.go | 9 +++++---- daemon/3client.go | 12 +++++++----- example.go | 15 ++++++++------- go.mod | 2 +- go.sum | 6 ++---- wrappers/aes.go | 1 - wrappers/chacha.go | 1 - wrappers/highLevel.go | 23 ++++++++++++++--------- wrappers/sanityCheck.go | 13 ++++--------- 11 files changed, 50 insertions(+), 47 deletions(-) diff --git a/daemon/2server.go b/daemon/2server.go index 494563f..92acc68 100644 --- a/daemon/2server.go +++ b/daemon/2server.go @@ -20,17 +20,17 @@ type RCWService struct{} // the global password and returns the decrypted data func (h *RCWService) DecryptRequest(encBytes []byte, reply *[]byte) error { var err error - *reply, err = wrappers.DecryptAndZeroizePassword(encBytes, append([]byte{}, globalPassword...)) // pass new slice to avoid zeroizing cached password) + *reply, err = wrappers.Decrypt(encBytes, globalPassword, false) if err != nil { return err } return nil } -// EncryptRequestAndZeroizeDecBytes is the RPC method that encrypts the incoming data using +// EncryptRequest is the RPC method that encrypts the incoming data using // the global password and returns the encrypted data -func (h *RCWService) EncryptRequestAndZeroizeDecBytes(decBytes []byte, reply *[]byte) error { - *reply = wrappers.EncryptAndZeroizeDecBytesAndPassword(decBytes, append([]byte{}, globalPassword...)) // pass new slice to avoid zeroizing cached password +func (h *RCWService) EncryptRequest(decBytes []byte, reply *[]byte) error { + *reply = wrappers.Encrypt(decBytes, globalPassword, true, false) return nil } diff --git a/daemon/2serverUNIXGeneric.go b/daemon/2serverUNIXGeneric.go index e1852a6..44a5dd8 100644 --- a/daemon/2serverUNIXGeneric.go +++ b/daemon/2serverUNIXGeneric.go @@ -13,6 +13,7 @@ import ( "syscall" "time" + "github.com/rwinkhart/go-boilerplate/security" "github.com/rwinkhart/peercred-mini" ) @@ -44,6 +45,7 @@ func Start(password []byte) { go func() { <-sigChan listener.Close() + security.ZeroizeBytes(globalPassword) os.Exit(0) }() @@ -56,6 +58,7 @@ func Start(password []byte) { if err.(net.Error).Timeout() { log.Println(strconv.Itoa(Timeout) + " seconds have passed without any connections. Exiting...") listener.Close() + security.ZeroizeBytes(globalPassword) os.Exit(0) } log.Printf("Accept error: %v", err) @@ -84,7 +87,7 @@ func handleConn(conn net.Conn, sigChan chan os.Signal) { // invalid client; close the connection w/o a response, // log the client's path, and kill the daemon conn.Close() - log.Printf("Request received from invalid client: PID(%d), UID(%s), Path(%s)", ucred.PID, ucred.UID, callingBinPath) // TODO log to file - sigChan <- syscall.SIGTERM + log.Printf("Request received from invalid client: PID(%d), UID(%s), Path(%s)", ucred.PID, ucred.UID, callingBinPath) + sigChan <- syscall.SIGTERM // this zeroizes globalPassword and triggers os.Exit(0) } } diff --git a/daemon/2serverWindows.go b/daemon/2serverWindows.go index a4ebd2d..3600a7b 100644 --- a/daemon/2serverWindows.go +++ b/daemon/2serverWindows.go @@ -10,10 +10,10 @@ import ( "os" "os/signal" "strconv" - "syscall" "time" "github.com/Microsoft/go-winio" + "github.com/rwinkhart/go-boilerplate/security" "github.com/rwinkhart/peercred-mini" "golang.org/x/sys/windows" ) @@ -63,11 +63,13 @@ func Start(password []byte) { case <-timer.C: log.Println(strconv.Itoa(Timeout) + " seconds have passed without any connections. Exiting...") listener.Close() + security.ZeroizeBytes(globalPassword) os.Exit(0) case <-killTimer: return case <-sigChan: listener.Close() + security.ZeroizeBytes(globalPassword) os.Exit(0) } }() @@ -108,8 +110,7 @@ func handleConn(conn net.Conn, sigChan chan os.Signal) { // invalid client; close the connection w/o a response, // log the client's path, and kill the daemon conn.Close() - log.Printf("Request received from invalid client: PID(%d), UID(%s), Path(%s)", ucred.PID, ucred.UID, callingBinPath) // TODO log to file - sigChan <- syscall.SIGTERM - os.Exit(2) + log.Printf("Request received from invalid client: PID(%d), UID(%s), Path(%s)", ucred.PID, ucred.UID, callingBinPath) + sigChan <- os.Interrupt // this zeroizes globalPassword and triggers os.Exit(0) } } diff --git a/daemon/3client.go b/daemon/3client.go index 941a765..1d9a772 100644 --- a/daemon/3client.go +++ b/daemon/3client.go @@ -23,19 +23,21 @@ func GetDec(encBytes []byte) []byte { return decBytes } -// GetEncAndZeroizeDecBytes requests the RCW daemon to encrypt the given data. +// GetEnc requests the RCW daemon to encrypt the given data. // It returns the encrypted data. -func GetEncAndZeroizeDecBytes(decBytes []byte) []byte { +func GetEnc(decBytes []byte, zeroizeDecBytes bool) []byte { conn, client := connectToDaemon() defer conn.Close() defer client.Close() // request encBytes from the RPC server var encBytes []byte - err := client.Call("RCWService.EncryptRequestAndZeroizeDecBytes", decBytes, &encBytes) - security.ZeroizeBytes(decBytes) + err := client.Call("RCWService.EncryptRequest", decBytes, &encBytes) + if zeroizeDecBytes { + security.ZeroizeBytes(decBytes) + } if err != nil { - log.Fatalf("Error calling RCWService.EncryptRequestAndZeroizeDecBytes: %v", err) + log.Fatalf("Error calling RCWService.EncryptRequest: %v", err) } return encBytes } diff --git a/example.go b/example.go index 7eca320..eeb7d45 100644 --- a/example.go +++ b/example.go @@ -66,7 +66,7 @@ func main() { if daemon.IsOpen() { decBytes = daemon.GetDec(encBytes) } else { - decBytes, err = wrappers.DecryptAndZeroizePassword(encBytes, front.InputSecret("Enter RCW password:")) + decBytes, err = wrappers.Decrypt(encBytes, front.InputSecret("Enter RCW password:"), true) if err != nil { fmt.Println(err) return @@ -87,27 +87,28 @@ func main() { } daemon.Start([]byte(os.Args[1])) case 3: - if os.Args[1] == "init" { + switch os.Args[1] { + case "init": // create sanity check file // rcw init - if err := wrappers.GenSanityCheckAndZeroizePassword(sanityFile, []byte(os.Args[2])); err != nil { + if err := wrappers.GenSanityCheck(sanityFile, []byte(os.Args[2]), true); err != nil { fmt.Println(err) } return - } else if os.Args[1] == "enc" { + case "enc": // encrypt data (using daemon if available) // rcw enc decBytes := []byte(os.Args[2]) var encBytes []byte if daemon.IsOpen() { - encBytes = daemon.GetEncAndZeroizeDecBytes(decBytes) + encBytes = daemon.GetEnc(decBytes, true) } else { password := front.InputSecret("Enter RCW password: ") - if err := wrappers.RunSanityCheck(sanityFile, append([]byte{}, password...)); err != nil { // pass new slice to avoid zeroizing password) + if err := wrappers.RunSanityCheck(sanityFile, password); err != nil { fmt.Println(err) return } - encBytes = wrappers.EncryptAndZeroizeDecBytesAndPassword(decBytes, password) + encBytes = wrappers.Encrypt(decBytes, password, true, true) } os.WriteFile(outputFile, encBytes, 0600) return diff --git a/go.mod b/go.mod index f54e10f..aa56ce7 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.7 require ( github.com/Microsoft/go-winio v0.6.2 - github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211013046-29315c4b764f + github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211031648-26686374bd0c github.com/rwinkhart/peercred-mini v0.1.3 golang.org/x/crypto v0.48.0 golang.org/x/sys v0.41.0 diff --git a/go.sum b/go.sum index 0798b29..3bda1cf 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -github.com/rwinkhart/go-boilerplate v0.2.3-0.20260210031547-48e6abea8b2f h1:QloiAudLU8WAVs+loMU4ZiSd8ve0eiSkGtFhQUibhTw= -github.com/rwinkhart/go-boilerplate v0.2.3-0.20260210031547-48e6abea8b2f/go.mod h1:ES13A2r9fnCVfyezwMBgY/RgA4pOIudOUXz3Jk/ikes= -github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211013046-29315c4b764f h1:ITbAql2EG50U0Z9JnPqupvdZ5zp0i44LnqZUsdz6DNc= -github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211013046-29315c4b764f/go.mod h1:ES13A2r9fnCVfyezwMBgY/RgA4pOIudOUXz3Jk/ikes= +github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211031648-26686374bd0c h1:N8rXbv1duCEF4qyiSx0hyb03A6AxWZ9w7mHAgM0kjUs= +github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211031648-26686374bd0c/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.3 h1:Pbyk/f4oXVtX38uuZAyxZZ8ZzRImL6WWgCr0pSktCKU= diff --git a/wrappers/aes.go b/wrappers/aes.go index d6dbff9..cc0825a 100644 --- a/wrappers/aes.go +++ b/wrappers/aes.go @@ -24,7 +24,6 @@ func encryptAES(decBytes, key2, salt2 []byte) []byte { // generate a random nonce nonce := getRandomBytes(nonceSizeAES) - defer security.ZeroizeBytes(nonce) // encrypt the data ciphertext := aesGCM.Seal(nil, nonce, decBytes, nil) diff --git a/wrappers/chacha.go b/wrappers/chacha.go index 5f62d76..a037572 100644 --- a/wrappers/chacha.go +++ b/wrappers/chacha.go @@ -18,7 +18,6 @@ func encryptCha(decBytes, key2, salt2 []byte) []byte { // generate a random nonce nonce := getRandomBytes(nonceSizeCha) - defer security.ZeroizeBytes(nonce) // encrypt the data ciphertext := stream.Seal(nil, nonce, decBytes, nil) diff --git a/wrappers/highLevel.go b/wrappers/highLevel.go index 4b43ba1..db9cb13 100644 --- a/wrappers/highLevel.go +++ b/wrappers/highLevel.go @@ -6,16 +6,18 @@ import ( "github.com/rwinkhart/go-boilerplate/security" ) -// DecryptAndZeroizePassword decrypts the provided byte slice using the provided password. -func DecryptAndZeroizePassword(encBytes, password []byte) ([]byte, error) { +// Decrypt decrypts the provided byte slice using the provided password. +func Decrypt(encBytes, password []byte, zeroizePassword bool) ([]byte, error) { if len(encBytes) < saltSize1 { return nil, errors.New("high-level decrypt: encrypted data is too short (invalid Argon2 salt)") } salt1 := encBytes[:saltSize1] encBytes = encBytes[saltSize1:] key1 := derivePrimaryKey(password, salt1) - security.ZeroizeBytes(password) - security.ZeroizeBytes(salt1) + defer security.ZeroizeBytes(key1) + if zeroizePassword { + security.ZeroizeBytes(password) + } var err error encBytes, err = decryptCha(encBytes, key1) if err != nil { @@ -25,19 +27,22 @@ func DecryptAndZeroizePassword(encBytes, password []byte) ([]byte, error) { if err != nil { return nil, err } - security.ZeroizeBytes(key1) return encBytes, err } -// EncryptAndZeroizeDecBytesAndPassword encrypts the provided byte slice using the provided password. -func EncryptAndZeroizeDecBytesAndPassword(decBytes, password []byte) []byte { - defer security.ZeroizeBytes(decBytes) +// Encrypt encrypts the provided byte slice using the provided password. +func Encrypt(decBytes, password []byte, zeroizeDecBytes, zeroizePassword bool) []byte { + if zeroizeDecBytes { + defer security.ZeroizeBytes(decBytes) + } salt1 := getRandomBytes(saltSize1) defer security.ZeroizeBytes(salt1) salt2AES := getRandomBytes(saltSize2) salt2Cha := getRandomBytes(saltSize2) key1 := derivePrimaryKey(password, salt1) - security.ZeroizeBytes(password) + if zeroizePassword { + security.ZeroizeBytes(password) + } key2AES := deriveSecondaryKey(key1, salt2AES, []byte(hkdfInfoAES)) key2Cha := deriveSecondaryKey(key1, salt2Cha, []byte(hkdfInfoCha)) security.ZeroizeBytes(key1) diff --git a/wrappers/sanityCheck.go b/wrappers/sanityCheck.go index ca56678..2b9c3ec 100644 --- a/wrappers/sanityCheck.go +++ b/wrappers/sanityCheck.go @@ -4,16 +4,13 @@ import ( "bytes" "errors" "os" - - "github.com/rwinkhart/go-boilerplate/security" ) -// GenSanityCheckAndZeroizePassword creates an encrypted file containing known plaintext +// GenSanityCheck creates an encrypted file containing known plaintext // to later be used for ensuring the user does not encrypt data with // an incorrect password. -func GenSanityCheckAndZeroizePassword(path string, password []byte) error { - err := os.WriteFile(path, EncryptAndZeroizeDecBytesAndPassword([]byte("thx4usin'rcw"), password), 0600) - security.ZeroizeBytes(password) +func GenSanityCheck(path string, password []byte, zeroizePassword bool) error { + err := os.WriteFile(path, Encrypt([]byte("thx4usin'rcw"), password, false, zeroizePassword), 0600) return err } @@ -25,9 +22,7 @@ func RunSanityCheck(path string, password []byte) error { if err != nil { return errors.New("Failed to read sanity check file (" + path + ")") } - - // avoid zeroizing password, as this function expects the user to use the password after running it - decBytes, err := DecryptAndZeroizePassword(encBytes, append([]byte{}, password...)) + decBytes, err := Decrypt(encBytes, password, false) if err == nil { if bytes.Equal(decBytes, []byte("thx4usin'rcw")) { return nil