//go:build darwin package secret import ( "encoding/json" "path/filepath" "testing" "time" "git.eeqj.de/sneak/secret/pkg/agehd" "github.com/awnumar/memguard" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // realVault is a minimal VaultInterface backed by a real afero filesystem, // using the same directory layout as vault.Vault. type realVault struct { name string stateDir string fs afero.Fs } func (v *realVault) GetDirectory() (string, error) { return filepath.Join(v.stateDir, "vaults.d", v.name), nil } func (v *realVault) GetName() string { return v.name } func (v *realVault) GetFilesystem() afero.Fs { return v.fs } // Unused by getLongTermPrivateKey — these satisfy VaultInterface. func (v *realVault) AddSecret(string, *memguard.LockedBuffer, bool) error { panic("not used") } func (v *realVault) GetCurrentUnlocker() (Unlocker, error) { panic("not used") } func (v *realVault) CreatePassphraseUnlocker(*memguard.LockedBuffer) (*PassphraseUnlocker, error) { panic("not used") } // createRealVault sets up a complete vault directory structure on an in-memory // filesystem, identical to what vault.CreateVault produces. func createRealVault(t *testing.T, fs afero.Fs, stateDir, name string, derivationIndex uint32) *realVault { t.Helper() vaultDir := filepath.Join(stateDir, "vaults.d", name) require.NoError(t, fs.MkdirAll(filepath.Join(vaultDir, "secrets.d"), DirPerms)) require.NoError(t, fs.MkdirAll(filepath.Join(vaultDir, "unlockers.d"), DirPerms)) metadata := VaultMetadata{ CreatedAt: time.Now(), DerivationIndex: derivationIndex, } metaBytes, err := json.Marshal(metadata) require.NoError(t, err) require.NoError(t, afero.WriteFile(fs, filepath.Join(vaultDir, "vault-metadata.json"), metaBytes, FilePerms)) return &realVault{name: name, stateDir: stateDir, fs: fs} } func TestGetLongTermPrivateKeyUsesVaultDerivationIndex(t *testing.T) { const testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" // Derive expected keys at two different indices to prove they differ. key0, err := agehd.DeriveIdentity(testMnemonic, 0) require.NoError(t, err) key5, err := agehd.DeriveIdentity(testMnemonic, 5) require.NoError(t, err) require.NotEqual(t, key0.String(), key5.String(), "sanity check: different derivation indices must produce different keys") // Build a real vault with DerivationIndex=5 on an in-memory filesystem. fs := afero.NewMemMapFs() vault := createRealVault(t, fs, "/state", "test-vault", 5) t.Setenv(EnvMnemonic, testMnemonic) result, err := getLongTermPrivateKey(fs, vault) require.NoError(t, err) defer result.Destroy() assert.Equal(t, key5.String(), string(result.Bytes()), "getLongTermPrivateKey should derive at vault's DerivationIndex (5)") assert.NotEqual(t, key0.String(), string(result.Bytes()), "getLongTermPrivateKey must not use hardcoded index 0") }