1package s3crypto_test
2
3import (
4	"bytes"
5	"encoding/base64"
6	"encoding/hex"
7	"fmt"
8	"io/ioutil"
9	"net/http"
10	"net/http/httptest"
11	"strings"
12	"testing"
13
14	"github.com/aws/aws-sdk-go/aws"
15	"github.com/aws/aws-sdk-go/aws/awserr"
16	"github.com/aws/aws-sdk-go/aws/request"
17	"github.com/aws/aws-sdk-go/awstesting"
18	"github.com/aws/aws-sdk-go/awstesting/unit"
19	"github.com/aws/aws-sdk-go/service/s3"
20	"github.com/aws/aws-sdk-go/service/s3/s3crypto"
21)
22
23func TestGetObjectGCM(t *testing.T) {
24	key, _ := hex.DecodeString("31bdadd96698c204aa9ce1448ea94ae1fb4a9a0b3c9d773b51bb1822666b8f22")
25	keyB64 := base64.StdEncoding.EncodeToString(key)
26	// This is our KMS response
27	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28		fmt.Fprintln(w, fmt.Sprintf("%s%s%s", `{"KeyId":"test-key-id","Plaintext":"`, keyB64, `"}`))
29	}))
30	defer ts.Close()
31
32	sess := unit.Session.Copy(&aws.Config{
33		MaxRetries:       aws.Int(0),
34		Endpoint:         aws.String(ts.URL),
35		DisableSSL:       aws.Bool(true),
36		S3ForcePathStyle: aws.Bool(true),
37		Region:           aws.String("us-west-2"),
38	})
39
40	c := s3crypto.NewDecryptionClient(sess)
41	if c == nil {
42		t.Error("expected non-nil value")
43	}
44	input := &s3.GetObjectInput{
45		Key:    aws.String("test"),
46		Bucket: aws.String("test"),
47	}
48	req, out := c.GetObjectRequest(input)
49	req.Handlers.Send.Clear()
50	req.Handlers.Send.PushBack(func(r *request.Request) {
51		iv, err := hex.DecodeString("0d18e06c7c725ac9e362e1ce")
52		if err != nil {
53			t.Errorf("expected no error, but received %v", err)
54		}
55
56		b, err := hex.DecodeString("fa4362189661d163fcd6a56d8bf0405ad636ac1bbedd5cc3ee727dc2ab4a9489")
57		if err != nil {
58			t.Errorf("expected no error, but received %v", err)
59		}
60
61		r.HTTPResponse = &http.Response{
62			StatusCode: 200,
63			Header: http.Header{
64				http.CanonicalHeaderKey("x-amz-meta-x-amz-key-v2"):   []string{"SpFRES0JyU8BLZSKo51SrwILK4lhtZsWiMNjgO4WmoK+joMwZPG7Hw=="},
65				http.CanonicalHeaderKey("x-amz-meta-x-amz-iv"):       []string{base64.URLEncoding.EncodeToString(iv)},
66				http.CanonicalHeaderKey("x-amz-meta-x-amz-matdesc"):  []string{`{"kms_cmk_id":"arn:aws:kms:us-east-1:172259396726:key/a22a4b30-79f4-4b3d-bab4-a26d327a231b"}`},
67				http.CanonicalHeaderKey("x-amz-meta-x-amz-wrap-alg"): []string{s3crypto.KMSWrap},
68				http.CanonicalHeaderKey("x-amz-meta-x-amz-cek-alg"):  []string{s3crypto.AESGCMNoPadding},
69				http.CanonicalHeaderKey("x-amz-meta-x-amz-tag-len"):  []string{"128"},
70			},
71			Body: ioutil.NopCloser(bytes.NewBuffer(b)),
72		}
73	})
74	err := req.Send()
75	if err != nil {
76		t.Errorf("expected no error, but received %v", err)
77	}
78	b, err := ioutil.ReadAll(out.Body)
79	if err != nil {
80		t.Errorf("expected no error, but received %v", err)
81	}
82	expected, err := hex.DecodeString("2db5168e932556f8089a0622981d017d")
83	if err != nil {
84		t.Errorf("expected no error, but received %v", err)
85	}
86
87	if !bytes.Equal(expected, b) {
88		t.Error("expected bytes to be equivalent")
89	}
90}
91
92func TestGetObjectCBC(t *testing.T) {
93	key, _ := hex.DecodeString("898be9cc5004ed0fa6e117c9a3099d31")
94	keyB64 := base64.StdEncoding.EncodeToString(key)
95	// This is our KMS response
96	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
97		fmt.Fprintln(w, fmt.Sprintf("%s%s%s", `{"KeyId":"test-key-id","Plaintext":"`, keyB64, `"}`))
98	}))
99	defer ts.Close()
100
101	sess := unit.Session.Copy(&aws.Config{
102		MaxRetries:       aws.Int(0),
103		Endpoint:         aws.String(ts.URL),
104		DisableSSL:       aws.Bool(true),
105		S3ForcePathStyle: aws.Bool(true),
106		Region:           aws.String("us-west-2"),
107	})
108
109	c := s3crypto.NewDecryptionClient(sess)
110	if c == nil {
111		t.Error("expected non-nil value")
112	}
113	input := &s3.GetObjectInput{
114		Key:    aws.String("test"),
115		Bucket: aws.String("test"),
116	}
117	req, out := c.GetObjectRequest(input)
118	req.Handlers.Send.Clear()
119	req.Handlers.Send.PushBack(func(r *request.Request) {
120		iv, err := hex.DecodeString("9dea7621945988f96491083849b068df")
121		if err != nil {
122			t.Errorf("expected no error, but received %v", err)
123		}
124		b, err := hex.DecodeString("e232cd6ef50047801ee681ec30f61d53cfd6b0bca02fd03c1b234baa10ea82ac9dab8b960926433a19ce6dea08677e34")
125		if err != nil {
126			t.Errorf("expected no error, but received %v", err)
127		}
128
129		r.HTTPResponse = &http.Response{
130			StatusCode: 200,
131			Header: http.Header{
132				http.CanonicalHeaderKey("x-amz-meta-x-amz-key-v2"):   []string{"SpFRES0JyU8BLZSKo51SrwILK4lhtZsWiMNjgO4WmoK+joMwZPG7Hw=="},
133				http.CanonicalHeaderKey("x-amz-meta-x-amz-iv"):       []string{base64.URLEncoding.EncodeToString(iv)},
134				http.CanonicalHeaderKey("x-amz-meta-x-amz-matdesc"):  []string{`{"kms_cmk_id":"arn:aws:kms:us-east-1:172259396726:key/a22a4b30-79f4-4b3d-bab4-a26d327a231b"}`},
135				http.CanonicalHeaderKey("x-amz-meta-x-amz-wrap-alg"): []string{s3crypto.KMSWrap},
136				http.CanonicalHeaderKey("x-amz-meta-x-amz-cek-alg"):  []string{strings.Join([]string{s3crypto.AESCBC, s3crypto.AESCBCPadder.Name()}, "/")},
137			},
138			Body: ioutil.NopCloser(bytes.NewBuffer(b)),
139		}
140	})
141	err := req.Send()
142	if err != nil {
143		t.Errorf("expected no error, but received %v", err)
144	}
145	b, err := ioutil.ReadAll(out.Body)
146	if err != nil {
147		t.Errorf("expected no error, but received %v", err)
148	}
149	expected, err := hex.DecodeString("0397f4f6820b1f9386f14403be5ac16e50213bd473b4874b9bcbf5f318ee686b1d")
150	if err != nil {
151		t.Errorf("expected no error, but received %v", err)
152	}
153
154	if !bytes.Equal(expected, b) {
155		t.Error("expected bytes to be equivalent")
156	}
157}
158
159func TestGetObjectCBC2(t *testing.T) {
160	key, _ := hex.DecodeString("8d70e92489c4e6cfb12261b4d17f4b85826da687fc8742fcf9f87fadb5b4cb89")
161	keyB64 := base64.StdEncoding.EncodeToString(key)
162	// This is our KMS response
163	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
164		fmt.Fprintln(w, fmt.Sprintf("%s%s%s", `{"KeyId":"test-key-id","Plaintext":"`, keyB64, `"}`))
165	}))
166	defer ts.Close()
167
168	sess := unit.Session.Copy(&aws.Config{
169		MaxRetries:       aws.Int(0),
170		Endpoint:         aws.String(ts.URL),
171		DisableSSL:       aws.Bool(true),
172		S3ForcePathStyle: aws.Bool(true),
173		Region:           aws.String("us-west-2"),
174	})
175
176	c := s3crypto.NewDecryptionClient(sess)
177	if c == nil {
178		t.Error("expected non-nil value")
179	}
180	input := &s3.GetObjectInput{
181		Key:    aws.String("test"),
182		Bucket: aws.String("test"),
183	}
184	req, out := c.GetObjectRequest(input)
185	req.Handlers.Send.Clear()
186	req.Handlers.Send.PushBack(func(r *request.Request) {
187		b, err := hex.DecodeString("fd0c71ecb7ed16a9bf42ea5f75501d416df608f190890c3b4d8897f24744cd7f9ea4a0b212e60634302450e1c5378f047ff753ccefe365d411c36339bf22e301fae4c3a6226719a4b93dc74c1af79d0296659b5d56c0892315f2c7cc30190220db1eaafae3920d6d9c65d0aa366499afc17af493454e141c6e0fbdeb6a990cb4")
188		if err != nil {
189			t.Errorf("expected no error, but received %v", err)
190		}
191
192		r.HTTPResponse = &http.Response{
193			StatusCode: 200,
194			Header: http.Header{
195				http.CanonicalHeaderKey("x-amz-meta-x-amz-key-v2"):   []string{"AQEDAHikdGvcj7Gil5VqAR/JWvvPp3ue26+t2vhWy4lL2hg4mAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDCcy43wCR0bSsnzTrAIBEIA7WdD2jxC3tCrK6TOdiEfbIN64m+UN7Velz4y0LRra5jn2U1CDClacwIpiBYuDp5ymPKO+ZqUGE0WEf20="},
196				http.CanonicalHeaderKey("x-amz-meta-x-amz-iv"):       []string{"EMMWJY8ZLcK/9FOj3iCpng=="},
197				http.CanonicalHeaderKey("x-amz-meta-x-amz-matdesc"):  []string{`{"kms_cmk_id":"arn:aws:kms:us-east-1:172259396726:key/a22a4b30-79f4-4b3d-bab4-a26d327a231b"}`},
198				http.CanonicalHeaderKey("x-amz-meta-x-amz-wrap-alg"): []string{s3crypto.KMSWrap},
199				http.CanonicalHeaderKey("x-amz-meta-x-amz-cek-alg"):  []string{strings.Join([]string{s3crypto.AESCBC, s3crypto.AESCBCPadder.Name()}, "/")},
200			},
201			Body: ioutil.NopCloser(bytes.NewBuffer(b)),
202		}
203	})
204	err := req.Send()
205	if err != nil {
206		t.Errorf("expected no error, but received %v", err)
207	}
208	b, err := ioutil.ReadAll(out.Body)
209	if err != nil {
210		t.Errorf("expected no error, but received %v", err)
211	}
212	expected, err := hex.DecodeString("a6ccd3482f5ce25c9ddeb69437cd0acbc0bdda2ef8696d90781de2b35704543529871b2032e68ef1c5baed1769aba8d420d1aca181341b49b8b3587a6580cdf1d809c68f06735f7735c16691f4b70c967d68fc08195b81ad71bcc4df452fd0a5799c1e1234f92f1cd929fc072167ccf9f2ac85b93170932b32")
213	if err != nil {
214		t.Errorf("expected no error, but received %v", err)
215	}
216
217	if !bytes.Equal(expected, b) {
218		t.Error("expected bytes to be equivalent")
219	}
220}
221
222func TestGetObjectWithContext(t *testing.T) {
223	c := s3crypto.NewDecryptionClient(unit.Session)
224
225	ctx := &awstesting.FakeContext{DoneCh: make(chan struct{})}
226	ctx.Error = fmt.Errorf("context canceled")
227	close(ctx.DoneCh)
228
229	input := s3.GetObjectInput{
230		Key:    aws.String("test"),
231		Bucket: aws.String("test"),
232	}
233	_, err := c.GetObjectWithContext(ctx, &input)
234	if err == nil {
235		t.Fatalf("expected error, did not get one")
236	}
237	aerr := err.(awserr.Error)
238	if e, a := request.CanceledErrorCode, aerr.Code(); e != a {
239		t.Errorf("expected error code %q, got %q", e, a)
240	}
241	if e, a := "canceled", aerr.Message(); !strings.Contains(a, e) {
242		t.Errorf("expected error message to contain %q, but did not %q", e, a)
243	}
244}
245
246func TestDecryptionClient_GetObject_V2Artifact(t *testing.T) {
247	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
248		fmt.Fprintln(w, fmt.Sprintf("%s%s%s", `{"KeyId":"test-key-id","Plaintext":"`, "hJUv7S6K2cHF64boS9ixHX0TZAjBZLT4ZpEO4XxkGnY=", `"}`))
249	}))
250	defer ts.Close()
251
252	c := s3crypto.NewDecryptionClient(unit.Session.Copy(&aws.Config{Endpoint: &ts.URL}))
253
254	input := &s3.GetObjectInput{
255		Bucket: aws.String("test"),
256		Key:    aws.String("test"),
257	}
258
259	req, out := c.GetObjectRequest(input)
260	req.Handlers.Send.Clear()
261	req.Handlers.Send.PushBack(func(r *request.Request) {
262		b, err := hex.DecodeString("6b134eb7a353131de92faff64f594b2794e3544e31776cca26fe3bbeeffc68742d1007234f11c6670522602326868e29f37e9d2678f1614ec1a2418009b9772100929aadbed9a21a")
263		if err != nil {
264			t.Errorf("expected no error, but received %v", err)
265		}
266
267		r.HTTPResponse = &http.Response{
268			StatusCode: 200,
269			Header: http.Header{
270				http.CanonicalHeaderKey("x-amz-meta-x-amz-key-v2"):   []string{"PsuclPnlo2O0MQoov6kL1TBlaZG6oyNwWuAqmAgq7g8b9ZeeORi3VTMg624FU9jx"},
271				http.CanonicalHeaderKey("x-amz-meta-x-amz-iv"):       []string{"dqqlq2dRVSQ5hFRb"},
272				http.CanonicalHeaderKey("x-amz-meta-x-amz-matdesc"):  []string{`{"aws:x-amz-cek-alg": "AES/GCM/NoPadding"}`},
273				http.CanonicalHeaderKey("x-amz-meta-x-amz-wrap-alg"): []string{s3crypto.KMSContextWrap},
274				http.CanonicalHeaderKey("x-amz-meta-x-amz-cek-alg"):  []string{"AES/GCM/NoPadding"},
275			},
276			Body: ioutil.NopCloser(bytes.NewBuffer(b)),
277		}
278	})
279	err := req.Send()
280	if err != nil {
281		t.Fatalf("expected no error, got %v", err)
282	}
283
284	actual, err := ioutil.ReadAll(out.Body)
285	if err != nil {
286		t.Fatalf("expected no error, got %v", err)
287	}
288
289	expected, err := hex.DecodeString("af150d7156bf5b3f5c461e5c6ac820acc5a33aab7085d920666c250ff251209d5a4029b3bd78250fab6e11aed52fae948d407056a9519b68")
290	if err != nil {
291		t.Fatalf("expected no error, got %v", err)
292	}
293
294	if bytes.Compare(expected, actual) != 0 {
295		t.Fatalf("expected content to match but it did not")
296	}
297}
298