enable strict IV checking by default during decryption
[python-delta-tar] / deltatar / deltatar.py
index a9f8a9c..9ebe6d7 100644 (file)
@@ -555,7 +555,13 @@ class DeltaTar(object):
         return path
 
 
-    def initialize_encryption (self, mode):
+    def initialize_encryption (self, mode, strict_validation=True):
+        """
+        :type  strict_validation:   bool
+        :param strict_validation:   Enable strict IV checking in the crypto
+                                    layer. Should be disabled when dealing with
+                                    potentially corrupted data.
+        """
         password = self.password
         key      = self.crypto_key
         nacl     = self.nacl
@@ -569,12 +575,14 @@ class DeltaTar(object):
                                    version=self.crypto_version,
                                    paramversion=self.crypto_paramversion)
         if mode == CRYPTO_MODE_DECRYPT:
-            return crypto.Decrypt (password=password, key=key)
+            return crypto.Decrypt (password=password, key=key,
+                                   strict_ivs=strict_validation)
 
         raise Exception ("invalid encryption mode [%r]" % mode)
 
 
-    def open_auxiliary_file(self, path, mode='r', kind=AUXILIARY_FILE_INDEX):
+    def open_auxiliary_file(self, path, mode='r', kind=AUXILIARY_FILE_INDEX,
+                            strict_validation=True):
         '''
         Given the specified configuration, opens a file for reading or writing,
         inheriting the encryption and compression settings from the backup.
@@ -600,7 +608,8 @@ class DeltaTar(object):
         if mode == "w":
             crypto_ctx = self.initialize_encryption (CRYPTO_MODE_ENCRYPT)
         elif mode == "r":
-            crypto_ctx = self.initialize_encryption (CRYPTO_MODE_DECRYPT)
+            crypto_ctx = self.initialize_encryption (CRYPTO_MODE_DECRYPT,
+                                                     strict_validation=strict_validation)
 
         if crypto_ctx is not None:
             if kind == AUXILIARY_FILE_INFO:
@@ -1004,7 +1013,7 @@ class DeltaTar(object):
         index_sink.close()
 
 
-    def iterate_index_path(self, index_path):
+    def iterate_index_path(self, index_path, strict_validation=True):
         '''
         Returns an index iterator. Internally, it uses a classic iterator class.
         We do that instead of just yielding so that the iterator object can have
@@ -1032,7 +1041,10 @@ class DeltaTar(object):
                 Allows this iterator to be used with the "with" statement
                 '''
                 if self.f is None:
-                    self.f = self.delta_tar.open_auxiliary_file(self.index_path, 'r')
+                    self.f = self.delta_tar.open_auxiliary_file \
+                                    (self.index_path,
+                                     'r',
+                                     strict_validation=strict_validation)
                     # check index header
                     j, l_no = self.delta_tar._parse_json_line(self.f, 0)
                     if j.get("type", '') != 'python-delta-tar-index' or\
@@ -1116,7 +1128,8 @@ class DeltaTar(object):
                     if self.delta_tar.password is not None:
                         decryptor = crypto.Decrypt \
                                         (password=self.delta_tar.password,
-                                         key=self.delta_tar.crypto_key)
+                                         key=self.delta_tar.crypto_key,
+                                         strict_ivs=False)
                     self.tar_obj = tarfile.TarFile.open(self.tar_path,
                         mode='r' + self.delta_tar.mode,
                         format=tarfile.GNU_FORMAT,
@@ -1325,7 +1338,9 @@ class DeltaTar(object):
             tarobj.open_volume(volume_path, encryption=encryption)
 
         if self.decryptor is None:
-            self.decryptor = self.initialize_encryption (CRYPTO_MODE_DECRYPT)
+            self.decryptor = \
+                self.initialize_encryption (CRYPTO_MODE_DECRYPT,
+                                            strict_validation=False)
 
         backup_path = os.path.dirname(backup_tar_path)
         if not os.path.isabs(backup_path):
@@ -1354,7 +1369,8 @@ class DeltaTar(object):
 
     def restore_backup(self, target_path, backup_indexes_paths=[],
                        backup_tar_path=None, restore_callback=None,
-                       disaster=tarfile.TOLERANCE_STRICT, backup_index=None):
+                       disaster=tarfile.TOLERANCE_STRICT, backup_index=None,
+                       strict_validation=True):
         '''
         Restores a backup.
 
@@ -1438,7 +1454,9 @@ class DeltaTar(object):
 
         # setup for decrypting payload
         if self.decryptor is None:
-            self.decryptor = self.initialize_encryption (CRYPTO_MODE_DECRYPT)
+            self.decryptor = \
+                self.initialize_encryption (CRYPTO_MODE_DECRYPT,
+                                            strict_validation=strict_validation)
 
         if mode == 'tar':
             index_it = self.iterate_tar_path(backup_tar_path)
@@ -1450,7 +1468,9 @@ class DeltaTar(object):
             try:
                 # get iterator from newest index at _data[0]
                 index1 = helper._data[0]["path"]
-                index_it = self.iterate_index_path(index1)
+                index_it = \
+                    self.iterate_index_path(index1,
+                                            strict_validation=strict_validation)
             except tarfile.DecryptionError as exn:
                 self.logger.error("failed to decrypt file [%s]: %s; is this an "
                                   "actual encrypted index file?"
@@ -1467,6 +1487,7 @@ class DeltaTar(object):
                                     backup_index=backup_index,
                                     disaster=disaster)
 
+        index_decryptor = helper._data[0]["decryptor"]
 
         dir_it = self._recursive_walk_dir('.')
         dir_path_it = self.jsonize_path_iterator(dir_it)
@@ -1552,7 +1573,8 @@ class DeltaTar(object):
         """
         return self.restore_backup(target_path,
                                    backup_indexes_paths=backup_indexes_paths,
-                                   disaster=tarfile.TOLERANCE_RECOVER)
+                                   disaster=tarfile.TOLERANCE_RECOVER,
+                                   strict_validation=False)
 
 
     def rescue_backup(self, target_path, backup_tar_path,
@@ -1576,7 +1598,8 @@ class DeltaTar(object):
         return self.restore_backup(target_path,
                                    backup_index=backup_index,
                                    backup_tar_path=backup_tar_path,
-                                   disaster=tarfile.TOLERANCE_RESCUE)
+                                   disaster=tarfile.TOLERANCE_RESCUE,
+                                   strict_validation=False)
 
 
     def _parse_json_line(self, f, l_no):
@@ -1649,6 +1672,10 @@ class RestoreHelper(object):
         self._decryptors = []
         self._disaster = disaster
 
+        # Disable strict checking for linearly increasing IVs when running
+        # in rescue or recover mode.
+        strict_validation = disaster == tarfile.TOLERANCE_STRICT
+
         try:
             import grp, pwd
         except ImportError:
@@ -1684,7 +1711,8 @@ class RestoreHelper(object):
                 decryptor = None
                 if self._password is not None:
                     decryptor = crypto.Decrypt (password=self._password,
-                                                key=self._crypto_key)
+                                                key=self._crypto_key,
+                                                strict_ivs=strict_validation)
 
                 # make paths absolute to avoid cwd problems
                 if not os.path.isabs(index):
@@ -1911,9 +1939,10 @@ class RestoreHelper(object):
                 self._deltatar.logger.warning('tarfile: %s' % e)
 
     @staticmethod
-    def new_volume_handler(deltarobj, cwd, is_full, backup_path, encryption, tarobj, base_name, volume_number):
+    def new_volume_handler(deltarobj, cwd, is_full, backup_path, decryptor, tarobj, base_name, volume_number):
         '''
-        Handles the new volumes
+        Set up a new volume and perform the tasks necessary for transitioning
+        to the next one.
         '''
         volume_name = deltarobj.volume_name_func(backup_path, is_full,
             volume_number, guess_name=True)
@@ -1922,7 +1951,8 @@ class RestoreHelper(object):
         # we convert relative paths into absolute because CWD is changed
         if not os.path.isabs(volume_path):
             volume_path = os.path.join(cwd, volume_path)
-        tarobj.open_volume(volume_path, encryption=encryption)
+
+        tarobj.open_volume(volume_path, encryption=decryptor)
 
     def restore_file(self, file_data, index_data, path, l_no, unprefixed_path):
         '''