WIP: refactor to use memguard for secure memory handling
- Add memguard dependency - Update ReadPassphrase to return LockedBuffer - Update EncryptWithPassphrase/DecryptWithPassphrase to accept LockedBuffer - Remove string wrapper functions - Update all callers to create LockedBuffers at entry points - Update interfaces and mock implementations
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"filippo.io/age"
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -15,16 +16,17 @@ type PassphraseUnlocker struct {
|
||||
Directory string
|
||||
Metadata UnlockerMetadata
|
||||
fs afero.Fs
|
||||
Passphrase string
|
||||
Passphrase *memguard.LockedBuffer // Secure buffer for passphrase
|
||||
}
|
||||
|
||||
// getPassphrase retrieves the passphrase from memory, environment, or user input
|
||||
func (p *PassphraseUnlocker) getPassphrase() (string, error) {
|
||||
// Returns a LockedBuffer for secure memory handling
|
||||
func (p *PassphraseUnlocker) getPassphrase() (*memguard.LockedBuffer, error) {
|
||||
// First check if we already have the passphrase
|
||||
if p.Passphrase != "" {
|
||||
if p.Passphrase != nil && p.Passphrase.IsAlive() {
|
||||
Debug("Using in-memory passphrase", "unlocker_id", p.GetID())
|
||||
|
||||
return p.Passphrase, nil
|
||||
// Return a copy of the passphrase buffer
|
||||
return memguard.NewBufferFromBytes(p.Passphrase.Bytes()), nil
|
||||
}
|
||||
|
||||
Debug("No passphrase in memory, checking environment")
|
||||
@@ -32,20 +34,22 @@ func (p *PassphraseUnlocker) getPassphrase() (string, error) {
|
||||
passphraseStr := os.Getenv(EnvUnlockPassphrase)
|
||||
if passphraseStr != "" {
|
||||
Debug("Using passphrase from environment", "unlocker_id", p.GetID())
|
||||
|
||||
return passphraseStr, nil
|
||||
// Convert to secure buffer
|
||||
secureBuffer := memguard.NewBufferFromBytes([]byte(passphraseStr))
|
||||
|
||||
return secureBuffer, nil
|
||||
}
|
||||
|
||||
Debug("No passphrase in environment, prompting user")
|
||||
// Prompt for passphrase
|
||||
passphraseStr, err := ReadPassphrase("Enter unlock passphrase: ")
|
||||
secureBuffer, err := ReadPassphrase("Enter unlock passphrase: ")
|
||||
if err != nil {
|
||||
Debug("Failed to read passphrase", "error", err, "unlocker_id", p.GetID())
|
||||
|
||||
return "", fmt.Errorf("failed to read passphrase: %w", err)
|
||||
|
||||
return nil, fmt.Errorf("failed to read passphrase: %w", err)
|
||||
}
|
||||
|
||||
return passphraseStr, nil
|
||||
return secureBuffer, nil
|
||||
}
|
||||
|
||||
// GetIdentity implements Unlocker interface for passphrase-based unlockers
|
||||
@@ -55,10 +59,11 @@ func (p *PassphraseUnlocker) GetIdentity() (*age.X25519Identity, error) {
|
||||
slog.String("unlocker_type", p.GetType()),
|
||||
)
|
||||
|
||||
passphraseStr, err := p.getPassphrase()
|
||||
passphraseBuffer, err := p.getPassphrase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer passphraseBuffer.Destroy()
|
||||
|
||||
// Read encrypted private key of unlocker
|
||||
unlockerPrivPath := filepath.Join(p.Directory, "priv.age")
|
||||
@@ -79,7 +84,7 @@ 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, passphraseStr)
|
||||
privKeyData, err := DecryptWithPassphrase(encryptedPrivKeyData, passphraseBuffer)
|
||||
if err != nil {
|
||||
Debug("Failed to decrypt unlocker private key", "error", err, "unlocker_id", p.GetID())
|
||||
|
||||
@@ -93,7 +98,17 @@ func (p *PassphraseUnlocker) GetIdentity() (*age.X25519Identity, error) {
|
||||
|
||||
// Parse the decrypted private key
|
||||
Debug("Parsing decrypted unlocker identity", "unlocker_id", p.GetID())
|
||||
identity, err := age.ParseX25519Identity(string(privKeyData))
|
||||
|
||||
// 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())
|
||||
|
||||
@@ -133,6 +148,11 @@ func (p *PassphraseUnlocker) GetID() string {
|
||||
|
||||
// Remove implements Unlocker interface - removes the passphrase unlocker
|
||||
func (p *PassphraseUnlocker) Remove() error {
|
||||
// Clean up the passphrase from memory if it exists
|
||||
if p.Passphrase != nil && p.Passphrase.IsAlive() {
|
||||
p.Passphrase.Destroy()
|
||||
}
|
||||
|
||||
// For passphrase unlockers, we just need to remove the directory
|
||||
// No external resources (like keychain items) to clean up
|
||||
if err := p.fs.RemoveAll(p.Directory); err != nil {
|
||||
@@ -152,7 +172,8 @@ func NewPassphraseUnlocker(fs afero.Fs, directory string, metadata UnlockerMetad
|
||||
}
|
||||
|
||||
// CreatePassphraseUnlocker creates a new passphrase-protected unlocker
|
||||
func CreatePassphraseUnlocker(fs afero.Fs, stateDir string, passphrase string) (*PassphraseUnlocker, error) {
|
||||
// The passphrase must be provided as a LockedBuffer for security
|
||||
func CreatePassphraseUnlocker(fs afero.Fs, stateDir string, passphrase *memguard.LockedBuffer) (*PassphraseUnlocker, error) {
|
||||
// Get current vault
|
||||
currentVault, err := GetCurrentVault(fs, stateDir)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user