19289170da
Guards the Argon2id parsing path against a regression where the encoded hash (which contains dollar-delimited base64) was fed through fmt.Sscanf and silently rejected valid passwords. Tests cover round-trip with a variety of passwords including unicode, rejection of wrong passwords, the six-segment encoded shape, and malformed-hash cases.
83 lines
2.4 KiB
Go
83 lines
2.4 KiB
Go
package crypto
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestHashVerifyRoundTrip guards the Argon2id parser: the encoded hash contains
|
|
// base64 fields that may include `$` or `+` characters, and an earlier version
|
|
// used fmt.Sscanf whose %s verb does not stop at `$`, silently returning a
|
|
// false negative for any valid password. See VerifyPassword docstring.
|
|
func TestHashVerifyRoundTrip(t *testing.T) {
|
|
cases := []string{
|
|
"admin",
|
|
"DevAdmin!2026",
|
|
"a",
|
|
"this is a reasonably long password with spaces 12345",
|
|
"πåß∂ƒ©˙∆˚¬",
|
|
}
|
|
for _, pw := range cases {
|
|
encoded, err := HashPassword(pw)
|
|
if err != nil {
|
|
t.Fatalf("HashPassword(%q) error: %v", pw, err)
|
|
}
|
|
ok, err := VerifyPassword(pw, encoded)
|
|
if err != nil {
|
|
t.Fatalf("VerifyPassword(%q) error: %v", pw, err)
|
|
}
|
|
if !ok {
|
|
t.Fatalf("VerifyPassword(%q) returned false for correct password", pw)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVerifyPasswordRejectsWrongPassword(t *testing.T) {
|
|
encoded, err := HashPassword("correct-horse-battery-staple")
|
|
if err != nil {
|
|
t.Fatalf("HashPassword error: %v", err)
|
|
}
|
|
ok, err := VerifyPassword("wrong-password", encoded)
|
|
if err != nil {
|
|
t.Fatalf("VerifyPassword error: %v", err)
|
|
}
|
|
if ok {
|
|
t.Fatalf("VerifyPassword returned true for wrong password")
|
|
}
|
|
}
|
|
|
|
// TestHashEncodedShape asserts the encoded string has the exact six-segment
|
|
// `$`-delimited layout the parser depends on. A regression in HashPassword
|
|
// that changed the shape would silently break every login.
|
|
func TestHashEncodedShape(t *testing.T) {
|
|
encoded, err := HashPassword("x")
|
|
if err != nil {
|
|
t.Fatalf("HashPassword error: %v", err)
|
|
}
|
|
parts := strings.Split(encoded, "$")
|
|
if len(parts) != 6 {
|
|
t.Fatalf("expected 6 `$`-delimited segments, got %d: %q", len(parts), encoded)
|
|
}
|
|
if parts[0] != "" || parts[1] != "argon2id" || parts[2] != "v=19" {
|
|
t.Fatalf("unexpected prefix: %q", encoded)
|
|
}
|
|
if !strings.HasPrefix(parts[3], "m=") {
|
|
t.Fatalf("expected parameters segment to start with m=, got %q", parts[3])
|
|
}
|
|
}
|
|
|
|
func TestVerifyPasswordRejectsMalformedHash(t *testing.T) {
|
|
cases := []string{
|
|
"",
|
|
"not-a-hash",
|
|
"$argon2id$v=19$m=65536,t=1,p=4$onlysalt", // 5 segments
|
|
"$argon2id$v=18$m=65536,t=1,p=4$salt$hash",
|
|
"$scrypt$v=19$m=65536,t=1,p=4$salt$hash",
|
|
}
|
|
for _, bad := range cases {
|
|
if _, err := VerifyPassword("x", bad); err == nil {
|
|
t.Errorf("VerifyPassword(%q) expected error, got nil", bad)
|
|
}
|
|
}
|
|
}
|