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
- [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

View File

@ -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")
}

View File

@ -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

View File

@ -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())

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
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)

View File

@ -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(),

View File

@ -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)
}

View File

@ -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)