refactor: Implement proper separation between unlock keys and secret decryption - Remove DecryptSecret methods from all unlock key implementations - Secrets now handle their own decryption via Secret.GetValue(unlockKey) - Unlock keys are only responsible for vault access (getting long-term key) - Add decryptWithLongTermKey helper for per-secret key architecture - Fix vault import to work in non-interactive mode without unlock keys - Maintain clean architecture: unlock keys → vault access → secret decryption - All tests passing with new architecture
This commit is contained in:
parent
4b59d6fb82
commit
345709a306
@ -1480,6 +1480,11 @@ func (cli *CLIInstance) importMnemonic(vaultName, mnemonic string) error {
|
||||
vault := NewVault(cli.fs, vaultName, cli.stateDir)
|
||||
vault.Unlock(ltIdentity)
|
||||
|
||||
fmt.Printf("Successfully imported mnemonic into vault '%s'\n", vaultName)
|
||||
fmt.Printf("Long-term public key: %s\n", ltPubKey)
|
||||
|
||||
// Try to create unlock key only if running interactively
|
||||
if term.IsTerminal(int(syscall.Stderr)) {
|
||||
// Get or create passphrase for unlock key
|
||||
var passphraseStr string
|
||||
if envPassphrase := os.Getenv(EnvUnlockPassphrase); envPassphrase != "" {
|
||||
@ -1488,19 +1493,25 @@ func (cli *CLIInstance) importMnemonic(vaultName, mnemonic string) error {
|
||||
// Use secure passphrase input with confirmation
|
||||
passphraseStr, err = readSecurePassphrase("Enter passphrase for unlock key: ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read passphrase: %w", err)
|
||||
fmt.Printf("Warning: Failed to create unlock key: %v\n", err)
|
||||
fmt.Printf("You can create unlock keys later with 'secret keys add passphrase'\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create passphrase-protected unlock key (vault is now unlocked)
|
||||
passphraseKey, err := vault.CreatePassphraseKey(passphraseStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create unlock key: %w", err)
|
||||
fmt.Printf("Warning: Failed to create unlock key: %v\n", err)
|
||||
fmt.Printf("You can create unlock keys later with 'secret keys add passphrase'\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully imported mnemonic into vault '%s'\n", vaultName)
|
||||
fmt.Printf("Long-term public key: %s\n", ltPubKey)
|
||||
fmt.Printf("Unlock key ID: %s\n", passphraseKey.GetMetadata().ID)
|
||||
} else {
|
||||
fmt.Printf("Running in non-interactive mode - unlock key not created\n")
|
||||
fmt.Printf("You can create unlock keys later with 'secret keys add passphrase'\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -176,104 +176,6 @@ func (k *KeychainUnlockKey) Remove() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecryptSecret decrypts a secret using this keychain unlock key's long-term key management
|
||||
func (k *KeychainUnlockKey) DecryptSecret(secret *Secret) ([]byte, error) {
|
||||
DebugWith("Decrypting secret with keychain unlock key",
|
||||
slog.String("secret_name", secret.Name),
|
||||
slog.String("key_id", k.GetID()),
|
||||
slog.String("key_type", k.GetType()),
|
||||
)
|
||||
|
||||
// Let the secret read its own encrypted data
|
||||
encryptedData, err := secret.GetEncryptedData()
|
||||
if err != nil {
|
||||
Debug("Failed to get encrypted secret data for keychain decryption", "error", err, "secret_name", secret.Name)
|
||||
return nil, fmt.Errorf("failed to get encrypted secret data: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Retrieved encrypted secret data for keychain decryption",
|
||||
slog.String("secret_name", secret.Name),
|
||||
slog.String("key_id", k.GetID()),
|
||||
slog.Int("encrypted_length", len(encryptedData)),
|
||||
)
|
||||
|
||||
// Get or derive the long-term private key
|
||||
var ltPrivKeyData []byte
|
||||
|
||||
// Check if mnemonic is available in environment variable
|
||||
if envMnemonic := os.Getenv(EnvMnemonic); envMnemonic != "" {
|
||||
// Use mnemonic directly to derive long-term key
|
||||
ltIdentity, err := agehd.DeriveIdentity(envMnemonic, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive long-term key from mnemonic: %w", err)
|
||||
}
|
||||
ltPrivKeyData = []byte(ltIdentity.String())
|
||||
} else {
|
||||
// Get keychain item name and retrieve data
|
||||
keychainItemName, err := k.GetKeychainItemName()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get keychain item name: %w", err)
|
||||
}
|
||||
|
||||
keychainDataBytes, err := retrieveFromKeychain(keychainItemName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve data from keychain: %w", err)
|
||||
}
|
||||
|
||||
var keychainData KeychainData
|
||||
if err := json.Unmarshal(keychainDataBytes, &keychainData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse keychain data: %w", err)
|
||||
}
|
||||
|
||||
// Decrypt the long-term private key using the encrypted data from keychain
|
||||
encryptedLtPrivKey, err := hex.DecodeString(keychainData.EncryptedLongtermKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode encrypted long-term key: %w", err)
|
||||
}
|
||||
|
||||
// Get our unlock key identity to decrypt the long-term key
|
||||
unlockIdentity, err := k.GetIdentity()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get unlock identity: %w", err)
|
||||
}
|
||||
|
||||
// Decrypt long-term private key using our unlock key
|
||||
ltPrivKeyData, err = decryptWithIdentity(encryptedLtPrivKey, unlockIdentity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse long-term private key
|
||||
Debug("Parsing long-term private key", "key_id", k.GetID())
|
||||
ltIdentity, err := age.ParseX25519Identity(string(ltPrivKeyData))
|
||||
if err != nil {
|
||||
Debug("Failed to parse long-term private key", "error", err, "key_id", k.GetID())
|
||||
return nil, fmt.Errorf("failed to parse long-term private key: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully parsed long-term identity",
|
||||
slog.String("key_id", k.GetID()),
|
||||
slog.String("public_key", ltIdentity.Recipient().String()),
|
||||
)
|
||||
|
||||
// Decrypt secret data using long-term key
|
||||
Debug("Decrypting secret data with long-term key", "secret_name", secret.Name, "key_id", k.GetID())
|
||||
decryptedData, err := decryptWithIdentity(encryptedData, ltIdentity)
|
||||
if err != nil {
|
||||
Debug("Failed to decrypt secret with long-term key", "error", err, "secret_name", secret.Name, "key_id", k.GetID())
|
||||
return nil, fmt.Errorf("failed to decrypt secret: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully decrypted secret with keychain unlock key",
|
||||
slog.String("secret_name", secret.Name),
|
||||
slog.String("key_id", k.GetID()),
|
||||
slog.Int("decrypted_length", len(decryptedData)),
|
||||
)
|
||||
|
||||
return decryptedData, nil
|
||||
}
|
||||
|
||||
// NewKeychainUnlockKey creates a new KeychainUnlockKey instance
|
||||
func NewKeychainUnlockKey(fs afero.Fs, directory string, metadata UnlockKeyMetadata) *KeychainUnlockKey {
|
||||
return &KeychainUnlockKey{
|
||||
|
@ -140,90 +140,3 @@ func CreatePassphraseKey(fs afero.Fs, stateDir string, passphrase string) (*Pass
|
||||
|
||||
return currentVault.CreatePassphraseKey(passphrase)
|
||||
}
|
||||
|
||||
// DecryptSecret decrypts a secret using this passphrase unlock key's long-term key management
|
||||
func (p *PassphraseUnlockKey) DecryptSecret(secret *Secret) ([]byte, error) {
|
||||
DebugWith("Decrypting secret with passphrase unlock key",
|
||||
slog.String("secret_name", secret.Name),
|
||||
slog.String("key_id", p.GetID()),
|
||||
slog.String("key_type", p.GetType()),
|
||||
)
|
||||
|
||||
// Get our unlock key encrypted data
|
||||
encryptedData, err := secret.GetEncryptedData()
|
||||
if err != nil {
|
||||
Debug("Failed to get encrypted secret data for passphrase decryption", "error", err, "secret_name", secret.Name)
|
||||
return nil, fmt.Errorf("failed to get encrypted secret data: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Retrieved encrypted secret data for passphrase decryption",
|
||||
slog.String("secret_name", secret.Name),
|
||||
slog.String("key_id", p.GetID()),
|
||||
slog.Int("encrypted_length", len(encryptedData)),
|
||||
)
|
||||
|
||||
// Get our age identity
|
||||
Debug("Getting passphrase unlock key identity for secret decryption", "key_id", p.GetID())
|
||||
unlockIdentity, err := p.GetIdentity()
|
||||
if err != nil {
|
||||
Debug("Failed to get passphrase unlock identity", "error", err, "key_id", p.GetID())
|
||||
return nil, fmt.Errorf("failed to get unlock identity: %w", err)
|
||||
}
|
||||
|
||||
// Read encrypted long-term private key
|
||||
encryptedLtPrivKeyPath := filepath.Join(p.Directory, "longterm.age")
|
||||
Debug("Reading encrypted long-term private key", "path", encryptedLtPrivKeyPath)
|
||||
|
||||
encryptedLtPrivKey, err := afero.ReadFile(p.fs, encryptedLtPrivKeyPath)
|
||||
if err != nil {
|
||||
Debug("Failed to read encrypted long-term private key", "error", err, "path", encryptedLtPrivKeyPath)
|
||||
return nil, fmt.Errorf("failed to read encrypted long-term private key: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Read encrypted long-term private key",
|
||||
slog.String("key_id", p.GetID()),
|
||||
slog.Int("encrypted_length", len(encryptedLtPrivKey)),
|
||||
)
|
||||
|
||||
// Decrypt long-term private key using our unlock key
|
||||
Debug("Decrypting long-term private key with passphrase unlock key", "key_id", p.GetID())
|
||||
ltPrivKeyData, err := decryptWithIdentity(encryptedLtPrivKey, unlockIdentity)
|
||||
if err != nil {
|
||||
Debug("Failed to decrypt long-term private key", "error", err, "key_id", p.GetID())
|
||||
return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully decrypted long-term private key",
|
||||
slog.String("key_id", p.GetID()),
|
||||
slog.Int("decrypted_length", len(ltPrivKeyData)),
|
||||
)
|
||||
|
||||
// Parse long-term private key
|
||||
Debug("Parsing long-term private key", "key_id", p.GetID())
|
||||
ltIdentity, err := age.ParseX25519Identity(string(ltPrivKeyData))
|
||||
if err != nil {
|
||||
Debug("Failed to parse long-term private key", "error", err, "key_id", p.GetID())
|
||||
return nil, fmt.Errorf("failed to parse long-term private key: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully parsed long-term identity",
|
||||
slog.String("key_id", p.GetID()),
|
||||
slog.String("public_key", ltIdentity.Recipient().String()),
|
||||
)
|
||||
|
||||
// Decrypt secret data using long-term key
|
||||
Debug("Decrypting secret data with long-term key", "secret_name", secret.Name, "key_id", p.GetID())
|
||||
decryptedData, err := decryptWithIdentity(encryptedData, ltIdentity)
|
||||
if err != nil {
|
||||
Debug("Failed to decrypt secret with long-term key", "error", err, "secret_name", secret.Name, "key_id", p.GetID())
|
||||
return nil, fmt.Errorf("failed to decrypt secret: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully decrypted secret with passphrase unlock key",
|
||||
slog.String("secret_name", secret.Name),
|
||||
slog.String("key_id", p.GetID()),
|
||||
slog.Int("decrypted_length", len(decryptedData)),
|
||||
)
|
||||
|
||||
return decryptedData, nil
|
||||
}
|
||||
|
@ -124,124 +124,6 @@ func (p *PGPUnlockKey) Remove() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecryptSecret decrypts a secret using this PGP unlock key's long-term key management
|
||||
func (p *PGPUnlockKey) DecryptSecret(secret *Secret) ([]byte, error) {
|
||||
DebugWith("Decrypting secret with PGP unlock key",
|
||||
slog.String("secret_name", secret.Name),
|
||||
slog.String("key_id", p.GetID()),
|
||||
slog.String("key_type", p.GetType()),
|
||||
)
|
||||
|
||||
// Let the secret read its own encrypted data
|
||||
encryptedData, err := secret.GetEncryptedData()
|
||||
if err != nil {
|
||||
Debug("Failed to get encrypted secret data for PGP decryption", "error", err, "secret_name", secret.Name)
|
||||
return nil, fmt.Errorf("failed to get encrypted secret data: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Retrieved encrypted secret data for PGP decryption",
|
||||
slog.String("secret_name", secret.Name),
|
||||
slog.String("key_id", p.GetID()),
|
||||
slog.Int("encrypted_length", len(encryptedData)),
|
||||
)
|
||||
|
||||
// Get our age identity
|
||||
Debug("Getting PGP unlock key identity for secret decryption", "key_id", p.GetID())
|
||||
_, err = p.GetIdentity()
|
||||
if err != nil {
|
||||
Debug("Failed to get PGP unlock identity", "error", err, "key_id", p.GetID())
|
||||
return nil, fmt.Errorf("failed to get unlock identity: %w", err)
|
||||
}
|
||||
|
||||
// Get or derive the long-term private key
|
||||
var ltPrivKeyData []byte
|
||||
|
||||
// Check if mnemonic is available in environment variable
|
||||
if envMnemonic := os.Getenv(EnvMnemonic); envMnemonic != "" {
|
||||
// Use mnemonic directly to derive long-term key
|
||||
ltIdentity, err := agehd.DeriveIdentity(envMnemonic, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive long-term key from mnemonic: %w", err)
|
||||
}
|
||||
ltPrivKeyData = []byte(ltIdentity.String())
|
||||
} else {
|
||||
// Get the vault to access current unlock key
|
||||
stateDir := filepath.Dir(filepath.Dir(filepath.Dir(p.Directory)))
|
||||
vault, err := GetCurrentVault(p.fs, stateDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get vault: %w", err)
|
||||
}
|
||||
|
||||
// Get current unlock key
|
||||
currentUnlockKey, err := vault.GetCurrentUnlockKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get current unlock key: %w", err)
|
||||
}
|
||||
|
||||
// Get the current unlock key identity
|
||||
currentUnlockIdentity, err := currentUnlockKey.GetIdentity()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get current unlock key identity: %w", err)
|
||||
}
|
||||
|
||||
// Get encrypted long-term key from current unlock key, handling different types
|
||||
var encryptedLtPrivKey []byte
|
||||
switch currentUnlockKey := currentUnlockKey.(type) {
|
||||
case *PassphraseUnlockKey:
|
||||
// Read the encrypted long-term private key from passphrase unlock key
|
||||
encryptedLtPrivKey, err = afero.ReadFile(p.fs, filepath.Join(currentUnlockKey.GetDirectory(), "longterm.age"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read encrypted long-term key from current passphrase unlock key: %w", err)
|
||||
}
|
||||
|
||||
case *PGPUnlockKey:
|
||||
// Read the encrypted long-term private key from PGP unlock key
|
||||
encryptedLtPrivKey, err = afero.ReadFile(p.fs, filepath.Join(currentUnlockKey.GetDirectory(), "longterm.age"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read encrypted long-term key from current PGP unlock key: %w", err)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported current unlock key type for PGP unlock key creation")
|
||||
}
|
||||
|
||||
// Decrypt long-term private key using current unlock key
|
||||
ltPrivKeyData, err = decryptWithIdentity(encryptedLtPrivKey, currentUnlockIdentity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse long-term private key
|
||||
Debug("Parsing long-term private key", "key_id", p.GetID())
|
||||
ltIdentity, err := age.ParseX25519Identity(string(ltPrivKeyData))
|
||||
if err != nil {
|
||||
Debug("Failed to parse long-term private key", "error", err, "key_id", p.GetID())
|
||||
return nil, fmt.Errorf("failed to parse long-term private key: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully parsed long-term identity",
|
||||
slog.String("key_id", p.GetID()),
|
||||
slog.String("public_key", ltIdentity.Recipient().String()),
|
||||
)
|
||||
|
||||
// Decrypt secret data using long-term key
|
||||
Debug("Decrypting secret data with long-term key", "secret_name", secret.Name, "key_id", p.GetID())
|
||||
decryptedData, err := decryptWithIdentity(encryptedData, ltIdentity)
|
||||
if err != nil {
|
||||
Debug("Failed to decrypt secret with long-term key", "error", err, "secret_name", secret.Name, "key_id", p.GetID())
|
||||
return nil, fmt.Errorf("failed to decrypt secret: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully decrypted secret with PGP unlock key",
|
||||
slog.String("secret_name", secret.Name),
|
||||
slog.String("key_id", p.GetID()),
|
||||
slog.Int("decrypted_length", len(decryptedData)),
|
||||
)
|
||||
|
||||
return decryptedData, nil
|
||||
}
|
||||
|
||||
// NewPGPUnlockKey creates a new PGPUnlockKey instance
|
||||
func NewPGPUnlockKey(fs afero.Fs, directory string, metadata UnlockKeyMetadata) *PGPUnlockKey {
|
||||
return &PGPUnlockKey{
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"filippo.io/age"
|
||||
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
@ -92,7 +93,7 @@ func (s *Secret) GetValue(unlockKey UnlockKey) ([]byte, error) {
|
||||
|
||||
// Check if we have SB_SECRET_MNEMONIC environment variable for direct decryption
|
||||
if envMnemonic := os.Getenv(EnvMnemonic); envMnemonic != "" {
|
||||
Debug("Using mnemonic from environment for secret decryption", "secret_name", s.Name)
|
||||
Debug("Using mnemonic from environment for direct long-term key derivation", "secret_name", s.Name)
|
||||
|
||||
// Use mnemonic directly to derive long-term key
|
||||
ltIdentity, err := agehd.DeriveIdentity(envMnemonic, 0)
|
||||
@ -103,62 +104,139 @@ func (s *Secret) GetValue(unlockKey UnlockKey) ([]byte, error) {
|
||||
|
||||
Debug("Successfully derived long-term key from mnemonic", "secret_name", s.Name)
|
||||
|
||||
// Read our own encrypted data
|
||||
encryptedData, err := s.GetEncryptedData()
|
||||
if err != nil {
|
||||
Debug("Failed to get encrypted data for mnemonic decryption", "error", err, "secret_name", s.Name)
|
||||
return nil, err
|
||||
// Use the long-term key to decrypt the secret using per-secret architecture
|
||||
return s.decryptWithLongTermKey(ltIdentity)
|
||||
}
|
||||
|
||||
DebugWith("Retrieved encrypted data for mnemonic decryption",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.Int("encrypted_length", len(encryptedData)),
|
||||
)
|
||||
Debug("Using unlock key for vault access", "secret_name", s.Name)
|
||||
|
||||
// Decrypt secret data
|
||||
Debug("Decrypting secret with long-term key from mnemonic", "secret_name", s.Name)
|
||||
decryptedData, err := decryptWithIdentity(encryptedData, ltIdentity)
|
||||
if err != nil {
|
||||
Debug("Failed to decrypt secret with mnemonic", "error", err, "secret_name", s.Name)
|
||||
return nil, fmt.Errorf("failed to decrypt secret: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully decrypted secret with mnemonic",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.Int("decrypted_length", len(decryptedData)),
|
||||
)
|
||||
|
||||
return decryptedData, nil
|
||||
}
|
||||
|
||||
Debug("Using unlock key for secret decryption", "secret_name", s.Name)
|
||||
|
||||
// Use the provided unlock key to decrypt the secret
|
||||
// Use the provided unlock key to get the vault's long-term private key
|
||||
if unlockKey == nil {
|
||||
Debug("No unlock key provided for secret decryption", "secret_name", s.Name)
|
||||
return nil, fmt.Errorf("unlock key required to decrypt secret")
|
||||
}
|
||||
|
||||
DebugWith("Delegating secret decryption to unlock key",
|
||||
DebugWith("Getting vault's long-term key using unlock key",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.String("unlock_key_type", unlockKey.GetType()),
|
||||
slog.String("unlock_key_id", unlockKey.GetID()),
|
||||
)
|
||||
|
||||
// Delegate decryption to the unlock key implementation
|
||||
decryptedData, err := unlockKey.DecryptSecret(s)
|
||||
// Step 1: Use the unlock key to get the vault's long-term private key
|
||||
unlockIdentity, err := unlockKey.GetIdentity()
|
||||
if err != nil {
|
||||
Debug("Unlock key failed to decrypt secret", "error", err, "secret_name", s.Name, "unlock_key_type", unlockKey.GetType())
|
||||
return nil, err
|
||||
Debug("Failed to get unlock key identity", "error", err, "secret_name", s.Name, "unlock_key_type", unlockKey.GetType())
|
||||
return nil, fmt.Errorf("failed to get unlock key identity: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully decrypted secret via unlock key",
|
||||
// Read the encrypted long-term private key from the unlock key directory
|
||||
encryptedLtPrivKeyPath := filepath.Join(unlockKey.GetDirectory(), "longterm.age")
|
||||
Debug("Reading encrypted long-term private key", "path", encryptedLtPrivKeyPath)
|
||||
|
||||
encryptedLtPrivKey, err := afero.ReadFile(s.vault.fs, encryptedLtPrivKeyPath)
|
||||
if err != nil {
|
||||
Debug("Failed to read encrypted long-term private key", "error", err, "path", encryptedLtPrivKeyPath)
|
||||
return nil, fmt.Errorf("failed to read encrypted long-term private key: %w", err)
|
||||
}
|
||||
|
||||
// Decrypt the vault's long-term private key using the unlock key
|
||||
Debug("Decrypting vault's long-term private key with unlock key", "secret_name", s.Name)
|
||||
ltPrivKeyData, 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)
|
||||
}
|
||||
|
||||
// Parse the long-term private key
|
||||
Debug("Parsing long-term private key", "secret_name", s.Name)
|
||||
ltIdentity, err := age.ParseX25519Identity(string(ltPrivKeyData))
|
||||
if err != nil {
|
||||
Debug("Failed to parse long-term private key", "error", err, "secret_name", s.Name)
|
||||
return nil, fmt.Errorf("failed to parse long-term private key: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully obtained vault's long-term key",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.String("unlock_key_type", unlockKey.GetType()),
|
||||
slog.Int("decrypted_length", len(decryptedData)),
|
||||
slog.String("public_key", ltIdentity.Recipient().String()),
|
||||
)
|
||||
|
||||
return decryptedData, nil
|
||||
// Use the long-term key to decrypt the secret using per-secret architecture
|
||||
return s.decryptWithLongTermKey(ltIdentity)
|
||||
}
|
||||
|
||||
// decryptWithLongTermKey decrypts the secret using the vault's long-term private key
|
||||
// This implements the per-secret key architecture: longterm -> secret private key -> secret value
|
||||
func (s *Secret) decryptWithLongTermKey(ltIdentity *age.X25519Identity) ([]byte, error) {
|
||||
DebugWith("Decrypting secret with long-term key using per-secret architecture",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.String("vault_name", s.vault.Name),
|
||||
)
|
||||
|
||||
// Step 1: Read the secret's encrypted private key from priv.age
|
||||
encryptedSecretPrivKeyPath := filepath.Join(s.Directory, "priv.age")
|
||||
Debug("Reading encrypted secret private key", "path", encryptedSecretPrivKeyPath)
|
||||
|
||||
encryptedSecretPrivKey, err := afero.ReadFile(s.vault.fs, encryptedSecretPrivKeyPath)
|
||||
if err != nil {
|
||||
Debug("Failed to read encrypted secret private key", "error", err, "path", encryptedSecretPrivKeyPath)
|
||||
return nil, fmt.Errorf("failed to read encrypted secret private key: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Read encrypted secret private key",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.Int("encrypted_length", len(encryptedSecretPrivKey)),
|
||||
)
|
||||
|
||||
// Step 2: Decrypt the secret's private key using the vault's long-term private key
|
||||
Debug("Decrypting secret's private key with vault's long-term key", "secret_name", s.Name)
|
||||
secretPrivKeyData, err := decryptWithIdentity(encryptedSecretPrivKey, ltIdentity)
|
||||
if err != nil {
|
||||
Debug("Failed to decrypt secret's private key", "error", err, "secret_name", s.Name)
|
||||
return nil, fmt.Errorf("failed to decrypt secret's private key: %w", err)
|
||||
}
|
||||
|
||||
// Parse the secret's private key
|
||||
Debug("Parsing secret's private key", "secret_name", s.Name)
|
||||
secretIdentity, err := age.ParseX25519Identity(string(secretPrivKeyData))
|
||||
if err != nil {
|
||||
Debug("Failed to parse secret's private key", "error", err, "secret_name", s.Name)
|
||||
return nil, fmt.Errorf("failed to parse secret's private key: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully decrypted and parsed secret's identity",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.String("secret_public_key", secretIdentity.Recipient().String()),
|
||||
)
|
||||
|
||||
// Step 3: Read the secret's encrypted value from value.age
|
||||
encryptedValuePath := filepath.Join(s.Directory, "value.age")
|
||||
Debug("Reading encrypted secret value", "path", encryptedValuePath)
|
||||
|
||||
encryptedValue, err := afero.ReadFile(s.vault.fs, encryptedValuePath)
|
||||
if err != nil {
|
||||
Debug("Failed to read encrypted secret value", "error", err, "path", encryptedValuePath)
|
||||
return nil, fmt.Errorf("failed to read encrypted secret value: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Read encrypted secret value",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.Int("encrypted_length", len(encryptedValue)),
|
||||
)
|
||||
|
||||
// Step 4: Decrypt the secret's value using the secret's private key
|
||||
Debug("Decrypting secret value with secret's private key", "secret_name", s.Name)
|
||||
decryptedValue, err := decryptWithIdentity(encryptedValue, secretIdentity)
|
||||
if err != nil {
|
||||
Debug("Failed to decrypt secret value", "error", err, "secret_name", s.Name)
|
||||
return nil, fmt.Errorf("failed to decrypt secret value: %w", err)
|
||||
}
|
||||
|
||||
DebugWith("Successfully decrypted secret value using per-secret key architecture",
|
||||
slog.String("secret_name", s.Name),
|
||||
slog.Int("decrypted_length", len(decryptedValue)),
|
||||
)
|
||||
|
||||
return decryptedValue, nil
|
||||
}
|
||||
|
||||
// LoadMetadata loads the secret metadata from disk
|
||||
|
@ -13,7 +13,4 @@ type UnlockKey interface {
|
||||
GetID() string
|
||||
ID() string // Generate ID from the key's public key
|
||||
Remove() error // Remove the unlock key and any associated resources
|
||||
|
||||
// DecryptSecret decrypts a secret using this unlock key's long-term key management
|
||||
DecryptSecret(secret *Secret) ([]byte, error)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user