rastrillo / aviso Public

Clone
git clone https://amadan.net/rastrillo/aviso

Plain git — no account needed to clone.

Download

Download this file

1package aviso
2
3import (
4 "crypto/ecdh"
5 "crypto/rand"
6 "crypto/sha256"
7 "encoding/base64"
8 "errors"
9 "math/big"
10 "strings"
11 "testing"
12)
13
14func TestGenerateKeyRoundTrips(t *testing.T) {
15 priv, err := GenerateKey()
16 if err != nil {
17 t.Fatal(err)
18 }
19 raw, err := base64.RawURLEncoding.DecodeString(priv)
20 if err != nil || len(raw) != 32 {
21 t.Fatalf("private key = %q: want 32 unpadded base64url bytes (err %v)", priv, err)
22 }
23 pub, id, err := parsePrivateKey(priv)
24 if err != nil {
25 t.Fatal(err)
26 }
27 pubRaw, err := base64.RawURLEncoding.DecodeString(pub)
28 if err != nil || len(pubRaw) != 65 || pubRaw[0] != 0x04 {
29 t.Fatalf("public key = %q: want 65-byte uncompressed point", pub)
30 }
31 if id == "" || strings.ContainsAny(id, "+/=") {
32 t.Fatalf("key id = %q: want unpadded base64url", id)
33 }
34 // The same key must yield the same id after a restart — Sweep and
35 // Send match rows on it.
36 _, id2, _ := parsePrivateKey(priv)
37 if id != id2 {
38 t.Fatal("key id not deterministic")
39 }
40}
41
42func TestParsePrivateKeyRefusesBadInput(t *testing.T) {
43 if _, _, err := parsePrivateKey(""); !errors.Is(err, ErrEmptyPrivateKey) {
44 t.Fatalf("empty: got %v, want ErrEmptyPrivateKey", err)
45 }
46 enc := base64.RawURLEncoding.EncodeToString
47 zero := enc(make([]byte, 32))
48 // The P-256 group order n, and n-1 (the largest valid scalar).
49 n, _ := new(big.Int).SetString("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16)
50 nMinus1 := new(big.Int).Sub(n, big.NewInt(1))
51 nPlus1 := new(big.Int).Add(n, big.NewInt(1))
52 one := make([]byte, 32)
53 one[31] = 1
54 good := enc(one)
55 if _, _, err := parsePrivateKey(enc(nMinus1.FillBytes(make([]byte, 32)))); err != nil {
56 t.Errorf("n-1 refused: %v", err)
57 }
58 for name, in := range map[string]string{
59 "not base64": "!!!",
60 "short": enc([]byte("short")),
61 "zero scalar": zero,
62 "order n": enc(n.FillBytes(make([]byte, 32))),
63 "above n": enc(nPlus1.FillBytes(make([]byte, 32))),
64 "padded": good + "=",
65 "newline": good[:10] + "\n" + good[10:],
66 "trailing bits": good[:len(good)-1] + "F", // canonical ending is "E"
67 } {
68 if _, _, err := parsePrivateKey(in); !errors.Is(err, ErrInvalidPrivateKey) {
69 t.Errorf("%s: got %v, want ErrInvalidPrivateKey", name, err)
70 }
71 }
72}
73
74// New must wire parsePrivateKey's outputs unchanged: two Services on
75// one key agree on the public key and the key id rows are stamped with.
76func TestNewWiresTheSameKeyIdentityEveryTime(t *testing.T) {
77 key, _ := GenerateKey()
78 mk := func() *Service {
79 s, err := New(Config{DB: openInternalDB(t), PrivateKey: key, Contact: "mailto:x@y", Origin: "https://a"})
80 if err != nil {
81 t.Fatal(err)
82 }
83 return s
84 }
85 a, b := mk(), mk()
86 pub, id, _ := parsePrivateKey(key)
87 if a.pub != pub || b.pub != pub || a.keyID != id || b.keyID != id {
88 t.Fatalf("New drifted from parsePrivateKey: %q/%q vs %q/%q", a.pub, a.keyID, pub, id)
89 }
90}
91
92func TestKeyIDIsSHA256OfThePublicPoint(t *testing.T) {
93 priv, _ := GenerateKey()
94 pub, id, err := parsePrivateKey(priv)
95 if err != nil {
96 t.Fatal(err)
97 }
98 point, _ := base64.RawURLEncoding.DecodeString(pub)
99 sum := sha256.Sum256(point)
100 if want := base64.RawURLEncoding.EncodeToString(sum[:]); id != want {
101 t.Fatalf("key id = %s, want %s", id, want)
102 }
103}
104
105func TestParsePrivateKeyAgreesWithECDH(t *testing.T) {
106 k, err := ecdh.P256().GenerateKey(rand.Reader)
107 if err != nil {
108 t.Fatal(err)
109 }
110 priv := base64.RawURLEncoding.EncodeToString(k.Bytes())
111 pub, _, err := parsePrivateKey(priv)
112 if err != nil {
113 t.Fatal(err)
114 }
115 want := base64.RawURLEncoding.EncodeToString(k.PublicKey().Bytes())
116 if pub != want {
117 t.Fatalf("public key = %s, want %s", pub, want)
118 }
119}
120