carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package doorkey
2
3import (
4 "crypto/ed25519"
5 "crypto/rand"
6 "encoding/base64"
7 "encoding/json"
8 "strings"
9 "testing"
10 "time"
11)
12
13func rec1(entry Entry) Record {
14 now := time.Now().UTC()
15 return Record{V: 1, Account: "bae", App: "titogo", Revision: 1, Keys: []Entry{entry},
16 SignedAt: now, ExpiresAt: now.Add(Lifetime)}
17}
18
19func TestTokenRoundTripAndAdmit(t *testing.T) {
20 raw, entry, err := NewToken("bae", "titogo")
21 if err != nil {
22 t.Fatal(err)
23 }
24 if !strings.HasPrefix(raw, "cdk1.bae.titogo.") {
25 t.Fatalf("token %q lacks its prefix", raw)
26 }
27 tok, err := ParseToken(raw)
28 if err != nil || tok.ID != entry.ID {
29 t.Fatalf("parse %q: %+v %v", raw, tok, err)
30 }
31 if !rec1(entry).Admits(tok) {
32 t.Fatal("the record does not admit its own key")
33 }
34 tok.Secret = strings.Repeat("A", 43)
35 if rec1(entry).Admits(tok) {
36 t.Fatal("a wrong secret was admitted")
37 }
38}
39
40func TestRecordAdmitsOnlyItsOwnAccountAndApp(t *testing.T) {
41 raw, entry, _ := NewToken("bae", "titogo")
42 tok, _ := ParseToken(raw)
43 other := rec1(entry)
44 other.App = "other"
45 if other.Admits(tok) {
46 t.Fatal("a record for another app admitted the key")
47 }
48}
49
50func TestParseTokenRefusesMalformed(t *testing.T) {
51 good := "0123456789abcdef." + strings.Repeat("A", 43)
52 for _, raw := range []string{
53 "", "cdk1", "cdk2.bae.titogo." + good, "cdk1.BAE.titogo." + good,
54 "cdk1.bae.titogo.0123456789ABCDEF." + strings.Repeat("A", 43),
55 "cdk1.bae.titogo.0123456789abcdef.short", "cdk1.bae.titogo." + good + ".extra",
56 } {
57 if _, err := ParseToken(raw); err == nil {
58 t.Errorf("ParseToken(%q) accepted a malformed token", raw)
59 }
60 }
61}
62
63func TestSignVerifyExpiryAndKeys(t *testing.T) {
64 pub, priv, _ := ed25519.GenerateKey(rand.Reader)
65 _, entry, _ := NewToken("bae", "titogo")
66 wire, err := Sign(priv, rec1(entry))
67 if err != nil {
68 t.Fatal(err)
69 }
70 got, err := Verify(pub, wire, time.Now())
71 if err != nil || got.Revision != 1 || got.Keys[0].ID != entry.ID {
72 t.Fatalf("Verify = %+v, %v", got, err)
73 }
74 if _, err := Verify(pub, wire, time.Now().Add(Lifetime+time.Minute)); err == nil {
75 t.Fatal("an expired record verified")
76 }
77 otherPub, _, _ := ed25519.GenerateKey(rand.Reader)
78 if _, err := Verify(otherPub, wire, time.Now()); err == nil {
79 t.Fatal("a record signed by another key verified")
80 }
81}
82
83// A record signed more than MaxClockSkew ahead of the verifier's clock is
84// refused, so a future-dated record cannot outlive Lifetime from now.
85func TestVerifyRefusesAFutureDatedRecord(t *testing.T) {
86 pub, priv, _ := ed25519.GenerateKey(rand.Reader)
87 _, entry, _ := NewToken("bae", "titogo")
88 r := rec1(entry)
89 r.SignedAt = time.Now().Add(time.Hour)
90 r.ExpiresAt = r.SignedAt.Add(Lifetime)
91 wire, err := Sign(priv, r)
92 if err != nil {
93 t.Fatal(err)
94 }
95 if _, err := Verify(pub, wire, time.Now()); err == nil {
96 t.Fatal("a record signed an hour ahead verified")
97 }
98 r.SignedAt = time.Now().Add(time.Minute)
99 r.ExpiresAt = r.SignedAt.Add(Lifetime)
100 wire, _ = Sign(priv, r)
101 if _, err := Verify(pub, wire, time.Now()); err != nil {
102 t.Fatalf("a record a minute ahead (within skew) was refused: %v", err)
103 }
104}
105
106// VerifyRevision is the publisher's read: signature and shape, not expiry,
107// so an expired record still yields the revision it carried.
108func TestVerifyRevisionIgnoresExpiry(t *testing.T) {
109 pub, priv, _ := ed25519.GenerateKey(rand.Reader)
110 _, entry, _ := NewToken("bae", "titogo")
111 r := rec1(entry)
112 r.Revision = 7
113 r.SignedAt = time.Now().Add(-100 * time.Hour)
114 r.ExpiresAt = r.SignedAt.Add(Lifetime)
115 wire, err := Sign(priv, r)
116 if err != nil {
117 t.Fatal(err)
118 }
119 if rev, err := VerifyRevision(pub, wire); err != nil || rev != 7 {
120 t.Fatalf("VerifyRevision = %d, %v; want 7", rev, err)
121 }
122 if _, err := VerifyRevision(pub, []byte("garbage")); err == nil {
123 t.Fatal("VerifyRevision accepted garbage")
124 }
125}
126
127// The domain prefix: a wrapper signed over the bare payload (every other
128// root-signed record's shape) must not verify as a door-key record.
129func TestVerifyRefusesABareSignedWrapper(t *testing.T) {
130 pub, priv, _ := ed25519.GenerateKey(rand.Reader)
131 _, entry, _ := NewToken("bae", "titogo")
132 payload, _ := json.Marshal(rec1(entry))
133 b64 := base64.StdEncoding.EncodeToString(payload)
134 wire, _ := json.Marshal(map[string]string{"payload": b64,
135 "sig": base64.StdEncoding.EncodeToString(ed25519.Sign(priv, []byte(b64)))})
136 if _, err := Verify(pub, wire, time.Now()); err == nil {
137 t.Fatal("a bare-signed wrapper verified as a door-key record")
138 }
139 // And the other direction: a door-key record's signature does not
140 // verify over its bare payload.
141 door, _ := Sign(priv, rec1(entry))
142 var w struct{ Payload, Sig string }
143 if err := json.Unmarshal(door, &w); err != nil {
144 t.Fatal(err)
145 }
146 sig, _ := base64.StdEncoding.DecodeString(w.Sig)
147 if ed25519.Verify(pub, []byte(w.Payload), sig) {
148 t.Fatal("a door-key signature verifies over the bare payload")
149 }
150}
151
152func TestSignRefusesTooManyKeys(t *testing.T) {
153 _, priv, _ := ed25519.GenerateKey(rand.Reader)
154 _, e, _ := NewToken("bae", "titogo")
155 r := rec1(e)
156 for i := 0; i < MaxKeys; i++ {
157 _, e, _ := NewToken("bae", "titogo")
158 r.Keys = append(r.Keys, e)
159 }
160 if _, err := Sign(priv, r); err == nil {
161 t.Fatal("signed a record over MaxKeys")
162 }
163}
164