Fix DecryptWithIdentity to return LockedBuffer

- Changed DecryptWithIdentity to return *memguard.LockedBuffer instead of []byte
- Updated all callers throughout the codebase to handle LockedBuffer
- This ensures decrypted data is protected in memory immediately after decryption
- Fixed all usages in vault, secret, version, and unlocker implementations
- Removed duplicate buffer creation and unnecessary memory clearing
This commit is contained in:
Jeffrey Paul 2025-07-15 09:04:34 +02:00
parent 8ec3fc877d
commit 63cc06b93c
8 changed files with 37 additions and 47 deletions

View File

@ -14,7 +14,7 @@ prioritized from most critical (top) to least critical (bottom).
### Functions returning unprotected secrets ### 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 - [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 - [ ] **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 - [ ] **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 - [ ] **9. getSecretValue returns unprotected data**: `internal/cli/crypto.go:269` - `getSecretValue()` returns bare []byte

View File

@ -54,7 +54,7 @@ func EncryptToRecipient(data *memguard.LockedBuffer, recipient age.Recipient) ([
} }
// DecryptWithIdentity decrypts data with an identity using age // 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) r, err := age.Decrypt(bytes.NewReader(data), identity)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create decryptor: %w", err) 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 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 // 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 // DecryptWithPassphrase decrypts data using a passphrase with age's scrypt-based decryption
// The passphrase parameter should be a LockedBuffer for secure memory handling // 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 { if passphrase == nil {
return nil, fmt.Errorf("passphrase buffer is nil") return nil, fmt.Errorf("passphrase buffer is nil")
} }

View File

@ -107,30 +107,22 @@ func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) {
passphraseBuffer := memguard.NewBufferFromBytes([]byte(keychainData.AgePrivKeyPassphrase)) passphraseBuffer := memguard.NewBufferFromBytes([]byte(keychainData.AgePrivKeyPassphrase))
defer passphraseBuffer.Destroy() defer passphraseBuffer.Destroy()
agePrivKeyData, err := DecryptWithPassphrase(encryptedAgePrivKeyData, passphraseBuffer) agePrivKeyBuffer, err := DecryptWithPassphrase(encryptedAgePrivKeyData, passphraseBuffer)
if err != nil { if err != nil {
Debug("Failed to decrypt age private key with keychain passphrase", "error", err, "unlocker_id", k.GetID()) 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) 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", DebugWith("Successfully decrypted age private key with keychain passphrase",
slog.String("unlocker_id", k.GetID()), 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 // Step 6: Parse the decrypted age private key
Debug("Parsing decrypted age private key", "unlocker_id", k.GetID()) 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()) ageIdentity, err := age.ParseX25519Identity(agePrivKeyBuffer.String())
if err != nil { if err != nil {
Debug("Failed to parse age private key", "error", err, "unlocker_id", k.GetID()) 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 // Decrypt long-term private key using current unlocker
ltPrivKeyData, err := DecryptWithIdentity(encryptedLtPrivKey, currentUnlockerIdentity) ltPrivKeyBuffer, err := DecryptWithIdentity(encryptedLtPrivKey, currentUnlockerIdentity)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err) return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err)
} }
// Return the decrypted key in a secure buffer // Return the decrypted key buffer
return memguard.NewBufferFromBytes(ltPrivKeyData), nil return ltPrivKeyBuffer, nil
} }
// CreateKeychainUnlocker creates a new keychain unlocker and stores it in the vault // CreateKeychainUnlocker creates a new keychain unlocker and stores it in the vault

View File

