From 345709a306b49e1db600234ee80dbbdd2b7e873b Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 29 May 2025 10:06:30 -0700 Subject: [PATCH] =?UTF-8?q?refactor:=20Implement=20proper=20separation=20b?= =?UTF-8?q?etween=20unlock=20keys=20and=20secret=20decryption=20-=20Remove?= =?UTF-8?q?=20DecryptSecret=20methods=20from=20all=20unlock=20key=20implem?= =?UTF-8?q?entations=20-=20Secrets=20now=20handle=20their=20own=20decrypti?= =?UTF-8?q?on=20via=20Secret.GetValue(unlockKey)=20-=20Unlock=20keys=20are?= =?UTF-8?q?=20only=20responsible=20for=20vault=20access=20(getting=20long-?= =?UTF-8?q?term=20key)=20-=20Add=20decryptWithLongTermKey=20helper=20for?= =?UTF-8?q?=20per-secret=20key=20architecture=20-=20Fix=20vault=20import?= =?UTF-8?q?=20to=20work=20in=20non-interactive=20mode=20without=20unlock?= =?UTF-8?q?=20keys=20-=20Maintain=20clean=20architecture:=20unlock=20keys?= =?UTF-8?q?=20=E2=86=92=20vault=20access=20=E2=86=92=20secret=20decryption?= =?UTF-8?q?=20-=20All=20tests=20passing=20with=20new=20architecture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/secret/cli.go | 49 +++++---- internal/secret/keychainunlock.go | 98 ------------------ internal/secret/passphraseunlock.go | 87 ---------------- internal/secret/pgpunlock.go | 118 --------------------- internal/secret/secret.go | 154 +++++++++++++++++++++------- internal/secret/unlock.go | 3 - 6 files changed, 146 insertions(+), 363 deletions(-) diff --git a/internal/secret/cli.go b/internal/secret/cli.go index 2d91a0c..df40e0f 100644 --- a/internal/secret/cli.go +++ b/internal/secret/cli.go @@ -1480,27 +1480,38 @@ func (cli *CLIInstance) importMnemonic(vaultName, mnemonic string) error { vault := NewVault(cli.fs, vaultName, cli.stateDir) vault.Unlock(ltIdentity) - // Get or create passphrase for unlock key - var passphraseStr string - if envPassphrase := os.Getenv(EnvUnlockPassphrase); envPassphrase != "" { - passphraseStr = envPassphrase - } else { - // 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) - } - } - - // 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("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) + + // 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 != "" { + passphraseStr = envPassphrase + } else { + // Use secure passphrase input with confirmation + passphraseStr, err = readSecurePassphrase("Enter passphrase for unlock key: ") + if err != nil { + 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 { + 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("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 } diff --git a/internal/secret/keychainunlock.go b/internal/secret/keychainunlock.go index 69b1a80..e4b6b76 100644 --- a/internal/secret/keychainunlock.go +++ b/internal/secret/keychainunlock.go @@ -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{ diff --git a/internal/secret/passphraseunlock.go b/internal/secret/passphraseunlock.go index 5792c79..a0a3350 100644 --- a/internal/secret/passphraseunlock.go +++ b/internal/secret/passphraseunlock.go @@ -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 -} diff --git a/internal/secret/pgpunlock.go b/internal/secret/pgpunlock.go index 6a5cbe9..7fbd19c 100644 --- a/internal/secret/pgpunlock.go +++ b/internal/secret/pgpunlock.go @@ -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{ diff --git a/internal/secret/secret.go b/internal/secret/secret.go index c78493f..02de075 100644 --- a/internal/secret/secret.go +++ b/internal/secret/secret.go @@ -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 - } - - DebugWith("Retrieved encrypted data for mnemonic decryption", - slog.String("secret_name", s.Name), - slog.Int("encrypted_length", len(encryptedData)), - ) - - // 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 + // Use the long-term key to decrypt the secret using per-secret architecture + return s.decryptWithLongTermKey(ltIdentity) } - Debug("Using unlock key for secret decryption", "secret_name", s.Name) + Debug("Using unlock key for vault access", "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 diff --git a/internal/secret/unlock.go b/internal/secret/unlock.go index 00ca4f6..ace4838 100644 --- a/internal/secret/unlock.go +++ b/internal/secret/unlock.go @@ -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) }