8ed474831976f2de3c7172e9839b1136905f7164
[python-delta-tar] / testing / test_crypto.py
1 import binascii
2 import os
3 import pylibscrypt
4 import struct
5 import unittest
6
7 import deltatar.crypto as crypto
8
9 import cryptography
10
11 def b(s):
12     return s.encode("UTF-8")
13
14 CRYPTO_NACL_SIZE  = 16
15 CRYPTO_KEY_SIZE   = 16
16
17 TEST_PLAINTEXT       = b("gentlemen don’t read each other’s mail")
18 TEST_PASSPHRASE      = b"test1234"
19 TEST_AES_GCM_AAD     = b"authenticated plain text"
20 TEST_DUMMY_FILENAME  = "insurance-file.txt"
21 TEST_VERSION         = 1
22 TEST_PARAMVERSION    = 1
23 TEST_STATIC_NACL     = os.urandom (CRYPTO_NACL_SIZE)
24
25 def faux_hdr (ctsize=1337, iv=None):
26     return \
27         {      "version" : 42
28         , "paramversion" : 2187
29         ,         "nacl" : binascii.unhexlify(b"0011223344556677"
30                                               b"8899aabbccddeeff")
31         ,           "iv" : iv or binascii.unhexlify(b"0011223344556677"
32                                                     b"8899aabb")
33         ,       "ctsize" : ctsize
34         ,          "tag" : binascii.unhexlify(b"deadbeefbadb100d"
35                                               b"b1eedc0ffeedea15")
36         }
37
38 FILL_MOD_MEMO = { }
39
40 def fill_mod (n, off=0):
41     global FILL_MOD_MEMO
42     k = (n, off)
43     m = FILL_MOD_MEMO.get (k, None)
44     if m is not None:
45         return m
46     buf = bytearray (n)
47     bufv = memoryview (buf)
48     for i in range (n):
49         off += 1
50         c = off % 64 + 32
51         struct.pack_into ("c", bufv, i, chr(c).encode("UTF-8"))
52     m = bytes (buf)
53     FILL_MOD_MEMO [k] = m
54     return m
55
56
57 def faux_payload ():
58     return "abcd" * 42
59
60
61 class CryptoLayerTest (unittest.TestCase):
62     pass
63
64
65 class AESGCMTest (CryptoLayerTest):
66
67     os_urandom = os.urandom
68
69     def tearDown (self):
70         """Reset globals altered for testing."""
71         _ = crypto._testing_set_AES_GCM_IV_CNT_MAX \
72                   ("I am fully aware that this will void my warranty.")
73         _ = crypto._testing_set_PDTCRYPT_MAX_OBJ_SIZE \
74                   ("I am fully aware that this will void my warranty.")
75         os.urandom = self.os_urandom
76
77     def test_crypto_aes_gcm_enc_ctor (self):
78         password   = str (os.urandom (42))
79         encryptor  = crypto.Encrypt (TEST_VERSION,
80                                      TEST_PARAMVERSION,
81                                      password=password,
82                                      nacl=TEST_STATIC_NACL)
83
84
85     def test_crypto_aes_gcm_enc_ctor_key (self):
86         key        = os.urandom (42)
87         encryptor  = crypto.Encrypt (TEST_VERSION,
88                                      TEST_PARAMVERSION,
89                                      key=key,
90                                      nacl=TEST_STATIC_NACL)
91
92
93     def test_crypto_aes_gcm_enc_ctor_no_key_pw (self):
94         """
95         Either key (+nacl) or password must be supplied, not both.
96         """
97         try:
98             encryptor = crypto.Encrypt (TEST_VERSION,
99                                         TEST_PARAMVERSION,
100                                         nacl=TEST_STATIC_NACL)
101         except crypto.InvalidParameter: # neither key nor pw
102             pass
103
104         password = str (os.urandom (42))
105         key      =      os.urandom (16)  # scrypt sized
106         try:
107             encryptor = crypto.Encrypt (TEST_VERSION,
108                                         TEST_PARAMVERSION,
109                                         password=password,
110                                         key=key,
111                                         nacl=TEST_STATIC_NACL)
112         except crypto.InvalidParameter: # both key and pw
113             pass
114
115         try:
116             encryptor = crypto.Encrypt (TEST_VERSION,
117                                         TEST_PARAMVERSION,
118                                         key=key,
119                                         nacl=None)
120         except crypto.InvalidParameter: # key, but salt missing
121             pass
122
123         try:
124             encryptor = crypto.Encrypt (TEST_VERSION,
125                                         TEST_PARAMVERSION,
126                                         password=b"",
127                                         nacl=TEST_STATIC_NACL)
128         except crypto.InvalidParameter: # empty pw
129             pass
130
131
132     def test_crypto_aes_gcm_enc_header_size (self):
133         password       = str (os.urandom (42))
134         encryptor      = crypto.Encrypt (TEST_VERSION,
135                                          TEST_PARAMVERSION,
136                                          password=password,
137                                          nacl=TEST_STATIC_NACL)
138
139         header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
140         assert len (header_dummy) == crypto.PDTCRYPT_HDR_SIZE
141         _, _           = encryptor.process (TEST_PLAINTEXT)
142         _, header, _   = encryptor.done (header_dummy)
143         assert len (header) == crypto.PDTCRYPT_HDR_SIZE
144
145
146     def test_crypto_aes_gcm_enc_chunk_size (self):
147         password       = str (os.urandom (42))
148         encryptor      = crypto.Encrypt (TEST_VERSION,
149                                          TEST_PARAMVERSION,
150                                          password=password,
151                                          nacl=TEST_STATIC_NACL)
152
153         header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
154         _, ciphertext  = encryptor.process (TEST_PLAINTEXT)
155         assert len (ciphertext) == len (TEST_PLAINTEXT)
156         rest, header, fixed = encryptor.done (header_dummy)
157         assert len (rest) == 0
158
159
160     def test_crypto_aes_gcm_dec_ctor (self):
161         """
162         Ensure that only either key or password is accepted.
163         """
164         password = str (os.urandom (42))
165         key      =      os.urandom (16)  # scrypt sized
166
167         decryptor = crypto.Decrypt (password=password)
168         decryptor = crypto.Decrypt (key=key)
169
170         try:
171             decryptor = crypto.Decrypt (password=password, key=key)
172         except crypto.InvalidParameter: # both password and key
173             pass
174
175         try:
176             decryptor = crypto.Decrypt (password=None, key=None)
177         except crypto.InvalidParameter: # neither password nor key
178             pass
179
180         try:
181             decryptor = crypto.Decrypt (password="")
182         except crypto.InvalidParameter: # empty password
183             pass
184
185
186     def test_crypto_aes_gcm_dec_simple (self):
187         password       = str (os.urandom (42))
188         encryptor      = crypto.Encrypt (TEST_VERSION,
189                                          TEST_PARAMVERSION,
190                                          password=password,
191                                          nacl=TEST_STATIC_NACL)
192
193         header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
194         _, ciphertext  = encryptor.process (TEST_PLAINTEXT)
195         rest, header, fixed = encryptor.done (header_dummy)
196         ciphertext    += rest
197
198         decryptor      = crypto.Decrypt (password=password, fixedparts=fixed)
199         decryptor.next (header)
200         plaintext      = decryptor.process (ciphertext)
201         rest           = decryptor.done ()
202         plaintext     += rest
203
204         assert plaintext == TEST_PLAINTEXT
205
206
207     def test_crypto_aes_gcm_dec_bad_tag (self):
208         password       = str (os.urandom (42))
209         encryptor      = crypto.Encrypt (TEST_VERSION,
210                                          TEST_PARAMVERSION,
211                                          password=password,
212                                          nacl=TEST_STATIC_NACL)
213
214         header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
215         _, ciphertext  = encryptor.process (TEST_PLAINTEXT)
216         ciphertext2, header, fixed = encryptor.done (header_dummy)
217
218         mut_header     = bytearray (header)
219         mut_header_vw  = memoryview (mut_header)
220         # replace one byte in the tag part of the header
221         second_byte    = mut_header_vw [crypto.HDR_OFF_TAG + 2]
222         mut_header_vw [crypto.HDR_OFF_TAG + 2] = (second_byte + 0x2a) % 256
223         header         = bytes (mut_header)
224
225         decryptor      = crypto.Decrypt (password=password, fixedparts=fixed)
226         decryptor.next (header)
227         plaintext      = decryptor.process (ciphertext)
228         try:
229             _ = decryptor.done ()
230         except crypto.InvalidGCMTag:
231             pass
232
233
234     def test_crypto_aes_gcm_enc_multicnk (self):
235         cnksiz = 1 << 10
236         pt    = fill_mod (1 << 14)
237         password       = str (os.urandom (42))
238         encryptor      = crypto.Encrypt (TEST_VERSION,
239                                          TEST_PARAMVERSION,
240                                          password=password,
241                                          nacl=TEST_STATIC_NACL)
242         header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
243
244         off = 0
245         ct = b""
246         while off < len (pt):
247             upto = min (off + cnksiz, len (pt))
248             _, cnk = encryptor.process (pt [off:upto])
249             ct += cnk
250             off += cnksiz
251         cnk, header, fixed = encryptor.done (header_dummy)
252         ct += cnk
253
254         assert len (pt) == len (ct)
255
256
257     def test_crypto_aes_gcm_enc_multicnk_strict_ivs (self):
258         cnksiz = 1 << 10
259         pt    = fill_mod (1 << 14)
260         password       = str (os.urandom (42))
261         encryptor      = crypto.Encrypt (TEST_VERSION,
262                                          TEST_PARAMVERSION,
263                                          password=password,
264                                          nacl=TEST_STATIC_NACL,
265                                          strict_ivs=True)
266         header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
267
268         off = 0
269         ct = b""
270         while off < len (pt):
271             upto = min (off + cnksiz, len (pt))
272             _, cnk = encryptor.process (pt [off:upto])
273             ct += cnk
274             off += cnksiz
275         cnk, header, fixed = encryptor.done (header_dummy)
276         ct += cnk
277
278         assert len (pt) == len (ct)
279
280
281     def test_crypto_aes_gcm_enc_multiobj (self):
282         cnksiz    = 1 << 10
283         password  = str (os.urandom (42))
284         encryptor = crypto.Encrypt (TEST_VERSION,
285                                     TEST_PARAMVERSION,
286                                     password=password,
287                                     nacl=TEST_STATIC_NACL,
288                                     strict_ivs=False)
289
290         def addobj (i):
291             pt           = fill_mod (1 << 14, off=i)
292             header_dummy = encryptor.next ("%s_%d" % (TEST_DUMMY_FILENAME, i))
293
294             off = 0
295             ct = b""
296             while off < len (pt):
297                 upto = min (off + cnksiz, len (pt))
298                 _, cnk = encryptor.process (pt [off:upto])
299                 ct += cnk
300                 off += cnksiz
301             cnk, header, fixed = encryptor.done (header_dummy)
302             ct += cnk
303
304             assert len (pt) == len (ct)
305
306         for i in range (5): addobj (i)
307
308         assert len (encryptor.fixed) == 1
309
310
311     def test_crypto_aes_gcm_enc_multiobj_strict_ivs (self):
312         cnksiz    = 1 << 10
313         password  = str (os.urandom (42))
314         encryptor = crypto.Encrypt (TEST_VERSION,
315                                     TEST_PARAMVERSION,
316                                     password=password,
317                                     nacl=TEST_STATIC_NACL,
318                                     strict_ivs=True)
319         curfixed  = None # must remain constant after first
320
321         def addobj (i):
322             pt           = fill_mod (1 << 14, off=i)
323             header_dummy = encryptor.next ("%s_%d" % (TEST_DUMMY_FILENAME, i))
324
325             off = 0
326             ct = b""
327             while off < len (pt):
328                 upto = min (off + cnksiz, len (pt))
329                 _, cnk = encryptor.process (pt [off:upto])
330                 ct += cnk
331                 off += cnksiz
332             cnk, header, fixed = encryptor.done (header_dummy)
333             nonlocal curfixed
334             if curfixed is None:
335                 curfixed = fixed
336             else:
337                 assert fixed == curfixed
338             ct += cnk
339
340             assert len (pt) == len (ct)
341
342         for i in range (5): addobj (i)
343
344         assert len (encryptor.fixed) == 1
345
346
347     def test_crypto_aes_gcm_enc_multiobj_cnt_wrap (self):
348         """
349         Test behavior when the file counter tops out.
350
351         Artificially lower the maximum possible file counter. Considering
352         invalid (0) and reserved (1, 2) values, the smallest possible file counter
353         for normal objects is 3. Starting from that, the header of the (max -
354         3)rd object must have both a different IV fixed part and a counter.
355         """
356         minimum = 3
357         new_max = 8
358         crypto._testing_set_AES_GCM_IV_CNT_MAX \
359                 ("I am fully aware that this will void my warranty.", new_max)
360         cnksiz    = 1 << 10
361         password  = str (os.urandom (42))
362         encryptor = crypto.Encrypt (TEST_VERSION,
363                                     TEST_PARAMVERSION,
364                                     password=password,
365                                     nacl=TEST_STATIC_NACL,
366                                     strict_ivs=True)
367
368         last_iv  = None
369         last_cnt = minimum
370
371         def addobj (i, wrap=False):
372             nonlocal last_iv
373             nonlocal last_cnt
374             pt           = fill_mod (1 << 14, off=i)
375             header_dummy = encryptor.next ("%s_%d" % (TEST_DUMMY_FILENAME, i))
376
377             off = 0
378             ct = b""
379             while off < len (pt):
380                 upto = min (off + cnksiz, len (pt))
381                 _, cnk = encryptor.process (pt [off:upto])
382                 ct += cnk
383                 off += cnksiz
384             cnk, header, fixed = encryptor.done (header_dummy)
385             this_iv = crypto.hdr_read (header) ["iv"]
386             if last_iv is not None:
387                 this_fixed, this_cnt = struct.unpack (crypto.FMT_I2N_IV, this_iv)
388                 last_fixed, last_cnt = struct.unpack (crypto.FMT_I2N_IV, last_iv)
389                 if wrap is False:
390                     assert last_fixed == this_fixed
391                     assert last_cnt   == this_cnt - 1
392                 else:
393                     assert last_fixed != this_fixed
394                     assert this_cnt   == minimum
395             last_iv = this_iv
396             ct += cnk
397
398             assert len (pt) == len (ct)
399
400         for i in range (minimum, new_max + 1): addobj (i) # counter range: [3, 8]
401         addobj (i + 1, True) # counter wraps to 3
402
403         for j in range (i + 2, i + new_max - 1): addobj (j) # counter range: [4, 8]
404         addobj (j + 1, True) # counter wraps to 3 again
405
406         assert len (encryptor.fixed) == 3
407
408
409     def test_crypto_aes_gcm_enc_multiobj_cnt_wrap_badfixed (self):
410         """
411         Test behavior when the file counter tops out and the transition to
412         the next IV fixed part fails on account of a bad random generator.
413
414         Replaces the ``urandom`` reference in ``os`` with a deterministic
415         function. The encryptor context must communicate this condition with an
416         ``IVFixedPartError``.
417         """
418         minimum = 3
419         new_max = 8
420         crypto._testing_set_AES_GCM_IV_CNT_MAX \
421                 ("I am fully aware that this will void my warranty.", new_max)
422         cnksiz    = 1 << 10
423         os.urandom = lambda n: bytes (bytearray ([n % 256] * n))
424         password  = str (os.urandom (42))
425         encryptor = crypto.Encrypt (TEST_VERSION,
426                                     TEST_PARAMVERSION,
427                                     password=password,
428                                     nacl=TEST_STATIC_NACL,
429                                     strict_ivs=True)
430
431         def addobj (i):
432             pt = fill_mod (1 << 14, off=i)
433             header_dummy = encryptor.next ("%s_%d" % (TEST_DUMMY_FILENAME, i))
434
435             off = 0
436             while off < len (pt):
437                 upto = min (off + cnksiz, len (pt))
438                 _, cnk = encryptor.process (pt [off:upto])
439                 off += cnksiz
440
441         for i in range (minimum, new_max): addobj (42 + i)
442
443         with self.assertRaises (crypto.IVFixedPartError):
444             addobj (42 + i)
445
446
447
448     def test_crypto_aes_gcm_enc_length_cap (self):
449         """
450         Artificially lower the maximum allowable data length and attempt to
451         encrypt a larger object. Verify that the crypto handler aborts with and
452         exception.
453         """
454         new_max = 2187
455         crypto._testing_set_PDTCRYPT_MAX_OBJ_SIZE \
456                 ("I am fully aware that this will void my warranty.", new_max)
457         cnksiz    = 1 << 10
458         password  = str (os.urandom (42))
459         encryptor = crypto.Encrypt (TEST_VERSION,
460                                     TEST_PARAMVERSION,
461                                     password=password,
462                                     nacl=TEST_STATIC_NACL)
463
464         def encobj (s):
465             pt, ct       = fill_mod (s), None
466             header_dummy = encryptor.next ("%s_%d" % (TEST_DUMMY_FILENAME, s))
467
468             n, ct = encryptor.process (pt)
469             rest, _, _ = encryptor.done (header_dummy)
470             ct += rest
471
472             if len (pt) > new_max:
473                 assert n < len (pt)
474             else:
475                 assert n == len (pt) == len (ct)
476
477         for i in range (16): encobj (1 << i)
478
479
480     def test_crypto_aes_gcm_dec_multicnk (self):
481         cnksiz         = 1 << 10
482         orig_pt        = fill_mod (1 << 14)
483         password       = str (os.urandom (42))
484         encryptor      = crypto.Encrypt (TEST_VERSION,
485                                          TEST_PARAMVERSION,
486                                          password=password,
487                                          nacl=TEST_STATIC_NACL)
488         header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
489
490         off = 0
491         ct = b""
492         while off < len (orig_pt):
493             upto = min (off + cnksiz, len (orig_pt))
494             _n, cnk = encryptor.process (orig_pt [off:upto])
495             ct += cnk
496             off += cnksiz
497         cnk, header, fixed = encryptor.done (header_dummy)
498         ct += cnk
499
500         decryptor      = crypto.Decrypt (password=password,
501                                          fixedparts=fixed)
502         decryptor.next (header)
503         off = 0
504         pt  = b""
505         while off < len (orig_pt):
506             upto = min (off + cnksiz, len (orig_pt))
507             cnk  = decryptor.process (ct [off:upto])
508             pt += cnk
509             off += cnksiz
510
511
512         pt += decryptor.done ()
513         assert pt == orig_pt
514
515
516     def test_crypto_aes_gcm_dec_multicnk_bad_tag (self):
517         cnksiz         = 1 << 10
518         orig_pt        = fill_mod (1 << 14)
519         password       = str (os.urandom (42))
520         encryptor      = crypto.Encrypt (TEST_VERSION,
521                                          TEST_PARAMVERSION,
522                                          password=password,
523                                          nacl=TEST_STATIC_NACL)
524         header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
525
526         off = 0
527         ct = b""
528         while off < len (orig_pt):
529             upto = min (off + cnksiz, len (orig_pt))
530             _n, cnk = encryptor.process (orig_pt [off:upto])
531             ct += cnk
532             off += cnksiz
533         cnk, header, fixed = encryptor.done (header_dummy)
534         ct += cnk
535
536         mut_header     = bytearray (header)
537         mut_header_vw  = memoryview (mut_header)
538         # replace one byte in the tag part of the header
539         second_byte    = mut_header_vw [crypto.HDR_OFF_TAG + 2]
540         mut_header_vw [crypto.HDR_OFF_TAG + 2] = (second_byte + 0x2a) % 256
541         header         = bytes (mut_header)
542
543         decryptor      = crypto.Decrypt (password=password,
544                                          fixedparts=fixed)
545         decryptor.next (header)
546         off = 0
547         pt  = b""
548         while off < len (orig_pt):
549             upto = min (off + cnksiz, len (orig_pt))
550             cnk = decryptor.process (ct [off:upto])
551             pt += cnk
552             off += cnksiz
553
554         try:
555             _ = decryptor.done ()
556         except crypto.InvalidGCMTag:
557             pass
558
559
560     def test_crypto_aes_gcm_dec_iv_reuse (self):
561         """
562         Meddle with encrypted content: extract the IV from one object
563         and inject it into the header of another. This must be rejected
564         by the decryptor.
565         """
566         cnksiz         = 1 << 10
567         orig_pt_1      = fill_mod (1 << 10)
568         orig_pt_2      = fill_mod (1 << 10, 42)
569         password       = str (os.urandom (42))
570         encryptor      = crypto.Encrypt (TEST_VERSION,
571                                          TEST_PARAMVERSION,
572                                          password=password,
573                                          nacl=TEST_STATIC_NACL)
574
575         def enc (pt):
576             header_dummy   = encryptor.next (TEST_DUMMY_FILENAME)
577
578             off = 0
579             ct = b""
580             while off < len (pt):
581                 upto = min (off + cnksiz, len (pt))
582                 _n, cnk = encryptor.process (pt [off:upto])
583                 ct += cnk
584                 off += cnksiz
585             cnk, header, fixed = encryptor.done (header_dummy)
586             return ct + cnk, header, fixed
587
588         ct_1, hdr_1, _____ = enc (orig_pt_1)
589         ct_2, hdr_2, fixed = enc (orig_pt_2)
590
591         mut_hdr_2    = bytearray (hdr_2)
592         mut_hdr_2_vw = memoryview (mut_hdr_2)
593         # get IV
594         iv_lo        = crypto.HDR_OFF_IV
595         iv_hi        = crypto.HDR_OFF_IV + crypto.PDTCRYPT_HDR_SIZE_IV
596         iv_1         = hdr_1 [iv_lo : iv_hi]
597         # transplant into other header
598         mut_hdr_2_vw [iv_lo : iv_hi] = iv_1
599         hdr_2_mod    = bytes (mut_hdr_2)
600         decryptor    = crypto.Decrypt (password=password, fixedparts=fixed,
601                                        strict_ivs=True)
602
603         def dec (hdr, ct):
604             decryptor.next (hdr)
605             off = 0
606             pt  = b""
607             while off < len (ct):
608                 upto = min (off + cnksiz, len (ct))
609                 cnk = decryptor.process (ct [off:upto])
610                 pt += cnk
611                 off += cnksiz
612             return pt + decryptor.done ()
613
614         decr_pt_1 = dec (hdr_1, ct_1)
615         decr_pt_2 = dec (hdr_2, ct_2) # good header, different IV
616         try:
617             decr_pt_2 = dec (hdr_2_mod, ct_2)
618         except crypto.DuplicateIV: # bad header, reuse detected
619             pass
620
621
622 class HeaderTest (CryptoLayerTest):
623
624     def test_crypto_fmt_hdr_make (self):
625         meta = faux_hdr()
626         ok, hdr = crypto.hdr_make (meta)
627         assert ok
628         assert len (hdr) == crypto.PDTCRYPT_HDR_SIZE
629
630
631     def test_crypto_fmt_hdr_make_useless (self):
632         ok, ret = crypto.hdr_make ({ 42: "x" })
633         assert ok is False
634         assert ret.startswith ("error assembling header:")
635
636
637     def test_crypto_fmt_hdr_read (self):
638         meta = faux_hdr()
639         ok, hdr = crypto.hdr_make (meta)
640         assert ok is True
641         assert hdr is not None
642         mmeta = crypto.hdr_read (hdr)
643         assert mmeta is not None
644         for k in meta:
645             if meta [k] != mmeta [k]:
646                 raise "header mismatch after reading: expected %r, got %r" \
647                       % (meta [k], mmeta [k])
648
649
650     def test_crypto_fmt_hdr_read_trailing_garbage (self):
651         meta = faux_hdr()
652         ok, hdr = crypto.hdr_make (meta)
653         ok, hdr = crypto.hdr_make (meta)
654         assert ok is True
655         assert hdr is not None
656         hdr += b"-junk"
657         try:
658             _ = crypto.hdr_read (hdr)
659         except crypto.InvalidHeader:
660             pass
661
662
663     def test_crypto_fmt_hdr_read_leading_garbage (self):
664         meta = faux_hdr()
665         ok, hdr = crypto.hdr_make (meta)
666         ok, hdr = crypto.hdr_make (meta)
667         assert ok is True
668         assert hdr is not None
669         hdr = b"junk-" + hdr
670         try:
671             _ = crypto.hdr_read (hdr)
672         except crypto.InvalidHeader:
673             pass
674
675
676     def test_crypto_fmt_hdr_inner_garbage (self):
677         meta = faux_hdr()
678         ok, hdr = crypto.hdr_make (meta)
679         assert ok
680         data = hdr[:len(hdr)//2] + b"junk-" + hdr[len(hdr)//2:]
681         try:
682             _ = crypto.hdr_read (data)
683         except crypto.InvalidHeader:
684             pass
685