1//go:build go1.7
2// +build go1.7
3
4package s3crypto
5
6import (
7	"bytes"
8	"encoding/hex"
9	"fmt"
10	"io"
11	"io/ioutil"
12	"net/http"
13	"net/http/httptest"
14	"reflect"
15	"strings"
16	"testing"
17
18	"github.com/aws/aws-sdk-go/aws"
19	"github.com/aws/aws-sdk-go/aws/request"
20	"github.com/aws/aws-sdk-go/aws/session"
21	"github.com/aws/aws-sdk-go/awstesting/unit"
22	"github.com/aws/aws-sdk-go/service/kms"
23	"github.com/aws/aws-sdk-go/service/s3"
24)
25
26func sessionWithLogCheck(message string) (*session.Session, *bool) {
27	gotWarning := false
28
29	u := unit.Session.Copy(&aws.Config{Logger: aws.LoggerFunc(func(i ...interface{}) {
30		if len(i) == 0 {
31			return
32		}
33		s, ok := i[0].(string)
34		if !ok {
35			return
36		}
37		if s == message {
38			gotWarning = true
39		}
40	})})
41
42	return u, &gotWarning
43}
44
45func TestNewEncryptionClientV2(t *testing.T) {
46	tUnit, gotWarning := sessionWithLogCheck(customTypeWarningMessage)
47
48	mcb := AESGCMContentCipherBuilderV2(NewKMSContextKeyGenerator(nil, "id", nil))
49	v2, err := NewEncryptionClientV2(tUnit, mcb)
50	if err != nil {
51		t.Fatalf("expected no error, got %v", err)
52	}
53	if v2 == nil {
54		t.Fatal("expected client to not be nil")
55	}
56
57	if *gotWarning {
58		t.Errorf("expected no warning for aws provided custom cipher builder")
59	}
60
61	if !reflect.DeepEqual(mcb, v2.options.ContentCipherBuilder) {
62		t.Errorf("content cipher builder did not match provided value")
63	}
64
65	_, ok := v2.options.SaveStrategy.(HeaderV2SaveStrategy)
66	if !ok {
67		t.Errorf("expected default save strategy to be s3 header strategy")
68	}
69
70	if v2.options.S3Client == nil {
71		t.Errorf("expected s3 client not be nil")
72	}
73
74	if e, a := DefaultMinFileSize, v2.options.MinFileSize; int64(e) != a {
75		t.Errorf("expected %v, got %v", e, a)
76	}
77
78	if e, a := "", v2.options.TempFolderPath; e != a {
79		t.Errorf("expected %v, got %v", e, a)
80	}
81}
82
83func TestNewEncryptionClientV2_NonDefaults(t *testing.T) {
84	tUnit, gotWarning := sessionWithLogCheck(customTypeWarningMessage)
85
86	s3Client := s3.New(tUnit)
87
88	mcb := mockCipherBuilderV2{}
89	v2, err := NewEncryptionClientV2(tUnit, nil, func(clientOptions *EncryptionClientOptions) {
90		clientOptions.S3Client = s3Client
91		clientOptions.ContentCipherBuilder = mcb
92		clientOptions.TempFolderPath = "/mock/path"
93		clientOptions.MinFileSize = 42
94		clientOptions.SaveStrategy = S3SaveStrategy{}
95	})
96	if err != nil {
97		t.Fatalf("expected no error, got %v", err)
98	}
99	if v2 == nil {
100		t.Fatal("expected client to not be nil")
101	}
102
103	if !*gotWarning {
104		t.Errorf("expected warning for custom provided content cipher builder")
105	}
106
107	if !reflect.DeepEqual(mcb, v2.options.ContentCipherBuilder) {
108		t.Errorf("content cipher builder did not match provided value")
109	}
110
111	_, ok := v2.options.SaveStrategy.(S3SaveStrategy)
112	if !ok {
113		t.Errorf("expected default save strategy to be s3 header strategy")
114	}
115
116	if v2.options.S3Client != s3Client {
117		t.Errorf("expected s3 client not be nil")
118	}
119
120	if e, a := 42, v2.options.MinFileSize; int64(e) != a {
121		t.Errorf("expected %v, got %v", e, a)
122	}
123
124	if e, a := "/mock/path", v2.options.TempFolderPath; e != a {
125		t.Errorf("expected %v, got %v", e, a)
126	}
127}
128
129// cdgWithStaticTestIV is a test structure that wraps a CipherDataGeneratorWithCEKAlg and stubs in a static IV
130// so that encryption tests can be guaranteed to be consistent.
131type cdgWithStaticTestIV struct {
132	IV []byte
133	CipherDataGeneratorWithCEKAlg
134}
135
136// isAWSFixture will avoid the warning log message when doing tests that need to mock the IV
137func (k cdgWithStaticTestIV) isAWSFixture() bool {
138	return true
139}
140
141func (k cdgWithStaticTestIV) GenerateCipherDataWithCEKAlg(ctx aws.Context, keySize, ivSize int, cekAlg string) (CipherData, error) {
142	cipherData, err := k.CipherDataGeneratorWithCEKAlg.GenerateCipherDataWithCEKAlg(ctx, keySize, ivSize, cekAlg)
143	if err == nil {
144		cipherData.IV = k.IV
145	}
146	return cipherData, err
147}
148
149func TestEncryptionClientV2_PutObject_KMSCONTEXT_AESGCM(t *testing.T) {
150	ts := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
151		fmt.Fprintln(writer, `{"CiphertextBlob":"8gSzlk7giyfFbLPUVgoVjvQebI1827jp8lDkO+n2chsiSoegx1sjm8NdPk0Bl70I","KeyId":"test-key-id","Plaintext":"lP6AbIQTmptyb/+WQq+ubDw+w7na0T1LGSByZGuaono="}`)
152	}))
153
154	sess := unit.Session.Copy()
155	kmsClient := kms.New(sess.Copy(&aws.Config{Endpoint: &ts.URL}))
156
157	var md MaterialDescription
158	iv, _ := hex.DecodeString("ae325acae2bfd5b9c3d0b813")
159	kmsWithStaticIV := cdgWithStaticTestIV{
160		IV:                            iv,
161		CipherDataGeneratorWithCEKAlg: NewKMSContextKeyGenerator(kmsClient, "test-key-id", md),
162	}
163	contentCipherBuilderV2 := AESGCMContentCipherBuilderV2(kmsWithStaticIV)
164	client, err := NewEncryptionClientV2(sess, contentCipherBuilderV2)
165	if err != nil {
166		t.Fatalf("expected no error, got %v", err)
167	}
168
169	req, _ := client.PutObjectRequest(&s3.PutObjectInput{
170		Bucket: aws.String("test-bucket"),
171		Key:    aws.String("test-key"),
172		Body: func() io.ReadSeeker {
173			content, _ := hex.DecodeString("8f2c59c6dbfcacf356f3da40788cbde67ca38161a4702cbcf757af663e1c24a600001b2f500417dbf5a050f57db6737422b2ed6a44c75e0d")
174			return bytes.NewReader(content)
175		}(),
176	})
177
178	req.Handlers.Send.Clear()
179	req.Handlers.Send.PushFront(func(r *request.Request) {
180		all, err := ioutil.ReadAll(r.Body)
181		if err != nil {
182			t.Fatalf("expected no error, got %v", err)
183		}
184
185		expected, _ := hex.DecodeString("4cd8e95a1c9b8b19640e02838b02c8c09e66250703a602956695afbc23cbb8647d51645955ab63b89733d0766f9a264adb88571b1d467b734ff72eb73d31de9a83670d59688c54ea")
186
187		if !bytes.Equal(all, expected) {
188			t.Error("encrypted bytes did not match expected")
189		}
190
191		req.HTTPResponse = &http.Response{
192			Status:     http.StatusText(200),
193			StatusCode: http.StatusOK,
194			Body:       aws.ReadSeekCloser(bytes.NewReader([]byte{})),
195		}
196	})
197	err = req.Send()
198	if err != nil {
199		t.Errorf("expected no error, got %v", err)
200	}
201}
202
203func TestNewEncryptionClientV2_FailsOnIncompatibleFixtures(t *testing.T) {
204	sess := unit.Session.Copy()
205	_, err := NewEncryptionClientV2(sess, AESGCMContentCipherBuilder(NewKMSKeyGenerator(kms.New(sess), "cmkId")))
206	if err == nil {
207		t.Fatal("expected to fail, but got nil")
208	}
209	if !strings.Contains(err.Error(), "attempted to use deprecated or incompatible cipher builder") {
210		t.Errorf("expected to get error for using dperecated cipher builder")
211	}
212}
213