@ -84,30 +84,22 @@ func (p *PassphraseUnlocker) GetIdentity() (*age.X25519Identity, error) {
Debug("Decrypting unlocker private key with passphrase", "unlocker_id", p.GetID()) Debug("Decrypting unlocker private key with passphrase", "unlocker_id", p.GetID())
// Decrypt the unlocker private key with passphrase // Decrypt the unlocker private key with passphrase
privKeyData, err := DecryptWithPassphrase(encryptedPrivKeyData, passphraseBuffer) privKeyBuffer, err := DecryptWithPassphrase(encryptedPrivKeyData, passphraseBuffer)
if err != nil { if err != nil {
Debug("Failed to decrypt unlocker private key", "error", err, "unlocker_id", p.GetID()) 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) return nil, fmt.Errorf("failed to decrypt unlocker private key: %w", err)
} }
defer privKeyBuffer.Destroy()
DebugWith("Successfully decrypted unlocker private key", DebugWith("Successfully decrypted unlocker private key",
slog.String("unlocker_id", p.GetID()), slog.String("unlocker_id", p.GetID()),
slog.Int("decrypted_length", len(privKeyData)), slog.Int("decrypted_length", privKeyBuffer.Size()),
) )
// Parse the decrypted private key // Parse the decrypted private key
Debug("Parsing decrypted unlocker identity", "unlocker_id", p.GetID()) 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()) identity, err := age.ParseX25519Identity(privKeyBuffer.String())
if err != nil { if err != nil {
Debug("Failed to parse unlocker private key", "error", err, "unlocker_id", p.GetID()) Debug("Failed to parse unlocker private key", "error", err, "unlocker_id", p.GetID())

View File

@ -179,16 +179,17 @@ func (s *Secret) GetValue(unlocker Unlocker) (*memguard.LockedBuffer, error) {
// Decrypt the encrypted long-term private key using the unlocker // Decrypt the encrypted long-term private key using the unlocker
Debug("Decrypting long-term private key using unlocker", "secret_name", s.Name) 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 { if err != nil {
Debug("Failed to decrypt long-term private key", "error", err, "secret_name", s.Name) 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) return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err)
} }
defer ltPrivKeyBuffer.Destroy()
// Parse the long-term private key // Parse the long-term private key
Debug("Parsing long-term private key", "secret_name", s.Name) 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 { if err != nil {
Debug("Failed to parse long-term private key", "error", err, "secret_name", s.Name) Debug("Failed to parse long-term private key", "error", err, "secret_name", s.Name)

View File

@ -277,15 +277,16 @@ func (sv *Version) LoadMetadata(ltIdentity *age.X25519Identity) error {
} }
// Step 2: Decrypt version private key using long-term key // Step 2: Decrypt version private key using long-term key
versionPrivKeyData, err := DecryptWithIdentity(encryptedPrivKey, ltIdentity) versionPrivKeyBuffer, err := DecryptWithIdentity(encryptedPrivKey, ltIdentity)
if err != nil { if err != nil {
Debug("Failed to decrypt version private key", "error", err, "version", sv.Version) Debug("Failed to decrypt version private key", "error", err, "version", sv.Version)
return fmt.Errorf("failed to decrypt version private key: %w", err) return fmt.Errorf("failed to decrypt version private key: %w", err)
} }
defer versionPrivKeyBuffer.Destroy()
// Step 3: Parse version private key // Step 3: Parse version private key
versionIdentity, err := age.ParseX25519Identity(string(versionPrivKeyData)) versionIdentity, err := age.ParseX25519Identity(versionPrivKeyBuffer.String())
if err != nil { if err != nil {
Debug("Failed to parse version private key", "error", err, "version", sv.Version) 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 // Step 5: Decrypt metadata using version key
metadataBytes, err := DecryptWithIdentity(encryptedMetadata, versionIdentity) metadataBuffer, err := DecryptWithIdentity(encryptedMetadata, versionIdentity)
if err != nil { if err != nil {
Debug("Failed to decrypt version metadata", "error", err, "version", sv.Version) Debug("Failed to decrypt version metadata", "error", err, "version", sv.Version)
return fmt.Errorf("failed to decrypt version metadata: %w", err) return fmt.Errorf("failed to decrypt version metadata: %w", err)
} }
defer metadataBuffer.Destroy()
// Step 6: Unmarshal metadata // Step 6: Unmarshal metadata
var metadata VersionMetadata 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) Debug("Failed to unmarshal version metadata", "error", err, "version", sv.Version)
return fmt.Errorf("failed to unmarshal version metadata: %w", err) 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 // Step 2: Decrypt version private key using long-term key
Debug("Decrypting version private key with long-term identity", "version", sv.Version) 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 { if err != nil {
Debug("Failed to decrypt version private key", "error", err, "version", sv.Version) Debug("Failed to decrypt version private key", "error", err, "version", sv.Version)
return nil, fmt.Errorf("failed to decrypt version private key: %w", err) 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 // Step 3: Parse version private key
versionIdentity, err := age.ParseX25519Identity(string(versionPrivKeyData)) versionIdentity, err := age.ParseX25519Identity(versionPrivKeyBuffer.String())
if err != nil { if err != nil {
Debug("Failed to parse version private key", "error", err, "version", sv.Version) 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 // Step 5: Decrypt value using version key
Debug("Decrypting value with version identity", "version", sv.Version) Debug("Decrypting value with version identity", "version", sv.Version)
value, err := DecryptWithIdentity(encryptedValue, versionIdentity) valueBuffer, err := DecryptWithIdentity(encryptedValue, versionIdentity)
if err != nil { if err != nil {
Debug("Failed to decrypt version value", "error", err, "version", sv.Version) Debug("Failed to decrypt version value", "error", err, "version", sv.Version)
return nil, fmt.Errorf("failed to decrypt version value: %w", err) 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", Debug("Successfully retrieved version value",
"version", sv.Version, "version", sv.Version,
"value_length", valueBuffer.Size(), "value_length", valueBuffer.Size(),

View File

@ -259,13 +259,14 @@ func updateVersionMetadata(fs afero.Fs, version *secret.Version, ltIdentity *age
} }
// Decrypt version private key using long-term key // Decrypt version private key using long-term key
versionPrivKeyData, err := secret.DecryptWithIdentity(encryptedPrivKey, ltIdentity) versionPrivKeyBuffer, err := secret.DecryptWithIdentity(encryptedPrivKey, ltIdentity)
if err != nil { if err != nil {
return fmt.Errorf("failed to decrypt version private key: %w", err) return fmt.Errorf("failed to decrypt version private key: %w", err)
} }
defer versionPrivKeyBuffer.Destroy()
// Parse version private key // Parse version private key
versionIdentity, err := age.ParseX25519Identity(string(versionPrivKeyData)) versionIdentity, err := age.ParseX25519Identity(versionPrivKeyBuffer.String())
if err != nil { if err != nil {
return fmt.Errorf("failed to parse version private key: %w", err) return fmt.Errorf("failed to parse version private key: %w", err)
} }

View File

@ -157,22 +157,23 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) {
// Decrypt long-term private key using unlocker // Decrypt long-term private key using unlocker
secret.Debug("Decrypting long-term private key with unlocker", "unlocker_type", unlocker.GetType()) 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 { if err != nil {
secret.Debug("Failed to decrypt long-term private key", "error", err, "unlocker_type", unlocker.GetType()) 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) return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err)
} }
defer ltPrivKeyBuffer.Destroy()
secret.DebugWith("Successfully decrypted long-term private key", secret.DebugWith("Successfully decrypted long-term private key",
slog.String("vault_name", v.Name), slog.String("vault_name", v.Name),
slog.String("unlocker_type", unlocker.GetType()), slog.String("unlocker_type", unlocker.GetType()),
slog.Int("decrypted_length", len(ltPrivKeyData)), slog.Int("decrypted_length", ltPrivKeyBuffer.Size()),
) )
// Parse long-term private key // Parse long-term private key
secret.Debug("Parsing long-term private key", "vault_name", v.Name) 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 { if err != nil {
secret.Debug("Failed to parse long-term private key", "error", err, "vault_name", v.Name) secret.Debug("Failed to parse long-term private key", "error", err, "vault_name", v.Name)