summaryrefslogtreecommitdiff
path: root/internal/server/auth_test.go
blob: ecd3c08b6e0ba69092a469ee0448e67ce47ce82d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package server

import (
	"crypto/subtle"
	"testing"
)

// TestConstantTimePasswordCompare verifies that password comparisons use
// constant-time comparison to prevent timing side-channel attacks.
// This is a regression test for the bug where `!=` was used directly,
// leaking timing information to an attacker who can measure response latency.
func TestConstantTimePasswordCompare(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		a        string
		b        string
		wantSame bool
	}{
		{"equal passwords", "secret123", "secret123", true},
		{"different passwords", "secret123", "wrongpass", false},
		{"empty vs non-empty", "", "secret", false},
		{"both empty", "", "", true},
		{"prefix match only", "secret", "secretXYZ", false},
		{"suffix match only", "XYZsecret", "secret", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			// secretsEqual is the function under test — it must use
			// crypto/subtle.ConstantTimeCompare, not plain ==.
			got := secretsEqual(tt.a, tt.b)
			if got != tt.wantSame {
				t.Errorf("secretsEqual(%q, %q) = %v, want %v",
					tt.a, tt.b, got, tt.wantSame)
			}

			// Cross-check with the reference implementation to ensure our
			// helper is semantically correct, not just timing-safe.
			reference := subtle.ConstantTimeCompare([]byte(tt.a), []byte(tt.b)) == 1
			if got != reference {
				t.Errorf("secretsEqual(%q, %q) = %v but crypto/subtle gives %v",
					tt.a, tt.b, got, reference)
			}
		})
	}
}