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 UnlockKeyMetadata = secret.UnlockKeyMetadata 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 }