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