diff --git a/.claude/settings.local.json b/.claude/settings.local.json index fe8b209..ed217fc 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -17,7 +17,12 @@ "Bash(gofumpt:*)", "Bash(git stash:*)", "Bash(git commit:*)", - "Bash(git push:*)" + "Bash(git push:*)", + "Bash(golangci-lint:*)", + "Bash(git checkout:*)", + "Bash(ls:*)", + "WebFetch(domain:golangci-lint.run)", + "Bash(go:*)" ], "deny": [] } diff --git a/.golangci.yml b/.golangci.yml index fb66e71..3e04d77 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -81,10 +81,6 @@ issues: max-issues-per-linter: 0 max-same-issues: 0 exclude-rules: - # Exclude all linters from running on test files - - path: _test\.go - - # Allow long lines in generated code or test data - path: ".*_gen\\.go" linters: - lll @@ -92,4 +88,4 @@ issues: # Exclude unused parameter warnings for cobra command signatures - text: "parameter '(args|cmd)' seems to be unused" linters: - - revive \ No newline at end of file + - revive diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 23e5372..5ce82d6 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -37,6 +37,7 @@ func NewCLIInstance() *Instance { // NewCLIInstanceWithFs creates a new CLI instance with the given filesystem (for testing) func NewCLIInstanceWithFs(fs afero.Fs) *Instance { stateDir := secret.DetermineStateDir("") + return &Instance{ fs: fs, stateDir: stateDir, diff --git a/internal/cli/crypto.go b/internal/cli/crypto.go index f9595fe..f3d4a70 100644 --- a/internal/cli/crypto.go +++ b/internal/cli/crypto.go @@ -236,6 +236,7 @@ func (cli *Instance) Decrypt(secretName, inputFile, outputFile string) error { // isValidAgeSecretKey checks if a string is a valid age secret key by attempting to parse it func isValidAgeSecretKey(key string) bool { _, err := age.ParseX25519Identity(key) + return err == nil } @@ -244,11 +245,11 @@ func (cli *Instance) getSecretValue(vlt *vault.Vault, secretObj *secret.Secret) if os.Getenv(secret.EnvMnemonic) != "" { return secretObj.GetValue(nil) } - + unlocker, err := vlt.GetCurrentUnlocker() if err != nil { return nil, fmt.Errorf("failed to get current unlocker: %w", err) } - + return secretObj.GetValue(unlocker) } diff --git a/internal/cli/generate.go b/internal/cli/generate.go index 86b3c12..2302023 100644 --- a/internal/cli/generate.go +++ b/internal/cli/generate.go @@ -38,6 +38,7 @@ func newGenerateMnemonicCmd() *cobra.Command { `or 'secret import'.`, RunE: func(cmd *cobra.Command, args []string) error { cli := NewCLIInstance() + return cli.GenerateMnemonic(cmd) }, } @@ -147,12 +148,14 @@ func (cli *Instance) GenerateSecret( // generateRandomBase58 generates a random base58 string of the specified length func generateRandomBase58(length int) (string, error) { const base58Chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + return generateRandomString(length, base58Chars) } // generateRandomAlnum generates a random alphanumeric string of the specified length func generateRandomAlnum(length int) (string, error) { const alnumChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + return generateRandomString(length, alnumChars) } diff --git a/internal/cli/init.go b/internal/cli/init.go index b6fec87..f668274 100644 --- a/internal/cli/init.go +++ b/internal/cli/init.go @@ -29,6 +29,7 @@ func NewInitCmd() *cobra.Command { // RunInit is the exported function that handles the init command func RunInit(cmd *cobra.Command, args []string) error { cli := NewCLIInstance() + return cli.Init(cmd) } @@ -42,6 +43,7 @@ func (cli *Instance) Init(cmd *cobra.Command) error { if err := cli.fs.MkdirAll(stateDir, secret.DirPerms); err != nil { secret.Debug("Failed to create state directory", "error", err) + return fmt.Errorf("failed to create state directory: %w", err) } @@ -62,12 +64,14 @@ func (cli *Instance) Init(cmd *cobra.Command) error { mnemonicStr, err = readLineFromStdin("Enter your BIP39 mnemonic phrase: ") if err != nil { secret.Debug("Failed to read mnemonic from stdin", "error", err) + return fmt.Errorf("failed to read mnemonic: %w", err) } } if mnemonicStr == "" { secret.Debug("Empty mnemonic provided") + return fmt.Errorf("mnemonic cannot be empty") } @@ -75,6 +79,7 @@ func (cli *Instance) Init(cmd *cobra.Command) error { secret.DebugWith("Validating BIP39 mnemonic", slog.Int("word_count", len(strings.Fields(mnemonicStr)))) if !bip39.IsMnemonicValid(mnemonicStr) { secret.Debug("Invalid BIP39 mnemonic provided") + return fmt.Errorf("invalid BIP39 mnemonic phrase\nRun 'secret generate mnemonic' to create a valid mnemonic") } @@ -94,6 +99,7 @@ func (cli *Instance) Init(cmd *cobra.Command) error { vlt, err := vault.CreateVault(cli.fs, cli.stateDir, "default") if err != nil { secret.Debug("Failed to create default vault", "error", err) + return fmt.Errorf("failed to create default vault: %w", err) } @@ -102,6 +108,7 @@ func (cli *Instance) Init(cmd *cobra.Command) error { metadata, err := vault.LoadVaultMetadata(cli.fs, vaultDir) if err != nil { secret.Debug("Failed to load vault metadata", "error", err) + return fmt.Errorf("failed to load vault metadata: %w", err) } @@ -109,6 +116,7 @@ func (cli *Instance) Init(cmd *cobra.Command) error { ltIdentity, err := agehd.DeriveIdentity(mnemonicStr, metadata.DerivationIndex) if err != nil { secret.Debug("Failed to derive long-term key", "error", err) + return fmt.Errorf("failed to derive long-term key from mnemonic: %w", err) } ltPubKey := ltIdentity.Recipient().String() @@ -127,6 +135,7 @@ func (cli *Instance) Init(cmd *cobra.Command) error { passphraseStr, err = readSecurePassphrase("Enter passphrase for unlocker: ") if err != nil { secret.Debug("Failed to read unlock passphrase", "error", err) + return fmt.Errorf("failed to read passphrase: %w", err) } } @@ -136,6 +145,7 @@ func (cli *Instance) Init(cmd *cobra.Command) error { passphraseUnlocker, err := vlt.CreatePassphraseUnlocker(passphraseStr) if err != nil { secret.Debug("Failed to create unlocker", "error", err) + return fmt.Errorf("failed to create unlocker: %w", err) } diff --git a/internal/cli/secrets.go b/internal/cli/secrets.go index 409e8e2..00900d4 100644 --- a/internal/cli/secrets.go +++ b/internal/cli/secrets.go @@ -135,6 +135,7 @@ func (cli *Instance) AddSecret(secretName string, force bool) error { secret.Debug("Calling vault.AddSecret", "secret_name", secretName, "value_length", len(value), "force", force) if err := vlt.AddSecret(secretName, value, force); err != nil { secret.Debug("vault.AddSecret failed", "error", err) + return err } @@ -156,6 +157,7 @@ func (cli *Instance) GetSecretWithVersion(cmd *cobra.Command, secretName string, vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir) if err != nil { secret.Debug("Failed to get current vault", "error", err) + return err } @@ -168,6 +170,7 @@ func (cli *Instance) GetSecretWithVersion(cmd *cobra.Command, secretName string, } if err != nil { secret.Debug("Failed to get secret", "error", err) + return err } diff --git a/internal/cli/unlockers.go b/internal/cli/unlockers.go index 6dd8925..4a69916 100644 --- a/internal/cli/unlockers.go +++ b/internal/cli/unlockers.go @@ -59,6 +59,7 @@ func newUnlockersAddCmd() *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cli := NewCLIInstance() + return cli.UnlockersAdd(args[0], cmd) }, } @@ -75,6 +76,7 @@ func newUnlockersRmCmd() *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cli := NewCLIInstance() + return cli.UnlockersRemove(args[0]) }, } @@ -99,6 +101,7 @@ func newUnlockerSelectSubCmd() *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cli := NewCLIInstance() + return cli.UnlockerSelect(args[0]) }, } diff --git a/internal/cli/vault.go b/internal/cli/vault.go index 0f661d4..52fc129 100644 --- a/internal/cli/vault.go +++ b/internal/cli/vault.go @@ -55,6 +55,7 @@ func newVaultCreateCmd() *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cli := NewCLIInstance() + return cli.CreateVault(cmd, args[0]) }, } @@ -67,6 +68,7 @@ func newVaultSelectCmd() *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cli := NewCLIInstance() + return cli.SelectVault(cmd, args[0]) }, } @@ -209,6 +211,7 @@ func (cli *Instance) VaultImport(cmd *cobra.Command, vaultName string) error { derivationIndex, err := vault.GetNextDerivationIndex(cli.fs, cli.stateDir, mnemonic) if err != nil { secret.Debug("Failed to get next derivation index", "error", err) + return fmt.Errorf("failed to get next derivation index: %w", err) } secret.Debug("Using derivation index", "index", derivationIndex) @@ -256,6 +259,7 @@ func (cli *Instance) VaultImport(cmd *cobra.Command, vaultName string) error { if err := vault.SaveVaultMetadata(cli.fs, vaultDir, existingMetadata); err != nil { secret.Debug("Failed to save vault metadata", "error", err) + return fmt.Errorf("failed to save vault metadata: %w", err) } secret.Debug("Saved vault metadata with derivation index and public key hash") @@ -276,6 +280,7 @@ func (cli *Instance) VaultImport(cmd *cobra.Command, vaultName string) error { passphraseUnlocker, err := vlt.CreatePassphraseUnlocker(passphraseStr) if err != nil { secret.Debug("Failed to create unlocker", "error", err) + return fmt.Errorf("failed to create unlocker: %w", err) } diff --git a/internal/cli/version.go b/internal/cli/version.go index f7dcfa8..342c36c 100644 --- a/internal/cli/version.go +++ b/internal/cli/version.go @@ -19,6 +19,7 @@ const ( // newVersionCmd returns the version management command func newVersionCmd() *cobra.Command { cli := NewCLIInstance() + return VersionCommands(cli) } @@ -64,12 +65,14 @@ func (cli *Instance) ListVersions(cmd *cobra.Command, secretName string) error { vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir) if err != nil { secret.Debug("Failed to get current vault", "error", err) + return err } vaultDir, err := vlt.GetDirectory() if err != nil { secret.Debug("Failed to get vault directory", "error", err) + return err } @@ -81,10 +84,12 @@ func (cli *Instance) ListVersions(cmd *cobra.Command, secretName string) error { exists, err := afero.DirExists(cli.fs, secretDir) if err != nil { secret.Debug("Failed to check if secret exists", "error", err) + return fmt.Errorf("failed to check if secret exists: %w", err) } if !exists { secret.Debug("Secret not found", "secret_name", secretName) + return fmt.Errorf("secret '%s' not found", secretName) } @@ -92,11 +97,13 @@ func (cli *Instance) ListVersions(cmd *cobra.Command, secretName string) error { versions, err := secret.ListVersions(cli.fs, secretDir) if err != nil { secret.Debug("Failed to list versions", "error", err) + return fmt.Errorf("failed to list versions: %w", err) } if len(versions) == 0 { cmd.Println("No versions found") + return nil } diff --git a/internal/secret/crypto.go b/internal/secret/crypto.go index 328f9a2..38b505e 100644 --- a/internal/secret/crypto.go +++ b/internal/secret/crypto.go @@ -20,6 +20,7 @@ func EncryptToRecipient(data []byte, recipient age.Recipient) ([]byte, error) { w, err := age.Encrypt(&buf, recipient) if err != nil { Debug("Failed to create encryptor", "error", err) + return nil, fmt.Errorf("failed to create encryptor: %w", err) } Debug("Created age encryptor successfully") @@ -27,6 +28,7 @@ func EncryptToRecipient(data []byte, recipient age.Recipient) ([]byte, error) { Debug("Writing data to encryptor") if _, err := w.Write(data); err != nil { Debug("Failed to write data to encryptor", "error", err) + return nil, fmt.Errorf("failed to write data: %w", err) } Debug("Wrote data to encryptor successfully") @@ -34,6 +36,7 @@ func EncryptToRecipient(data []byte, recipient age.Recipient) ([]byte, error) { Debug("Closing encryptor") if err := w.Close(); err != nil { Debug("Failed to close encryptor", "error", err) + return nil, fmt.Errorf("failed to close encryptor: %w", err) } Debug("Closed encryptor successfully") diff --git a/internal/secret/debug.go b/internal/secret/debug.go index 29d7acc..e85b8dc 100644 --- a/internal/secret/debug.go +++ b/internal/secret/debug.go @@ -29,6 +29,7 @@ func InitDebugLogging() { if !debugEnabled { // Create a no-op logger that discards all output debugLogger = slog.New(slog.NewTextHandler(io.Discard, nil)) + return } diff --git a/internal/secret/helpers.go b/internal/secret/helpers.go index a06e6e8..26bd7e0 100644 --- a/internal/secret/helpers.go +++ b/internal/secret/helpers.go @@ -48,6 +48,7 @@ func DetermineStateDir(customConfigDir string) string { if err != nil { // Fallback to a reasonable default if we can't determine user config dir homeDir, _ := os.UserHomeDir() + return filepath.Join(homeDir, ".config", AppID) } diff --git a/internal/secret/keychainunlocker.go b/internal/secret/keychainunlocker.go index f59ddbd..dff17a6 100644 --- a/internal/secret/keychainunlocker.go +++ b/internal/secret/keychainunlocker.go @@ -56,6 +56,7 @@ func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) { keychainItemName, err := k.GetKeychainItemName() if err != nil { Debug("Failed to get keychain item name", "error", err, "unlocker_id", k.GetID()) + return nil, fmt.Errorf("failed to get keychain item name: %w", err) } @@ -64,6 +65,7 @@ func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) { keychainDataBytes, err := retrieveFromKeychain(keychainItemName) if err != nil { Debug("Failed to retrieve data from keychain", "error", err, "keychain_item", keychainItemName) + return nil, fmt.Errorf("failed to retrieve data from keychain: %w", err) } @@ -76,6 +78,7 @@ func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) { var keychainData KeychainData if err := json.Unmarshal(keychainDataBytes, &keychainData); err != nil { Debug("Failed to parse keychain data", "error", err, "unlocker_id", k.GetID()) + return nil, fmt.Errorf("failed to parse keychain data: %w", err) } @@ -88,6 +91,7 @@ func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) { encryptedAgePrivKeyData, err := afero.ReadFile(k.fs, agePrivKeyPath) if err != nil { Debug("Failed to read encrypted age private key", "error", err, "path", agePrivKeyPath) + return nil, fmt.Errorf("failed to read encrypted age private key: %w", err) } @@ -101,6 +105,7 @@ func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) { agePrivKeyData, err := DecryptWithPassphrase(encryptedAgePrivKeyData, keychainData.AgePrivKeyPassphrase) if err != nil { Debug("Failed to decrypt age private key with keychain passphrase", "error", err, "unlocker_id", k.GetID()) + return nil, fmt.Errorf("failed to decrypt age private key with keychain passphrase: %w", err) } @@ -114,6 +119,7 @@ func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) { ageIdentity, err := age.ParseX25519Identity(string(agePrivKeyData)) if err != nil { Debug("Failed to parse age private key", "error", err, "unlocker_id", k.GetID()) + return nil, fmt.Errorf("failed to parse age private key: %w", err) } @@ -159,6 +165,7 @@ func (k *KeychainUnlocker) Remove() error { keychainItemName, err := k.GetKeychainItemName() if err != nil { Debug("Failed to get keychain item name during removal", "error", err, "unlocker_id", k.GetID()) + return fmt.Errorf("failed to get keychain item name: %w", err) } @@ -166,6 +173,7 @@ func (k *KeychainUnlocker) Remove() error { Debug("Removing keychain item", "keychain_item", keychainItemName) if err := deleteFromKeychain(keychainItemName); err != nil { Debug("Failed to remove keychain item", "error", err, "keychain_item", keychainItemName) + return fmt.Errorf("failed to remove keychain item: %w", err) } @@ -173,6 +181,7 @@ func (k *KeychainUnlocker) Remove() error { Debug("Removing keychain unlocker directory", "directory", k.Directory) if err := k.fs.RemoveAll(k.Directory); err != nil { Debug("Failed to remove keychain unlocker directory", "error", err, "directory", k.Directory) + return fmt.Errorf("failed to remove keychain unlocker directory: %w", err) } @@ -230,6 +239,7 @@ func getLongTermPrivateKey(fs afero.Fs, vault VaultInterface) ([]byte, error) { if err != nil { return nil, fmt.Errorf("failed to derive long-term key from mnemonic: %w", err) } + return []byte(ltIdentity.String()), nil } diff --git a/internal/secret/passphraseunlocker.go b/internal/secret/passphraseunlocker.go index df7dd88..d09603b 100644 --- a/internal/secret/passphraseunlocker.go +++ b/internal/secret/passphraseunlocker.go @@ -23,6 +23,7 @@ func (p *PassphraseUnlocker) getPassphrase() (string, error) { // First check if we already have the passphrase if p.Passphrase != "" { Debug("Using in-memory passphrase", "unlocker_id", p.GetID()) + return p.Passphrase, nil } @@ -31,6 +32,7 @@ func (p *PassphraseUnlocker) getPassphrase() (string, error) { passphraseStr := os.Getenv(EnvUnlockPassphrase) if passphraseStr != "" { Debug("Using passphrase from environment", "unlocker_id", p.GetID()) + return passphraseStr, nil } @@ -39,8 +41,10 @@ func (p *PassphraseUnlocker) getPassphrase() (string, error) { passphraseStr, err := ReadPassphrase("Enter unlock passphrase: ") if err != nil { Debug("Failed to read passphrase", "error", err, "unlocker_id", p.GetID()) + return "", fmt.Errorf("failed to read passphrase: %w", err) } + return passphraseStr, nil } @@ -63,6 +67,7 @@ func (p *PassphraseUnlocker) GetIdentity() (*age.X25519Identity, error) { encryptedPrivKeyData, err := afero.ReadFile(p.fs, unlockerPrivPath) if err != nil { Debug("Failed to read passphrase unlocker private key", "error", err, "path", unlockerPrivPath) + return nil, fmt.Errorf("failed to read unlocker private key: %w", err) } @@ -77,6 +82,7 @@ func (p *PassphraseUnlocker) GetIdentity() (*age.X25519Identity, error) { privKeyData, err := DecryptWithPassphrase(encryptedPrivKeyData, passphraseStr) if err != nil { Debug("Failed to decrypt unlocker private key", "error", err, "unlocker_id", p.GetID()) + return nil, fmt.Errorf("failed to decrypt unlocker private key: %w", err) } @@ -90,6 +96,7 @@ func (p *PassphraseUnlocker) GetIdentity() (*age.X25519Identity, error) { identity, err := age.ParseX25519Identity(string(privKeyData)) if err != nil { Debug("Failed to parse unlocker private key", "error", err, "unlocker_id", p.GetID()) + return nil, fmt.Errorf("failed to parse unlocker private key: %w", err) } @@ -120,6 +127,7 @@ func (p *PassphraseUnlocker) GetDirectory() string { func (p *PassphraseUnlocker) GetID() 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")) } diff --git a/internal/secret/pgpunlocker.go b/internal/secret/pgpunlocker.go index 0ce0318..163e7d8 100644 --- a/internal/secret/pgpunlocker.go +++ b/internal/secret/pgpunlocker.go @@ -67,6 +67,7 @@ func (p *PGPUnlocker) GetIdentity() (*age.X25519Identity, error) { encryptedAgePrivKeyData, err := afero.ReadFile(p.fs, agePrivKeyPath) if err != nil { Debug("Failed to read PGP-encrypted age private key", "error", err, "path", agePrivKeyPath) + return nil, fmt.Errorf("failed to read encrypted age private key: %w", err) } @@ -80,6 +81,7 @@ func (p *PGPUnlocker) GetIdentity() (*age.X25519Identity, error) { agePrivKeyData, err := GPGDecryptFunc(encryptedAgePrivKeyData) if err != nil { Debug("Failed to decrypt age private key with GPG", "error", err, "unlocker_id", p.GetID()) + return nil, fmt.Errorf("failed to decrypt age private key with GPG: %w", err) } @@ -93,6 +95,7 @@ func (p *PGPUnlocker) GetIdentity() (*age.X25519Identity, error) { ageIdentity, err := age.ParseX25519Identity(string(agePrivKeyData)) if err != nil { Debug("Failed to parse age private key", "error", err, "unlocker_id", p.GetID()) + return nil, fmt.Errorf("failed to parse age private key: %w", err) } diff --git a/internal/secret/secret.go b/internal/secret/secret.go index de60c2f..3171a54 100644 --- a/internal/secret/secret.go +++ b/internal/secret/secret.go @@ -74,6 +74,7 @@ func (s *Secret) Save(value []byte, force bool) error { err := s.vault.AddSecret(s.Name, value, force) if err != nil { Debug("Failed to save secret", "error", err, "secret_name", s.Name) + return err } @@ -93,10 +94,12 @@ func (s *Secret) GetValue(unlocker Unlocker) ([]byte, error) { exists, err := s.Exists() if err != nil { Debug("Failed to check if secret exists during GetValue", "error", err, "secret_name", s.Name) + return nil, fmt.Errorf("failed to check if secret exists: %w", err) } if !exists { Debug("Secret not found during GetValue", "secret_name", s.Name, "vault_name", s.vault.GetName()) + return nil, fmt.Errorf("secret %s not found", s.Name) } @@ -106,6 +109,7 @@ func (s *Secret) GetValue(unlocker Unlocker) ([]byte, error) { currentVersion, err := GetCurrentVersion(s.vault.GetFilesystem(), s.Directory) if err != nil { Debug("Failed to get current version", "error", err, "secret_name", s.Name) + return nil, fmt.Errorf("failed to get current version: %w", err) } @@ -120,6 +124,7 @@ func (s *Secret) GetValue(unlocker Unlocker) ([]byte, error) { vaultDir, err := s.vault.GetDirectory() if err != nil { Debug("Failed to get vault directory", "error", err, "secret_name", s.Name) + return nil, fmt.Errorf("failed to get vault directory: %w", err) } @@ -128,12 +133,14 @@ func (s *Secret) GetValue(unlocker Unlocker) ([]byte, error) { metadataBytes, err := afero.ReadFile(s.vault.GetFilesystem(), metadataPath) if err != nil { Debug("Failed to read vault metadata", "error", err, "path", metadataPath) + return nil, fmt.Errorf("failed to read vault metadata: %w", err) } var metadata VaultMetadata if err := json.Unmarshal(metadataBytes, &metadata); err != nil { Debug("Failed to parse vault metadata", "error", err, "secret_name", s.Name) + return nil, fmt.Errorf("failed to parse vault metadata: %w", err) } @@ -147,6 +154,7 @@ func (s *Secret) GetValue(unlocker Unlocker) ([]byte, error) { ltIdentity, err := agehd.DeriveIdentity(envMnemonic, metadata.DerivationIndex) if err != nil { Debug("Failed to derive long-term key from mnemonic for secret", "error", err, "secret_name", s.Name) + return nil, fmt.Errorf("failed to derive long-term key from mnemonic: %w", err) } @@ -161,6 +169,7 @@ func (s *Secret) GetValue(unlocker Unlocker) ([]byte, error) { // Use the provided unlocker to get the vault's long-term private key if unlocker == nil { Debug("No unlocker provided for secret decryption", "secret_name", s.Name) + return nil, fmt.Errorf("unlocker required to decrypt secret") } @@ -174,6 +183,7 @@ func (s *Secret) GetValue(unlocker Unlocker) ([]byte, error) { unlockIdentity, err := unlocker.GetIdentity() if err != nil { Debug("Failed to get unlocker identity", "error", err, "secret_name", s.Name, "unlocker_type", unlocker.GetType()) + return nil, fmt.Errorf("failed to get unlocker identity: %w", err) } @@ -184,6 +194,7 @@ func (s *Secret) GetValue(unlocker Unlocker) ([]byte, error) { encryptedLtPrivKey, err := afero.ReadFile(s.vault.GetFilesystem(), 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) } @@ -192,6 +203,7 @@ func (s *Secret) GetValue(unlocker Unlocker) ([]byte, error) { 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) } @@ -200,6 +212,7 @@ func (s *Secret) GetValue(unlocker Unlocker) ([]byte, error) { 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) } @@ -228,12 +241,14 @@ func (s *Secret) LoadMetadata() error { // GetMetadata returns the secret metadata (deprecated) func (s *Secret) GetMetadata() Metadata { Debug("GetMetadata called but is deprecated in versioned model", "secret_name", s.Name) + return s.Metadata } // GetEncryptedData is deprecated - data is now stored in versions func (s *Secret) GetEncryptedData() ([]byte, error) { Debug("GetEncryptedData called but is deprecated in versioned model", "secret_name", s.Name) + return nil, fmt.Errorf("GetEncryptedData is deprecated - use version-specific methods") } @@ -248,11 +263,13 @@ func (s *Secret) Exists() (bool, error) { exists, err := afero.DirExists(s.vault.GetFilesystem(), s.Directory) if err != nil { Debug("Failed to check secret directory existence", "error", err, "secret_dir", s.Directory) + return false, err } if !exists { Debug("Secret directory does not exist", "secret_dir", s.Directory) + return false, nil } @@ -260,6 +277,7 @@ func (s *Secret) Exists() (bool, error) { _, err = GetCurrentVersion(s.vault.GetFilesystem(), s.Directory) if err != nil { Debug("No current version found", "error", err, "secret_name", s.Name) + return false, nil } diff --git a/internal/secret/version.go b/internal/secret/version.go index 6d1efd9..f3dfd2e 100644 --- a/internal/secret/version.go +++ b/internal/secret/version.go @@ -132,6 +132,7 @@ func (sv *Version) Save(value []byte) error { // Create version directory if err := fs.MkdirAll(sv.Directory, DirPerms); err != nil { Debug("Failed to create version directory", "error", err, "dir", sv.Directory) + return fmt.Errorf("failed to create version directory: %w", err) } @@ -140,6 +141,7 @@ func (sv *Version) Save(value []byte) error { versionIdentity, err := age.GenerateX25519Identity() if err != nil { Debug("Failed to generate version keypair", "error", err, "version", sv.Version) + return fmt.Errorf("failed to generate version keypair: %w", err) } @@ -156,6 +158,7 @@ func (sv *Version) Save(value []byte) error { Debug("Writing version public key", "path", pubKeyPath) if err := afero.WriteFile(fs, pubKeyPath, []byte(versionPublicKey), FilePerms); err != nil { Debug("Failed to write version public key", "error", err, "path", pubKeyPath) + return fmt.Errorf("failed to write version public key: %w", err) } @@ -164,6 +167,7 @@ func (sv *Version) Save(value []byte) error { encryptedValue, err := EncryptToRecipient(value, versionIdentity.Recipient()) if err != nil { Debug("Failed to encrypt version value", "error", err, "version", sv.Version) + return fmt.Errorf("failed to encrypt version value: %w", err) } @@ -172,6 +176,7 @@ func (sv *Version) Save(value []byte) error { Debug("Writing encrypted version value", "path", valuePath) if err := afero.WriteFile(fs, valuePath, encryptedValue, FilePerms); err != nil { Debug("Failed to write encrypted version value", "error", err, "path", valuePath) + return fmt.Errorf("failed to write encrypted version value: %w", err) } @@ -183,6 +188,7 @@ func (sv *Version) Save(value []byte) error { ltPubKeyData, err := afero.ReadFile(fs, ltPubKeyPath) if err != nil { Debug("Failed to read long-term public key", "error", err, "path", ltPubKeyPath) + return fmt.Errorf("failed to read long-term public key: %w", err) } @@ -190,6 +196,7 @@ func (sv *Version) Save(value []byte) error { ltRecipient, err := age.ParseX25519Recipient(string(ltPubKeyData)) if err != nil { Debug("Failed to parse long-term public key", "error", err) + return fmt.Errorf("failed to parse long-term public key: %w", err) } @@ -198,6 +205,7 @@ func (sv *Version) Save(value []byte) error { encryptedPrivKey, err := EncryptToRecipient([]byte(versionPrivateKey), ltRecipient) if err != nil { Debug("Failed to encrypt version private key", "error", err, "version", sv.Version) + return fmt.Errorf("failed to encrypt version private key: %w", err) } @@ -206,6 +214,7 @@ func (sv *Version) Save(value []byte) error { Debug("Writing encrypted version private key", "path", privKeyPath) if err := afero.WriteFile(fs, privKeyPath, encryptedPrivKey, FilePerms); err != nil { Debug("Failed to write encrypted version private key", "error", err, "path", privKeyPath) + return fmt.Errorf("failed to write encrypted version private key: %w", err) } @@ -214,6 +223,7 @@ func (sv *Version) Save(value []byte) error { metadataBytes, err := json.MarshalIndent(sv.Metadata, "", " ") if err != nil { Debug("Failed to marshal version metadata", "error", err) + return fmt.Errorf("failed to marshal version metadata: %w", err) } @@ -221,6 +231,7 @@ func (sv *Version) Save(value []byte) error { encryptedMetadata, err := EncryptToRecipient(metadataBytes, versionIdentity.Recipient()) if err != nil { Debug("Failed to encrypt version metadata", "error", err, "version", sv.Version) + return fmt.Errorf("failed to encrypt version metadata: %w", err) } @@ -228,6 +239,7 @@ func (sv *Version) Save(value []byte) error { Debug("Writing encrypted version metadata", "path", metadataPath) if err := afero.WriteFile(fs, metadataPath, encryptedMetadata, FilePerms); err != nil { Debug("Failed to write encrypted version metadata", "error", err, "path", metadataPath) + return fmt.Errorf("failed to write encrypted version metadata: %w", err) } @@ -250,6 +262,7 @@ func (sv *Version) LoadMetadata(ltIdentity *age.X25519Identity) error { encryptedPrivKey, err := afero.ReadFile(fs, encryptedPrivKeyPath) if err != nil { Debug("Failed to read encrypted version private key", "error", err, "path", encryptedPrivKeyPath) + return fmt.Errorf("failed to read encrypted version private key: %w", err) } @@ -257,6 +270,7 @@ func (sv *Version) LoadMetadata(ltIdentity *age.X25519Identity) error { versionPrivKeyData, err := DecryptWithIdentity(encryptedPrivKey, ltIdentity) if err != nil { Debug("Failed to decrypt version private key", "error", err, "version", sv.Version) + return fmt.Errorf("failed to decrypt version private key: %w", err) } @@ -264,6 +278,7 @@ func (sv *Version) LoadMetadata(ltIdentity *age.X25519Identity) error { versionIdentity, err := age.ParseX25519Identity(string(versionPrivKeyData)) if err != nil { Debug("Failed to parse version private key", "error", err, "version", sv.Version) + return fmt.Errorf("failed to parse version private key: %w", err) } @@ -272,6 +287,7 @@ func (sv *Version) LoadMetadata(ltIdentity *age.X25519Identity) error { encryptedMetadata, err := afero.ReadFile(fs, encryptedMetadataPath) if err != nil { Debug("Failed to read encrypted version metadata", "error", err, "path", encryptedMetadataPath) + return fmt.Errorf("failed to read encrypted version metadata: %w", err) } @@ -279,6 +295,7 @@ func (sv *Version) LoadMetadata(ltIdentity *age.X25519Identity) error { metadataBytes, err := DecryptWithIdentity(encryptedMetadata, versionIdentity) if err != nil { Debug("Failed to decrypt version metadata", "error", err, "version", sv.Version) + return fmt.Errorf("failed to decrypt version metadata: %w", err) } @@ -286,6 +303,7 @@ func (sv *Version) LoadMetadata(ltIdentity *age.X25519Identity) error { var metadata VersionMetadata if err := json.Unmarshal(metadataBytes, &metadata); err != nil { Debug("Failed to unmarshal version metadata", "error", err, "version", sv.Version) + return fmt.Errorf("failed to unmarshal version metadata: %w", err) } @@ -317,6 +335,7 @@ func (sv *Version) GetValue(ltIdentity *age.X25519Identity) ([]byte, error) { encryptedPrivKey, err := afero.ReadFile(fs, encryptedPrivKeyPath) if err != nil { Debug("Failed to read encrypted version private key", "error", err, "path", encryptedPrivKeyPath) + return nil, fmt.Errorf("failed to read encrypted version private key: %w", err) } Debug("Successfully read encrypted version private key", "path", encryptedPrivKeyPath, "size", len(encryptedPrivKey)) @@ -326,6 +345,7 @@ func (sv *Version) GetValue(ltIdentity *age.X25519Identity) ([]byte, error) { versionPrivKeyData, err := DecryptWithIdentity(encryptedPrivKey, ltIdentity) if err != nil { Debug("Failed to decrypt version private key", "error", err, "version", sv.Version) + return nil, fmt.Errorf("failed to decrypt version private key: %w", err) } Debug("Successfully decrypted version private key", "version", sv.Version, "size", len(versionPrivKeyData)) @@ -334,6 +354,7 @@ func (sv *Version) GetValue(ltIdentity *age.X25519Identity) ([]byte, error) { versionIdentity, err := age.ParseX25519Identity(string(versionPrivKeyData)) if err != nil { Debug("Failed to parse version private key", "error", err, "version", sv.Version) + return nil, fmt.Errorf("failed to parse version private key: %w", err) } @@ -343,6 +364,7 @@ func (sv *Version) GetValue(ltIdentity *age.X25519Identity) ([]byte, error) { encryptedValue, err := afero.ReadFile(fs, encryptedValuePath) if err != nil { Debug("Failed to read encrypted version value", "error", err, "path", encryptedValuePath) + return nil, fmt.Errorf("failed to read encrypted version value: %w", err) } Debug("Successfully read encrypted value", "path", encryptedValuePath, "size", len(encryptedValue)) @@ -352,6 +374,7 @@ func (sv *Version) GetValue(ltIdentity *age.X25519Identity) ([]byte, error) { value, err := DecryptWithIdentity(encryptedValue, versionIdentity) if err != nil { Debug("Failed to decrypt version value", "error", err, "version", sv.Version) + return nil, fmt.Errorf("failed to decrypt version value: %w", err) } diff --git a/internal/vault/management.go b/internal/vault/management.go index 9489d5c..024b924 100644 --- a/internal/vault/management.go +++ b/internal/vault/management.go @@ -54,6 +54,7 @@ func resolveRelativeSymlink(symlinkPath, target string) (string, error) { if err != nil { // Try to restore original directory before returning error _ = os.Chdir(originalDir) + return "", fmt.Errorf("failed to get absolute path: %w", err) } secret.Debug("Got absolute path", "absolute_path", absolutePath) @@ -79,6 +80,7 @@ func ResolveVaultSymlink(fs afero.Fs, symlinkPath string) (string, error) { target, err := tryResolveOsSymlink(symlinkPath) if err == nil { secret.Debug("resolveVaultSymlink completed successfully", "result", target) + return target, nil } // Fall through to fallback if symlink resolution failed @@ -92,6 +94,7 @@ func ResolveVaultSymlink(fs afero.Fs, symlinkPath string) (string, error) { fileData, err := afero.ReadFile(fs, symlinkPath) if err != nil { secret.Debug("Failed to read target path file", "error", err) + return "", fmt.Errorf("failed to read vault symlink: %w", err) } @@ -106,14 +109,14 @@ func ResolveVaultSymlink(fs afero.Fs, symlinkPath string) (string, error) { // tryResolveOsSymlink attempts to resolve a symlink on OS filesystems func tryResolveOsSymlink(symlinkPath string) (string, error) { secret.Debug("Using real filesystem symlink resolution") - + // Check if the symlink exists secret.Debug("Checking symlink target", "symlink_path", symlinkPath) target, err := os.Readlink(symlinkPath) if err != nil { return "", err } - + secret.Debug("Symlink points to", "target", target) // On real filesystem, we need to handle relative symlinks @@ -136,6 +139,7 @@ func GetCurrentVault(fs afero.Fs, stateDir string) (*Vault, error) { _, err := fs.Stat(currentVaultPath) if err != nil { secret.Debug("Failed to stat current vault symlink", "error", err, "path", currentVaultPath) + return nil, fmt.Errorf("failed to read current vault symlink: %w", err) } @@ -195,7 +199,7 @@ func ListVaults(fs afero.Fs, stateDir string) ([]string, error) { func processMnemonicForVault(fs afero.Fs, stateDir, vaultDir, vaultName string) (derivationIndex uint32, publicKeyHash string, familyHash string, err error) { // Check if mnemonic is available in environment mnemonic := os.Getenv(secret.EnvMnemonic) - + if mnemonic == "" { secret.Debug("No mnemonic in environment, vault created without long-term key", "vault", vaultName) // Use 0 for derivation index when no mnemonic is provided @@ -245,6 +249,7 @@ func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) { // Validate vault name if !isValidVaultName(name) { secret.Debug("Invalid vault name provided", "vault_name", name) + return nil, fmt.Errorf("invalid vault name '%s': must match pattern [a-z0-9.\\-_]+", name) } secret.Debug("Vault name validation passed", "vault_name", name) @@ -306,6 +311,7 @@ func SelectVault(fs afero.Fs, stateDir string, name string) error { // Validate vault name if !isValidVaultName(name) { secret.Debug("Invalid vault name provided", "vault_name", name) + return fmt.Errorf("invalid vault name '%s': must match pattern [a-z0-9.\\-_]+", name) } secret.Debug("Vault name validation passed", "vault_name", name) @@ -337,6 +343,7 @@ func SelectVault(fs afero.Fs, stateDir string, name string) error { secret.Debug("Creating vault symlink", "target", targetPath, "link", currentVaultPath) if err := os.Symlink(targetPath, currentVaultPath); err == nil { secret.Debug("Successfully selected vault", "vault_name", name) + return nil } // If symlink creation fails, fall back to regular file diff --git a/internal/vault/secrets.go b/internal/vault/secrets.go index 878dc2b..4300094 100644 --- a/internal/vault/secrets.go +++ b/internal/vault/secrets.go @@ -21,6 +21,7 @@ func (v *Vault) ListSecrets() ([]string, error) { vaultDir, err := v.GetDirectory() if err != nil { secret.Debug("Failed to get vault directory for secret listing", "error", err, "vault_name", v.Name) + return nil, err } @@ -30,10 +31,12 @@ func (v *Vault) ListSecrets() ([]string, error) { exists, err := afero.DirExists(v.fs, secretsDir) if err != nil { secret.Debug("Failed to check secrets directory", "error", err, "secrets_dir", secretsDir) + return nil, fmt.Errorf("failed to check if secrets directory exists: %w", err) } if !exists { secret.Debug("Secrets directory does not exist", "secrets_dir", secretsDir, "vault_name", v.Name) + return []string{}, nil } @@ -41,6 +44,7 @@ func (v *Vault) ListSecrets() ([]string, error) { files, err := afero.ReadDir(v.fs, secretsDir) if err != nil { secret.Debug("Failed to read secrets directory", "error", err, "secrets_dir", secretsDir) + return nil, fmt.Errorf("failed to read secrets directory: %w", err) } @@ -105,6 +109,7 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { // Validate secret name if !isValidSecretName(name) { secret.Debug("Invalid secret name provided", "secret_name", name) + return fmt.Errorf("invalid secret name '%s': must match pattern [a-z0-9.\\-_/]+", name) } secret.Debug("Secret name validation passed", "secret_name", name) @@ -113,6 +118,7 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { vaultDir, err := v.GetDirectory() if err != nil { secret.Debug("Failed to get vault directory for secret addition", "error", err, "vault_name", v.Name) + return err } secret.Debug("Got vault directory", "vault_dir", vaultDir) @@ -131,6 +137,7 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { exists, err := afero.DirExists(v.fs, secretDir) if err != nil { secret.Debug("Failed to check if secret exists", "error", err, "secret_dir", secretDir) + return fmt.Errorf("failed to check if secret exists: %w", err) } secret.Debug("Secret existence check complete", "exists", exists) @@ -142,6 +149,7 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { if exists { if !force { secret.Debug("Secret already exists and force not specified", "secret_name", name, "secret_dir", secretDir) + return fmt.Errorf("secret %s already exists (use --force to overwrite)", name) } @@ -156,6 +164,7 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { secret.Debug("Creating secret directory", "secret_dir", secretDir) if err := v.fs.MkdirAll(secretDir, secret.DirPerms); err != nil { secret.Debug("Failed to create secret directory", "error", err, "secret_dir", secretDir) + return fmt.Errorf("failed to create secret directory: %w", err) } secret.Debug("Created secret directory successfully") @@ -165,6 +174,7 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { versionName, err := secret.GenerateVersionName(v.fs, secretDir) if err != nil { secret.Debug("Failed to generate version name", "error", err, "secret_name", name) + return fmt.Errorf("failed to generate version name: %w", err) } @@ -188,6 +198,7 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { // Save the new version if err := newVersion.Save(value); err != nil { secret.Debug("Failed to save new version", "error", err, "version", versionName) + return fmt.Errorf("failed to save version: %w", err) } @@ -197,12 +208,14 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { ltIdentity, err := v.GetOrDeriveLongTermKey() if err != nil { secret.Debug("Failed to get long-term key for metadata update", "error", err) + return fmt.Errorf("failed to get long-term key: %w", err) } // Load previous version metadata if err := previousVersion.LoadMetadata(ltIdentity); err != nil { secret.Debug("Failed to load previous version metadata", "error", err) + return fmt.Errorf("failed to load previous version metadata: %w", err) } @@ -212,6 +225,7 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { // Re-save the metadata (we need to implement an update method) if err := updateVersionMetadata(v.fs, previousVersion, ltIdentity); err != nil { secret.Debug("Failed to update previous version metadata", "error", err) + return fmt.Errorf("failed to update previous version metadata: %w", err) } } @@ -219,6 +233,7 @@ func (v *Vault) AddSecret(name string, value []byte, force bool) error { // Set current symlink to new version if err := secret.SetCurrentVersion(v.fs, secretDir, versionName); err != nil { secret.Debug("Failed to set current version", "error", err, "version", versionName) + return fmt.Errorf("failed to set current version: %w", err) } @@ -293,6 +308,7 @@ func (v *Vault) GetSecretVersion(name string, version string) ([]byte, error) { vaultDir, err := v.GetDirectory() if err != nil { secret.Debug("Failed to get vault directory", "error", err, "vault_name", v.Name) + return nil, err } @@ -304,10 +320,12 @@ func (v *Vault) GetSecretVersion(name string, version string) ([]byte, error) { exists, err := afero.DirExists(v.fs, secretDir) if err != nil { secret.Debug("Failed to check if secret exists", "error", err, "secret_name", name) + return nil, fmt.Errorf("failed to check if secret exists: %w", err) } if !exists { secret.Debug("Secret not found in vault", "secret_name", name, "vault_name", v.Name) + return nil, fmt.Errorf("secret %s not found", name) } @@ -317,6 +335,7 @@ func (v *Vault) GetSecretVersion(name string, version string) ([]byte, error) { currentVersion, err := secret.GetCurrentVersion(v.fs, secretDir) if err != nil { secret.Debug("Failed to get current version", "error", err, "secret_name", name) + return nil, fmt.Errorf("failed to get current version: %w", err) } version = currentVersion @@ -331,10 +350,12 @@ func (v *Vault) GetSecretVersion(name string, version string) ([]byte, error) { exists, err = afero.DirExists(v.fs, versionPath) if err != nil { secret.Debug("Failed to check if version exists", "error", err, "version", version) + return nil, fmt.Errorf("failed to check if version exists: %w", err) } if !exists { secret.Debug("Version not found", "version", version, "secret_name", name) + return nil, fmt.Errorf("version %s not found for secret %s", version, name) } @@ -344,6 +365,7 @@ func (v *Vault) GetSecretVersion(name string, version string) ([]byte, error) { longTermIdentity, err := v.UnlockVault() if err != nil { secret.Debug("Failed to unlock vault", "error", err, "vault_name", v.Name) + return nil, fmt.Errorf("failed to unlock vault: %w", err) } @@ -359,6 +381,7 @@ func (v *Vault) GetSecretVersion(name string, version string) ([]byte, error) { decryptedValue, err := secretVersion.GetValue(longTermIdentity) if err != nil { secret.Debug("Failed to decrypt version value", "error", err, "version", version, "secret_name", name) + return nil, fmt.Errorf("failed to decrypt version: %w", err) } @@ -386,6 +409,7 @@ func (v *Vault) UnlockVault() (*age.X25519Identity, error) { // If vault is already unlocked, return the cached key if !v.Locked() { secret.Debug("Vault already unlocked, returning cached long-term key", "vault_name", v.Name) + return v.longTermKey, nil } @@ -393,6 +417,7 @@ func (v *Vault) UnlockVault() (*age.X25519Identity, error) { longTermIdentity, err := v.GetOrDeriveLongTermKey() if err != nil { secret.Debug("Failed to get or derive long-term key", "error", err, "vault_name", v.Name) + return nil, fmt.Errorf("failed to get long-term key: %w", err) } diff --git a/internal/vault/unlockers.go b/internal/vault/unlockers.go index 117abd7..af8d3de 100644 --- a/internal/vault/unlockers.go +++ b/internal/vault/unlockers.go @@ -20,6 +20,7 @@ func (v *Vault) GetCurrentUnlocker() (secret.Unlocker, error) { vaultDir, err := v.GetDirectory() if err != nil { secret.Debug("Failed to get vault directory for unlocker", "error", err, "vault_name", v.Name) + return nil, err } @@ -29,6 +30,7 @@ func (v *Vault) GetCurrentUnlocker() (secret.Unlocker, error) { _, err = v.fs.Stat(currentUnlockerPath) if err != nil { secret.Debug("Failed to stat current unlocker symlink", "error", err, "path", currentUnlockerPath) + return nil, fmt.Errorf("failed to read current unlocker: %w", err) } @@ -50,12 +52,14 @@ func (v *Vault) GetCurrentUnlocker() (secret.Unlocker, error) { metadataBytes, err := afero.ReadFile(v.fs, metadataPath) if err != nil { secret.Debug("Failed to read unlocker metadata", "error", err, "path", metadataPath) + return nil, fmt.Errorf("failed to read unlocker metadata: %w", err) } var metadata UnlockerMetadata if err := json.Unmarshal(metadataBytes, &metadata); err != nil { secret.Debug("Failed to parse unlocker metadata", "error", err, "path", metadataPath) + return nil, fmt.Errorf("failed to parse unlocker metadata: %w", err) } @@ -80,6 +84,7 @@ func (v *Vault) GetCurrentUnlocker() (secret.Unlocker, error) { unlocker = secret.NewKeychainUnlocker(v.fs, unlockerDir, metadata) default: secret.Debug("Unsupported unlocker type", "type", metadata.Type) + return nil, fmt.Errorf("unsupported unlocker type: %s", metadata.Type) } @@ -119,8 +124,10 @@ func (v *Vault) readUnlockerPathFromFile(path string) (string, error) { unlockerDirBytes, err := afero.ReadFile(v.fs, path) if err != nil { secret.Debug("Failed to read unlocker path file", "error", err, "path", path) + return "", fmt.Errorf("failed to read current unlocker: %w", err) } + return strings.TrimSpace(string(unlockerDirBytes)), nil } diff --git a/internal/vault/vault.go b/internal/vault/vault.go index 2f93781..5ea5e95 100644 --- a/internal/vault/vault.go +++ b/internal/vault/vault.go @@ -76,12 +76,14 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) { metadata, err := LoadVaultMetadata(v.fs, vaultDir) if err != nil { secret.Debug("Failed to load vault metadata", "error", err, "vault_name", v.Name) + return nil, fmt.Errorf("failed to load vault metadata: %w", err) } ltIdentity, err := agehd.DeriveIdentity(envMnemonic, metadata.DerivationIndex) if err != nil { secret.Debug("Failed to derive long-term key from mnemonic", "error", err, "vault_name", v.Name) + return nil, fmt.Errorf("failed to derive long-term key from mnemonic: %w", err) } @@ -117,6 +119,7 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) { unlocker, err := v.GetCurrentUnlocker() if err != nil { secret.Debug("Failed to get current unlocker", "error", err, "vault_name", v.Name) + return nil, fmt.Errorf("failed to get current unlocker: %w", err) } @@ -130,6 +133,7 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) { unlockerIdentity, err := unlocker.GetIdentity() if err != nil { secret.Debug("Failed to get unlocker identity", "error", err, "unlocker_type", unlocker.GetType()) + return nil, fmt.Errorf("failed to get unlocker identity: %w", err) } @@ -141,6 +145,7 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) { encryptedLtPrivKey, err := afero.ReadFile(v.fs, encryptedLtPrivKeyPath) if err != nil { secret.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) } @@ -155,6 +160,7 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) { ltPrivKeyData, err := secret.DecryptWithIdentity(encryptedLtPrivKey, unlockerIdentity) if err != nil { secret.Debug("Failed to decrypt long-term private key", "error", err, "unlocker_type", unlocker.GetType()) + return nil, fmt.Errorf("failed to decrypt long-term private key: %w", err) } @@ -169,6 +175,7 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) { ltIdentity, err := age.ParseX25519Identity(string(ltPrivKeyData)) if err != nil { secret.Debug("Failed to parse long-term private key", "error", err, "vault_name", v.Name) + return nil, fmt.Errorf("failed to parse long-term private key: %w", err) } diff --git a/pkg/agehd/agehd.go b/pkg/agehd/agehd.go index c8b84ef..15f8358 100644 --- a/pkg/agehd/agehd.go +++ b/pkg/agehd/agehd.go @@ -21,11 +21,11 @@ import ( ) const ( - purpose = uint32(83696968) // fixed by BIP-85 ("bip") - vendorID = uint32(592366788) // berlin.sneak - appID = uint32(733482323) // secret - hrp = "age-secret-key-" // Bech32 HRP used by age - x25519KeySize = 32 // 256-bit key size for X25519 + purpose = uint32(83696968) // fixed by BIP-85 ("bip") + vendorID = uint32(592366788) // berlin.sneak + appID = uint32(733482323) // secret + hrp = "age-secret-key-" // Bech32 HRP used by age + x25519KeySize = 32 // 256-bit key size for X25519 ) // clamp applies RFC-7748 clamping to a 32-byte scalar. diff --git a/pkg/bip85/bip85.go b/pkg/bip85/bip85.go index cb0c812..63c03eb 100644 --- a/pkg/bip85/bip85.go +++ b/pkg/bip85/bip85.go @@ -28,13 +28,13 @@ const ( BIP85_KEY_HMAC_KEY = "bip-entropy-from-k" //nolint:revive // ALL_CAPS used for BIP85 constants // Application numbers - AppBIP39 = 39 // BIP39 mnemonics - AppHDWIF = 2 // WIF for Bitcoin Core - AppXPRV = 32 // Extended private key - APP_HEX = 128169 //nolint:revive // ALL_CAPS used for BIP85 constants - APP_PWD64 = 707764 // Base64 passwords //nolint:revive // ALL_CAPS used for BIP85 constants - AppPWD85 = 707785 // Base85 passwords - APP_RSA = 828365 //nolint:revive // ALL_CAPS used for BIP85 constants + AppBIP39 = 39 // BIP39 mnemonics + AppHDWIF = 2 // WIF for Bitcoin Core + AppXPRV = 32 // Extended private key + APP_HEX = 128169 //nolint:revive // ALL_CAPS used for BIP85 constants + APP_PWD64 = 707764 // Base64 passwords //nolint:revive // ALL_CAPS used for BIP85 constants + AppPWD85 = 707785 // Base85 passwords + APP_RSA = 828365 //nolint:revive // ALL_CAPS used for BIP85 constants ) // Version bytes for extended keys @@ -178,7 +178,7 @@ func DeriveBIP39Entropy(masterKey *hdkeychain.ExtendedKey, language, words, inde words21 = 21 // 224 bits of entropy words24 = 24 // 256 bits of entropy ) - + var bits int switch words { case words12: