unit test crypto file counter wraparound
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 9 May 2017 08:59:28 +0000 (10:59 +0200)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Mon, 2 Apr 2018 11:34:08 +0000 (13:34 +0200)
After the file counter reaches UINT_MAX, it wraps around and a
new fixed part must be created.

The file counter is 32 bit unsigned integer so it needs to be
lowered to make bounds testing feasible.

deltatar/crypto.py
testing/test_crypto.py

index e33de7a..d01c20d 100755 (executable)
@@ -804,6 +804,20 @@ class Decrypt (Crypto):
 
 
 ###############################################################################
+## testing helpers
+###############################################################################
+
+def _testing_set_AES_GCM_IV_CNT_MAX (vow, n):
+    """
+    Adapt upper file counter bound for testing IV logic. Completely unsafe.
+    """
+    assert vow == "I am fully aware that this will void my warranty."
+    global AES_GCM_IV_CNT_MAX
+    r = AES_GCM_IV_CNT_MAX
+    AES_GCM_IV_CNT_MAX = n
+    return r
+
+###############################################################################
 ## freestanding invocation
 ###############################################################################
 
index 30ea1d5..aad35f7 100644 (file)
@@ -324,6 +324,71 @@ class AESGCMTest (CryptoLayerTest):
         for i in range (5): addobj (i)
 
 
+    def test_crypto_aes_gcm_enc_multiobj_cnt_wrap (self):
+        """
+        Test behavior when the file counter tops out.
+
+        Artificially lower the maximum possible file counter. Considering
+        invalid (0) and reserved (1, 2) values, the least possible file counter
+        for normal objects is 3. Starting from that, the header of the (max -
+        3)rd object must have both a different IV fixed part and a counter.
+        """
+        minimum = 3
+        new_max = 8
+        old_max = crypto._testing_set_AES_GCM_IV_CNT_MAX \
+                        ("I am fully aware that this will void my warranty.",
+                         new_max)
+        cnksiz    = 1 << 10
+        password  = str (os.urandom (42))
+        encryptor = crypto.Encrypt (TEST_VERSION,
+                                    TEST_PARAMVERSION,
+                                    password=password,
+                                    nacl=TEST_STATIC_NACL,
+                                    strict_ivs=True)
+
+        last_iv  = None
+        last_cnt = minimum
+
+        def addobj (i, wrap=False):
+            nonlocal last_iv
+            nonlocal last_cnt
+            pt           = fill_mod (1 << 14, off=i)
+            header_dummy = encryptor.next ("%s_%d" % (TEST_DUMMY_FILENAME, i))
+
+            off = 0
+            ct = b""
+            while off < len (pt):
+                upto = min (off + cnksiz, len (pt))
+                cnk = encryptor.process (pt [off:upto])
+                ct += cnk
+                off += cnksiz
+            cnk, header, fixed = encryptor.done (header_dummy)
+            this_iv = crypto.hdr_read (header) ["iv"]
+            if last_iv is not None:
+                this_fixed, this_cnt = struct.unpack (crypto.FMT_I2N_IV, this_iv)
+                last_fixed, last_cnt = struct.unpack (crypto.FMT_I2N_IV, last_iv)
+                if wrap is False:
+                    assert last_fixed == this_fixed
+                    assert last_cnt   == this_cnt - 1
+                else:
+                    assert last_fixed != this_fixed
+                    assert this_cnt   == minimum
+            last_iv = this_iv
+            ct += cnk
+
+            assert len (pt) == len (ct)
+
+        for i in range (minimum, new_max + 1): addobj (i) # counter range: [3, 8]
+        addobj (i + 1, True) # counter wraps to 3
+
+        for j in range (i + 2, i + new_max - 1): addobj (j) # counter range: [4, 8]
+        addobj (j + 1, True) # counter wraps to 3 again
+
+        _ = crypto._testing_set_AES_GCM_IV_CNT_MAX \
+                  ("I am fully aware that this will void my warranty.",
+                   old_max)
+
+
     def test_crypto_aes_gcm_dec_multicnk (self):
         cnksiz         = 1 << 10
         orig_pt        = fill_mod (1 << 14)