passes tests now!
This commit is contained in:
@@ -207,6 +207,7 @@ func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) {
|
||||
mnemonic := os.Getenv(secret.EnvMnemonic)
|
||||
var derivationIndex uint32
|
||||
var publicKeyHash string
|
||||
var familyHash string
|
||||
|
||||
if mnemonic != "" {
|
||||
secret.Debug("Mnemonic found in environment, deriving long-term key", "vault", name)
|
||||
@@ -232,13 +233,16 @@ func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) {
|
||||
}
|
||||
secret.Debug("Wrote long-term public key", "path", ltPubKeyPath)
|
||||
|
||||
// Compute public key hash from index 0 (same for all vaults with this mnemonic)
|
||||
// Compute verification hash from actual derivation index
|
||||
publicKeyHash = ComputeDoubleSHA256([]byte(ltIdentity.Recipient().String()))
|
||||
|
||||
// Compute family hash from index 0 (same for all vaults with this mnemonic)
|
||||
// This is used to identify which vaults belong to the same mnemonic family
|
||||
identity0, err := agehd.DeriveIdentity(mnemonic, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive identity for index 0: %w", err)
|
||||
}
|
||||
publicKeyHash = ComputeDoubleSHA256([]byte(identity0.Recipient().String()))
|
||||
familyHash = ComputeDoubleSHA256([]byte(identity0.Recipient().String()))
|
||||
} else {
|
||||
secret.Debug("No mnemonic in environment, vault created without long-term key", "vault", name)
|
||||
// Use 0 for derivation index when no mnemonic is provided
|
||||
@@ -247,9 +251,10 @@ func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) {
|
||||
|
||||
// Save vault metadata
|
||||
metadata := &VaultMetadata{
|
||||
CreatedAt: time.Now(),
|
||||
DerivationIndex: derivationIndex,
|
||||
PublicKeyHash: publicKeyHash,
|
||||
CreatedAt: time.Now(),
|
||||
DerivationIndex: derivationIndex,
|
||||
PublicKeyHash: publicKeyHash,
|
||||
MnemonicFamilyHash: familyHash,
|
||||
}
|
||||
if err := SaveVaultMetadata(fs, vaultDir, metadata); err != nil {
|
||||
return nil, fmt.Errorf("failed to save vault metadata: %w", err)
|
||||
|
||||
@@ -75,8 +75,8 @@ func GetNextDerivationIndex(fs afero.Fs, stateDir string, mnemonic string) (uint
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this vault uses the same mnemonic by comparing public key hashes
|
||||
if metadata.PublicKeyHash == pubKeyHash {
|
||||
// Check if this vault uses the same mnemonic by comparing family hashes
|
||||
if metadata.MnemonicFamilyHash == pubKeyHash {
|
||||
usedIndices[metadata.DerivationIndex] = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,9 @@ func TestVaultMetadata(t *testing.T) {
|
||||
}
|
||||
|
||||
metadata1 := &VaultMetadata{
|
||||
DerivationIndex: 0,
|
||||
PublicKeyHash: pubKeyHash0,
|
||||
DerivationIndex: 0,
|
||||
PublicKeyHash: pubKeyHash0, // Hash of the actual key (index 0)
|
||||
MnemonicFamilyHash: pubKeyHash0, // Hash of index 0 key (for family identification)
|
||||
}
|
||||
if err := SaveVaultMetadata(fs, vaultDir, metadata1); err != nil {
|
||||
t.Fatalf("Failed to save metadata: %v", err)
|
||||
@@ -115,9 +116,13 @@ func TestVaultMetadata(t *testing.T) {
|
||||
t.Fatalf("Failed to write public key: %v", err)
|
||||
}
|
||||
|
||||
// Compute the hash for index 5 key
|
||||
pubKeyHash5 := ComputeDoubleSHA256([]byte(pubKey5))
|
||||
|
||||
metadata2 := &VaultMetadata{
|
||||
DerivationIndex: 5,
|
||||
PublicKeyHash: pubKeyHash0, // Same hash since it's from the same mnemonic
|
||||
DerivationIndex: 5,
|
||||
PublicKeyHash: pubKeyHash5, // Hash of the actual key (index 5)
|
||||
MnemonicFamilyHash: pubKeyHash0, // Same family hash since it's from the same mnemonic
|
||||
}
|
||||
if err := SaveVaultMetadata(fs, vaultDir2, metadata2); err != nil {
|
||||
t.Fatalf("Failed to save metadata: %v", err)
|
||||
|
||||
@@ -351,6 +351,7 @@ func (v *Vault) GetSecretVersion(name string, version string) ([]byte, error) {
|
||||
)
|
||||
|
||||
// Get the version's value
|
||||
secret.Debug("About to call secretVersion.GetValue", "version", version, "secret_name", name)
|
||||
decryptedValue, err := secretVersion.GetValue(longTermIdentity)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to decrypt version value", "error", err, "version", version, "secret_name", name)
|
||||
@@ -364,6 +365,13 @@ func (v *Vault) GetSecretVersion(name string, version string) ([]byte, error) {
|
||||
slog.Int("decrypted_length", len(decryptedValue)),
|
||||
)
|
||||
|
||||
// Debug: Log metadata about the decrypted value without exposing the actual secret
|
||||
secret.Debug("Vault secret decryption debug info",
|
||||
"secret_name", name,
|
||||
"version", version,
|
||||
"decrypted_value_length", len(decryptedValue),
|
||||
"is_empty", len(decryptedValue) == 0)
|
||||
|
||||
return decryptedValue, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -34,17 +34,23 @@ func (v *Vault) GetCurrentUnlocker() (secret.Unlocker, error) {
|
||||
|
||||
// Resolve the symlink to get the target directory
|
||||
var unlockerDir string
|
||||
if _, ok := v.fs.(*afero.OsFs); ok {
|
||||
secret.Debug("Resolving unlocker symlink (real filesystem)")
|
||||
// For real filesystems, resolve the symlink properly
|
||||
unlockerDir, err = ResolveVaultSymlink(v.fs, currentUnlockerPath)
|
||||
if linkReader, ok := v.fs.(afero.LinkReader); ok {
|
||||
secret.Debug("Resolving unlocker symlink using afero")
|
||||
// Try to read as symlink first
|
||||
unlockerDir, err = linkReader.ReadlinkIfPossible(currentUnlockerPath)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to resolve unlocker symlink", "error", err, "symlink_path", currentUnlockerPath)
|
||||
return nil, fmt.Errorf("failed to resolve current unlocker symlink: %w", err)
|
||||
secret.Debug("Failed to read symlink, falling back to file contents", "error", err, "symlink_path", currentUnlockerPath)
|
||||
// Fallback: read the path from file contents
|
||||
unlockerDirBytes, err := afero.ReadFile(v.fs, currentUnlockerPath)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to read unlocker path file", "error", err, "path", currentUnlockerPath)
|
||||
return nil, fmt.Errorf("failed to read current unlocker: %w", err)
|
||||
}
|
||||
unlockerDir = strings.TrimSpace(string(unlockerDirBytes))
|
||||
}
|
||||
} else {
|
||||
secret.Debug("Reading unlocker path (mock filesystem)")
|
||||
// Fallback for mock filesystems: read the path from file contents
|
||||
secret.Debug("Reading unlocker path (filesystem doesn't support symlinks)")
|
||||
// Fallback for filesystems that don't support symlinks: read the path from file contents
|
||||
unlockerDirBytes, err := afero.ReadFile(v.fs, currentUnlockerPath)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to read unlocker path file", "error", err, "path", currentUnlockerPath)
|
||||
@@ -319,8 +325,21 @@ func (v *Vault) SelectUnlocker(unlockerID string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Create new symlink
|
||||
return afero.WriteFile(v.fs, currentUnlockerPath, []byte(targetUnlockerDir), secret.FilePerms)
|
||||
// Create new symlink using afero's SymlinkIfPossible
|
||||
if linker, ok := v.fs.(afero.Linker); ok {
|
||||
secret.Debug("Creating unlocker symlink", "target", targetUnlockerDir, "link", currentUnlockerPath)
|
||||
if err := linker.SymlinkIfPossible(targetUnlockerDir, currentUnlockerPath); err != nil {
|
||||
return fmt.Errorf("failed to create unlocker symlink: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Fallback: create a regular file with the target path for filesystems that don't support symlinks
|
||||
secret.Debug("Fallback: creating regular file with target path", "target", targetUnlockerDir)
|
||||
if err := afero.WriteFile(v.fs, currentUnlockerPath, []byte(targetUnlockerDir), secret.FilePerms); err != nil {
|
||||
return fmt.Errorf("failed to create unlocker symlink file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePassphraseUnlocker creates a new passphrase-protected unlocker
|
||||
|
||||
@@ -84,6 +84,17 @@ func (v *Vault) GetOrDeriveLongTermKey() (*age.X25519Identity, error) {
|
||||
return nil, fmt.Errorf("failed to derive long-term key from mnemonic: %w", err)
|
||||
}
|
||||
|
||||
// Verify that the derived key matches the stored public key hash
|
||||
derivedPubKeyHash := ComputeDoubleSHA256([]byte(ltIdentity.Recipient().String()))
|
||||
if derivedPubKeyHash != metadata.PublicKeyHash {
|
||||
secret.Debug("Derived public key hash does not match stored hash",
|
||||
"vault_name", v.Name,
|
||||
"derived_hash", derivedPubKeyHash,
|
||||
"stored_hash", metadata.PublicKeyHash,
|
||||
"derivation_index", metadata.DerivationIndex)
|
||||
return nil, fmt.Errorf("derived public key does not match vault: mnemonic may be incorrect")
|
||||
}
|
||||
|
||||
secret.DebugWith("Successfully derived long-term key from mnemonic",
|
||||
slog.String("vault_name", v.Name),
|
||||
slog.String("public_key", ltIdentity.Recipient().String()),
|
||||
|
||||
Reference in New Issue
Block a user