rastrillo / abono Public

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

Plain git — no account needed to clone.

Download

Download this file

1package stripeapi
2
3import (
4 "crypto/hmac"
5 "crypto/sha256"
6 "encoding/hex"
7 "errors"
8 "strconv"
9 "testing"
10 "time"
11)
12
13func signed(body string, at time.Time, secret string) string {
14 ts := strconv.FormatInt(at.Unix(), 10)
15 mac := hmac.New(sha256.New, []byte(secret))
16 mac.Write([]byte(ts + "." + body))
17 return "t=" + ts + ",v1=" + hex.EncodeToString(mac.Sum(nil))
18}
19
20func TestVerifySignature(t *testing.T) {
21 t.Parallel()
22 now := time.Unix(1_800_000_000, 0)
23 body := `{"id":"evt_1"}`
24 ok := signed(body, now, "whsec_new")
25 for name, c := range map[string]struct {
26 header, body string
27 secrets []string
28 want bool
29 }{
30 "good": {ok, body, []string{"whsec_new"}, true},
31 "rotating, either": {ok, body, []string{"whsec_old", "whsec_new"}, true},
32 "two v1s, one good": {ok + ",v1=00ff", body, []string{"whsec_new"}, true},
33 "wrong secret": {ok, body, []string{"whsec_old"}, false},
34 "body changed": {ok, `{"id":"evt_2"}`, []string{"whsec_new"}, false},
35 "too old": {signed(body, now.Add(-6*time.Minute), "whsec_new"), body, []string{"whsec_new"}, false},
36 "from the future": {signed(body, now.Add(6*time.Minute), "whsec_new"), body, []string{"whsec_new"}, false},
37 "no timestamp": {"v1=00", body, []string{"whsec_new"}, false},
38 "empty": {"", body, []string{"whsec_new"}, false},
39 "v0 only (Stripe's test scheme)": {"t=" + strconv.FormatInt(now.Unix(), 10) + ",v0=00", body, []string{"whsec_new"}, false},
40 } {
41 err := VerifySignature(c.header, []byte(c.body), c.secrets, now)
42 if got := err == nil; got != c.want {
43 t.Errorf("%s: verified %v, want %v", name, got, c.want)
44 }
45 if err != nil && !errors.Is(err, ErrBadSignature) {
46 t.Errorf("%s: error %v is not ErrBadSignature", name, err)
47 }
48 }
49}
50
51// The window is five minutes either side of now: just inside passes,
52// just outside fails.
53func TestVerifySignatureTolerance(t *testing.T) {
54 t.Parallel()
55 now := time.Unix(1_800_000_000, 0)
56 body := `{"id":"evt_1"}`
57 secrets := []string{"whsec"}
58 for name, c := range map[string]struct {
59 at time.Time
60 want bool
61 }{
62 "4m59s old": {now.Add(-4*time.Minute - 59*time.Second), true},
63 "4m59s ahead": {now.Add(4*time.Minute + 59*time.Second), true},
64 "5m01s old": {now.Add(-5*time.Minute - time.Second), false},
65 "5m01s ahead": {now.Add(5*time.Minute + time.Second), false},
66 } {
67 err := VerifySignature(signed(body, c.at, "whsec"), []byte(body), secrets, now)
68 if got := err == nil; got != c.want {
69 t.Errorf("%s: verified %v (%v), want %v", name, got, err, c.want)
70 }
71 }
72}
73
74// While a secret is being rotated Stripe signs with both; the second
75// of two configured secrets is enough.
76func TestVerifySignatureRotatingSecrets(t *testing.T) {
77 t.Parallel()
78 now := time.Unix(1_800_000_000, 0)
79 body := `{"id":"evt_1"}`
80 header := signed(body, now, "whsec_second")
81 if err := VerifySignature(header, []byte(body), []string{"whsec_first", "whsec_second"}, now); err != nil {
82 t.Fatalf("second secret did not match: %v", err)
83 }
84 if err := VerifySignature(header, []byte(body), []string{"whsec_first", "whsec_third"}, now); !errors.Is(err, ErrBadSignature) {
85 t.Fatalf("neither secret matches, got %v", err)
86 }
87}
88