Remove duplicated wrapper crypto functions and use exported implementations directly
This commit is contained in:
parent
8cc15fde3d
commit
8dc2e9d748
@ -6,7 +6,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"filippo.io/age"
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
@ -15,7 +14,6 @@ import (
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func newInitCmd() *cobra.Command {
|
||||
@ -173,45 +171,24 @@ func (cli *CLIInstance) Init(cmd *cobra.Command) error {
|
||||
}
|
||||
|
||||
// readSecurePassphrase reads a passphrase securely from the terminal without echoing
|
||||
// and prompts for confirmation. Falls back to regular input when not on a terminal.
|
||||
// This version adds confirmation (read twice) for creating new unlock keys
|
||||
func readSecurePassphrase(prompt string) (string, error) {
|
||||
// Check if stdin is a terminal
|
||||
if !term.IsTerminal(int(syscall.Stdin)) {
|
||||
// Not a terminal - never read passphrases from piped input for security reasons
|
||||
return "", fmt.Errorf("cannot read passphrase from non-terminal stdin (piped input or script). Please set the SB_UNLOCK_PASSPHRASE environment variable or run interactively")
|
||||
}
|
||||
|
||||
// Check if stderr is a terminal - if not, we can't prompt interactively
|
||||
if !term.IsTerminal(int(syscall.Stderr)) {
|
||||
return "", fmt.Errorf("cannot prompt for passphrase: stderr is not a terminal (running in non-interactive mode). Please set the SB_UNLOCK_PASSPHRASE environment variable")
|
||||
}
|
||||
|
||||
// Terminal input - use secure password reading with confirmation
|
||||
fmt.Fprint(os.Stderr, prompt) // Write prompt to stderr, not stdout
|
||||
|
||||
// Read first passphrase
|
||||
passphrase1, err := term.ReadPassword(int(syscall.Stdin))
|
||||
// Get the first passphrase
|
||||
passphrase1, err := secret.ReadPassphrase(prompt)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read passphrase: %w", err)
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintln(os.Stderr) // Print newline to stderr since ReadPassword doesn't echo
|
||||
|
||||
// Read confirmation passphrase
|
||||
fmt.Fprint(os.Stderr, "Confirm passphrase: ") // Write prompt to stderr, not stdout
|
||||
passphrase2, err := term.ReadPassword(int(syscall.Stdin))
|
||||
passphrase2, err := secret.ReadPassphrase("Confirm passphrase: ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read passphrase confirmation: %w", err)
|
||||
}
|
||||
fmt.Fprintln(os.Stderr) // Print newline to stderr since ReadPassword doesn't echo
|
||||
|
||||
// Compare passphrases
|
||||
if string(passphrase1) != string(passphrase2) {
|
||||
if passphrase1 != passphrase2 {
|
||||
return "", fmt.Errorf("passphrases do not match")
|
||||
}
|
||||
|
||||
if len(passphrase1) == 0 {
|
||||
return "", fmt.Errorf("passphrase cannot be empty")
|
||||
}
|
||||
|
||||
return string(passphrase1), nil
|
||||
return passphrase1, nil
|
||||
}
|
||||
|
@ -14,6 +14,10 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Import from init.go
|
||||
|
||||
// ... existing imports ...
|
||||
|
||||
func newKeysCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "keys",
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
|
||||
// EncryptToRecipient encrypts data to a recipient using age
|
||||
func EncryptToRecipient(data []byte, recipient age.Recipient) ([]byte, error) {
|
||||
Debug("encryptToRecipient starting", "data_length", len(data))
|
||||
Debug("EncryptToRecipient starting", "data_length", len(data))
|
||||
|
||||
var buf bytes.Buffer
|
||||
Debug("Creating age encryptor")
|
||||
@ -39,22 +39,12 @@ func EncryptToRecipient(data []byte, recipient age.Recipient) ([]byte, error) {
|
||||
Debug("Closed encryptor successfully")
|
||||
|
||||
result := buf.Bytes()
|
||||
Debug("encryptToRecipient completed successfully", "result_length", len(result))
|
||||
Debug("EncryptToRecipient completed successfully", "result_length", len(result))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// encryptToRecipient encrypts data to a recipient using age (internal version)
|
||||
func encryptToRecipient(data []byte, recipient age.Recipient) ([]byte, error) {
|
||||
return EncryptToRecipient(data, recipient)
|
||||
}
|
||||
|
||||
// DecryptWithIdentity decrypts data with an identity using age (public version)
|
||||
// DecryptWithIdentity decrypts data with an identity using age
|
||||
func DecryptWithIdentity(data []byte, identity age.Identity) ([]byte, error) {
|
||||
return decryptWithIdentity(data, identity)
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -68,34 +58,29 @@ func decryptWithIdentity(data []byte, identity age.Identity) ([]byte, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EncryptWithPassphrase encrypts data using a passphrase with age's scrypt-based encryption (public version)
|
||||
// EncryptWithPassphrase encrypts data using a passphrase with age's scrypt-based encryption
|
||||
func EncryptWithPassphrase(data []byte, passphrase string) ([]byte, error) {
|
||||
return encryptWithPassphrase(data, passphrase)
|
||||
}
|
||||
|
||||
// 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)
|
||||
return EncryptToRecipient(data, recipient)
|
||||
}
|
||||
|
||||
// decryptWithPassphrase decrypts data using a passphrase with age's scrypt-based decryption
|
||||
func decryptWithPassphrase(encryptedData []byte, passphrase string) ([]byte, error) {
|
||||
// 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)
|
||||
return DecryptWithIdentity(encryptedData, identity)
|
||||
}
|
||||
|
||||
// readPassphrase reads a passphrase securely from the terminal without echoing
|
||||
// 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) {
|
||||
func ReadPassphrase(prompt string) (string, error) {
|
||||
// Check if stdin is a terminal
|
||||
if !term.IsTerminal(int(syscall.Stdin)) {
|
||||
// Not a terminal - never read passphrases from piped input for security reasons
|
||||
|
@ -91,7 +91,7 @@ func (k *KeychainUnlockKey) GetIdentity() (*age.X25519Identity, error) {
|
||||
|
||||
// Step 5: Decrypt the age private key using the passphrase from keychain
|
||||
Debug("Decrypting age private key with keychain passphrase", "key_id", k.GetID())
|
||||
agePrivKeyData, err := decryptWithPassphrase(encryptedAgePrivKeyData, keychainData.AgePrivKeyPassphrase)
|
||||
agePrivKeyData, err := DecryptWithPassphrase(encryptedAgePrivKeyData, keychainData.AgePrivKeyPassphrase)
|
||||
if err != nil {
|
||||
Debug("Failed to decrypt age private key with keychain passphrase", "error", err, "key_id", k.GetID())
|
||||
return nil, fmt.Errorf("failed to decrypt age private key with keychain passphrase: %w", err)
|
||||
@ -265,7 +265,7 @@ func CreateKeychainUnlockKey(fs afero.Fs, stateDir string) (*KeychainUnlockKey,
|
||||
|
||||
// Step 4: Encrypt age private key with the generated passphrase and store on disk
|
||||
agePrivateKeyBytes := []byte(ageIdentity.String())
|
||||
encryptedAgePrivKey, err := encryptWithPassphrase(agePrivateKeyBytes, agePrivKeyPassphrase)
|
||||
encryptedAgePrivKey, err := EncryptWithPassphrase(agePrivateKeyBytes, agePrivKeyPassphrase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt age private key with passphrase: %w", err)
|
||||
}
|
||||
@ -328,14 +328,14 @@ func CreateKeychainUnlockKey(fs afero.Fs, stateDir string) (*KeychainUnlockKey,
|
||||
}
|
||||
|
||||
// Decrypt long-term private key using current unlock key
|
||||
ltPrivKeyData, err = decryptWithIdentity(encryptedLtPrivKey, currentUnlockIdentity)
|
||||
ltPrivKeyData, err = DecryptWithIdentity(encryptedLtPrivKey, currentUnlockIdentity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Encrypt long-term private key to the new age unlock key
|
||||
encryptedLtPrivKeyToAge, err := encryptToRecipient(ltPrivKeyData, ageIdentity.Recipient())
|
||||
encryptedLtPrivKeyToAge, err := EncryptToRecipient(ltPrivKeyData, ageIdentity.Recipient())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt long-term private key to age unlock key: %w", err)
|
||||
}
|
||||
|
@ -12,9 +12,10 @@ import (
|
||||
|
||||
// PassphraseUnlockKey represents a passphrase-protected unlock key
|
||||
type PassphraseUnlockKey struct {
|
||||
Directory string
|
||||
Metadata UnlockKeyMetadata
|
||||
fs afero.Fs
|
||||
Directory string
|
||||
Metadata UnlockKeyMetadata
|
||||
fs afero.Fs
|
||||
Passphrase string
|
||||
}
|
||||
|
||||
// GetIdentity implements UnlockKey interface for passphrase-based unlock keys
|
||||
@ -24,6 +25,28 @@ func (p *PassphraseUnlockKey) GetIdentity() (*age.X25519Identity, error) {
|
||||
slog.String("key_type", p.GetType()),
|
||||
)
|
||||
|
||||
// First check if we already have the passphrase
|
||||
passphraseStr := p.Passphrase
|
||||
if passphraseStr == "" {
|
||||
Debug("No passphrase in memory, checking environment")
|
||||
// Check environment variable for passphrase
|
||||
passphraseStr = os.Getenv(EnvUnlockPassphrase)
|
||||
if passphraseStr == "" {
|
||||
Debug("No passphrase in environment, prompting user")
|
||||
// Prompt for passphrase
|
||||
var err error
|
||||
passphraseStr, err = ReadPassphrase("Enter unlock passphrase: ")
|
||||
if err != nil {
|
||||
Debug("Failed to read passphrase", "error", err, "key_id", p.GetID())
|
||||
return nil, fmt.Errorf("failed to read passphrase: %w", err)
|
||||
}
|
||||
} else {
|
||||
Debug("Using passphrase from environment", "key_id", p.GetID())
|
||||
}
|
||||
} else {
|
||||
Debug("Using in-memory passphrase", "key_id", p.GetID())
|
||||
}
|
||||
|
||||
// Read encrypted private key of unlock key
|
||||
unlockKeyPrivPath := filepath.Join(p.Directory, "priv.age")
|
||||
Debug("Reading encrypted passphrase unlock key", "path", unlockKeyPrivPath)
|
||||
@ -39,25 +62,10 @@ func (p *PassphraseUnlockKey) GetIdentity() (*age.X25519Identity, error) {
|
||||
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)
|
||||
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)
|
||||
|
@ -250,15 +250,15 @@ func CreatePGPUnlockKey(fs afero.Fs, stateDir string, gpgKeyID string) (*PGPUnlo
|
||||
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)
|
||||
// Step 6: 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)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Encrypt long-term private key to the new age unlock key
|
||||
encryptedLtPrivKeyToAge, err := encryptToRecipient(ltPrivKeyData, ageIdentity.Recipient())
|
||||
// Step 7: Encrypt long-term private key to the new age unlock key
|
||||
encryptedLtPrivKeyToAge, err := EncryptToRecipient(ltPrivKeyData, ageIdentity.Recipient())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt long-term private key to age unlock key: %w", err)
|
||||
}
|
||||
@ -269,7 +269,7 @@ func CreatePGPUnlockKey(fs afero.Fs, stateDir string, gpgKeyID string) (*PGPUnlo
|
||||
return nil, fmt.Errorf("failed to write encrypted long-term private key: %w", err)
|
||||
}
|
||||
|
||||
// Step 5: Encrypt age private key to the GPG key ID
|
||||
// Step 8: Encrypt age private key to the GPG key ID
|
||||
agePrivateKeyBytes := []byte(ageIdentity.String())
|
||||
encryptedAgePrivKey, err := gpgEncrypt(agePrivateKeyBytes, gpgKeyID)
|
||||
if err != nil {
|
||||
@ -281,7 +281,7 @@ func CreatePGPUnlockKey(fs afero.Fs, stateDir string, gpgKeyID string) (*PGPUnlo
|
||||
return nil, fmt.Errorf("failed to write encrypted age private key: %w", err)
|
||||
}
|
||||
|
||||
// Step 6: Create and write enhanced metadata
|
||||
// Step 9: Create and write enhanced metadata
|
||||
// Generate the key ID directly using the GPG key ID
|
||||
keyID := fmt.Sprintf("%s-pgp", gpgKeyID)
|
||||
|
||||
|
@ -149,9 +149,9 @@ func (s *Secret) GetValue(unlockKey UnlockKey) ([]byte, error) {
|
||||
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)
|
||||
// Decrypt the encrypted long-term private key using the unlock key
|
||||
Debug("Decrypting long-term private key using 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)
|
||||
@ -198,11 +198,11 @@ func (s *Secret) decryptWithLongTermKey(ltIdentity *age.X25519Identity) ([]byte,
|
||||
)
|
||||
|
||||
// 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)
|
||||
Debug("Decrypting secret private key using 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)
|
||||
Debug("Failed to decrypt secret private key", "error", err, "secret_name", s.Name)
|
||||
return nil, fmt.Errorf("failed to decrypt secret private key: %w", err)
|
||||
}
|
||||
|
||||
// Parse the secret's private key
|
||||
@ -234,8 +234,8 @@ func (s *Secret) decryptWithLongTermKey(ltIdentity *age.X25519Identity) ([]byte,
|
||||
)
|
||||
|
||||
// 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)
|
||||
Debug("Decrypting value using secret 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)
|
||||
|
Loading…
Reference in New Issue
Block a user