man what a clusterfuck

This commit is contained in:
Jeffrey Paul 2025-05-29 08:21:05 -07:00
parent 1b8ea9695b
commit ee49ace397
9 changed files with 437 additions and 102 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
secret

122
README.md
View File

@ -22,7 +22,7 @@ Build from source:
```bash ```bash
git clone <repository> git clone <repository>
cd secret cd secret
make build go build -o secret ./cmd/secret
``` ```
## Quick Start ## Quick Start
@ -106,15 +106,15 @@ Lists all unlock keys in the current vault with their metadata.
Creates a new unlock key of the specified type: Creates a new unlock key of the specified type:
**Types:** **Types:**
- `passphrase`: Password-protected unlock key - `passphrase`: Traditional passphrase-protected unlock key
- `keychain`: macOS Keychain unlock key (Touch ID/Face ID) - `keychain`: macOS Keychain-protected unlock key (macOS only)
- `pgp`: GPG/PGP key unlock key - `pgp`: Uses an existing GPG key for encryption/decryption
**Options:** **Options:**
- `--keyid <id>`: GPG key ID (required for PGP type) - `--keyid <id>`: GPG key ID (required for PGP type)
#### `secret keys rm <key-id>` #### `secret keys rm <key-id>`
Removes an unlock key from the current vault. Removes an unlock key.
#### `secret key select <key-id>` #### `secret key select <key-id>`
Selects an unlock key as the current default for operations. Selects an unlock key as the current default for operations.
@ -127,9 +127,6 @@ Imports a secret from a file and stores it in the current vault under the given
#### `secret vault import [vault-name]` #### `secret vault import [vault-name]`
Imports a mnemonic phrase into the specified vault (defaults to "default"). Imports a mnemonic phrase into the specified vault (defaults to "default").
#### `secret enroll`
Enrolls a macOS Keychain unlock key for biometric authentication.
### Encryption Operations ### Encryption Operations
#### `secret encrypt <secret-name> [--input=file] [--output=file]` #### `secret encrypt <secret-name> [--input=file] [--output=file]`
@ -143,43 +140,23 @@ Decrypts data using an Age key stored as a secret.
### Directory Structure ### Directory Structure
``` ```
$BASE/ # ~/.config/berlin.sneak.pkg.secret (Linux) or ~/Library/Application Support/berlin.sneak.pkg.secret (macOS) ~/.local/share/secret/
├── configuration.json # Global configuration ├── vaults.d/
├── currentvault -> vaults.d/default # Symlink to current vault │ ├── default/
└── vaults.d/ │ │ ├── unlock-keys.d/
├── default/ # Default vault │ │ │ ├── passphrase/ # Passphrase unlock key
│ ├── vault-metadata.json # Vault metadata │ │ │ ├── keychain/ # Keychain unlock key (macOS)
│ ├── pub.age # Long-term public key │ │ │ └── pgp/ # PGP unlock key
│ ├── current-unlock-key -> unlock.d/passphrase # Current unlock key symlink │ │ ├── secrets.d/
│ ├── unlock.d/ # Unlock keys directory │ │ │ ├── api%key/ # Secret: api/key
│ │ ├── passphrase/ # Passphrase unlock key │ │ │ └── database%password/ # Secret: database/password
│ │ │ ├── unlock-metadata.json # Unlock key metadata │ │ └── current-unlock-key -> ../unlock-keys.d/passphrase
│ │ │ ├── pub.age # Unlock key public key │ └── work/
│ │ │ ├── priv.age # Unlock key private key (encrypted) │ ├── unlock-keys.d/
│ │ │ └── longterm.age # Long-term private key (encrypted to this unlock key) │ ├── secrets.d/
│ │ ├── keychain/ # Keychain unlock key │ └── current-unlock-key
│ │ │ ├── unlock-metadata.json ├── currentvault -> vaults.d/default
│ │ │ ├── pub.age └── configuration.json
│ │ │ ├── priv.age
│ │ │ └── longterm.age
│ │ └── pgp/ # PGP unlock key
│ │ ├── unlock-metadata.json
│ │ ├── pub.age
│ │ ├── priv.asc # PGP-encrypted private key
│ │ └── longterm.age
│ └── secrets.d/ # Secrets directory
│ ├── my-service%password/ # Secret directory (slashes encoded as %)
│ │ ├── value.age # Encrypted secret value
│ │ ├── pub.age # Secret-specific public key
│ │ ├── priv.age # Secret-specific private key (encrypted to long-term key)
│ │ └── secret-metadata.json # Secret metadata
│ └── api%keys%production/
│ ├── value.age
│ ├── pub.age
│ ├── priv.age
│ └── secret-metadata.json
└── work/ # Additional vault
└── ... (same structure as default)
``` ```
### Key Management and Encryption Flow ### Key Management and Encryption Flow
@ -192,19 +169,22 @@ $BASE/ # ~/.config/berlin.sneak.pkg.secret
#### Unlock Keys #### Unlock Keys
Unlock keys provide different authentication methods to access the long-term keys: Unlock keys provide different authentication methods to access the long-term keys:
1. **Passphrase Unlock Keys**: 1. **Passphrase Keys**:
- Private key encrypted using a user-provided passphrase - Encrypted with user-provided passphrase
- Stored as encrypted Age identity in `priv.age` - Stored as encrypted Age keys
- Cross-platform compatible
2. **macOS Keychain Keys**: 2. **Keychain Keys** (macOS only):
- Private key stored in the macOS Keychain - Uses macOS Keychain for secure storage
- Requires biometric authentication (Touch ID/Face ID) - Provides seamless authentication on macOS systems
- Provides hardware-backed security - Age private key encrypted with random passphrase stored in Keychain
3. **PGP Unlock Keys**: 3. **PGP Keys**:
- Private key encrypted using an existing GPG key - Uses existing GPG key infrastructure
- Compatible with hardware tokens (YubiKey, etc.) - Leverages existing key management workflows
- Stored as PGP-encrypted data in `priv.asc` - Strong authentication through GPG
Each vault maintains its own set of unlock keys and one long-term key. The long-term key is encrypted to each unlock key, allowing any authorized unlock key to access vault secrets.
#### Secret-specific Keys #### Secret-specific Keys
- Each secret has its own encryption key pair - Each secret has its own encryption key pair
@ -234,9 +214,9 @@ Unlock keys provide different authentication methods to access the long-term key
- Per-secret encryption keys limit exposure if compromised - Per-secret encryption keys limit exposure if compromised
- Long-term keys protected by multiple unlock key layers - Long-term keys protected by multiple unlock key layers
### Hardware Integration ### Platform Integration
- macOS Keychain support for biometric authentication - macOS Keychain integration for seamless authentication
- Hardware token support via PGP/GPG integration - GPG integration for existing key management workflows
## Examples ## Examples
@ -266,7 +246,7 @@ secret vault create personal
# Work with work vault # Work with work vault
secret vault select work secret vault select work
echo "work-db-pass" | secret add database/password echo "work-db-pass" | secret add database/password
secret keys add keychain # Add Touch ID authentication secret keys add passphrase # Add passphrase authentication
# Switch to personal vault # Switch to personal vault
secret vault select personal secret vault select personal
@ -280,7 +260,7 @@ secret vault list
```bash ```bash
# Add multiple unlock methods # Add multiple unlock methods
secret keys add passphrase # Password-based secret keys add passphrase # Password-based
secret keys add keychain # Touch ID (macOS only) secret keys add keychain # macOS Keychain (macOS only)
secret keys add pgp --keyid ABCD1234 # GPG key secret keys add pgp --keyid ABCD1234 # GPG key
# List unlock keys # List unlock keys
@ -325,11 +305,11 @@ secret decrypt encryption/mykey --input document.txt.age --output document.txt
### Threat Model ### Threat Model
- Protects against unauthorized access to secret values - Protects against unauthorized access to secret values
- Provides defense against compromise of individual components - Provides defense against compromise of individual components
- Supports hardware-backed authentication where available - Supports platform-specific authentication where available
### Best Practices ### Best Practices
1. Use strong, unique passphrases for unlock keys 1. Use strong, unique passphrases for unlock keys
2. Enable hardware authentication (Keychain, hardware tokens) when available 2. Enable platform-specific authentication (Keychain) when available
3. Regularly audit unlock keys and remove unused ones 3. Regularly audit unlock keys and remove unused ones
4. Keep mnemonic phrases securely backed up offline 4. Keep mnemonic phrases securely backed up offline
5. Use separate vaults for different security contexts 5. Use separate vaults for different security contexts
@ -337,15 +317,15 @@ secret decrypt encryption/mykey --input document.txt.age --output document.txt
### Limitations ### Limitations
- Requires access to unlock keys for secret retrieval - Requires access to unlock keys for secret retrieval
- Mnemonic phrases must be securely stored and backed up - Mnemonic phrases must be securely stored and backed up
- Hardware features limited to supported platforms - Platform-specific features limited to supported platforms
## Development ## Development
### Building ### Building
```bash ```bash
make build # Build binary go build -o secret ./cmd/secret # Build binary
make test # Run tests go test ./... # Run tests
make lint # Run linter go vet ./... # Run static analysis
``` ```
### Testing ### Testing
@ -355,3 +335,11 @@ The project includes comprehensive tests:
go test ./... # Unit tests go test ./... # Unit tests
``` ```
## Features
- **Multiple Authentication Methods**: Supports passphrase-based, keychain-based (macOS), and PGP-based unlock keys
- **Vault Isolation**: Complete separation between different vaults
- **Per-Secret Encryption**: Each secret has its own encryption key
- **BIP39 Mnemonic Support**: Keyless operation using mnemonic phrases
- **Cross-Platform**: Works on macOS, Linux, and other Unix-like systems

