1
2
3
4
5 package cryptotest
6
7 import (
8 "bytes"
9 "crypto/cipher"
10 "fmt"
11 "testing"
12 )
13
14 var lengths = []int{0, 156, 8192, 8193, 8208}
15
16
17
18
19
20 type MakeAEAD func() (cipher.AEAD, error)
21
22
23
24 func TestAEAD(t *testing.T, mAEAD MakeAEAD) {
25 aead, err := mAEAD()
26 if err != nil {
27 t.Fatal(err)
28 }
29
30 t.Run("Roundtrip", func(t *testing.T) {
31
32
33 for _, ptLen := range lengths {
34 for _, adLen := range lengths {
35 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
36 rng := newRandReader(t)
37
38 nonce := make([]byte, aead.NonceSize())
39 rng.Read(nonce)
40
41 before, addData := make([]byte, adLen), make([]byte, ptLen)
42 rng.Read(before)
43 rng.Read(addData)
44
45 ciphertext := sealMsg(t, aead, nil, nonce, before, addData)
46 after := openWithoutError(t, aead, nil, nonce, ciphertext, addData)
47
48 if !bytes.Equal(after, before) {
49 t.Errorf("plaintext is different after a seal/open cycle; got %s, want %s", truncateHex(after), truncateHex(before))
50 }
51 })
52 }
53 }
54 })
55
56 t.Run("OutOfBounds", func(t *testing.T) {
57
58
59 MustMinimumFIPS140ModuleVersion(t, "v1.28.0")
60
61
62
63 var boundaryLengths = []int{
64 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
65 16, 17, 18, 127, 128, 129, 130, 131, 156, 8193,
66 }
67 for _, ptLen := range boundaryLengths {
68 t.Run(fmt.Sprintf("Plaintext-Length=%d", ptLen), func(t *testing.T) {
69 rng := newRandReader(t)
70
71 nonce := make([]byte, aead.NonceSize())
72 rng.Read(nonce)
73
74 plaintext, addData := make([]byte, ptLen), make([]byte, 16)
75 rng.Read(plaintext)
76 rng.Read(addData)
77
78
79
80
81
82 for _, boundary := range []string{"Start", "End"} {
83 t.Run("Boundary="+boundary, func(t *testing.T) {
84 guarded := func(b []byte) []byte {
85 start, end := BoundarySlices(t, len(b))
86 if boundary == "Start" {
87 copy(start, b)
88 return start
89 }
90 copy(end, b)
91 return end
92 }
93
94 src, ad := guarded(plaintext), guarded(addData)
95 dst := guarded(make([]byte, ptLen+aead.Overhead()))
96
97 ciphertext := sealMsg(t, aead, dst[:0], nonce, src, ad)
98
99 after := openWithoutError(t, aead, guarded(plaintext)[:0], nonce, ciphertext, ad)
100 if !bytes.Equal(after, plaintext) {
101 t.Errorf("plaintext is different after a seal/open cycle; got %s, want %s", truncateHex(after), truncateHex(plaintext))
102 }
103 })
104 }
105 })
106 }
107 })
108
109 t.Run("InputNotModified", func(t *testing.T) {
110
111
112 for _, ptLen := range lengths {
113 for _, adLen := range lengths {
114 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
115 t.Run("Seal", func(t *testing.T) {
116 rng := newRandReader(t)
117
118 nonce := make([]byte, aead.NonceSize())
119 rng.Read(nonce)
120
121 src, before := make([]byte, ptLen), make([]byte, ptLen)
122 rng.Read(src)
123 copy(before, src)
124
125 addData := make([]byte, adLen)
126 rng.Read(addData)
127
128 sealMsg(t, aead, nil, nonce, src, addData)
129 if !bytes.Equal(src, before) {
130 t.Errorf("Seal modified src; got %s, want %s", truncateHex(src), truncateHex(before))
131 }
132 })
133
134 t.Run("Open", func(t *testing.T) {
135 rng := newRandReader(t)
136
137 nonce := make([]byte, aead.NonceSize())
138 rng.Read(nonce)
139
140 plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
141 rng.Read(plaintext)
142 rng.Read(addData)
143
144
145
146 ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
147 before := make([]byte, len(ciphertext))
148 copy(before, ciphertext)
149
150 openWithoutError(t, aead, nil, nonce, ciphertext, addData)
151 if !bytes.Equal(ciphertext, before) {
152 t.Errorf("Open modified src; got %s, want %s", truncateHex(ciphertext), truncateHex(before))
153 }
154 })
155 })
156 }
157 }
158 })
159
160 t.Run("BufferOverlap", func(t *testing.T) {
161
162
163 for _, ptLen := range lengths {
164 if ptLen <= 1 {
165 continue
166 }
167 for _, adLen := range lengths {
168 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
169 t.Run("Seal", func(t *testing.T) {
170 rng := newRandReader(t)
171
172 nonce := make([]byte, aead.NonceSize())
173 rng.Read(nonce)
174
175
176
177 ctLen := ptLen + aead.Overhead()
178 buff := make([]byte, ptLen+ctLen)
179 rng.Read(buff)
180
181 addData := make([]byte, adLen)
182 rng.Read(addData)
183
184
185 plaintext := buff[:ptLen]
186 dst := buff[1:1]
187 mustPanic(t, "invalid buffer overlap", func() { sealMsg(t, aead, dst, nonce, plaintext, addData) })
188
189
190 plaintext = buff[:ptLen]
191 dst = buff[ptLen-1 : ptLen-1]
192 mustPanic(t, "invalid buffer overlap", func() { sealMsg(t, aead, dst, nonce, plaintext, addData) })
193 })
194
195 t.Run("Open", func(t *testing.T) {
196 rng := newRandReader(t)
197
198 nonce := make([]byte, aead.NonceSize())
199 rng.Read(nonce)
200
201
202 plaintext := make([]byte, ptLen)
203 rng.Read(plaintext)
204 addData := make([]byte, adLen)
205 rng.Read(addData)
206 validCT := sealMsg(t, aead, nil, nonce, plaintext, addData)
207
208
209
210 buff := make([]byte, ptLen+len(validCT))
211
212
213 ciphertext := buff[:len(validCT)]
214 copy(ciphertext, validCT)
215 dst := buff[1:1]
216 mustPanic(t, "invalid buffer overlap", func() { aead.Open(dst, nonce, ciphertext, addData) })
217
218
219 ciphertext = buff[:len(validCT)]
220 copy(ciphertext, validCT)
221
222
223
224
225 beforeTag := len(validCT) - aead.Overhead()
226 dst = buff[beforeTag-1 : beforeTag-1]
227 mustPanic(t, "invalid buffer overlap", func() { aead.Open(dst, nonce, ciphertext, addData) })
228 })
229 })
230 }
231 }
232 })
233
234 t.Run("AppendDst", func(t *testing.T) {
235
236
237 for _, ptLen := range lengths {
238 for _, adLen := range lengths {
239 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
240
241 t.Run("Seal", func(t *testing.T) {
242 rng := newRandReader(t)
243
244 nonce := make([]byte, aead.NonceSize())
245 rng.Read(nonce)
246
247 shortBuff := []byte("a")
248 longBuff := make([]byte, 512)
249 rng.Read(longBuff)
250 prefixes := [][]byte{shortBuff, longBuff}
251
252
253 for _, prefix := range prefixes {
254 plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
255 rng.Read(plaintext)
256 rng.Read(addData)
257 out := sealMsg(t, aead, prefix, nonce, plaintext, addData)
258
259
260 if !bytes.Equal(out[:len(prefix)], prefix) {
261 t.Errorf("Seal alters dst instead of appending; got %s, want %s", truncateHex(out[:len(prefix)]), truncateHex(prefix))
262 }
263
264 if isDeterministic(aead) {
265 ciphertext := out[len(prefix):]
266
267 if expectedCT := sealMsg(t, aead, nil, nonce, plaintext, addData); !bytes.Equal(ciphertext, expectedCT) {
268 t.Errorf("Seal behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(ciphertext), truncateHex(expectedCT))
269 }
270 }
271 }
272 })
273
274 t.Run("Open", func(t *testing.T) {
275 rng := newRandReader(t)
276
277 nonce := make([]byte, aead.NonceSize())
278 rng.Read(nonce)
279
280 shortBuff := []byte("a")
281 longBuff := make([]byte, 512)
282 rng.Read(longBuff)
283 prefixes := [][]byte{shortBuff, longBuff}
284
285
286 for _, prefix := range prefixes {
287 before, addData := make([]byte, adLen), make([]byte, ptLen)
288 rng.Read(before)
289 rng.Read(addData)
290 ciphertext := sealMsg(t, aead, nil, nonce, before, addData)
291
292 out := openWithoutError(t, aead, prefix, nonce, ciphertext, addData)
293
294
295 if !bytes.Equal(out[:len(prefix)], prefix) {
296 t.Errorf("Open alters dst instead of appending; got %s, want %s", truncateHex(out[:len(prefix)]), truncateHex(prefix))
297 }
298
299 after := out[len(prefix):]
300
301 if !bytes.Equal(after, before) {
302 t.Errorf("Open behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(after), truncateHex(before))
303 }
304 }
305 })
306 })
307 }
308 }
309 })
310
311 t.Run("WrongNonce", func(t *testing.T) {
312 if aead.NonceSize() == 0 {
313 t.Skip("AEAD does not use a nonce")
314 }
315
316 for _, ptLen := range lengths {
317 for _, adLen := range lengths {
318 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
319 rng := newRandReader(t)
320
321 nonce := make([]byte, aead.NonceSize())
322 rng.Read(nonce)
323
324 plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
325 rng.Read(plaintext)
326 rng.Read(addData)
327
328 ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
329
330
331 alterNonce := make([]byte, aead.NonceSize())
332 copy(alterNonce, nonce)
333 alterNonce[len(alterNonce)-1] += 1
334 _, err := aead.Open(nil, alterNonce, ciphertext, addData)
335
336 if err == nil {
337 t.Errorf("Open did not error when given different nonce than Sealed with")
338 }
339 })
340 }
341 }
342 })
343
344 t.Run("WrongAddData", func(t *testing.T) {
345
346
347 for _, ptLen := range lengths {
348 for _, adLen := range lengths {
349 if adLen == 0 {
350 continue
351 }
352
353 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
354 rng := newRandReader(t)
355
356 nonce := make([]byte, aead.NonceSize())
357 rng.Read(nonce)
358
359 plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
360 rng.Read(plaintext)
361 rng.Read(addData)
362
363 ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
364
365
366 alterAD := make([]byte, adLen)
367 copy(alterAD, addData)
368 alterAD[len(alterAD)-1] += 1
369 _, err := aead.Open(nil, nonce, ciphertext, alterAD)
370
371 if err == nil {
372 t.Errorf("Open did not error when given different Additional Data than Sealed with")
373 }
374 })
375 }
376 }
377 })
378
379 t.Run("WrongCiphertext", func(t *testing.T) {
380
381
382 for _, ptLen := range lengths {
383 for _, adLen := range lengths {
384
385 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
386 rng := newRandReader(t)
387
388 nonce := make([]byte, aead.NonceSize())
389 rng.Read(nonce)
390
391 plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
392 rng.Read(plaintext)
393 rng.Read(addData)
394
395 ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
396
397
398 alterCT := make([]byte, len(ciphertext))
399 copy(alterCT, ciphertext)
400 alterCT[len(alterCT)-1] += 1
401 _, err := aead.Open(nil, nonce, alterCT, addData)
402
403 if err == nil {
404 t.Errorf("Open did not error when given different ciphertext than was produced by Seal")
405 }
406 })
407 }
408 }
409 })
410 }
411
412
413
414 func sealMsg(t *testing.T, aead cipher.AEAD, ciphertext, nonce, plaintext, addData []byte) []byte {
415 t.Helper()
416
417 initialLen := len(ciphertext)
418
419 ciphertext = aead.Seal(ciphertext, nonce, plaintext, addData)
420
421 lenCT := len(ciphertext) - initialLen
422
423
424
425 if lenCT > len(plaintext)+aead.Overhead() {
426 t.Errorf("length of ciphertext from Seal exceeds length of plaintext by more than Overhead(); got %d, want <=%d", lenCT, len(plaintext)+aead.Overhead())
427 }
428
429 return ciphertext
430 }
431
432 func isDeterministic(aead cipher.AEAD) bool {
433
434
435
436 nonce := make([]byte, aead.NonceSize())
437 addData := []byte("additional data")
438 plaintext := []byte("plaintext")
439 ciphertext1 := aead.Seal(nil, nonce, plaintext, addData)
440 ciphertext2 := aead.Seal(nil, nonce, plaintext, addData)
441 return bytes.Equal(ciphertext1, ciphertext2)
442 }
443
444
445
446
447 func openWithoutError(t *testing.T, aead cipher.AEAD, plaintext, nonce, ciphertext, addData []byte) []byte {
448 t.Helper()
449
450 plaintext, err := aead.Open(plaintext, nonce, ciphertext, addData)
451 if err != nil {
452 t.Fatalf("Open returned error on properly formed ciphertext; got \"%s\", want \"nil\"", err)
453 }
454
455 return plaintext
456 }
457
View as plain text