diff options
author | T <t@tjp.lol> | 2025-06-26 11:42:17 -0600 |
---|---|---|
committer | T <t@tjp.lol> | 2025-07-01 17:50:49 -0600 |
commit | 639ad6a02cbb4b713434671ec09f309aa5410921 (patch) | |
tree | 7dde9cce8136636d11f2f7c961072984cfc705e7 /password_test.go |
Create authentic_kate: user authentication for go HTTP applications
Diffstat (limited to 'password_test.go')
-rw-r--r-- | password_test.go | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/password_test.go b/password_test.go new file mode 100644 index 0000000..cfe90e0 --- /dev/null +++ b/password_test.go @@ -0,0 +1,230 @@ +package kate + +import ( + "strings" + "testing" + "testing/quick" +) + +func TestHashPasswordComparePasswordRoundtrip(t *testing.T) { + property := func(password string) bool { + hash, err := HashPassword(password, nil) + if err != nil { + return false + } + + match, err := ComparePassword(password, hash) + if err != nil { + return false + } + + return match + } + + if err := quick.Check(property, nil); err != nil { + t.Errorf("roundtrip property failed: %v", err) + } +} + +func TestComparePasswordWrongPassword(t *testing.T) { + property := func(password, wrongPassword string) bool { + if password == wrongPassword { + return true + } + + hash, err := HashPassword(password, nil) + if err != nil { + return false + } + + match, err := ComparePassword(wrongPassword, hash) + if err != nil { + return false + } + + return !match + } + + if err := quick.Check(property, nil); err != nil { + t.Errorf("wrong password property failed: %v", err) + } +} + +func TestHashPasswordUniqueness(t *testing.T) { + property := func(password string) bool { + hash1, err1 := HashPassword(password, nil) + hash2, err2 := HashPassword(password, nil) + + if err1 != nil || err2 != nil { + return false + } + + return hash1 != hash2 + } + + if err := quick.Check(property, nil); err != nil { + t.Errorf("uniqueness property failed: %v", err) + } +} + +func TestHashPasswordFormat(t *testing.T) { + property := func(password string) bool { + hash, err := HashPassword(password, nil) + if err != nil { + return false + } + + parts := strings.Split(hash, "$") + if len(parts) != 6 { + return false + } + + return parts[0] == "" && parts[1] == "argon2id" + } + + if err := quick.Check(property, nil); err != nil { + t.Errorf("format property failed: %v", err) + } +} + +func TestComparePasswordMalformedHash(t *testing.T) { + tests := []string{ + "", + "invalid", + "$argon2id", + "$argon2id$v=19$m=65536,t=1,p=4", + "$argon2id$v=19$m=65536,t=1,p=4$salt", + "$wrong$v=19$m=65536,t=1,p=4$salt$hash", + "$argon2id$v=999$m=65536,t=1,p=4$salt$hash", + "$argon2id$v=19$invalid$salt$hash", + "$argon2id$v=19$m=65536,t=1,p=4$!!!$hash", + "$argon2id$v=19$m=65536,t=1,p=4$salt$!!!", + } + + for _, malformedHash := range tests { + t.Run("malformed_"+malformedHash, func(t *testing.T) { + match, err := ComparePassword("password", malformedHash) + if err == nil { + t.Error("expected error for malformed hash") + } + if match { + t.Error("should not match with malformed hash") + } + }) + } +} + +func TestEmptyPassword(t *testing.T) { + hash, err := HashPassword("", nil) + if err != nil { + t.Fatalf("HashPassword failed for empty password: %v", err) + } + + match, err := ComparePassword("", hash) + if err != nil { + t.Fatalf("ComparePassword failed: %v", err) + } + if !match { + t.Error("empty password should match its hash") + } + + match, err = ComparePassword("nonempty", hash) + if err != nil { + t.Fatalf("ComparePassword failed: %v", err) + } + if match { + t.Error("non-empty password should not match empty password hash") + } +} + +func TestHashPasswordWithConfig(t *testing.T) { + tests := []struct { + name string + config *Argon2Config + want string // Expected parameters in PHC format + }{ + { + name: "nil config uses defaults", + config: nil, + want: "$argon2id$v=19$m=65536,t=1,p=4$", + }, + { + name: "custom time parameter", + config: &Argon2Config{ + Time: 3, + }, + want: "$argon2id$v=19$m=65536,t=3,p=4$", + }, + { + name: "custom memory parameter", + config: &Argon2Config{ + Memory: 32 * 1024, // 32MB + }, + want: "$argon2id$v=19$m=32768,t=1,p=4$", + }, + { + name: "custom threads parameter", + config: &Argon2Config{ + Threads: 2, + }, + want: "$argon2id$v=19$m=65536,t=1,p=2$", + }, + { + name: "all custom parameters", + config: &Argon2Config{ + Time: 2, + Memory: 128 * 1024, + Threads: 8, + KeyLen: 64, + }, + want: "$argon2id$v=19$m=131072,t=2,p=8$", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hash, err := HashPassword("test", tt.config) + if err != nil { + t.Fatalf("HashPassword() error = %v", err) + } + + // Check that the hash starts with expected PHC format parameters + if !strings.HasPrefix(hash, tt.want) { + t.Errorf("HashPassword() = %v, want prefix %v", hash, tt.want) + } + + // Verify password still works + match, err := ComparePassword("test", hash) + if err != nil { + t.Fatalf("ComparePassword() error = %v", err) + } + if !match { + t.Error("Password should match its hash") + } + + // Verify wrong password doesn't match + match, err = ComparePassword("wrong", hash) + if err != nil { + t.Fatalf("ComparePassword() error = %v", err) + } + if match { + t.Error("Wrong password should not match hash") + } + }) + } +} + +func TestDefaultArgon2Config(t *testing.T) { + if defaultArgon2Config.Time != 1 { + t.Errorf("Expected defaultArgon2Config.Time=1, got %d", defaultArgon2Config.Time) + } + if defaultArgon2Config.Memory != 64*1024 { + t.Errorf("Expected defaultArgon2Config.Memory=65536, got %d", defaultArgon2Config.Memory) + } + if defaultArgon2Config.Threads != 4 { + t.Errorf("Expected defaultArgon2Config.Threads=4, got %d", defaultArgon2Config.Threads) + } + if defaultArgon2Config.KeyLen != 32 { + t.Errorf("Expected defaultArgon2Config.KeyLen=32, got %d", defaultArgon2Config.KeyLen) + } +} |