5
cmd/secret/main.go Normal file
View File

@ -0,0 +1,5 @@
package main
func main() {
CLIEntry()
}

View File

@ -134,7 +134,6 @@ func newRootCmd() *cobra.Command {
cmd.AddCommand(newKeysCmd()) cmd.AddCommand(newKeysCmd())
cmd.AddCommand(newKeyCmd()) cmd.AddCommand(newKeyCmd())
cmd.AddCommand(newImportCmd()) cmd.AddCommand(newImportCmd())
cmd.AddCommand(newEnrollCmd())
cmd.AddCommand(newEncryptCmd()) cmd.AddCommand(newEncryptCmd())
cmd.AddCommand(newDecryptCmd()) cmd.AddCommand(newDecryptCmd())
@ -431,19 +430,6 @@ func newImportCmd() *cobra.Command {
return cmd return cmd
} }
func newEnrollCmd() *cobra.Command {
return &cobra.Command{
Use: "enroll",
Short: "Enroll a macOS Keychain unlock key",
Long: `Enroll a macOS Keychain unlock key that uses Touch ID/Face ID for biometric authentication.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cli := NewCLIInstance()
return cli.Enroll()
},
}
}
func newEncryptCmd() *cobra.Command { func newEncryptCmd() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "encrypt <secret-name>", Use: "encrypt <secret-name>",
@ -1073,7 +1059,16 @@ func (cli *CLIInstance) KeysAdd(keyType string, cmd *cobra.Command) error {
return nil return nil
case "keychain": case "keychain":
return fmt.Errorf("macOS Keychain unlock keys should be created using 'secret enroll' command") keychainKey, err := CreateKeychainUnlockKey(cli.fs, cli.stateDir)
if err != nil {
return fmt.Errorf("failed to create macOS Keychain unlock key: %w", err)
}
cmd.Printf("Created macOS Keychain unlock key: %s\n", keychainKey.GetMetadata().ID)
if keyName, err := keychainKey.GetKeychainItemName(); err == nil {
cmd.Printf("Keychain Item Name: %s\n", keyName)
}
return nil
case "pgp": case "pgp":
// Get GPG key ID from flag or environment variable // Get GPG key ID from flag or environment variable
@ -1150,25 +1145,6 @@ func (cli *CLIInstance) Import(vaultName string) error {
return cli.importMnemonic(vaultName, mnemonicStr) return cli.importMnemonic(vaultName, mnemonicStr)
} }
// Enroll enrolls a hardware security module
func (cli *CLIInstance) Enroll() error {
keychainKey, err := CreateKeychainUnlockKey(cli.fs, cli.stateDir)
if err != nil {
return fmt.Errorf("failed to enroll macOS Keychain unlock key: %w", err)
}
fmt.Printf("macOS Keychain unlock key enrolled successfully!\n")
fmt.Printf("Key ID: %s\n", keychainKey.GetMetadata().ID)
fmt.Printf("Directory: %s\n", keychainKey.GetDirectory())
// Load the key name to show the keychain key name
if keyName, err := keychainKey.GetKeyName(); err == nil {
fmt.Printf("Keychain Key Name: %s\n", keyName)
}
return nil
}
// Encrypt encrypts data using an age secret key stored in a secret // Encrypt encrypts data using an age secret key stored in a secret
func (cli *CLIInstance) Encrypt(secretName, inputFile, outputFile string) error { func (cli *CLIInstance) Encrypt(secretName, inputFile, outputFile string) error {
// Get current vault // Get current vault

112
internal/secret/crypto.go Normal file
View File

@ -0,0 +1,112 @@
package secret
import (
"bytes"
"fmt"
"io"
"syscall"
"filippo.io/age"
"golang.org/x/term"
)
// encryptToRecipient encrypts data to a recipient using age
func encryptToRecipient(data []byte, recipient age.Recipient) ([]byte, error) {
var buf bytes.Buffer
w, err := age.Encrypt(&buf, recipient)
if err != nil {
return nil, fmt.Errorf("failed to create encryptor: %w", err)
}
if _, err := w.Write(data); err != nil {
return nil, fmt.Errorf("failed to write data: %w", err)
}
if err := w.Close(); err != nil {
return nil, fmt.Errorf("failed to close encryptor: %w", err)
}
return buf.Bytes(), nil
}
// decryptWithIdentity decrypts data with an identity using age
func decryptWithIdentity(data []byte, identity age.Identity) ([]byte, error) {
r, err := age.Decrypt(bytes.NewReader(data), identity)
if err != nil {
return nil, fmt.Errorf("failed to create decryptor: %w", err)
}
result, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("failed to read decrypted data: %w", err)
}
return result, nil
}
// encryptWithPassphrase encrypts data using a passphrase with age's scrypt-based encryption
func encryptWithPassphrase(data []byte, passphrase string) ([]byte, error) {
recipient, err := age.NewScryptRecipient(passphrase)
if err != nil {
return nil, fmt.Errorf("failed to create scrypt recipient: %w", err)
}
return encryptToRecipient(data, recipient)
}
// decryptWithPassphrase decrypts data using a passphrase with age's scrypt-based decryption
func decryptWithPassphrase(encryptedData []byte, passphrase string) ([]byte, error) {
identity, err := age.NewScryptIdentity(passphrase)
if err != nil {
return nil, fmt.Errorf("failed to create scrypt identity: %w", err)
}
return decryptWithIdentity(encryptedData, identity)
}
// readPassphrase reads a passphrase securely from the terminal without echoing
// This version is for unlocking and doesn't require confirmation
func readPassphrase(prompt string) (string, error) {
// Check if stdin is a terminal
if !term.IsTerminal(int(syscall.Stdin)) {
// Not a terminal - fall back to regular input
fmt.Print(prompt)
var passphrase string
_, err := fmt.Scanln(&passphrase)
if err != nil {
return "", fmt.Errorf("failed to read passphrase: %w", err)
}
return passphrase, nil
}
// Terminal input - use secure password reading
fmt.Print(prompt)
passphrase, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return "", fmt.Errorf("failed to read passphrase: %w", err)
}
fmt.Println() // Print newline since ReadPassword doesn't echo
if len(passphrase) == 0 {
return "", fmt.Errorf("passphrase cannot be empty")
}
return string(passphrase), nil
}
// decryptSecretWithLongTermKey is a helper that parses a long-term private key and uses it to decrypt secret data
func decryptSecretWithLongTermKey(ltPrivKeyData []byte, encryptedData []byte) ([]byte, error) {
// Parse long-term private key
ltIdentity, err := age.ParseX25519Identity(string(ltPrivKeyData))
if err != nil {
return nil, fmt.Errorf("failed to parse long-term private key: %w", err)
}
// Decrypt secret data using long-term key
decryptedData, err := decryptWithIdentity(encryptedData, ltIdentity)
if err != nil {
return nil, fmt.Errorf("failed to decrypt secret: %w", err)
}
return decryptedData, nil
}

View File

@ -0,0 +1,231 @@
package secret
import (
"fmt"
"log/slog"
"os"
"path/filepath"
"filippo.io/age"
"github.com/spf13/afero"
)
// PassphraseUnlockKey represents a passphrase-protected unlock key
type PassphraseUnlockKey struct {
Directory string
Metadata UnlockKeyMetadata
fs afero.Fs
}
// GetIdentity implements UnlockKey interface for passphrase-based unlock keys
func (p *PassphraseUnlockKey) GetIdentity() (*age.X25519Identity, error) {
DebugWith("Getting passphrase unlock key identity",
slog.String("key_id", p.GetID()),
slog.String("key_type", p.GetType()),
)
// Read encrypted private key of unlock key
unlockKeyPrivPath := filepath.Join(p.Directory, "priv.age")
Debug("Reading encrypted passphrase unlock key", "path", unlockKeyPrivPath)
encryptedPrivKeyData, err := afero.ReadFile(p.fs, unlockKeyPrivPath)
if err != nil {
Debug("Failed to read passphrase unlock key private key", "error", err, "path", unlockKeyPrivPath)
return nil, fmt.Errorf("failed to read unlock key private key: %w", err)
}
DebugWith("Read encrypted passphrase unlock key",
slog.String("key_id", p.GetID()),
slog.Int("encrypted_length", len(encryptedPrivKeyData)),
)
// Get passphrase for decrypting the unlock key
var passphraseStr string
if envPassphrase := os.Getenv(EnvUnlockPassphrase); envPassphrase != "" {
Debug("Using passphrase from environment variable", "key_id", p.GetID())
passphraseStr = envPassphrase
} else {
Debug("Prompting for passphrase", "key_id", p.GetID())
// Prompt for passphrase
passphraseStr, err = readPassphrase("Enter passphrase to unlock vault: ")
if err != nil {
Debug("Failed to read passphrase", "error", err, "key_id", p.GetID())
return nil, fmt.Errorf("failed to read passphrase: %w", err)
}
}
Debug("Decrypting unlock key private key with passphrase", "key_id", p.GetID())
// Decrypt the unlock key private key with passphrase
privKeyData, err := decryptWithPassphrase(encryptedPrivKeyData, passphraseStr)
if err != nil {
Debug("Failed to decrypt unlock key private key", "error", err, "key_id", p.GetID())
return nil, fmt.Errorf("failed to decrypt unlock key private key: %w", err)
}
DebugWith("Successfully decrypted unlock key private key",
slog.String("key_id", p.GetID()),
slog.Int("decrypted_length", len(privKeyData)),
)
// Parse the decrypted private key
Debug("Parsing decrypted unlock key identity", "key_id", p.GetID())
identity, err := age.ParseX25519Identity(string(privKeyData))
if err != nil {
Debug("Failed to parse unlock key private key", "error", err, "key_id", p.GetID())
return nil, fmt.Errorf("failed to parse unlock key private key: %w", err)
}
DebugWith("Successfully parsed passphrase unlock key identity",
slog.String("key_id", p.GetID()),
slog.String("public_key", identity.Recipient().String()),
)
return identity, nil
}
// GetType implements UnlockKey interface
func (p *PassphraseUnlockKey) GetType() string {
return "passphrase"
}
// GetMetadata implements UnlockKey interface
func (p *PassphraseUnlockKey) GetMetadata() UnlockKeyMetadata {
return p.Metadata
}
// GetDirectory implements UnlockKey interface
func (p *PassphraseUnlockKey) GetDirectory() string {
return p.Directory
}
// GetID implements UnlockKey interface
func (p *PassphraseUnlockKey) GetID() string {
return p.Metadata.ID
}
// ID implements UnlockKey interface - generates ID from creation timestamp
func (p *PassphraseUnlockKey) ID() string {
// Generate ID using creation timestamp: YYYY-MM-DD.HH.mm-passphrase
createdAt := p.Metadata.CreatedAt
return fmt.Sprintf("%s-passphrase", createdAt.Format("2006-01-02.15.04"))
}
// Remove implements UnlockKey interface - removes the passphrase unlock key
func (p *PassphraseUnlockKey) Remove() error {
// For passphrase keys, 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 {
return fmt.Errorf("failed to remove passphrase unlock key directory: %w", err)
}
return nil
}
// NewPassphraseUnlockKey creates a new PassphraseUnlockKey instance
func NewPassphraseUnlockKey(fs afero.Fs, directory string, metadata UnlockKeyMetadata) *PassphraseUnlockKey {
return &PassphraseUnlockKey{
Directory: directory,
Metadata: metadata,
fs: fs,
}
}
// CreatePassphraseKey creates a new passphrase-protected unlock key
func CreatePassphraseKey(fs afero.Fs, stateDir string, passphrase string) (*PassphraseUnlockKey, error) {
// Get current vault
currentVault, err := GetCurrentVault(fs, stateDir)
if err != nil {
return nil, fmt.Errorf("failed to get current vault: %w", err)
}
return currentVault.CreatePassphraseKey(passphrase)
}
// DecryptLongTermKey decrypts and returns the long-term private key for this vault
func (p *PassphraseUnlockKey) DecryptLongTermKey() ([]byte, error) {
DebugWith("Decrypting long-term key with passphrase unlock key",
slog.String("key_id", p.GetID()),
slog.String("key_type", p.GetType()),
)
// Get our unlock key identity
unlockIdentity, err := p.GetIdentity()
if err != nil {
Debug("Failed to get passphrase unlock identity for long-term decryption", "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)),
)
return ltPrivKeyData, nil
}
// 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 encrypted secret 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)),
)
// Decrypt long-term private key using our unlock key
ltPrivKeyData, err := p.DecryptLongTermKey()
if err != nil {
Debug("Failed to decrypt long-term private key for secret decryption", "error", err, "key_id", p.GetID())
return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err)
}
// Use helper to parse long-term key and decrypt secret
decryptedData, err := decryptSecretWithLongTermKey(ltPrivKeyData, encryptedData)
if err != nil {
Debug("Failed to decrypt secret with long-term key", "error", err, "secret_name", secret.Name, "key_id", p.GetID())
return nil, 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
}

View File

@ -0,0 +1 @@

22
internal/secret/unlock.go Normal file
View File

@ -0,0 +1,22 @@
package secret
import (
"filippo.io/age"
)
// UnlockKey interface defines the methods all unlock key types must implement
type UnlockKey interface {
GetIdentity() (*age.X25519Identity, error)
GetType() string
GetMetadata() UnlockKeyMetadata
GetDirectory() string
GetID() string
ID() string // Generate ID from the key's public key
Remove() error // Remove the unlock key and any associated resources
// DecryptLongTermKey decrypts and returns the long-term private key for this vault
DecryptLongTermKey() ([]byte, error)
// DecryptSecret decrypts a secret using this unlock key's long-term key management
DecryptSecret(secret *Secret) ([]byte, error)
}

1
internal/secret/vault.go Normal file
View File

@ -0,0 +1 @@