secret/internal/vault/metadata.go

120 lines
3.3 KiB
Go

package vault
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"path/filepath"
"git.eeqj.de/sneak/secret/internal/secret"
"github.com/spf13/afero"
)
// Alias the metadata types from secret package for convenience
type VaultMetadata = secret.VaultMetadata
type UnlockerMetadata = secret.UnlockerMetadata
type SecretMetadata = secret.SecretMetadata
type Configuration = secret.Configuration
// ComputeDoubleSHA256 computes the double SHA256 hash of data and returns it as hex
func ComputeDoubleSHA256(data []byte) string {
firstHash := sha256.Sum256(data)
secondHash := sha256.Sum256(firstHash[:])
return hex.EncodeToString(secondHash[:])
}
// GetNextDerivationIndex finds the next available derivation index for a given mnemonic hash
func GetNextDerivationIndex(fs afero.Fs, stateDir string, mnemonicHash string) (uint32, error) {
vaultsDir := filepath.Join(stateDir, "vaults.d")
// Check if vaults directory exists
exists, err := afero.DirExists(fs, vaultsDir)
if err != nil {
return 0, fmt.Errorf("failed to check if vaults directory exists: %w", err)
}
if !exists {
// No vaults yet, start with index 0
return 0, nil
}
// Read all vault directories
entries, err := afero.ReadDir(fs, vaultsDir)
if err != nil {
return 0, fmt.Errorf("failed to read vaults directory: %w", err)
}
// Track the highest index for this mnemonic
var highestIndex uint32 = 0
foundMatch := false
for _, entry := range entries {
if !entry.IsDir() {
continue
}
// Try to read vault metadata
metadataPath := filepath.Join(vaultsDir, entry.Name(), "vault-metadata.json")
metadataBytes, err := afero.ReadFile(fs, metadataPath)
if err != nil {
// Skip vaults without metadata
continue
}
var metadata VaultMetadata
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
// Skip vaults with invalid metadata
continue
}
// Check if this vault uses the same mnemonic
if metadata.MnemonicHash == mnemonicHash {
foundMatch = true
if metadata.DerivationIndex >= highestIndex {
highestIndex = metadata.DerivationIndex
}
}
}
// If we found a match, use the next index
if foundMatch {
return highestIndex + 1, nil
}
// No existing vault with this mnemonic, start at 0
return 0, nil
}
// SaveVaultMetadata saves vault metadata to the vault directory
func SaveVaultMetadata(fs afero.Fs, vaultDir string, metadata *VaultMetadata) error {
metadataPath := filepath.Join(vaultDir, "vault-metadata.json")
metadataBytes, err := json.MarshalIndent(metadata, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal vault metadata: %w", err)
}
if err := afero.WriteFile(fs, metadataPath, metadataBytes, secret.FilePerms); err != nil {
return fmt.Errorf("failed to write vault metadata: %w", err)
}
return nil
}
// LoadVaultMetadata loads vault metadata from the vault directory
func LoadVaultMetadata(fs afero.Fs, vaultDir string) (*VaultMetadata, error) {
metadataPath := filepath.Join(vaultDir, "vault-metadata.json")
metadataBytes, err := afero.ReadFile(fs, metadataPath)
if err != nil {
return nil, fmt.Errorf("failed to read vault metadata: %w", err)
}
var metadata VaultMetadata
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
return nil, fmt.Errorf("failed to unmarshal vault metadata: %w", err)
}
return &metadata, nil
}