1
2
3
4
5
6
7
8
9
10 package mongocrypt
11
12 import (
13 "bufio"
14 "bytes"
15 "io/ioutil"
16 "os"
17 "path"
18 "strings"
19 "testing"
20
21 "go.mongodb.org/mongo-driver/bson"
22 "go.mongodb.org/mongo-driver/bson/primitive"
23 "go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
24 "go.mongodb.org/mongo-driver/x/mongo/driver/mongocrypt/options"
25 )
26
27 const resourcesDir = "../../../../testdata/mongocrypt"
28
29 func noerr(t *testing.T, err error) {
30 if err != nil {
31 t.Helper()
32 t.Errorf("Unexpected error: (%T)%v", err, err)
33 t.FailNow()
34 }
35 }
36
37 func compareStates(t *testing.T, expected, actual State) {
38 t.Helper()
39 if expected != actual {
40 t.Fatalf("state mismatch; expected %s, got %s", expected, actual)
41 }
42 }
43
44 func compareResources(t *testing.T, expected, actual bsoncore.Document) {
45 t.Helper()
46 if !bytes.Equal(expected, actual) {
47 t.Fatalf("resource mismatch; expected %v, got %v", expected, actual)
48 }
49 }
50
51 func createMongoCrypt(t *testing.T) *MongoCrypt {
52 t.Helper()
53
54 awsProvider := bsoncore.NewDocumentBuilder().
55 AppendString("accessKeyId", "example").
56 AppendString("secretAccessKey", "example").
57 Build()
58 localProvider := bsoncore.NewDocumentBuilder().
59 AppendBinary("key", 0, make([]byte, 96)).
60 Build()
61 kmsProviders := bsoncore.NewDocumentBuilder().
62 AppendDocument("aws", awsProvider).
63 AppendDocument("local", localProvider).
64 Build()
65
66 cryptOpts := options.MongoCrypt().SetKmsProviders(kmsProviders)
67 crypt, err := NewMongoCrypt(cryptOpts)
68 noerr(t, err)
69 if crypt == nil {
70 t.Fatalf("expected MongoCrypt instance but got nil")
71 }
72 return crypt
73 }
74
75 func resourceToDocument(t *testing.T, filename string) bsoncore.Document {
76 t.Helper()
77 filepath := path.Join(resourcesDir, filename)
78 content, err := ioutil.ReadFile(filepath)
79 noerr(t, err)
80
81 var doc bsoncore.Document
82 noerr(t, bson.UnmarshalExtJSON(content, false, &doc))
83 return doc
84 }
85
86 func httpResponseToBytes(t *testing.T, filename string) []byte {
87 t.Helper()
88 file, err := os.Open(path.Join(resourcesDir, filename))
89 noerr(t, err)
90 defer func() {
91 _ = file.Close()
92 }()
93
94 firstLine := true
95 var fileStr string
96 scanner := bufio.NewScanner(file)
97 for scanner.Scan() {
98 if !firstLine {
99 fileStr += "\r\n"
100 }
101 firstLine = false
102 fileStr += scanner.Text()
103 }
104 noerr(t, scanner.Err())
105
106 return []byte(fileStr)
107 }
108
109
110 func testKmsCtx(t *testing.T, ctx *Context, keyAltName bool) {
111
112 keyFilter, err := ctx.NextOperation()
113 noerr(t, err)
114 filterFile := "key-filter.json"
115 if keyAltName {
116 filterFile = "key-filter-keyAltName.json"
117 }
118 compareResources(t, resourceToDocument(t, filterFile), keyFilter)
119
120
121 noerr(t, ctx.AddOperationResult(resourceToDocument(t, "key-document.json")))
122 noerr(t, ctx.CompleteOperation())
123 compareStates(t, NeedKms, ctx.State())
124
125
126 kmsCtx := ctx.NextKmsContext()
127 hostname, err := kmsCtx.HostName()
128 noerr(t, err)
129
130
131
132
133
134
135 expectedHost := "kms.us-east-1.amazonaws.com"
136 if !strings.Contains(hostname, expectedHost) {
137 t.Fatalf("hostname mismatch; expected %s to contain %s", hostname, expectedHost)
138 }
139
140
141 kmsMsg, err := kmsCtx.Message()
142 noerr(t, err)
143 if len(kmsMsg) != 790 {
144 t.Fatalf("message length mismatch; expected 790, got %d", len(kmsMsg))
145 }
146
147
148 bytesNeeded := kmsCtx.BytesNeeded()
149 if bytesNeeded != 1024 {
150 t.Fatalf("number of bytes mismatch; expected 1024, got %d", bytesNeeded)
151 }
152 noerr(t, kmsCtx.FeedResponse(httpResponseToBytes(t, "kms-reply.txt")))
153 bytesNeeded = kmsCtx.BytesNeeded()
154 if bytesNeeded != 0 {
155 t.Fatalf("number of bytes mismatch; expected 0, got %d", bytesNeeded)
156 }
157
158
159 kmsCtx = ctx.NextKmsContext()
160 if kmsCtx != nil {
161 t.Fatalf("expected nil but got a KmsContext")
162 }
163 noerr(t, ctx.FinishKmsContexts())
164 }
165
166 func TestMongoCrypt(t *testing.T) {
167 t.Run("encrypt", func(t *testing.T) {
168 t.Run("remote schema", func(t *testing.T) {
169 crypt := createMongoCrypt(t)
170 defer crypt.Close()
171
172
173 cmdDoc := resourceToDocument(t, "command.json")
174 encryptCtx, err := crypt.CreateEncryptionContext("test", cmdDoc)
175 noerr(t, err)
176 defer encryptCtx.Close()
177 compareStates(t, NeedMongoCollInfo, encryptCtx.State())
178
179
180 listCollFilter, err := encryptCtx.NextOperation()
181 noerr(t, err)
182 compareResources(t, resourceToDocument(t, "list-collections-filter.json"), listCollFilter)
183
184
185 noerr(t, encryptCtx.AddOperationResult(resourceToDocument(t, "collection-info.json")))
186 noerr(t, encryptCtx.CompleteOperation())
187 compareStates(t, NeedMongoMarkings, encryptCtx.State())
188
189
190 mongocryptdCmd, err := encryptCtx.NextOperation()
191 noerr(t, err)
192 compareResources(t, resourceToDocument(t, "mongocryptd-command-remote.json"), mongocryptdCmd)
193
194
195 noerr(t, encryptCtx.AddOperationResult(resourceToDocument(t, "mongocryptd-reply.json")))
196 noerr(t, encryptCtx.CompleteOperation())
197 compareStates(t, NeedMongoKeys, encryptCtx.State())
198
199
200 testKmsCtx(t, encryptCtx, false)
201 compareStates(t, Ready, encryptCtx.State())
202
203
204 encryptedDoc, err := encryptCtx.Finish()
205 noerr(t, err)
206 compareResources(t, resourceToDocument(t, "encrypted-command.json"), encryptedDoc)
207 })
208 t.Run("local schema", func(t *testing.T) {
209
210 collInfo := resourceToDocument(t, "collection-info.json")
211 schema := collInfo.Lookup("options", "validator", "$jsonSchema").Document()
212 schemaMap := map[string]bsoncore.Document{
213 "test.test": schema,
214 }
215 kmsProviders := bsoncore.NewDocumentBuilder().
216 StartDocument("aws").
217 AppendString("accessKeyId", "example").
218 AppendString("secretAccessKey", "example").
219 FinishDocument().
220 Build()
221 cryptOpts := options.MongoCrypt().SetKmsProviders(kmsProviders).SetLocalSchemaMap(schemaMap)
222 crypt, err := NewMongoCrypt(cryptOpts)
223 noerr(t, err)
224 defer crypt.Close()
225
226
227 encryptCtx, err := crypt.CreateEncryptionContext("test", resourceToDocument(t, "command.json"))
228 noerr(t, err)
229 defer encryptCtx.Close()
230 compareStates(t, NeedMongoMarkings, encryptCtx.State())
231
232
233 mongocryptdCmd, err := encryptCtx.NextOperation()
234 noerr(t, err)
235 compareResources(t, resourceToDocument(t, "mongocryptd-command-local.json"), mongocryptdCmd)
236
237
238 noerr(t, encryptCtx.AddOperationResult(resourceToDocument(t, "mongocryptd-reply.json")))
239 noerr(t, encryptCtx.CompleteOperation())
240 compareStates(t, NeedMongoKeys, encryptCtx.State())
241
242
243 testKmsCtx(t, encryptCtx, false)
244 compareStates(t, Ready, encryptCtx.State())
245
246
247 encryptedDoc, err := encryptCtx.Finish()
248 noerr(t, err)
249 compareResources(t, resourceToDocument(t, "encrypted-command.json"), encryptedDoc)
250 })
251 t.Run("invalid bson", func(t *testing.T) {
252 crypt := createMongoCrypt(t)
253 defer crypt.Close()
254
255 _, err := crypt.CreateEncryptionContext("test", []byte{0x1, 0x2, 0x3})
256 if err == nil {
257 t.Fatalf("expected error creating encryption context for invalid BSON but got nil")
258 }
259 if _, ok := err.(Error); !ok {
260 t.Fatalf("error type mismatch; expected Error, got %v", err)
261 }
262 })
263 })
264 t.Run("decrypt", func(t *testing.T) {
265 crypt := createMongoCrypt(t)
266 defer crypt.Close()
267
268
269 decryptCtx, err := crypt.CreateDecryptionContext(resourceToDocument(t, "encrypted-command-reply.json"))
270 noerr(t, err)
271 defer decryptCtx.Close()
272 compareStates(t, NeedMongoKeys, decryptCtx.State())
273
274
275 testKmsCtx(t, decryptCtx, false)
276 compareStates(t, Ready, decryptCtx.State())
277
278
279 decryptedDoc, err := decryptCtx.Finish()
280 noerr(t, err)
281 compareResources(t, resourceToDocument(t, "command-reply.json"), decryptedDoc)
282 })
283 t.Run("data key creation", func(t *testing.T) {
284 crypt := createMongoCrypt(t)
285 defer crypt.Close()
286
287
288 var midx int32
289 var masterKey bsoncore.Document
290 midx, masterKey = bsoncore.AppendDocumentStart(nil)
291 masterKey, _ = bsoncore.AppendDocumentEnd(masterKey, midx)
292
293
294 dataKeyOpts := options.DataKey().SetMasterKey(masterKey)
295 dataKeyCtx, err := crypt.CreateDataKeyContext("local", dataKeyOpts)
296 noerr(t, err)
297 defer dataKeyCtx.Close()
298 compareStates(t, Ready, dataKeyCtx.State())
299
300
301 dataKeyDoc, err := dataKeyCtx.Finish()
302 noerr(t, err)
303 if len(dataKeyDoc) == 0 {
304 t.Fatalf("expected data key document but got empty doc")
305 }
306 compareStates(t, Done, dataKeyCtx.State())
307 })
308 t.Run("explicit roundtrip", func(t *testing.T) {
309
310 algorithm := "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
311
312 idx, originalDoc := bsoncore.AppendDocumentStart(nil)
313 originalDoc = bsoncore.AppendStringElement(originalDoc, "v", "hello")
314 originalDoc, _ = bsoncore.AppendDocumentEnd(originalDoc, idx)
315
316 t.Run("no keyAltName", func(t *testing.T) {
317 crypt := createMongoCrypt(t)
318 defer crypt.Close()
319
320
321 keyID := primitive.Binary{
322 Subtype: 0x04,
323 Data: []byte("aaaaaaaaaaaaaaaa"),
324 }
325 opts := options.ExplicitEncryption().SetKeyID(keyID).SetAlgorithm(algorithm)
326 encryptCtx, err := crypt.CreateExplicitEncryptionContext(originalDoc, opts)
327 noerr(t, err)
328 defer encryptCtx.Close()
329 compareStates(t, NeedMongoKeys, encryptCtx.State())
330
331
332 testKmsCtx(t, encryptCtx, false)
333 compareStates(t, Ready, encryptCtx.State())
334
335
336 encryptedDoc, err := encryptCtx.Finish()
337 noerr(t, err)
338 compareStates(t, Done, encryptCtx.State())
339 compareResources(t, resourceToDocument(t, "encrypted-value.json"), encryptedDoc)
340
341
342 decryptCtx, err := crypt.CreateDecryptionContext(encryptedDoc)
343 noerr(t, err)
344 defer decryptCtx.Close()
345 compareStates(t, Ready, decryptCtx.State())
346
347
348 decryptedDoc, err := decryptCtx.Finish()
349 noerr(t, err)
350 compareStates(t, Done, decryptCtx.State())
351 compareResources(t, originalDoc, decryptedDoc)
352 })
353 t.Run("keyAltName", func(t *testing.T) {
354 crypt := createMongoCrypt(t)
355 defer crypt.Close()
356
357
358 opts := options.ExplicitEncryption().SetKeyAltName("altKeyName").SetAlgorithm(algorithm)
359 encryptCtx, err := crypt.CreateExplicitEncryptionContext(originalDoc, opts)
360 noerr(t, err)
361 defer encryptCtx.Close()
362 compareStates(t, NeedMongoKeys, encryptCtx.State())
363
364
365 testKmsCtx(t, encryptCtx, true)
366 compareStates(t, Ready, encryptCtx.State())
367
368
369 encryptedDoc, err := encryptCtx.Finish()
370 noerr(t, err)
371 compareStates(t, Done, encryptCtx.State())
372 compareResources(t, resourceToDocument(t, "encrypted-value.json"), encryptedDoc)
373
374
375
376
377 decryptCtx, err := crypt.CreateExplicitDecryptionContext(encryptedDoc)
378 noerr(t, err)
379 defer decryptCtx.Close()
380 compareStates(t, Ready, decryptCtx.State())
381
382
383 decryptedDoc, err := decryptCtx.Finish()
384 noerr(t, err)
385 compareStates(t, Done, decryptCtx.State())
386 compareResources(t, originalDoc, decryptedDoc)
387 })
388 })
389 }
390
View as plain text