package vault import ( "testing" "path/filepath" "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{ Name: "vault1", 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{ Name: "vault2", 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{ Name: "test-vault", 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.Name != metadata.Name { t.Errorf("Name mismatch: expected %s, got %s", metadata.Name, loaded.Name) } 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") } }) }