diff --git a/TODO.md b/TODO.md index e4bf515..ff98eeb 100644 --- a/TODO.md +++ b/TODO.md @@ -18,6 +18,19 @@ https://git.eeqj.de/sneak/secret/milestone/12 # Completed Steps +- 2026-10-04: `secret init` and `secret vault create` create a vault whole or + not at all (https://git.eeqj.de/sneak/secret/issues/105). + `vault.CreateVault` now takes the unlocker passphrase too, writes the vault + directory with its metadata, long-term public key and passphrase unlocker, + `longterm.age` included, into a temporary directory, renames that into + `vaults.d` once it is complete, and only then makes the vault current. + Before, either command killed after the passphrase prompt but before the + unlocker was written left a vault with no unlocker, which `vault create` had + already made current and which neither command would create again. Killed + part-way now, it leaves no vault, and the next command that takes the lock + deletes the temporary directory; or, killed between the rename and making + the vault current, a complete vault that is not current, which + `secret vault select` makes current. - 2026-10-04: A failed `secret unlocker add keychain` or `secret unlocker add secure-enclave` no longer leaves its keychain item or Secure Enclave key behind (https://git.eeqj.de/sneak/secret/issues/89). @@ -258,11 +271,7 @@ https://git.eeqj.de/sneak/secret/milestone/12 `current-unlocker` never go missing. New versions, new secrets and cross-vault copies are built in a temporary directory and renamed into place, and removals rename out of the way first, so a version - or secret is never half-added and never half-removed. An - interrupted command can still leave, from `init` or `vault create` - killed after the passphrase prompt but before the unlocker is - written, a vault with no unlocker, which `vault create` has already - made the current vault. + or secret is never half-added and never half-removed. - 2026-10-03: The checks run before changing a vault now stop with an error naming the path and cause when they cannot read what they inspect, instead of reading the failure as "nothing there": the diff --git a/internal/cli/confirm_test.go b/internal/cli/confirm_test.go index 8569e70..9385580 100644 --- a/internal/cli/confirm_test.go +++ b/internal/cli/confirm_test.go @@ -63,10 +63,10 @@ func newConfirmTestVaults( fs := &afero.MemMapFs{} mnemonic := testMnemonicBuffer(t) - _, err := vault.CreateVault(fs, testStateDir, "other", mnemonic) + _, err := vault.CreateVault(fs, testStateDir, "other", mnemonic, nil) require.NoError(t, err) - vlt, err := vault.CreateVault(fs, testStateDir, "work", mnemonic) + vlt, err := vault.CreateVault(fs, testStateDir, "work", mnemonic, nil) require.NoError(t, err) addTestSecret(t, vlt, []byte("older"), false) diff --git a/internal/cli/create_vault_test.go b/internal/cli/create_vault_test.go index b778851..d1c1e2a 100644 --- a/internal/cli/create_vault_test.go +++ b/internal/cli/create_vault_test.go @@ -2,7 +2,11 @@ package cli_test import ( "bytes" + "io" + "maps" "os" + "slices" + "strings" "testing" "git.eeqj.de/sneak/secret/internal/cli" @@ -155,7 +159,7 @@ func TestStopAtPassphrasePromptLeavesNothing(t *testing.T) { require.NoError(t, empty.MkdirAll(testStateDir, secret.DirPerms)) withDefault := afero.NewMemMapFs() - _, err := vault.CreateVault(withDefault, testStateDir, "default", mnemonic) + _, err := vault.CreateVault(withDefault, testStateDir, "default", mnemonic, nil) require.NoError(t, err) cmd := &cobra.Command{} @@ -193,3 +197,192 @@ func TestStopAtPassphrasePromptLeavesNothing(t *testing.T) { }) } } + +// TestStopDuringCreateLeavesWholeVaultOrNone is a regression test for +// https://git.eeqj.de/sneak/secret/issues/105: `secret init` or `secret vault +// create` killed after the passphrase prompt but before the unlocker was +// written left a vault with no unlocker, which neither command would then +// create again. After the prompt, each command changes the state directory +// only through vault.CreateVault. The test makes that call as the command +// does and records the state directory before each change it makes, and once +// after it returns: what a stop at that point leaves. Each must hold either +// no vault, and not name it current, or exactly the finished vault, which +// opens with the passphrase through its current unlocker. The command run +// again after a stop first takes the lock, which must delete what the stop +// left under a temporary name. Running the command is slow, so it runs once +// on each different state the lock leaves, and must create the vault there, +// or refuse the one there. +// +//nolint:paralleltest // commands on the in-memory filesystem share one lock +func TestStopDuringCreateLeavesWholeVaultOrNone(t *testing.T) { + mnemonic := testMnemonicBuffer(t) + passphrase := memguard.NewBufferFromBytes([]byte(testPassphrase)) + t.Cleanup(passphrase.Destroy) + + cmd := &cobra.Command{} + cmd.SetOut(io.Discard) + + t.Run("init", func(t *testing.T) { + // From an empty state directory + fs := afero.NewMemMapFs() + require.NoError(t, fs.MkdirAll(testStateDir, secret.DirPerms)) + + requireStopsLeaveWholeVaultOrNone(t, fs, "default", + "failed to create default vault: vault default already exists", + mnemonic, passphrase, + func(c *cli.Instance) error { return c.Init(cmd) }) + }) + + t.Run("vault create work", func(t *testing.T) { + // From a state directory holding the vault "default" + fs := afero.NewMemMapFs() + _, err := vault.CreateVault(fs, testStateDir, "default", mnemonic, nil) + require.NoError(t, err) + + requireStopsLeaveWholeVaultOrNone(t, fs, "work", "vault work already exists", + mnemonic, passphrase, + func(c *cli.Instance) error { return c.CreateVault(cmd, "work") }) + }) +} + +// requireStopsLeaveWholeVaultOrNone checks, as +// TestStopDuringCreateLeavesWholeVaultOrNone describes, the stops of the +// command run, creating the vault name on fs with mnemonic and passphrase. +// Run again where the vault is there, the command must fail with exists. +func requireStopsLeaveWholeVaultOrNone( + t *testing.T, fs afero.Fs, name, exists string, + mnemonic, passphrase *memguard.LockedBuffer, + run func(c *cli.Instance) error, +) { + t.Helper() + + var stops []map[string]string + + record := func() { stops = append(stops, snapshotStateDir(t, fs)) } + + _, err := vault.CreateVault(hookFs{Fs: fs, before: record}, + testStateDir, name, mnemonic, passphrase) + require.NoError(t, err) + record() + + vaultDir := testStateDir + "/vaults.d/" + name + require.NotContains(t, stops[0], vaultDir+"/", "no stop before the vault") + + finished := entriesUnder(stops[len(stops)-1], vaultDir) + + opener := vault.NewVault(fs, testStateDir, name) + opener.UnlockPassphrase = passphrase + + key, err := opener.UnlockVault() + require.NoError(t, err) + require.Equal(t, finished[vaultDir+"/pub.age"], key.Recipient().String()) + + // Each different state the command run again finds once it holds the lock + var locked []map[string]string + + for i, stop := range stops { + if _, there := stop[vaultDir+"/"]; there { + require.Equal(t, finished, entriesUnder(stop, vaultDir), + "stop %d left a partial vault", i) + } else { + require.NotEqual(t, name, stop[testStateDir+"/currentvault"], + "stop %d made a missing vault current", i) + } + + stopped := newFsFromSnapshot(t, stop) + release, err := vault.LockStateDir(stopped, testStateDir) + require.NoError(t, err) + release() + + state := snapshotStateDir(t, stopped) + for path := range state { + require.NotContains(t, path, ".tmp-", "stop %d", i) + } + + if !slices.ContainsFunc(locked, func(s map[string]string) bool { + return maps.Equal(s, state) + }) { + locked = append(locked, state) + } + } + + for _, state := range locked { + c := cli.NewCLIInstanceWithStateDir(newFsFromSnapshot(t, state), testStateDir) + c.Mnemonic = mnemonic + c.UnlockPassphrase = passphrase + + if _, there := state[vaultDir+"/"]; there { + require.EqualError(t, run(c), exists) + } else { + require.NoError(t, run(c)) + } + } +} + +// entriesUnder returns the entries of a tree recorded by snapshotStateDir +// that are under dir. +func entriesUnder(tree map[string]string, dir string) map[string]string { + entries := map[string]string{} + + for path, content := range tree { + if strings.HasPrefix(path, dir+"/") { + entries[path] = content + } + } + + return entries +} + +// hookFs passes every call through to Fs, but first calls before for each +// call that can change the filesystem. +type hookFs struct { + afero.Fs + + before func() +} + +//nolint:ireturn // implements afero.Fs +func (h hookFs) Create(name string) (afero.File, error) { + h.before() + + return h.Fs.Create(name) +} + +//nolint:ireturn // implements afero.Fs +func (h hookFs) OpenFile( + name string, flag int, perm os.FileMode, +) (afero.File, error) { + h.before() + + return h.Fs.OpenFile(name, flag, perm) +} + +func (h hookFs) Mkdir(name string, perm os.FileMode) error { + h.before() + + return h.Fs.Mkdir(name, perm) +} + +func (h hookFs) MkdirAll(path string, perm os.FileMode) error { + h.before() + + return h.Fs.MkdirAll(path, perm) +} + +func (h hookFs) Remove(name string) error { + h.before() + + return h.Fs.Remove(name) +} + +func (h hookFs) RemoveAll(path string) error { + h.before() + + return h.Fs.RemoveAll(path) +} + +func (h hookFs) Rename(oldname, newname string) error { + h.before() + + return h.Fs.Rename(oldname, newname) +} diff --git a/internal/cli/init.go b/internal/cli/init.go index a451b37..1b9142e 100644 --- a/internal/cli/init.go +++ b/internal/cli/init.go @@ -6,13 +6,10 @@ import ( "log" "log/slog" "os" - "path/filepath" "strings" - "filippo.io/age" "git.eeqj.de/sneak/secret/internal/secret" "git.eeqj.de/sneak/secret/internal/vault" - "git.eeqj.de/sneak/secret/pkg/agehd" "github.com/awnumar/memguard" "github.com/spf13/cobra" "github.com/tyler-smith/go-bip39" @@ -70,43 +67,6 @@ func (cli *Instance) promptMnemonic() (*memguard.LockedBuffer, func(), error) { return mnemonicBuffer, mnemonicBuffer.Destroy, nil } -// setupDefaultVault creates the default vault and derives its long-term -// identity from the mnemonic -func (cli *Instance) setupDefaultVault( - stateDir string, mnemonic *memguard.LockedBuffer, -) (*vault.Vault, *age.X25519Identity, error) { - // Create the default vault - it will handle key derivation internally - secret.Debug("Creating default vault") - - vlt, err := vault.CreateVault(cli.fs, cli.stateDir, "default", mnemonic) - if err != nil { - secret.Debug("Failed to create default vault", "error", err) - - return nil, nil, fmt.Errorf("failed to create default vault: %w", err) - } - - // Get the vault metadata to retrieve the derivation index - vaultDir := filepath.Join(stateDir, "vaults.d", "default") - - metadata, err := vault.LoadVaultMetadata(cli.fs, vaultDir) - if err != nil { - secret.Debug("Failed to load vault metadata", "error", err) - - return nil, nil, fmt.Errorf("failed to load vault metadata: %w", err) - } - - // Derive the long-term key using the same index that CreateVault used - ltIdentity, err := agehd.DeriveIdentity(mnemonic.String(), metadata.DerivationIndex) - if err != nil { - secret.Debug("Failed to derive long-term key", "error", err) - - return nil, nil, fmt.Errorf( - "failed to derive long-term key from mnemonic: %w", err) - } - - return vlt, ltIdentity, nil -} - // Init initializes the secret manager, holding the state directory lock // while initialize runs func (cli *Instance) Init(cmd *cobra.Command) error { @@ -173,34 +133,31 @@ func (cli *Instance) initialize(cmd *cobra.Command) error { } defer cleanupPassphrase() - // Create the default vault and derive its long-term key - vlt, ltIdentity, err := cli.setupDefaultVault(stateDir, mnemonic) + // Create the default vault with its passphrase unlocker + secret.Debug("Creating default vault") + + vlt, err := vault.CreateVault(cli.fs, cli.stateDir, "default", + mnemonic, passphraseBuffer) + if err != nil { + secret.Debug("Failed to create default vault", "error", err) + + return fmt.Errorf("failed to create default vault: %w", err) + } + + ltIdentity, err := vlt.GetOrDeriveLongTermKey() + if err != nil { + return fmt.Errorf("failed to get long-term key: %w", err) + } + + unlocker, err := vlt.GetCurrentUnlocker() if err != nil { return err } - ltPubKey := ltIdentity.Recipient().String() - - // Unlock the vault with the derived long-term key - vlt.Unlock(ltIdentity) - - // Create passphrase-protected unlocker - secret.Debug("Creating passphrase-protected unlocker") - - passphraseUnlocker, err := vlt.CreatePassphraseUnlocker(passphraseBuffer) - if err != nil { - secret.Debug("Failed to create unlocker", "error", err) - - return fmt.Errorf("failed to create unlocker: %w", err) - } - - // Note: CreatePassphraseUnlocker already encrypts and writes the long-term - // private key to longterm.age, so no need to do it again here. - if cmd != nil { cmd.Printf("\nDefault vault created and configured\n") - cmd.Printf("Long-term public key: %s\n", ltPubKey) - cmd.Printf("Unlocker ID: %s\n", passphraseUnlocker.GetID()) + cmd.Printf("Long-term public key: %s\n", ltIdentity.Recipient().String()) + cmd.Printf("Unlocker ID: %s\n", unlocker.GetID()) cmd.Println("\nYour secret manager is ready to use!") cmd.Println("Note: When using SB_SECRET_MNEMONIC environment variable,") cmd.Println("unlockers are not required for secret operations.") diff --git a/internal/cli/integration_test.go b/internal/cli/integration_test.go index a77ae0e..d6a54e9 100644 --- a/internal/cli/integration_test.go +++ b/internal/cli/integration_test.go @@ -2563,7 +2563,7 @@ func secretRmCommand(ctx context.Context, t *testing.T) (*exec.Cmd, string) { mnemonic := memguard.NewBufferFromBytes([]byte(testMnemonic)) defer mnemonic.Destroy() - vlt, err := vault.CreateVault(afero.NewOsFs(), stateDir, "default", mnemonic) + vlt, err := vault.CreateVault(afero.NewOsFs(), stateDir, "default", mnemonic, nil) require.NoError(t, err) value := memguard.NewBufferFromBytes([]byte("value")) diff --git a/internal/cli/leftovers_test.go b/internal/cli/leftovers_test.go index 04d5a1e..6c044d3 100644 --- a/internal/cli/leftovers_test.go +++ b/internal/cli/leftovers_test.go @@ -28,7 +28,7 @@ func TestLeftoversRemovedByNextChangingCommand(t *testing.T) { fs := newTwoVaultFs(t) - _, err := vault.CreateVault(fs, testStateDir, ".tmp-0", nil) + _, err := vault.CreateVault(fs, testStateDir, ".tmp-0", nil, nil) require.NoError(t, err) require.NoError(t, vault.SelectVault(fs, testStateDir, "default")) diff --git a/internal/cli/lock_test.go b/internal/cli/lock_test.go index abe7b8b..5919d9c 100644 --- a/internal/cli/lock_test.go +++ b/internal/cli/lock_test.go @@ -110,7 +110,7 @@ func TestConcurrentAddsKeepEveryVersion(t *testing.T) { {"real", afero.NewOsFs(), t.TempDir()}, } { t.Run(tc.name, func(t *testing.T) { - _, err := vault.CreateVault(tc.fs, tc.stateDir, "default", mnemonic) + _, err := vault.CreateVault(tc.fs, tc.stateDir, "default", mnemonic, nil) require.NoError(t, err) // One add creates the secret; the others find that it exists @@ -185,7 +185,7 @@ func (r *readNotifier) Read(p []byte) (int, error) { //nolint:paralleltest // times commands against the in-memory lock all tests share func TestEncryptPipedIntoAdd(t *testing.T) { fs := afero.NewMemMapFs() - _, err := vault.CreateVault(fs, testStateDir, "default", testMnemonicBuffer(t)) + _, err := vault.CreateVault(fs, testStateDir, "default", testMnemonicBuffer(t), nil) require.NoError(t, err) require.NoError(t, afero.WriteFile(fs, testInput, []byte("piped"), 0o600)) @@ -292,14 +292,14 @@ func setupEveryCommand( mnemonic := testMnemonicBuffer(t) - other, err := vault.CreateVault(fs, testStateDir, "other", mnemonic) + other, err := vault.CreateVault(fs, testStateDir, "other", mnemonic, nil) require.NoError(t, err) otherDir, err := other.GetDirectory() require.NoError(t, err) require.NoError(t, fs.Remove(filepath.Join(otherDir, "pub.age"))) - vlt, err := vault.CreateVault(fs, testStateDir, "work", mnemonic) + vlt, err := vault.CreateVault(fs, testStateDir, "work", mnemonic, nil) require.NoError(t, err) addTestSecret(t, vlt, []byte("older"), false) @@ -487,7 +487,7 @@ func TestEncryptWithExistingKeyTakesNoLock(t *testing.T) { mnemonic := testMnemonicBuffer(t) fs := afero.NewMemMapFs() - _, err := vault.CreateVault(fs, testStateDir, "default", mnemonic) + _, err := vault.CreateVault(fs, testStateDir, "default", mnemonic, nil) require.NoError(t, err) require.NoError(t, afero.WriteFile(fs, testInput, []byte("input"), 0o600)) @@ -525,7 +525,7 @@ func TestEncryptWithExistingKeyTakesNoLock(t *testing.T) { //nolint:paralleltest // times commands against the in-memory lock all tests share func TestEncryptStreamsUnlocked(t *testing.T) { fs := afero.NewMemMapFs() - _, err := vault.CreateVault(fs, testStateDir, "default", testMnemonicBuffer(t)) + _, err := vault.CreateVault(fs, testStateDir, "default", testMnemonicBuffer(t), nil) require.NoError(t, err) require.NoError(t, afero.WriteFile(fs, testInput, []byte("streamed"), 0o600)) diff --git a/internal/cli/move_test.go b/internal/cli/move_test.go index d77ca29..1ddb70e 100644 --- a/internal/cli/move_test.go +++ b/internal/cli/move_test.go @@ -156,10 +156,10 @@ func TestMoveOntoSameSecretUnderAnotherNameIsRejected(t *testing.T) { vaultsDir := filepath.Join(stateDir, "vaults.d") // "default" is created last, so it is the current vault. - _, err := vault.CreateVault(fs, stateDir, "other", testMnemonicBuffer(t)) + _, err := vault.CreateVault(fs, stateDir, "other", testMnemonicBuffer(t), nil) require.NoError(t, err) - vlt, err := vault.CreateVault(fs, stateDir, "default", testMnemonicBuffer(t)) + vlt, err := vault.CreateVault(fs, stateDir, "default", testMnemonicBuffer(t), nil) require.NoError(t, err) err = vlt.AddSecret("x", memguard.NewBufferFromBytes([]byte("value")), false) @@ -205,7 +205,7 @@ func TestForcedCaseOnlyMoveOnCaseSensitiveFilesystem(t *testing.T) { fs := afero.NewOsFs() stateDir := t.TempDir() - vlt, err := vault.CreateVault(fs, stateDir, "default", testMnemonicBuffer(t)) + vlt, err := vault.CreateVault(fs, stateDir, "default", testMnemonicBuffer(t), nil) require.NoError(t, err) err = vlt.AddSecret("Foo", memguard.NewBufferFromBytes([]byte("upper")), false) diff --git a/internal/cli/path_traversal_test.go b/internal/cli/path_traversal_test.go index 090ff10..a7d472a 100644 --- a/internal/cli/path_traversal_test.go +++ b/internal/cli/path_traversal_test.go @@ -68,7 +68,7 @@ func newTwoVaultFs(t *testing.T) afero.Fs { mnemonic := testMnemonicBuffer(t) for _, name := range []string{"work", "default"} { - vlt, err := vault.CreateVault(fs, testStateDir, name, mnemonic) + vlt, err := vault.CreateVault(fs, testStateDir, name, mnemonic, nil) require.NoError(t, err) err = vlt.AddSecret("x", memguard.NewBufferFromBytes([]byte("value")), false) diff --git a/internal/cli/secrets_size_test.go b/internal/cli/secrets_size_test.go index 2c7b345..3df84b8 100644 --- a/internal/cli/secrets_size_test.go +++ b/internal/cli/secrets_size_test.go @@ -71,7 +71,8 @@ func newSizeTestVault(t *testing.T) (afero.Fs, *vault.Vault) { fs := afero.NewMemMapFs() // Create vault - _, err := vault.CreateVault(fs, testStateDir, testVaultName, testMnemonicBuffer(t)) + _, err := vault.CreateVault(fs, testStateDir, testVaultName, + testMnemonicBuffer(t), nil) require.NoError(t, err) // Set current vault diff --git a/internal/cli/unlockers_add_test.go b/internal/cli/unlockers_add_test.go index 66f5c9e..da070d6 100644 --- a/internal/cli/unlockers_add_test.go +++ b/internal/cli/unlockers_add_test.go @@ -47,7 +47,7 @@ func TestAddPGPUnlocker(t *testing.T) { t.Run(test.name, func(t *testing.T) { fs := afero.NewMemMapFs() vlt, err := vault.CreateVault(fs, listTestStateDir, listTestVaultName, - testMnemonicBuffer(t)) + testMnemonicBuffer(t), nil) require.NoError(t, err) err = vlt.AddSecret(addTestSecretName, diff --git a/internal/cli/vault.go b/internal/cli/vault.go index 4cd699c..e3a45be 100644 --- a/internal/cli/vault.go +++ b/internal/cli/vault.go @@ -293,40 +293,26 @@ func (cli *Instance) CreateVault(cmd *cobra.Command, name string) error { } defer cleanupPassphrase() - // Create the vault - it will handle key derivation internally - vlt, err := vault.CreateVault(cli.fs, cli.stateDir, name, mnemonic) + // Create the vault with its passphrase unlocker + vlt, err := vault.CreateVault(cli.fs, cli.stateDir, name, + mnemonic, passphraseBuffer) if err != nil { return err } - // Get the vault metadata to retrieve the derivation index - vaultDir := filepath.Join(cli.stateDir, "vaults.d", name) - - metadata, err := vault.LoadVaultMetadata(cli.fs, vaultDir) + ltIdentity, err := vlt.GetOrDeriveLongTermKey() if err != nil { - return fmt.Errorf("failed to load vault metadata: %w", err) + return fmt.Errorf("failed to get long-term key: %w", err) } - // Derive the long-term key using the same index that CreateVault used - ltIdentity, err := agehd.DeriveIdentity(mnemonicStr, metadata.DerivationIndex) + unlocker, err := vlt.GetCurrentUnlocker() if err != nil { - return fmt.Errorf("failed to derive long-term key from mnemonic: %w", err) - } - - // Unlock the vault with the derived long-term key - vlt.Unlock(ltIdentity) - - // Create passphrase-protected unlocker - secret.Debug("Creating passphrase-protected unlocker") - - passphraseUnlocker, err := vlt.CreatePassphraseUnlocker(passphraseBuffer) - if err != nil { - return fmt.Errorf("failed to create unlocker: %w", err) + return err } cmd.Printf("Created vault '%s'\n", vlt.GetName()) cmd.Printf("Long-term public key: %s\n", ltIdentity.Recipient().String()) - cmd.Printf("Unlocker ID: %s\n", passphraseUnlocker.GetID()) + cmd.Printf("Unlocker ID: %s\n", unlocker.GetID()) return nil } diff --git a/internal/cli/version_test.go b/internal/cli/version_test.go index b71cdec..2e81e60 100644 --- a/internal/cli/version_test.go +++ b/internal/cli/version_test.go @@ -73,7 +73,8 @@ func setupTestVault(t *testing.T, fs afero.Fs) { t.Helper() // Create vault - vlt, err := vault.CreateVault(fs, testStateDir, "default", testMnemonicBuffer(t)) + vlt, err := vault.CreateVault(fs, testStateDir, "default", + testMnemonicBuffer(t), nil) require.NoError(t, err) // Derive and store long-term key from mnemonic diff --git a/internal/secret/atomic_test.go b/internal/secret/atomic_test.go index a976205..98f3df3 100644 --- a/internal/secret/atomic_test.go +++ b/internal/secret/atomic_test.go @@ -236,7 +236,7 @@ func newVaultWithSecret( ) *vault.Vault { t.Helper() - vlt, err := vault.CreateVault(fs, stateDir, name, testMnemonicBuffer(t)) + vlt, err := vault.CreateVault(fs, stateDir, name, testMnemonicBuffer(t), nil) require.NoError(t, err) buffer := memguard.NewBufferFromBytes([]byte(value)) @@ -353,7 +353,7 @@ func TestLongestNames(t *testing.T) { fs := afero.NewOsFs() name := strings.Repeat("a", longestName) - vlt, err := vault.CreateVault(fs, t.TempDir(), name, testMnemonicBuffer(t)) + vlt, err := vault.CreateVault(fs, t.TempDir(), name, testMnemonicBuffer(t), nil) require.NoError(t, err) value := memguard.NewBufferFromBytes([]byte("long")) @@ -647,7 +647,7 @@ func TestPassphraseUnlockerGetsKeyFirst(t *testing.T) { // No mnemonic, and no current unlocker to get the key from base := afero.NewMemMapFs() - _, err := vault.CreateVault(base, testVaultStateDir, testVaultName, nil) + _, err := vault.CreateVault(base, testVaultStateDir, testVaultName, nil, nil) require.NoError(t, err) fs := hookFs{Fs: base, before: func(_, path string) error { @@ -679,7 +679,7 @@ func TestPassphraseUnlockerIsWholeOrAbsent(t *testing.T) { base, stateDir := tfs.open(t) vlt, err := vault.CreateVault(base, stateDir, testVaultName, - testMnemonicBuffer(t)) + testMnemonicBuffer(t), nil) require.NoError(t, err) vaultDir, err := vlt.GetDirectory() @@ -728,7 +728,7 @@ func TestPassphraseUnlockerReplacementKeepsVaultOpen(t *testing.T) { base, stateDir := tfs.open(t) vlt, err := vault.CreateVault(base, stateDir, testVaultName, - testMnemonicBuffer(t)) + testMnemonicBuffer(t), nil) require.NoError(t, err) ltIdentity, err := vlt.GetOrDeriveLongTermKey() @@ -911,7 +911,7 @@ func TestSecureEnclaveUnlockerFailureDeletesKey(t *testing.T) { mnemonic := testMnemonicBuffer(t) base := afero.NewMemMapFs() - _, err := vault.CreateVault(base, testVaultStateDir, testVaultName, mnemonic) + _, err := vault.CreateVault(base, testVaultStateDir, testVaultName, mnemonic, nil) require.NoError(t, err) // The unlocker's directory is named se-