eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3// đŸ¤– Pins the hand-rolled signer against the official worked examples in the
4// AWS docs ("Signature Calculations for the Authorization Header" — the
5// examplebucket vectors, signing key AKIAIOSFODNN7EXAMPLE, 2013-05-24), so the
6// signer is testable offline. TestS3VaultStore exercises it for real.
7
8import (
9 "net/http"
10 "strings"
11 "testing"
12 "time"
13)
14
15func TestSigV4Vectors(t *testing.T) {
16 const (
17 accessKey = "AKIAIOSFODNN7EXAMPLE"
18 secretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
19 amzDate = "20130524T000000Z"
20 )
21 ts := time.Date(2013, 5, 24, 0, 0, 0, 0, time.UTC)
22
23 cases := []struct {
24 name, method, url string
25 headers map[string]string
26 wantSignedHeaders string
27 wantSig string
28 }{
29 {
30 // GET Object: GET /test.txt with a Range header.
31 name: "GetObject",
32 method: "GET",
33 url: "https://examplebucket.s3.amazonaws.com/test.txt",
34 headers: map[string]string{
35 "Range": "bytes=0-9",
36 "x-amz-content-sha256": emptyPayloadHash,
37 "x-amz-date": amzDate,
38 },
39 wantSignedHeaders: "host;range;x-amz-content-sha256;x-amz-date",
40 wantSig: "f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41",
41 },
42 {
43 // GET Bucket Lifecycle: a valueless subresource must canonicalize
44 // as "lifecycle=" — the same shape our ListObjectVersions uses.
45 name: "GetBucketLifecycle",
46 method: "GET",
47 url: "https://examplebucket.s3.amazonaws.com/?lifecycle",
48 headers: map[string]string{
49 "x-amz-content-sha256": emptyPayloadHash,
50 "x-amz-date": amzDate,
51 },
52 wantSignedHeaders: "host;x-amz-content-sha256;x-amz-date",
53 wantSig: "fea454ca298b7da1c68078a5d1bdbfbbe0d65c699e0f91ac7a200a0136783543",
54 },
55 {
56 // List Objects: multiple query params, sorted canonically.
57 name: "ListObjects",
58 method: "GET",
59 url: "https://examplebucket.s3.amazonaws.com/?max-keys=2&prefix=J",
60 headers: map[string]string{
61 "x-amz-content-sha256": emptyPayloadHash,
62 "x-amz-date": amzDate,
63 },
64 wantSignedHeaders: "host;x-amz-content-sha256;x-amz-date",
65 wantSig: "34b48302e7b5fa45bde8084f4b7868a86f0a534bc59db6670ed5711ef69dc6f7",
66 },
67 }
68
69 for _, tc := range cases {
70 t.Run(tc.name, func(t *testing.T) {
71 req, err := http.NewRequest(tc.method, tc.url, nil)
72 if err != nil {
73 t.Fatalf("NewRequest: %v", err)
74 }
75 for k, v := range tc.headers {
76 req.Header.Set(k, v)
77 }
78 awsSignV4(req, accessKey, secretKey, "us-east-1", "s3", ts)
79 auth := req.Header.Get("Authorization")
80 if !strings.Contains(auth, "SignedHeaders="+tc.wantSignedHeaders+",") {
81 t.Errorf("signed headers: want %q in %q", tc.wantSignedHeaders, auth)
82 }
83 if !strings.HasSuffix(auth, "Signature="+tc.wantSig) {
84 t.Errorf("signature mismatch:\n got %q\nwant …Signature=%s", auth, tc.wantSig)
85 }
86 })
87 }
88}
89
90// The SigV4 path/query encoders have their own rules (uppercase hex, '+' is
91// %2B never a space, '/' survives only in paths); a wrong encoder signs
92// requests S3 will reject, so pin the corners.
93func TestAWSURIEncode(t *testing.T) {
94 for _, tc := range []struct {
95 in string
96 encodeSlash bool
97 want string
98 }{
99 {"blobs/u1", false, "blobs/u1"},
100 {"blobs/u1", true, "blobs%2Fu1"},
101 {"a b+c", true, "a%20b%2Bc"},
102 {"ok-._~", true, "ok-._~"},
103 {"Ă©", true, "%C3%A9"},
104 } {
105 if got := awsURIEncode(tc.in, tc.encodeSlash); got != tc.want {
106 t.Errorf("awsURIEncode(%q, %v) = %q, want %q", tc.in, tc.encodeSlash, got, tc.want)
107 }
108 }
109}
110