- 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)
134 lines
4.2 KiB
Go
134 lines
4.2 KiB
Go
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$<validb64salt>$<validb64hash>
|
|
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)
|
|
}
|
|
}
|