From 3bb3828cdcbc189ccbdb2b183d960f57954663b2 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 14 May 2026 21:59:38 +0200 Subject: [PATCH] =?UTF-8?q?test(02):=20RED=20=E2=80=94=20failing=20argon2i?= =?UTF-8?q?d=20password=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Six tests: HashVerify, VerifyWrong, VerifyMalformed, VerifyVersion, DistinctSaltsPerCall, DefaultParamsShape - Tests use auth.TestParams for fast wall time (Pitfall 4 guard) - Guards ErrInvalidHash + ErrIncompatibleVersion sentinel errors - All tests fail with undefined: auth.Hash / auth.TestParams / auth.Verify (implementation missing) --- backend/internal/auth/password_test.go | 134 +++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 backend/internal/auth/password_test.go diff --git a/backend/internal/auth/password_test.go b/backend/internal/auth/password_test.go new file mode 100644 index 0000000..07d6e59 --- /dev/null +++ b/backend/internal/auth/password_test.go @@ -0,0 +1,134 @@ +package auth_test + +import ( + "strings" + "testing" + + "backend/internal/auth" +) + +// TestPassword_HashVerify verifies that Hash returns a valid PHC string +// starting with the expected argon2id prefix and that Verify confirms the +// original password. +func TestPassword_HashVerify(t *testing.T) { + const pw = "correct-horse-battery-staple" + phc, err := auth.Hash(pw, auth.TestParams) + if err != nil { + t.Fatalf("Hash returned error: %v", err) + } + if phc == "" { + t.Fatal("Hash returned empty string") + } + + // TestParams: Memory=8192, Iterations=1, Parallelism=2 + const wantPrefix = "$argon2id$v=19$m=8192,t=1,p=2$" + if !strings.HasPrefix(phc, wantPrefix) { + t.Errorf("PHC string does not start with expected prefix\n got: %s\n want prefix: %s", phc, wantPrefix) + } + + ok, err := auth.Verify(phc, pw) + if err != nil { + t.Fatalf("Verify returned unexpected error: %v", err) + } + if !ok { + t.Fatal("Verify returned false for the original password") + } +} + +// TestPassword_VerifyWrong ensures that Verify returns (false, nil) — not an +// error — when the supplied password does not match the hash. +func TestPassword_VerifyWrong(t *testing.T) { + const pw = "correct-horse-battery-staple" + phc, err := auth.Hash(pw, auth.TestParams) + if err != nil { + t.Fatalf("Hash returned error: %v", err) + } + + ok, err := auth.Verify(phc, pw+"x") + if err != nil { + t.Fatalf("Verify returned an unexpected error on wrong password: %v", err) + } + if ok { + t.Fatal("Verify returned true for a wrong password") + } +} + +// TestPassword_VerifyMalformed checks that malformed or foreign-algorithm PHC +// strings return (false, ErrInvalidHash). +func TestPassword_VerifyMalformed(t *testing.T) { + cases := []struct { + name string + encoded string + }{ + {"plain string", "not-a-phc-string"}, + {"too few segments", "$argon2id$v=19$m=8192,t=1,p=2$only-three-segments"}, + {"wrong algorithm", "$bcrypt$v=19$m=8192,t=1,p=2$saltb64$hashb64"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ok, err := auth.Verify(tc.encoded, "anypassword") + if ok { + t.Error("Verify returned true for malformed input") + } + if err != auth.ErrInvalidHash { + t.Errorf("Verify error = %v, want ErrInvalidHash", err) + } + }) + } +} + +// TestPassword_VerifyVersion ensures that a synthetic PHC string with v=18 +// (one less than argon2.Version == 19) returns (false, ErrIncompatibleVersion). +func TestPassword_VerifyVersion(t *testing.T) { + // Craft a syntactically valid PHC string with an old version number. + // The segment count must be correct (6 after split on "$"): + // $argon2id$v=18$m=8192,t=1,p=2$$ + synthetic := "$argon2id$v=18$m=8192,t=1,p=2$c29tZXNhbHQxNjBiYXNl$c29tZWhhc2gzMmJ5dGVzMDAwMDAwMDAwMDAwMDA" + + ok, err := auth.Verify(synthetic, "anypassword") + if ok { + t.Error("Verify returned true for incompatible version") + } + if err != auth.ErrIncompatibleVersion { + t.Errorf("Verify error = %v, want ErrIncompatibleVersion", err) + } +} + +// TestPassword_DistinctSaltsPerCall ensures that two Hash calls for the same +// password produce different PHC strings (random salt per call — Pitfall guard). +func TestPassword_DistinctSaltsPerCall(t *testing.T) { + const pw = "correct-horse-battery-staple" + phc1, err := auth.Hash(pw, auth.TestParams) + if err != nil { + t.Fatalf("first Hash returned error: %v", err) + } + phc2, err := auth.Hash(pw, auth.TestParams) + if err != nil { + t.Fatalf("second Hash returned error: %v", err) + } + if phc1 == phc2 { + t.Error("Two Hash calls for the same password returned identical PHC strings (salt reuse detected)") + } +} + +// TestPassword_DefaultParamsShape guards against accidental drift in DefaultParams. +// Values must match OWASP 2024 baseline as locked in D-08. +func TestPassword_DefaultParamsShape(t *testing.T) { + p := auth.DefaultParams + if p.Memory != 64*1024 { + t.Errorf("DefaultParams.Memory = %d, want %d", p.Memory, 64*1024) + } + if p.Iterations != 1 { + t.Errorf("DefaultParams.Iterations = %d, want 1", p.Iterations) + } + if p.Parallelism != 4 { + t.Errorf("DefaultParams.Parallelism = %d, want 4", p.Parallelism) + } + if p.SaltLength != 16 { + t.Errorf("DefaultParams.SaltLength = %d, want 16", p.SaltLength) + } + if p.KeyLength != 32 { + t.Errorf("DefaultParams.KeyLength = %d, want 32", p.KeyLength) + } +}