summaryrefslogtreecommitdiff
path: root/password_test.go
diff options
context:
space:
mode:
authorT <t@tjp.lol>2025-06-26 11:42:17 -0600
committerT <t@tjp.lol>2025-07-01 17:50:49 -0600
commit639ad6a02cbb4b713434671ec09f309aa5410921 (patch)
tree7dde9cce8136636d11f2f7c961072984cfc705e7 /password_test.go
Create authentic_kate: user authentication for go HTTP applications
Diffstat (limited to 'password_test.go')
-rw-r--r--password_test.go230
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)
+ }
+}