File view with blame information shown in the left gutter beside each line.

barerepo / server / internal/auth/verify_test.go
152 lines · 5.1kb · 133728eaa05486504991b230f9d4c7e986b7defc
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
package auth
133728e barerepo 1mo
2
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"context"
133728e barerepo 1mo
5
"os"
133728e barerepo 1mo
6
"os/exec"
133728e barerepo 1mo
7
"path/filepath"
133728e barerepo 1mo
8
"strings"
133728e barerepo 1mo
9
"testing"
133728e barerepo 1mo
10
133728e barerepo 1mo
11
"github.com/barerepo/server/internal/store"
133728e barerepo 1mo
12
)
133728e barerepo 1mo
13
133728e barerepo 1mo
14
// makeKey generates a real pair and returns the private path and the stored PubKey.
133728e barerepo 1mo
15
func makeKey(t *testing.T, comment string) (string, store.PubKey) {
133728e barerepo 1mo
16
t.Helper()
133728e barerepo 1mo
17
if _, err := exec.LookPath("ssh-keygen"); err != nil {
133728e barerepo 1mo
18
t.Skip("ssh-keygen is not installed")
133728e barerepo 1mo
19
}
133728e barerepo 1mo
20
dir := t.TempDir()
133728e barerepo 1mo
21
priv := filepath.Join(dir, "id")
133728e barerepo 1mo
22
cmd := exec.Command("ssh-keygen", "-t", "ed25519", "-N", "", "-C", comment, "-f", priv, "-q")
133728e barerepo 1mo
23
if out, err := cmd.CombinedOutput(); err != nil {
133728e barerepo 1mo
24
t.Fatalf("ssh-keygen: %v\n%s", err, out)
133728e barerepo 1mo
25
}
133728e barerepo 1mo
26
raw, err := os.ReadFile(priv + ".pub")
133728e barerepo 1mo
27
if err != nil {
133728e barerepo 1mo
28
t.Fatal(err)
133728e barerepo 1mo
29
}
133728e barerepo 1mo
30
algo, blob, cmt, fp, err := store.ParsePubKey(string(raw))
133728e barerepo 1mo
31
if err != nil {
133728e barerepo 1mo
32
t.Fatal(err)
133728e barerepo 1mo
33
}
133728e barerepo 1mo
34
return priv, store.PubKey{Algo: algo, Blob: blob, Comment: cmt, Fingerprint: fp}
133728e barerepo 1mo
35
}
133728e barerepo 1mo
36
133728e barerepo 1mo
37
// sign does what chapter 31.3 tells the user to do, by hand.
133728e barerepo 1mo
38
func sign(t *testing.T, priv, nonce, namespace string) string {
133728e barerepo 1mo
39
t.Helper()
133728e barerepo 1mo
40
dir := t.TempDir()
133728e barerepo 1mo
41
msg := filepath.Join(dir, "nonce")
133728e barerepo 1mo
42
if err := os.WriteFile(msg, []byte(nonce), 0o600); err != nil {
133728e barerepo 1mo
43
t.Fatal(err)
133728e barerepo 1mo
44
}
133728e barerepo 1mo
45
cmd := exec.Command("ssh-keygen", "-Y", "sign", "-f", priv, "-n", namespace, msg)
133728e barerepo 1mo
46
if out, err := cmd.CombinedOutput(); err != nil {
133728e barerepo 1mo
47
t.Fatalf("sign: %v\n%s", err, out)
133728e barerepo 1mo
48
}
133728e barerepo 1mo
49
sig, err := os.ReadFile(msg + ".sig")
133728e barerepo 1mo
50
if err != nil {
133728e barerepo 1mo
51
t.Fatal(err)
133728e barerepo 1mo
52
}
133728e barerepo 1mo
53
return string(sig)
133728e barerepo 1mo
54
}
133728e barerepo 1mo
55
133728e barerepo 1mo
56
func TestVerify(t *testing.T) {
133728e barerepo 1mo
57
ctx := context.Background()
133728e barerepo 1mo
58
privA, keyA := makeKey(t, "laptop")
133728e barerepo 1mo
59
privB, keyB := makeKey(t, "uproar")
133728e barerepo 1mo
60
_, keyC := makeKey(t, "somebody else")
133728e barerepo 1mo
61
133728e barerepo 1mo
62
nonce, err := NewNonce()
133728e barerepo 1mo
63
if err != nil {
133728e barerepo 1mo
64
t.Fatal(err)
133728e barerepo 1mo
65
}
133728e barerepo 1mo
66
133728e barerepo 1mo
67
// The key that signed is on the account.
133728e barerepo 1mo
68
if k, err := Verify(ctx, "john", nonce, sign(t, privA, nonce, Namespace), []store.PubKey{keyA}); err != nil || k == nil {
133728e barerepo 1mo
69
t.Errorf("a good signature was rejected: %v", err)
133728e barerepo 1mo
70
}
133728e barerepo 1mo
71
// Chapter 10 step 5: every key is tried, not just the first.
133728e barerepo 1mo
72
k, err := Verify(ctx, "john", nonce, sign(t, privB, nonce, Namespace), []store.PubKey{keyA, keyB})
133728e barerepo 1mo
73
if err != nil {
133728e barerepo 1mo
74
t.Errorf("the second key on the account was not tried: %v", err)
133728e barerepo 1mo
75
} else if k.Fingerprint != keyB.Fingerprint {
133728e barerepo 1mo
76
// Chapter 32.5 times each key, so the right key has to come back, not just a yes.
133728e barerepo 1mo
77
t.Errorf("Verify named the wrong key: got %s, want %s", k.Fingerprint, keyB.Fingerprint)
133728e barerepo 1mo
78
}
133728e barerepo 1mo
79
// A key that is not on the account.
133728e barerepo 1mo
80
if _, err := Verify(ctx, "john", nonce, sign(t, privA, nonce, Namespace), []store.PubKey{keyC}); err == nil {
133728e barerepo 1mo
81
t.Error("a signature from an unknown key was accepted")
133728e barerepo 1mo
82
}
133728e barerepo 1mo
83
// A signature over a different nonce. This is the replay case.
133728e barerepo 1mo
84
other, _ := NewNonce()
133728e barerepo 1mo
85
if _, err := Verify(ctx, "john", nonce, sign(t, privA, other, Namespace), []store.PubKey{keyA}); err == nil {
133728e barerepo 1mo
86
t.Error("a signature over a different nonce was accepted")
133728e barerepo 1mo
87
}
133728e barerepo 1mo
88
// A signature made for another service. This is what the namespace is for.
133728e barerepo 1mo
89
if _, err := Verify(ctx, "john", nonce, sign(t, privA, nonce, "git"), []store.PubKey{keyA}); err == nil {
133728e barerepo 1mo
90
t.Error("a signature from another namespace was accepted")
133728e barerepo 1mo
91
}
133728e barerepo 1mo
92
// Nothing that is not a signature gets as far as ssh-keygen.
133728e barerepo 1mo
93
for _, junk := range []string{"", "hello", "-----BEGIN RSA PRIVATE KEY-----"} {
133728e barerepo 1mo
94
if _, err := Verify(ctx, "john", nonce, junk, []store.PubKey{keyA}); err == nil {
133728e barerepo 1mo
95
t.Errorf("Verify accepted %q", junk)
133728e barerepo 1mo
96
}
133728e barerepo 1mo
97
}
133728e barerepo 1mo
98
// An account with no keys can never sign in.
133728e barerepo 1mo
99
if _, err := Verify(ctx, "john", nonce, sign(t, privA, nonce, Namespace), nil); err == nil {
133728e barerepo 1mo
100
t.Error("an account with no keys was signed in")
133728e barerepo 1mo
101
}
133728e barerepo 1mo
102
}
133728e barerepo 1mo
103
133728e barerepo 1mo
104
func TestNonceIsUnique(t *testing.T) {
133728e barerepo 1mo
105
seen := map[string]bool{}
133728e barerepo 1mo
106
for i := 0; i < 100; i++ {
133728e barerepo 1mo
107
n, err := NewNonce()
133728e barerepo 1mo
108
if err != nil {
133728e barerepo 1mo
109
t.Fatal(err)
133728e barerepo 1mo
110
}
133728e barerepo 1mo
111
if len(n) < 40 || seen[n] {
133728e barerepo 1mo
112
t.Fatalf("nonce %q is short or repeated", n)
133728e barerepo 1mo
113
}
133728e barerepo 1mo
114
seen[n] = true
133728e barerepo 1mo
115
}
133728e barerepo 1mo
116
if strings.Contains(strings.Join(keysOf(seen), ""), " ") {
133728e barerepo 1mo
117
t.Error("a nonce contains a space, which breaks the echo -n in chapter 31.3")
133728e barerepo 1mo
118
}
133728e barerepo 1mo
119
}
133728e barerepo 1mo
120
133728e barerepo 1mo
121
func keysOf(m map[string]bool) []string {
133728e barerepo 1mo
122
out := make([]string, 0, len(m))
133728e barerepo 1mo
123
for k := range m {
133728e barerepo 1mo
124
out = append(out, k)
133728e barerepo 1mo
125
}
133728e barerepo 1mo
126
return out
133728e barerepo 1mo
127
}
133728e barerepo 1mo
128
133728e barerepo 1mo
129
// Signup proves the private half, in its own namespace, so no sign-in can be replayed.
133728e barerepo 1mo
130
func TestVerifySignup(t *testing.T) {
133728e barerepo 1mo
131
ctx := context.Background()
133728e barerepo 1mo
132
priv, key := makeKey(t, "laptop")
133728e barerepo 1mo
133
pub := key.Algo + " " + key.Blob + " laptop"
133728e barerepo 1mo
134
nonce, _ := NewNonce()
133728e barerepo 1mo
135
133728e barerepo 1mo
136
if err := VerifySignup(ctx, "rock", nonce, sign(t, priv, nonce, SignupNamespace), pub); err != nil {
133728e barerepo 1mo
137
t.Errorf("a good signup signature was rejected: %v", err)
133728e barerepo 1mo
138
}
133728e barerepo 1mo
139
// The sign-in namespace must not work here.
133728e barerepo 1mo
140
if err := VerifySignup(ctx, "rock", nonce, sign(t, priv, nonce, Namespace), pub); err == nil {
133728e barerepo 1mo
141
t.Error("a barerepo-auth signature was accepted at signup")
133728e barerepo 1mo
142
}
133728e barerepo 1mo
143
// Nor should a signature from a different key than the one being claimed.
133728e barerepo 1mo
144
_, other := makeKey(t, "other")
133728e barerepo 1mo
145
otherPub := other.Algo + " " + other.Blob + " other"
133728e barerepo 1mo
146
if err := VerifySignup(ctx, "rock", nonce, sign(t, priv, nonce, SignupNamespace), otherPub); err == nil {
133728e barerepo 1mo
147
t.Error("a signature from a different key was accepted")
133728e barerepo 1mo
148
}
133728e barerepo 1mo
149
if err := VerifySignup(ctx, "rock", nonce, "not a signature", pub); err == nil {
133728e barerepo 1mo
150
t.Error("junk was accepted as a signature")
133728e barerepo 1mo
151
}
133728e barerepo 1mo
152
}
history · rawbarerepo 0.1.0