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("InputNotModified", func(t *testing.T) {
57
58
59 for _, ptLen := range lengths {
60 for _, adLen := range lengths {
61 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
62 t.Run("Seal", func(t *testing.T) {
63 rng := newRandReader(t)
64
65 nonce := make([]byte, aead.NonceSize())
66 rng.Read(nonce)
67
68 src, before := make([]byte, ptLen), make([]byte, ptLen)
69 rng.Read(src)
70 copy(before, src)
71
72 addData := make([]byte, adLen)
73 rng.Read(addData)
74
75 sealMsg(t, aead, nil, nonce, src, addData)
76 if !bytes.Equal(src, before) {
77 t.Errorf("Seal modified src; got %s, want %s", truncateHex(src), truncateHex(before))
78 }
79 })
80
81 t.Run("Open", func(t *testing.T) {
82 rng := newRandReader(t)
83
84 nonce := make([]byte, aead.NonceSize())
85 rng.Read(nonce)
86
87 plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
88 rng.Read(plaintext)
89 rng.Read(addData)
90
91
92
93 ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
94 before := make([]byte, len(ciphertext))
95 copy(before, ciphertext)
96
97 openWithoutError(t, aead, nil, nonce, ciphertext, addData)
98 if !bytes.Equal(ciphertext, before) {
99 t.Errorf("Open modified src; got %s, want %s", truncateHex(ciphertext), truncateHex(before))
100 }
101 })
102 })
103 }
104 }
105 })
106
107 t.Run("BufferOverlap", func(t *testing.T) {
108
109
110 for _, ptLen := range lengths {
111 if ptLen <= 1 {
112 continue
113 }
114 for _, adLen := range lengths {
115 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
116 t.Run("Seal", func(t *testing.T) {
117 rng := newRandReader(t)
118
119 nonce := make([]byte, aead.NonceSize())
120 rng.Read(nonce)
121
122
123
124 ctLen := ptLen + aead.Overhead()
125 buff := make([]byte, ptLen+ctLen)
126 rng.Read(buff)
127
128 addData := make([]byte, adLen)
129 rng.Read(addData)
130
131
132 plaintext := buff[:ptLen]
133 dst := buff[1:1]
134 mustPanic(t, "invalid buffer overlap", func() { sealMsg(t, aead, dst, nonce, plaintext, addData) })
135
136
137 plaintext = buff[:ptLen]
138 dst = buff[ptLen-1 : ptLen-1]
139 mustPanic(t, "invalid buffer overlap", func() { sealMsg(t, aead, dst, nonce, plaintext, addData) })
140 })
141
142 t.Run("Open", func(t *testing.T) {
143 rng := newRandReader(t)
144
145 nonce := make([]byte, aead.NonceSize())
146 rng.Read(nonce)
147
148
149 plaintext := make([]byte, ptLen)
150 rng.Read(plaintext)
151 addData := make([]byte, adLen)
152 rng.Read(addData)
153 validCT := sealMsg(t, aead, nil, nonce, plaintext, addData)
154
155
156
157 buff := make([]byte, ptLen+len(validCT))
158
159
160 ciphertext := buff[:len(validCT)]
161 copy(ciphertext, validCT)
162 dst := buff[1:1]
163 mustPanic(t, "invalid buffer overlap", func() { aead.Open(dst, nonce, ciphertext, addData) })
164
165
166 ciphertext = buff[:len(validCT)]
167 copy(ciphertext, validCT)
168
169
170
171
172 beforeTag := len(validCT) - aead.Overhead()
173 dst = buff[beforeTag-1 : beforeTag-1]
174 mustPanic(t, "invalid buffer overlap", func() { aead.Open(dst, nonce, ciphertext, addData) })
175 })
176 })
177 }
178 }
179 })
180
181 t.Run("AppendDst", func(t *testing.T) {
182
183
184 for _, ptLen := range lengths {
185 for _, adLen := range lengths {
186 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
187
188 t.Run("Seal", func(t *testing.T) {
189 rng := newRandReader(t)
190
191 nonce := make([]byte, aead.NonceSize())
192 rng.Read(nonce)
193
194 shortBuff := []byte("a")
195 longBuff := make([]byte, 512)
196 rng.Read(longBuff)
197 prefixes := [][]byte{shortBuff, longBuff}
198
199
200 for _, prefix := range prefixes {
201 plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
202 rng.Read(plaintext)
203 rng.Read(addData)
204 out := sealMsg(t, aead, prefix, nonce, plaintext, addData)
205
206
207 if !bytes.Equal(out[:len(prefix)], prefix) {
208 t.Errorf("Seal alters dst instead of appending; got %s, want %s", truncateHex(out[:len(prefix)]), truncateHex(prefix))
209 }
210
211 if isDeterministic(aead) {
212 ciphertext := out[len(prefix):]
213
214 if expectedCT := sealMsg(t, aead, nil, nonce, plaintext, addData); !bytes.Equal(ciphertext, expectedCT) {
215 t.Errorf("Seal behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(ciphertext), truncateHex(expectedCT))
216 }
217 }
218 }
219 })
220
221 t.Run("Open", func(t *testing.T) {
222 rng := newRandReader(t)
223
224 nonce := make([]byte, aead.NonceSize())
225 rng.Read(nonce)
226
227 shortBuff := []byte("a")
228 longBuff := make([]byte, 512)
229 rng.Read(longBuff)
230 prefixes := [][]byte{shortBuff, longBuff}
231
232
233 for _, prefix := range prefixes {
234 before, addData := make([]byte, adLen), make([]byte, ptLen)
235 rng.Read(before)
236 rng.Read(addData)
237 ciphertext := sealMsg(t, aead, nil, nonce, before, addData)
238
239 out := openWithoutError(t, aead, prefix, nonce, ciphertext, addData)
240
241
242 if !bytes.Equal(out[:len(prefix)], prefix) {
243 t.Errorf("Open alters dst instead of appending; got %s, want %s", truncateHex(out[:len(prefix)]), truncateHex(prefix))
244 }
245
246 after := out[len(prefix):]
247
248 if !bytes.Equal(after, before) {
249 t.Errorf("Open behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(after), truncateHex(before))
250 }
251 }
252 })
253 })
254 }
255 }
256 })
257
258 t.Run("WrongNonce", func(t *testing.T) {
259 if aead.NonceSize() == 0 {
260 t.Skip("AEAD does not use a nonce")
261 }
262
263 for _, ptLen := range lengths {
264 for _, adLen := range lengths {
265 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
266 rng := newRandReader(t)
267
268 nonce := make([]byte, aead.NonceSize())
269 rng.Read(nonce)
270
271 plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
272 rng.Read(plaintext)
273 rng.Read(addData)
274
275 ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
276
277
278 alterNonce := make([]byte, aead.NonceSize())
279 copy(alterNonce, nonce)
280 alterNonce[len(alterNonce)-1] += 1
281 _, err := aead.Open(nil, alterNonce, ciphertext, addData)
282
283 if err == nil {
284 t.Errorf("Open did not error when given different nonce than Sealed with")
285 }
286 })
287 }
288 }
289 })
290
291 t.Run("WrongAddData", func(t *testing.T) {
292
293
294 for _, ptLen := range lengths {
295 for _, adLen := range lengths {
296 if adLen == 0 {
297 continue
298 }
299
300 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
301 rng := newRandReader(t)
302
303 nonce := make([]byte, aead.NonceSize())
304 rng.Read(nonce)
305
306 plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
307 rng.Read(plaintext)
308 rng.Read(addData)
309
310 ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
311
312
313 alterAD := make([]byte, adLen)
314 copy(alterAD, addData)
315 alterAD[len(alterAD)-1] += 1
316 _, err := aead.Open(nil, nonce, ciphertext, alterAD)
317
318 if err == nil {
319 t.Errorf("Open did not error when given different Additional Data than Sealed with")
320 }
321 })
322 }
323 }
324 })
325
326 t.Run("WrongCiphertext", func(t *testing.T) {
327
328
329 for _, ptLen := range lengths {
330 for _, adLen := range lengths {
331
332 t.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", ptLen, adLen), func(t *testing.T) {
333 rng := newRandReader(t)
334
335 nonce := make([]byte, aead.NonceSize())
336 rng.Read(nonce)
337
338 plaintext, addData := make([]byte, ptLen), make([]byte, adLen)
339 rng.Read(plaintext)
340 rng.Read(addData)
341
342 ciphertext := sealMsg(t, aead, nil, nonce, plaintext, addData)
343
344
345 alterCT := make([]byte, len(ciphertext))
346 copy(alterCT, ciphertext)
347 alterCT[len(alterCT)-1] += 1
348 _, err := aead.Open(nil, nonce, alterCT, addData)
349
350 if err == nil {
351 t.Errorf("Open did not error when given different ciphertext than was produced by Seal")
352 }
353 })
354 }
355 }
356 })
357 }
358
359
360
361 func sealMsg(t *testing.T, aead cipher.AEAD, ciphertext, nonce, plaintext, addData []byte) []byte {
362 t.Helper()
363
364 initialLen := len(ciphertext)
365
366 ciphertext = aead.Seal(ciphertext, nonce, plaintext, addData)
367
368 lenCT := len(ciphertext) - initialLen
369
370
371
372 if lenCT > len(plaintext)+aead.Overhead() {
373 t.Errorf("length of ciphertext from Seal exceeds length of plaintext by more than Overhead(); got %d, want <=%d", lenCT, len(plaintext)+aead.Overhead())
374 }
375
376 return ciphertext
377 }
378
379 func isDeterministic(aead cipher.AEAD) bool {
380
381
382
383 nonce := make([]byte, aead.NonceSize())
384 addData := []byte("additional data")
385 plaintext := []byte("plaintext")
386 ciphertext1 := aead.Seal(nil, nonce, plaintext, addData)
387 ciphertext2 := aead.Seal(nil, nonce, plaintext, addData)
388 return bytes.Equal(ciphertext1, ciphertext2)
389 }
390
391
392
393
394 func openWithoutError(t *testing.T, aead cipher.AEAD, plaintext, nonce, ciphertext, addData []byte) []byte {
395 t.Helper()
396
397 plaintext, err := aead.Open(plaintext, nonce, ciphertext, addData)
398 if err != nil {
399 t.Fatalf("Open returned error on properly formed ciphertext; got \"%s\", want \"nil\"", err)
400 }
401
402 return plaintext
403 }
404
View as plain text