412 lines
13 KiB
Go
412 lines
13 KiB
Go
package vault
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"git.eeqj.de/sneak/secret/pkg/agehd"
|
|
"github.com/spf13/afero"
|
|
)
|
|
|
|
func TestVaultMetadata(t *testing.T) {
|
|
fs := afero.NewMemMapFs()
|
|
stateDir := "/test/state"
|
|
|
|
// Test mnemonic for consistent testing
|
|
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
|
|
t.Run("ComputeDoubleSHA256", func(t *testing.T) {
|
|
// Test data
|
|
data := []byte("test data")
|
|
hash := ComputeDoubleSHA256(data)
|
|
|
|
// Verify it's a valid hex string of 64 characters (32 bytes * 2)
|
|
if len(hash) != 64 {
|
|
t.Errorf("Expected hash length of 64, got %d", len(hash))
|
|
}
|
|
|
|
// Verify consistency
|
|
hash2 := ComputeDoubleSHA256(data)
|
|
if hash != hash2 {
|
|
t.Errorf("Hash should be consistent for same input")
|
|
}
|
|
|
|
// Verify different input produces different hash
|
|
hash3 := ComputeDoubleSHA256([]byte("different data"))
|
|
if hash == hash3 {
|
|
t.Errorf("Different input should produce different hash")
|
|
}
|
|
})
|
|
|
|
t.Run("GetNextDerivationIndex", func(t *testing.T) {
|
|
// Test with no existing vaults
|
|
index, err := GetNextDerivationIndex(fs, stateDir, testMnemonic)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get derivation index: %v", err)
|
|
}
|
|
if index != 0 {
|
|
t.Errorf("Expected index 0 for first vault, got %d", index)
|
|
}
|
|
|
|
// Create a vault with metadata and matching public key
|
|
vaultDir := filepath.Join(stateDir, "vaults.d", "vault1")
|
|
if err := fs.MkdirAll(vaultDir, 0700); err != nil {
|
|
t.Fatalf("Failed to create vault directory: %v", err)
|
|
}
|
|
|
|
// Derive identity for index 0
|
|
identity0, err := agehd.DeriveIdentity(testMnemonic, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive identity: %v", err)
|
|
}
|
|
pubKey0 := identity0.Recipient().String()
|
|
pubKeyHash0 := ComputeDoubleSHA256([]byte(pubKey0))
|
|
|
|
// Write public key
|
|
if err := afero.WriteFile(fs, filepath.Join(vaultDir, "pub.age"), []byte(pubKey0), 0600); err != nil {
|
|
t.Fatalf("Failed to write public key: %v", err)
|
|
}
|
|
|
|
metadata1 := &VaultMetadata{
|
|
DerivationIndex: 0,
|
|
PublicKeyHash: pubKeyHash0,
|
|
}
|
|
if err := SaveVaultMetadata(fs, vaultDir, metadata1); err != nil {
|
|
t.Fatalf("Failed to save metadata: %v", err)
|
|
}
|
|
|
|
// Next index for same mnemonic should be 1
|
|
index, err = GetNextDerivationIndex(fs, stateDir, testMnemonic)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get derivation index: %v", err)
|
|
}
|
|
if index != 1 {
|
|
t.Errorf("Expected index 1 for second vault with same mnemonic, got %d", index)
|
|
}
|
|
|
|
// Different mnemonic should start at 0
|
|
differentMnemonic := "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong"
|
|
index, err = GetNextDerivationIndex(fs, stateDir, differentMnemonic)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get derivation index: %v", err)
|
|
}
|
|
if index != 0 {
|
|
t.Errorf("Expected index 0 for first vault with different mnemonic, got %d", index)
|
|
}
|
|
|
|
// Add another vault with same mnemonic but higher index
|
|
vaultDir2 := filepath.Join(stateDir, "vaults.d", "vault2")
|
|
if err := fs.MkdirAll(vaultDir2, 0700); err != nil {
|
|
t.Fatalf("Failed to create vault directory: %v", err)
|
|
}
|
|
|
|
// Derive identity for index 5
|
|
identity5, err := agehd.DeriveIdentity(testMnemonic, 5)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive identity: %v", err)
|
|
}
|
|
pubKey5 := identity5.Recipient().String()
|
|
|
|
// Write public key
|
|
if err := afero.WriteFile(fs, filepath.Join(vaultDir2, "pub.age"), []byte(pubKey5), 0600); err != nil {
|
|
t.Fatalf("Failed to write public key: %v", err)
|
|
}
|
|
|
|
metadata2 := &VaultMetadata{
|
|
DerivationIndex: 5,
|
|
PublicKeyHash: pubKeyHash0, // Same hash since it's from the same mnemonic
|
|
}
|
|
if err := SaveVaultMetadata(fs, vaultDir2, metadata2); err != nil {
|
|
t.Fatalf("Failed to save metadata: %v", err)
|
|
}
|
|
|
|
// Next index should be 1 (not 6) because we look for the first available slot
|
|
index, err = GetNextDerivationIndex(fs, stateDir, testMnemonic)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get derivation index: %v", err)
|
|
}
|
|
if index != 1 {
|
|
t.Errorf("Expected index 1 (first available), got %d", index)
|
|
}
|
|
})
|
|
|
|
t.Run("MetadataPersistence", func(t *testing.T) {
|
|
vaultDir := filepath.Join(stateDir, "vaults.d", "test-vault")
|
|
if err := fs.MkdirAll(vaultDir, 0700); err != nil {
|
|
t.Fatalf("Failed to create vault directory: %v", err)
|
|
}
|
|
|
|
// Create and save metadata
|
|
metadata := &VaultMetadata{
|
|
DerivationIndex: 3,
|
|
PublicKeyHash: "test-public-key-hash",
|
|
}
|
|
|
|
if err := SaveVaultMetadata(fs, vaultDir, metadata); err != nil {
|
|
t.Fatalf("Failed to save metadata: %v", err)
|
|
}
|
|
|
|
// Load and verify
|
|
loaded, err := LoadVaultMetadata(fs, vaultDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load metadata: %v", err)
|
|
}
|
|
|
|
if loaded.DerivationIndex != metadata.DerivationIndex {
|
|
t.Errorf("DerivationIndex mismatch: expected %d, got %d", metadata.DerivationIndex, loaded.DerivationIndex)
|
|
}
|
|
if loaded.PublicKeyHash != metadata.PublicKeyHash {
|
|
t.Errorf("PublicKeyHash mismatch: expected %s, got %s", metadata.PublicKeyHash, loaded.PublicKeyHash)
|
|
}
|
|
})
|
|
|
|
t.Run("DifferentKeysForDifferentIndices", func(t *testing.T) {
|
|
// Derive keys with different indices
|
|
identity0, err := agehd.DeriveIdentity(testMnemonic, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive identity with index 0: %v", err)
|
|
}
|
|
|
|
identity1, err := agehd.DeriveIdentity(testMnemonic, 1)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive identity with index 1: %v", err)
|
|
}
|
|
|
|
// Compute public key hashes
|
|
pubKey0 := identity0.Recipient().String()
|
|
pubKey1 := identity1.Recipient().String()
|
|
hash0 := ComputeDoubleSHA256([]byte(pubKey0))
|
|
|
|
// Verify different indices produce different public keys
|
|
if pubKey0 == pubKey1 {
|
|
t.Errorf("Different derivation indices should produce different public keys")
|
|
}
|
|
|
|
// But the hash of index 0's public key should be the same for the same mnemonic
|
|
// This is what we use as the identifier
|
|
identity0Again, _ := agehd.DeriveIdentity(testMnemonic, 0)
|
|
pubKey0Again := identity0Again.Recipient().String()
|
|
hash0Again := ComputeDoubleSHA256([]byte(pubKey0Again))
|
|
|
|
if hash0 != hash0Again {
|
|
t.Errorf("Same mnemonic should produce same public key hash for index 0")
|
|
}
|
|
})
|
|
}
|
|
|
|
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!")
|
|
}
|
|
}
|
|
}
|