From 8c82ce628fce09902dec073d2592815dac4538a2 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 5 Oct 2026 23:50:49 +0000 Subject: [PATCH] WIP: cheap scrypt in tests --- internal/cli/confirm_test.go | 4 ++-- internal/cli/integration_test.go | 6 +++++- internal/cli/lock_test.go | 4 ++-- internal/secret/crypto.go | 15 +++++++++++++++ internal/secret/secret_test.go | 8 ++++++++ internal/vault/vault_test.go | 9 +++++++++ 6 files changed, 41 insertions(+), 5 deletions(-) diff --git a/internal/cli/confirm_test.go b/internal/cli/confirm_test.go index 32a56b8..af2ec91 100644 --- a/internal/cli/confirm_test.go +++ b/internal/cli/confirm_test.go @@ -353,9 +353,9 @@ func TestRemovalWithoutTerminalFailsAtOnce(t *testing.T) { // for its answer, another command can take the state directory lock and // change the secret, and that the removal then removes nothing, since the // secret is no longer what the question named. +// +//nolint:paralleltest // times commands against the in-memory lock all tests share func TestRemovalAsksWithoutHoldingLock(t *testing.T) { - t.Parallel() - r := newRemoval(t, "rm") answers, answerWriter := io.Pipe() diff --git a/internal/cli/integration_test.go b/internal/cli/integration_test.go index d572e5c..9e66629 100644 --- a/internal/cli/integration_test.go +++ b/internal/cli/integration_test.go @@ -52,8 +52,12 @@ func runSecretWithStdin(stdin string, env map[string]string, args ...string) (st return cli.ExecuteCommandInProcess(args, stdin, env) } -// TestMain runs before all tests and ensures the binary is built +// TestMain runs before all tests and ensures the binary is built. It also +// makes passphrase encryption in the tests cheap (see +// secret.ScryptWorkFactor); the binary keeps age's work factor. func TestMain(m *testing.M) { + secret.ScryptWorkFactor = 1 + // Get the current working directory wd, err := os.Getwd() if err != nil { diff --git a/internal/cli/lock_test.go b/internal/cli/lock_test.go index 502553f..55f68b7 100644 --- a/internal/cli/lock_test.go +++ b/internal/cli/lock_test.go @@ -235,9 +235,9 @@ func TestEncryptPipedIntoAdd(t *testing.T) { // TestFailedCommandReleasesLock checks that a command failing after it // took the state directory lock leaves the lock free for the next command. +// +//nolint:paralleltest // times commands against the in-memory lock all tests share func TestFailedCommandReleasesLock(t *testing.T) { - t.Parallel() - fs := afero.NewMemMapFs() cli := NewCLIInstanceWithStateDir(fs, testStateDir) diff --git a/internal/secret/crypto.go b/internal/secret/crypto.go index 9c017a1..f6005f0 100644 --- a/internal/secret/crypto.go +++ b/internal/secret/crypto.go @@ -28,6 +28,17 @@ var ( // terminal to read the mnemonic from, reading it failed, or it was empty. var ErrMnemonicNotRead = errors.New("failed to read mnemonic") +// ScryptWorkFactor is, when not zero, the scrypt work factor that +// EncryptWithPassphrase uses instead of age's, 18: log2 of scrypt's cost +// parameter N. Deriving a key with age's takes about a second and 256 MiB, on +// purpose, since so does every guess at the passphrase. Only tests set it, +// lower, before any test runs, so that the passphrase unlockers they create +// cost nothing; the program leaves it zero. Decryption takes the work factor +// from the encrypted data, so it needs no setting. +// +//nolint:gochecknoglobals // set by the tests of the packages that use this one +var ScryptWorkFactor int + // EncryptToRecipient encrypts data to a recipient using age // The data parameter should be a LockedBuffer for secure memory handling func EncryptToRecipient( @@ -142,6 +153,10 @@ func EncryptWithPassphrase( return nil, fmt.Errorf("failed to create scrypt recipient: %w", err) } + if ScryptWorkFactor != 0 { + recipient.SetWorkFactor(ScryptWorkFactor) + } + return EncryptToRecipient(data, recipient) } diff --git a/internal/secret/secret_test.go b/internal/secret/secret_test.go index e5dae85..94bd5d4 100644 --- a/internal/secret/secret_test.go +++ b/internal/secret/secret_test.go @@ -25,6 +25,14 @@ var ( errNotImplementedInMock = errors.New("not implemented in mock") ) +// TestMain makes passphrase encryption in the tests cheap; see +// ScryptWorkFactor. +func TestMain(m *testing.M) { + ScryptWorkFactor = 1 + + os.Exit(m.Run()) +} + // MockVault is a test implementation of the VaultInterface type MockVault struct { name string diff --git a/internal/vault/vault_test.go b/internal/vault/vault_test.go index 2b6967d..72e0243 100644 --- a/internal/vault/vault_test.go +++ b/internal/vault/vault_test.go @@ -3,6 +3,7 @@ package vault_test import ( "bytes" "errors" + "os" "path/filepath" "slices" "testing" @@ -28,6 +29,14 @@ const ( testPassphrase = "test-passphrase" ) +// TestMain makes passphrase encryption in the tests cheap; see +// secret.ScryptWorkFactor. +func TestMain(m *testing.M) { + secret.ScryptWorkFactor = 1 + + os.Exit(m.Run()) +} + // testMnemonicBuffer returns testMnemonic in a locked buffer that is // destroyed when the test ends. func testMnemonicBuffer(t *testing.T) *memguard.LockedBuffer {