This commit is contained in:
Jeffrey Paul 2025-06-09 05:59:26 -07:00
parent 512b742c46
commit 1f89fce21b
5 changed files with 1984 additions and 1588 deletions

File diff suppressed because it is too large Load Diff

View File

@ -223,8 +223,13 @@ func (cli *CLIInstance) VaultImport(vaultName string) error {
return fmt.Errorf("failed to store long-term public key: %w", err) return fmt.Errorf("failed to store long-term public key: %w", err)
} }
// Calculate public key hash // Calculate public key hash from index 0 (same for all vaults with this mnemonic)
publicKeyHash := vault.ComputeDoubleSHA256([]byte(ltPublicKey)) // This is used to identify which vaults belong to the same mnemonic family
identity0, err := agehd.DeriveIdentity(mnemonic, 0)
if err != nil {
return fmt.Errorf("failed to derive identity for index 0: %w", err)
}
publicKeyHash := vault.ComputeDoubleSHA256([]byte(identity0.Recipient().String()))
// Load existing metadata // Load existing metadata
existingMetadata, err := vault.LoadVaultMetadata(cli.fs, vaultDir) existingMetadata, err := vault.LoadVaultMetadata(cli.fs, vaultDir)

View File

@ -108,8 +108,18 @@ func TestVaultWithRealFilesystem(t *testing.T) {
t.Fatalf("Failed to create vault: %v", err) t.Fatalf("Failed to create vault: %v", err)
} }
// Derive long-term key from mnemonic // Load vault metadata to get its derivation index
ltIdentity, err := agehd.DeriveIdentity(testMnemonic, 0) vaultDir, err := vlt.GetDirectory()
if err != nil {
t.Fatalf("Failed to get vault directory: %v", err)
}
vaultMetadata, err := vault.LoadVaultMetadata(fs, vaultDir)
if err != nil {
t.Fatalf("Failed to load vault metadata: %v", err)
}
// Derive long-term key from mnemonic using the vault's derivation index
ltIdentity, err := agehd.DeriveIdentity(testMnemonic, vaultMetadata.DerivationIndex)
if err != nil { if err != nil {
t.Fatalf("Failed to derive long-term key: %v", err) t.Fatalf("Failed to derive long-term key: %v", err)
} }
@ -169,8 +179,18 @@ func TestVaultWithRealFilesystem(t *testing.T) {
t.Fatalf("Failed to create vault: %v", err) t.Fatalf("Failed to create vault: %v", err)
} }
// Derive long-term key from mnemonic for verification // Load vault metadata to get its derivation index
ltIdentity, err := agehd.DeriveIdentity(testMnemonic, 0) vaultDir, err := vlt.GetDirectory()
if err != nil {
t.Fatalf("Failed to get vault directory: %v", err)
}
vaultMetadata, err := vault.LoadVaultMetadata(fs, vaultDir)
if err != nil {
t.Fatalf("Failed to load vault metadata: %v", err)
}
// Derive long-term key from mnemonic for verification using the vault's derivation index
ltIdentity, err := agehd.DeriveIdentity(testMnemonic, vaultMetadata.DerivationIndex)
if err != nil { if err != nil {
t.Fatalf("Failed to derive long-term key: %v", err) t.Fatalf("Failed to derive long-term key: %v", err)
} }
@ -333,12 +353,33 @@ func TestVaultWithRealFilesystem(t *testing.T) {
// Derive long-term key from mnemonic // Derive long-term key from mnemonic
// Note: Both vaults will have different derivation indexes due to GetNextDerivationIndex // Note: Both vaults will have different derivation indexes due to GetNextDerivationIndex
ltIdentity1, err := agehd.DeriveIdentity(testMnemonic, 0) // vault1 gets index 0
// Load vault1 metadata to get its derivation index
vault1Dir, err := vault1.GetDirectory()
if err != nil {
t.Fatalf("Failed to get vault1 directory: %v", err)
}
vault1Metadata, err := vault.LoadVaultMetadata(fs, vault1Dir)
if err != nil {
t.Fatalf("Failed to load vault1 metadata: %v", err)
}
ltIdentity1, err := agehd.DeriveIdentity(testMnemonic, vault1Metadata.DerivationIndex)
if err != nil { if err != nil {
t.Fatalf("Failed to derive long-term key for vault1: %v", err) t.Fatalf("Failed to derive long-term key for vault1: %v", err)
} }
ltIdentity2, err := agehd.DeriveIdentity(testMnemonic, 1) // vault2 gets index 1 // Load vault2 metadata to get its derivation index
vault2Dir, err := vault2.GetDirectory()
if err != nil {
t.Fatalf("Failed to get vault2 directory: %v", err)
}
vault2Metadata, err := vault.LoadVaultMetadata(fs, vault2Dir)
if err != nil {
t.Fatalf("Failed to load vault2 metadata: %v", err)
}
ltIdentity2, err := agehd.DeriveIdentity(testMnemonic, vault2Metadata.DerivationIndex)
if err != nil { if err != nil {
t.Fatalf("Failed to derive long-term key for vault2: %v", err) t.Fatalf("Failed to derive long-term key for vault2: %v", err)
} }

View File

@ -218,7 +218,7 @@ func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) {
return nil, fmt.Errorf("failed to get next derivation index: %w", err) return nil, fmt.Errorf("failed to get next derivation index: %w", err)
} }
// Derive the long-term key // Derive the long-term key using the actual derivation index
ltIdentity, err := agehd.DeriveIdentity(mnemonic, derivationIndex) ltIdentity, err := agehd.DeriveIdentity(mnemonic, derivationIndex)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to derive long-term key: %w", err) return nil, fmt.Errorf("failed to derive long-term key: %w", err)
@ -233,6 +233,7 @@ func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) {
secret.Debug("Wrote long-term public key", "path", ltPubKeyPath) secret.Debug("Wrote long-term public key", "path", ltPubKeyPath)
// Compute public key hash from index 0 (same for all vaults with this mnemonic) // Compute public key 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) identity0, err := agehd.DeriveIdentity(mnemonic, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to derive identity for index 0: %w", err) return nil, fmt.Errorf("failed to derive identity for index 0: %w", err)

View File

@ -5,6 +5,8 @@ import (
"path/filepath" "path/filepath"
"strings"
"git.eeqj.de/sneak/secret/pkg/agehd" "git.eeqj.de/sneak/secret/pkg/agehd"
"github.com/spf13/afero" "github.com/spf13/afero"
) )
@ -200,3 +202,216 @@ func TestVaultMetadata(t *testing.T) {
} }
}) })
} }
func TestPublicKeyHashConsistency(t *testing.T) {
// Use the same test mnemonic that the integration test uses
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
// Derive identity from index 0 multiple times
identity1, err := agehd.DeriveIdentity(testMnemonic, 0)
if err != nil {
t.Fatalf("Failed to derive first identity: %v", err)
}
identity2, err := agehd.DeriveIdentity(testMnemonic, 0)
if err != nil {
t.Fatalf("Failed to derive second identity: %v", err)
}
// Verify identities are the same
if identity1.Recipient().String() != identity2.Recipient().String() {
t.Errorf("Identity derivation is not deterministic")
t.Logf("First: %s", identity1.Recipient().String())
t.Logf("Second: %s", identity2.Recipient().String())
}
// Compute public key hashes
hash1 := ComputeDoubleSHA256([]byte(identity1.Recipient().String()))
hash2 := ComputeDoubleSHA256([]byte(identity2.Recipient().String()))
// Verify hashes are the same
if hash1 != hash2 {
t.Errorf("Public key hash computation is not deterministic")
t.Logf("First hash: %s", hash1)
t.Logf("Second hash: %s", hash2)
}
t.Logf("Test mnemonic public key hash (index 0): %s", hash1)
}
func TestSampleHashCalculation(t *testing.T) {
// Test with the exact mnemonic from integration test if available
// We'll also test with a few different mnemonics to make sure they produce different hashes
mnemonics := []string{
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"legal winner thank year wave sausage worth useful legal winner thank yellow",
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",
}
for i, mnemonic := range mnemonics {
identity, err := agehd.DeriveIdentity(mnemonic, 0)
if err != nil {
t.Fatalf("Failed to derive identity for mnemonic %d: %v", i, err)
}
hash := ComputeDoubleSHA256([]byte(identity.Recipient().String()))
t.Logf("Mnemonic %d hash (index 0): %s", i, hash)
t.Logf(" Recipient: %s", identity.Recipient().String())
}
}
func TestWorkflowMismatch(t *testing.T) {
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
// Create a temporary directory for testing
tempDir := t.TempDir()
fs := afero.NewOsFs()
// Test Case 1: Create vault WITH mnemonic (like init command)
t.Setenv("SB_SECRET_MNEMONIC", testMnemonic)
_, err := CreateVault(fs, tempDir, "default")
if err != nil {
t.Fatalf("Failed to create vault with mnemonic: %v", err)
}
// Load metadata for vault1
vault1Dir := filepath.Join(tempDir, "vaults.d", "default")
metadata1, err := LoadVaultMetadata(fs, vault1Dir)
if err != nil {
t.Fatalf("Failed to load vault1 metadata: %v", err)
}
t.Logf("Vault1 (with mnemonic) - DerivationIndex: %d, PublicKeyHash: %s",
metadata1.DerivationIndex, metadata1.PublicKeyHash)
// Test Case 2: Create vault WITHOUT mnemonic, then import (like work vault)
t.Setenv("SB_SECRET_MNEMONIC", "")
_, err = CreateVault(fs, tempDir, "work")
if err != nil {
t.Fatalf("Failed to create vault without mnemonic: %v", err)
}
vault2Dir := filepath.Join(tempDir, "vaults.d", "work")
// Simulate the vault import process
t.Setenv("SB_SECRET_MNEMONIC", testMnemonic)
// Get the next available derivation index for this mnemonic
derivationIndex, err := GetNextDerivationIndex(fs, tempDir, testMnemonic)
if err != nil {
t.Fatalf("Failed to get next derivation index: %v", err)
}
t.Logf("Next derivation index for import: %d", derivationIndex)
// Calculate public key hash from index 0 (same as in VaultImport)
identity0, err := agehd.DeriveIdentity(testMnemonic, 0)
if err != nil {
t.Fatalf("Failed to derive identity for index 0: %v", err)
}
publicKeyHash := ComputeDoubleSHA256([]byte(identity0.Recipient().String()))
// Load existing metadata and update it (same as in VaultImport)
existingMetadata, err := LoadVaultMetadata(fs, vault2Dir)
if err != nil {
t.Fatalf("Failed to load existing metadata: %v", err)
}
// Update metadata with new derivation info
existingMetadata.DerivationIndex = derivationIndex
existingMetadata.PublicKeyHash = publicKeyHash
if err := SaveVaultMetadata(fs, vault2Dir, existingMetadata); err != nil {
t.Fatalf("Failed to save vault metadata: %v", err)
}
// Load updated metadata for vault2
metadata2, err := LoadVaultMetadata(fs, vault2Dir)
if err != nil {
t.Fatalf("Failed to load vault2 metadata: %v", err)
}
t.Logf("Vault2 (imported mnemonic) - DerivationIndex: %d, PublicKeyHash: %s",
metadata2.DerivationIndex, metadata2.PublicKeyHash)
// Verify that both vaults have the same public key hash
if metadata1.PublicKeyHash != metadata2.PublicKeyHash {
t.Errorf("Public key hashes don't match!")
t.Logf("Vault1 hash: %s", metadata1.PublicKeyHash)
t.Logf("Vault2 hash: %s", metadata2.PublicKeyHash)
} else {
t.Logf("SUCCESS: Both vaults have the same public key hash: %s", metadata1.PublicKeyHash)
}
}
func TestReverseEngineerHash(t *testing.T) {
// This is the hash that the work vault is getting in the failing test
wrongHash := "e34a2f500e395d8934a90a99ee9311edcfffd68cb701079575e50cbac7bb9417"
correctHash := "992552b00b3879dfae461fab9a084b47784a032771c7a9accaebdde05ec7a7d1"
// Test mnemonic from integration test
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
// Calculate hash for test mnemonic
identity, err := agehd.DeriveIdentity(testMnemonic, 0)
if err != nil {
t.Fatalf("Failed to derive identity: %v", err)
}
calculatedHash := ComputeDoubleSHA256([]byte(identity.Recipient().String()))
t.Logf("Test mnemonic hash: %s", calculatedHash)
if calculatedHash == correctHash {
t.Logf("✓ Test mnemonic produces the correct hash")
} else {
t.Errorf("✗ Test mnemonic does not produce the correct hash")
}
if calculatedHash == wrongHash {
t.Logf("✗ Test mnemonic unexpectedly produces the wrong hash")
}
// Let's try some other possibilities - maybe there's a string normalization issue?
variations := []string{
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
" abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ",
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\n",
strings.TrimSpace("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"),
}
for i, variation := range variations {
identity, err := agehd.DeriveIdentity(variation, 0)
if err != nil {
t.Logf("Variation %d failed: %v", i, err)
continue
}
hash := ComputeDoubleSHA256([]byte(identity.Recipient().String()))
t.Logf("Variation %d hash: %s", i, hash)
if hash == wrongHash {
t.Logf("✗ Found variation that produces wrong hash: '%s'", variation)
}
}
// Maybe let's try an empty mnemonic or something else?
emptyMnemonics := []string{
"",
" ",
}
for i, emptyMnemonic := range emptyMnemonics {
identity, err := agehd.DeriveIdentity(emptyMnemonic, 0)
if err != nil {
t.Logf("Empty mnemonic %d failed (expected): %v", i, err)
continue
}
hash := ComputeDoubleSHA256([]byte(identity.Recipient().String()))
t.Logf("Empty mnemonic %d hash: %s", i, hash)
if hash == wrongHash {
t.Logf("✗ Empty mnemonic produces wrong hash!")
}
}
}