enable strict IV checking by default during decryption
[python-delta-tar] / deltatar / crypto.py
index 388e9c7..178a469 100755 (executable)
@@ -919,7 +919,7 @@ class Crypto (object):
     iv   = None # current IV
     fixed        = None  # accu for 64 bit fixed parts of IV
     used_ivs     = None  # tracks IVs
-    strict_ivs   = False # if True, panic on duplicate object IV
+    strict_ivs   = False # if True, panic on duplicate or non-consecutive object IV
     password     = None
     paramversion = None
     insecure     = False # allow plaintext parameters
@@ -1051,19 +1051,16 @@ class Crypto (object):
         return out
 
 
-    def next (self, password, paramversion, nacl, iv):
+    def next (self, password, paramversion, nacl):
         """
         Prepare for encrypting another object: Reset the data counters and
         change the configuration in case one of the variable parameters differs
-        from the last object. Also check the IV for duplicates and error out
-        if strict checking was requested.
+        from the last object.
         """
         self.ctsize = 0
         self.ptsize = 0
         self.stats ["obj"] += 1
 
-        self.check_duplicate_iv (iv)
-
         if (   self.paramversion != paramversion
             or self.password     != password
             or self.nacl         != nacl):
@@ -1072,18 +1069,6 @@ class Crypto (object):
                                  insecure=self.insecure)
 
 
-    def check_duplicate_iv (self, iv):
-        """
-        Add an IV (the 12 byte representation as in the header) to the list. With
-        strict checking enabled, this will throw a ``DuplicateIV``. Depending on
-        the context, this may indicate a serious error (IV reuse).
-        """
-        if self.strict_ivs is True and iv in self.used_ivs:
-            raise DuplicateIV ("iv %s was reused" % iv_fmt (iv))
-        # vi has not been used before; add to collection
-        self.used_ivs.add (iv)
-
-
     def counters (self):
         """
         Access the data counters.
@@ -1108,6 +1093,13 @@ class Crypto (object):
         return self.used_ivs
 
 
+    def reset_last_iv (self):
+        """
+        Implemented only for decryptor; no-op otherwise.
+        """
+        pass
+
+
 class Encrypt (Crypto):
 
     lastinfo     = None
@@ -1115,7 +1107,7 @@ class Encrypt (Crypto):
     paramenc     = None
 
     def __init__ (self, version, paramversion, password=None, key=None, nacl=None,
-                  counter=AES_GCM_IV_CNT_DATA, strict_ivs=True, insecure=False):
+                  counter=AES_GCM_IV_CNT_DATA, strict_ivs=False, insecure=False):
         """
         The ctor will throw immediately if one of the parameters does not conform
         to our expectations.
@@ -1132,7 +1124,9 @@ class Encrypt (Crypto):
                             ``AES_GCM_IV_CNT_INDEX`` are unique in each backup set
                             and cannot be reused even with different fixed parts.
         :type   strict_ivs: bool
-        :type     insecure: bool, whether to permit passthrough mode
+        :param  strict_ivs: Enable paranoid tracking of IVs.
+        :type     insecure: bool
+        :param    insecure: whether to permit passthrough mode
 
         *Security considerations*: The ``class Encrypt`` handle guarantees that
         all random parts (first eight bytes) of the IVs used for encrypting
@@ -1275,12 +1269,32 @@ class Encrypt (Crypto):
                                     % self.paramversion)
         hdrdum = hdr_make_dummy (filename)
         self.lastinfo = (filename, hdrdum)
-        super().next (self.password, self.paramversion, self.nacl, self.iv)
+
+        self.check_duplicate_iv (self.iv)
+
+        super().next (self.password, self.paramversion, self.nacl)
 
         self.set_object_counter (self.cnt + 1)
         return hdrdum
 
 
+    def check_duplicate_iv (self, iv):
+        """
+        Add an IV (the 12 byte representation as in the header) to the list. With
+        strict checking enabled, this will throw a ``DuplicateIV``. Depending on
+        the context, this may indicate a serious error (IV reuse).
+
+        IVs are only tracked in strict_ivs mode.
+        """
+        if self.strict_ivs is False:
+            return
+
+        if iv in self.used_ivs:
+            raise DuplicateIV ("iv %s was reused" % iv_fmt (iv))
+        # vi has not been used before; add to collection
+        self.used_ivs.add (iv)
+
+
     def done (self, cmpdata):
         """
         Complete encryption of an object. After this has been called, attempts
@@ -1344,7 +1358,7 @@ class Decrypt (Crypto):
     hdr_ctsize = -1
 
     def __init__ (self, password=None, key=None, counter=None, fixedparts=None,
-                  strict_ivs=False, insecure=False):
+                  strict_ivs=True, insecure=False):
         """
         Sanitizing ctor for the decryption context. ``fixedparts`` specifies a
         list of IV fixed parts accepted during decryption. If a fixed part is
@@ -1359,9 +1373,20 @@ class Decrypt (Crypto):
                             ``AES_GCM_IV_CNT_INDEX`` are unique in each backup set
                             and cannot be reused even with different fixed parts.
         :type   fixedparts: bytes list
+        :type   strict_ivs: bool
+        :param  strict_ivs: fail if IVs of decrypted objects are not linearly
+                            increasing
         :type     insecure: bool
         :param    insecure: whether to process objects encrypted in
                             passthrough mode (*``paramversion`` < 1*)
+
+        *Security considerations*: The ``strict_ivs`` setting protects against
+        ciphertext reordering and injection attacks. For this to work it relies
+        on a property of how the object counters are created during encryption.
+        If multiple ``Encrypt`` handles have been used during encryption, this
+        is property is unlikely to apply as it would require manual management
+        of counters across Encrypt handles. In these cases it may thus be
+        necessary to disable the ```strict_ivs`` protection.
         """
         if         password is     None and key is     None \
                 or password is not None and key is not None :
@@ -1401,6 +1426,17 @@ class Decrypt (Crypto):
         return i != len (self.fixed) and self.fixed [i] == fixed
 
 
+    def reset_last_iv (self):
+        """
+        Force a new IV sequence start. The last IV counter will be set from the
+        next IV encountered and the check for consecutive IVs will be suppressed.
+
+        The intended use is backup volume boundaries or handling batches of
+        objects encrypted with ``Encrypt`` handles initialized with different
+        initial counter values.
+        """
+        self.last_iv = None
+
     def check_consecutive_iv (self, iv):
         """
         Check whether the counter part of the given IV is indeed the successor
@@ -1412,10 +1448,10 @@ class Decrypt (Crypto):
         if self.strict_ivs is True \
                 and self.last_iv is not None \
                 and self.last_iv [0] == fixed \
-                and self.last_iv [1] != cnt - 1:
+                and self.last_iv [1] + 1 != cnt:
             raise NonConsecutiveIV ("iv %s counter not successor of "
                                     "last object (expected %d, found %d)"
-                                    % (iv_fmt (iv), self.last_iv [1], cnt))
+                                    % (iv_fmt (iv), self.last_iv [1] + 1, cnt))
         self.last_iv = (fixed, cnt)
 
 
@@ -1447,10 +1483,11 @@ class Decrypt (Crypto):
 
         self.hdr_ctsize = ctsize
 
-        super().next (self.password, paramversion, nacl, iv)
+        super().next (self.password, paramversion, nacl)
         if self.fixed is not None and self.valid_fixed_part (iv) is False:
             raise InvalidIVFixedPart ("iv %s has invalid fixed part"
                                       % iv_fmt (iv))
+
         self.check_consecutive_iv (iv)
 
         self.tag = tag