diff --git a/TODO.md b/TODO.md index 44d9b46..39b5bcc 100644 --- a/TODO.md +++ b/TODO.md @@ -14,7 +14,7 @@ prioritized from most critical (top) to least critical (bottom). ### Functions returning unprotected secrets - [x] **5. GetValue returns unprotected secret**: `internal/secret/secret.go:93` - `GetValue(unlocker Unlocker) ([]byte, error)` - ✓ FIXED - now returns LockedBuffer internally -- [ ] **6. DecryptWithIdentity returns unprotected data**: `internal/secret/crypto.go:57` - `DecryptWithIdentity(data []byte, identity age.Identity) ([]byte, error)` - returns decrypted data unprotected +- [x] **6. DecryptWithIdentity returns unprotected data**: `internal/secret/crypto.go:57` - `DecryptWithIdentity(data []byte, identity age.Identity) ([]byte, error)` - ✓ FIXED - now returns LockedBuffer - [ ] **7. DecryptWithPassphrase returns unprotected data**: `internal/secret/crypto.go:94` - `DecryptWithPassphrase(encryptedData []byte, passphrase *memguard.LockedBuffer) ([]byte, error)` - returns decrypted data unprotected - [ ] **8. gpgDecryptDefault returns unprotected data**: `internal/secret/pgpunlocker.go:368` - `gpgDecryptDefault(encryptedData []byte) ([]byte, error)` - returns decrypted data unprotected - [ ] **9. getSecretValue returns unprotected data**: `internal/cli/crypto.go:269` - `getSecretValue()` returns bare []byte diff --git a/internal/secret/crypto.go b/internal/secret/crypto.go index 710ed86..124edf6 100644 --- a/internal/secret/crypto.go +++ b/internal/secret/crypto.go @@ -54,7 +54,7 @@ func EncryptToRecipient(data *memguard.LockedBuffer, recipient age.Recipient) ([ } // DecryptWithIdentity decrypts data with an identity using age -func DecryptWithIdentity(data []byte, identity age.Identity) ([]byte, error) { +func DecryptWithIdentity(data []byte, identity age.Identity) (*memguard.LockedBuffer, error) { r, err := age.Decrypt(bytes.NewReader(data), identity) if err != nil { return nil, fmt.Errorf("failed to create decryptor: %w", err) @@ -65,7 +65,10 @@ func DecryptWithIdentity(data []byte, identity age.Identity) ([]byte, error) { return nil, fmt.Errorf("failed to read decrypted data: %w", err) } - return result, nil + // Create a secure buffer for the decrypted data + resultBuffer := memguard.NewBufferFromBytes(result) + + return resultBuffer, nil } // EncryptWithPassphrase encrypts data using a passphrase with age's scrypt-based encryption @@ -90,7 +93,7 @@ func EncryptWithPassphrase(data *memguard.LockedBuffer, passphrase *memguard.Loc // DecryptWithPassphrase decrypts data using a passphrase with age's scrypt-based decryption // The passphrase parameter should be a LockedBuffer for secure memory handling -func DecryptWithPassphrase(encryptedData []byte, passphrase *memguard.LockedBuffer) ([]byte, error) { +func DecryptWithPassphrase(encryptedData []byte, passphrase *memguard.LockedBuffer) (*memguard.LockedBuffer, error) { if passphrase == nil { return nil, fmt.Errorf("passphrase buffer is nil") } diff --git a/internal/secret/keychainunlocker.go b/internal/secret/keychainunlocker.go index 4babe5b..a214238 100644 --- a/internal/secret/keychainunlocker.go +++ b/internal/secret/keychainunlocker.go @@ -107,30 +107,22 @@ func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) { passphraseBuffer := memguard.NewBufferFromBytes([]byte(keychainData.AgePrivKeyPassphrase)) defer passphraseBuffer.Destroy() - agePrivKeyData, err := DecryptWithPassphrase(encryptedAgePrivKeyData, passphraseBuffer) + agePrivKeyBuffer, err := DecryptWithPassphrase(encryptedAgePrivKeyData, passphraseBuffer) if err != nil { Debug("Failed to decrypt age private key with keychain passphrase", "error", err, "unlocker_id", k.GetID()) return nil, fmt.Errorf("failed to decrypt age private key with keychain passphrase: %w", err) } + defer agePrivKeyBuffer.Destroy() DebugWith("Successfully decrypted age private key with keychain passphrase", slog.String("unlocker_id", k.GetID()), - slog.Int("decrypted_length", len(agePrivKeyData)), + slog.Int("decrypted_length", agePrivKeyBuffer.Size()), ) // Step 6: Parse the decrypted age private key Debug("Parsing decrypted age private key", "unlocker_id", k.GetID()) - // Create a secure buffer for the private key data - agePrivKeyBuffer := memguard.NewBufferFromBytes(agePrivKeyData) - defer agePrivKeyBuffer.Destroy() - - // Clear the original private key data - for i := range agePrivKeyData { - agePrivKeyData[i] = 0 - } - ageIdentity, err := age.ParseX25519Identity(agePrivKeyBuffer.String()) if err != nil { Debug("Failed to parse age private key", "error", err, "unlocker_id", k.GetID()) @@ -301,13 +293,13 @@ func getLongTermPrivateKey(fs afero.Fs, vault VaultInterface) (*memguard.LockedB } // Decrypt long-term private key using current unlocker - ltPrivKeyData, err := DecryptWithIdentity(encryptedLtPrivKey, currentUnlockerIdentity) + ltPrivKeyBuffer, err := DecryptWithIdentity(encryptedLtPrivKey, currentUnlockerIdentity) if err != nil { return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err) } - // Return the decrypted key in a secure buffer - return memguard.NewBufferFromBytes(ltPrivKeyData), nil + // Return the decrypted key buffer + return ltPrivKeyBuffer, nil } // CreateKeychainUnlocker creates a new keychain unlocker and stores it in the vault diff --git a/internal/secret/passphraseunlocker.go b/internal/secret/passphraseunlocker.go index de1f882..ef7f22f 100644 --- a/internal/secret/passphraseunlocker.go +++ b/internal/secret/passphraseunlocker.go @@ -84,30 +84,22 @@ func (p *PassphraseUnlocker) GetIdentity() (*age.X25519Identity, error) { Debug("Decrypting unlocker private key with passphrase", "unlocker_id", p.GetID()) // Decrypt the unlocker private key with passphrase - privKeyData, err := DecryptWithPassphrase(encryptedPrivKeyData, passphraseBuffer) + privKeyBuffer, err := DecryptWithPassphrase(encryptedPrivKeyData, passphraseBuffer) if err != nil { Debug("Failed to decrypt unlocker private key", "error", err, "unlocker_id", p.GetID()) return nil, fmt.Errorf("failed to decrypt unlocker private key: %w", err) } + defer privKeyBuffer.Destroy() DebugWith("Successfully decrypted unlocker private key", slog.String("unlocker_id", p.GetID()), - slog.Int("decrypted_length", len(privKeyData)), + slog.Int("decrypted_length", privKeyBuffer.Size()), ) // Parse the decrypted private key Debug("Parsing decrypted unlocker identity", "unlocker_id", p.GetID()) - // Create a secure buffer for the private key data - privKeyBuffer := memguard.NewBufferFromBytes(privKeyData) - defer privKeyBuffer.Destroy() - - // Clear the original private key data - for i := range privKeyData { - privKeyData[i] = 0 - } - identity, err := age.ParseX25519Identity(privKeyBuffer.String()) if err != nil { Debug("Failed to parse unlocker private key", "error", err, "unlocker_id", p.GetID()) diff --git a/internal/secret/secret.go b/internal/secret/secret.go index 287ffc8..a37ba7f 100644 --- a/internal/secret/secret.go +++ b/internal/secret/secret.go @@ -179,16 +179,17 @@ func (s *Secret) GetValue(unlocker Unlocker) (*memguard.LockedBuffer, error) { // Decrypt the encrypted long-term private key using the unlocker Debug("Decrypting long-term private key using unlocker", "secret_name", s.Name) - ltPrivKeyData, err := DecryptWithIdentity(encryptedLtPrivKey, unlockIdentity) + ltPrivKeyBuffer, err := DecryptWithIdentity(encryptedLtPrivKey, unlockIdentity) if err != nil { Debug("Failed to decrypt long-term private key", "error", err, "secret_name", s.Name) return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err) } + defer ltPrivKeyBuffer.Destroy() // Parse the long-term private key Debug("Parsing long-term private key", "secret_name", s.Name) - ltIdentity, err := age.ParseX25519Identity(string(ltPrivKeyData)) + ltIdentity, err := age.ParseX25519Identity(ltPrivKeyBuffer.String()) if err != nil { Debug("Failed to parse long-term private key", "error", err, "secret_name", s.Name) diff --git a/internal/secret/version.go b/internal/secret/version.go index 1b2ffed..8ab48f8 100644 --- a/internal/secret/version.go +++ b/internal/secret/version.go @@ -277,15 +277,16 @@ func (sv *Version) LoadMetadata(ltIdentity *age.X25519Identity) error { } // Step 2: Decrypt version private key using long-term key - versionPrivKeyData, err := DecryptWithIdentity(encryptedPrivKey, ltIdentity) + versionPrivKeyBuffer, err := DecryptWithIdentity(encryptedPrivKey, ltIdentity) if err != nil { Debug("Failed to decrypt version private key", "error", err, "version", sv.Version) return fmt.Errorf("failed to decrypt version private key: %w", err) } + defer versionPrivKeyBuffer.Destroy() // Step 3: Parse version private key - versionIdentity, err := age.ParseX25519Identity(string(versionPrivKeyData)) + versionIdentity, err := age.ParseX25519Identity(versionPrivKeyBuffer.String()) if err != nil { Debug("Failed to parse version private key", "error", err, "version", sv.Version) @@ -302,16 +303,17 @@ func (sv *Version) LoadMetadata(ltIdentity *age.X25519Identity) error { } // Step 5: Decrypt metadata using version key - metadataBytes, err := DecryptWithIdentity(encryptedMetadata, versionIdentity) + metadataBuffer, err := DecryptWithIdentity(encryptedMetadata, versionIdentity) if err != nil { Debug("Failed to decrypt version metadata", "error", err, "version", sv.Version) return fmt.Errorf("failed to decrypt version metadata: %w", err) } + defer metadataBuffer.Destroy() // Step 6: Unmarshal metadata var metadata VersionMetadata - if err := json.Unmarshal(metadataBytes, &metadata); err != nil { + if err := json.Unmarshal(metadataBuffer.Bytes(), &metadata); err != nil { Debug("Failed to unmarshal version metadata", "error", err, "version", sv.Version) return fmt.Errorf("failed to unmarshal version metadata: %w", err) @@ -352,16 +354,17 @@ func (sv *Version) GetValue(ltIdentity *age.X25519Identity) (*memguard.LockedBuf // Step 2: Decrypt version private key using long-term key Debug("Decrypting version private key with long-term identity", "version", sv.Version) - versionPrivKeyData, err := DecryptWithIdentity(encryptedPrivKey, ltIdentity) + versionPrivKeyBuffer, err := DecryptWithIdentity(encryptedPrivKey, ltIdentity) if err != nil { Debug("Failed to decrypt version private key", "error", err, "version", sv.Version) return nil, fmt.Errorf("failed to decrypt version private key: %w", err) } - Debug("Successfully decrypted version private key", "version", sv.Version, "size", len(versionPrivKeyData)) + defer versionPrivKeyBuffer.Destroy() + Debug("Successfully decrypted version private key", "version", sv.Version, "size", versionPrivKeyBuffer.Size()) // Step 3: Parse version private key - versionIdentity, err := age.ParseX25519Identity(string(versionPrivKeyData)) + versionIdentity, err := age.ParseX25519Identity(versionPrivKeyBuffer.String()) if err != nil { Debug("Failed to parse version private key", "error", err, "version", sv.Version) @@ -381,16 +384,13 @@ func (sv *Version) GetValue(ltIdentity *age.X25519Identity) (*memguard.LockedBuf // Step 5: Decrypt value using version key Debug("Decrypting value with version identity", "version", sv.Version) - value, err := DecryptWithIdentity(encryptedValue, versionIdentity) + valueBuffer, err := DecryptWithIdentity(encryptedValue, versionIdentity) if err != nil { Debug("Failed to decrypt version value", "error", err, "version", sv.Version) return nil, fmt.Errorf("failed to decrypt version value: %w", err) } - // Create a secure buffer for the decrypted value - valueBuffer := memguard.NewBufferFromBytes(value) - Debug("Successfully retrieved version value", "version", sv.Version, "value_length", valueBuffer.Size(), diff --git a/internal/vault/secrets.go b/internal/vault/secrets.go index 5d3296c..a41357c 100644 --- a/internal/vault/secrets.go +++ b/internal/vault/secrets.go @@ -259,13 +259,14 @@ func updateVersionMetadata(fs afero.Fs, version *secret.Version, ltIdentity *age } // Decrypt version private key using long-term key - versionPrivKeyData, err := secret.DecryptWithIdentity(encryptedPrivKey, ltIdentity) + versionPrivKeyBuffer, err := secret.DecryptWithIdentity(encryptedPrivKey, ltIdentity) if err != nil { return fmt.Errorf("failed to decrypt version private key: %w", err) } + defer versionPrivKeyBuffer.Destroy() // Parse version private key - versionIdentity, err := age.ParseX25519Identity(string(versionPrivKeyData)) + versionIdentity, err := age.ParseX25519Identity(versionPrivKeyBuffer.String()) if err != nil { return fmt.Errorf("failed to parse version private key: %w", err) } diff --git a/internal/vault/vault.go b/internal/vault/vault.go index 5ea5e95..1e64b41 100644 --- a/internal/vault/vault.go +++ b/internal/vault/vault.go @@ -157,22 +157,23 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) { // Decrypt long-term private key using unlocker secret.Debug("Decrypting long-term private key with unlocker", "unlocker_type", unlocker.GetType()) - ltPrivKeyData, err := secret.DecryptWithIdentity(encryptedLtPrivKey, unlockerIdentity) + ltPrivKeyBuffer, err := secret.DecryptWithIdentity(encryptedLtPrivKey, unlockerIdentity) if err != nil { secret.Debug("Failed to decrypt long-term private key", "error", err, "unlocker_type", unlocker.GetType()) return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err) } + defer ltPrivKeyBuffer.Destroy() secret.DebugWith("Successfully decrypted long-term private key", slog.String("vault_name", v.Name), slog.String("unlocker_type", unlocker.GetType()), - slog.Int("decrypted_length", len(ltPrivKeyData)), + slog.Int("decrypted_length", ltPrivKeyBuffer.Size()), ) // Parse long-term private key secret.Debug("Parsing long-term private key", "vault_name", v.Name) - ltIdentity, err := age.ParseX25519Identity(string(ltPrivKeyData)) + ltIdentity, err := age.ParseX25519Identity(ltPrivKeyBuffer.String()) if err != nil { secret.Debug("Failed to parse long-term private key", "error", err, "vault_name", v.Name)