keep separate encryptor and decryptor contexts in deltatar.py
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 25 Apr 2017 09:13:17 +0000 (11:13 +0200)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Mon, 2 Apr 2018 11:34:08 +0000 (13:34 +0200)
The same Deltatar object appears to function as handle for
reading and writing files simultaneously. To support this,
introduce two different crypto contexts that are created
on demand.

deltatar/deltatar.py
deltatar/tarfile.py

index 6f21340..d28617a 100644 (file)
@@ -50,6 +50,10 @@ NO_MATCH        = False
 MATCH           = True
 PARENT_MATCH    = 2
 
+# encryption direction
+CRYPTO_MODE_ENCRYPT = 0
+CRYPTO_MODE_DECRYPT = 1
+
 # The canonical extension for encrypted backup files regardless of the actual
 # encryption parameters is “.pdtcrypt”. This is analogous to the encryption
 # header which starts with the eight ASCII bytes “PDTCRYPT”. Historical note:
@@ -94,9 +98,10 @@ class DeltaTar(object):
     # on decryption since the required settings are determined from the headers
     crypto_paramversion = None
 
-    # when encrypting or decrypting, this holds crypto handler; created before
+    # when encrypting or decrypting, these hold crypto handlers; created before
     # establishing the Tarfile stream iff a password is supplied.
-    crypto_ctx = None
+    encryptor = None
+    decryptor = None
 
     # python logger object.
     logger = None
@@ -257,7 +262,7 @@ class DeltaTar(object):
         if kind == PDT_TYPE_ARCHIVE:
             ret += ".tar"
         ret += mode
-        if self.crypto_ctx is not None:
+        if self.encryptor is not None or self.decryptor is not None:
             ret += "." + PDTCRYPT_EXTENSION
         return ret
 
@@ -511,6 +516,27 @@ class DeltaTar(object):
                 return path[len(prefix):]
         return path
 
+
+    def initialize_encryption (self, mode):
+        password = self.password
+
+        if password is None:
+            return
+        if mode == CRYPTO_MODE_ENCRYPT:
+            if self.encryptor is None:
+                self.encryptor = \
+                        crypto.Encrypt (password,
+                                        version=DELTATAR_HEADER_VERSION,
+                                        paramversion=self.crypto_paramversion)
+            return
+        if mode == CRYPTO_MODE_DECRYPT:
+            if self.decryptor is None:
+                self.decryptor = crypto.Decrypt (password)
+            return
+
+        raise Exception ("invalid encryption mode [%r]" % mode)
+
+
     def open_auxiliary_file(self, path, mode='r', kind=AUXILIARY_FILE_INDEX):
         '''
         Given the specified configuration, opens a file for reading or writing,
@@ -532,10 +558,18 @@ class DeltaTar(object):
         else:
             comptype = 'tar'
 
+        crypto_ctx = None
+        if mode == "w":
+            self.initialize_encryption (CRYPTO_MODE_ENCRYPT)
+            crypto_ctx = self.encryptor
+        elif mode == "r":
+            self.initialize_encryption (CRYPTO_MODE_DECRYPT)
+            crypto_ctx = self.decryptor
+
         sink = tarfile._Stream(name=path, mode=mode, comptype=comptype,
                                bufsize=tarfile.RECORDSIZE, fileobj=None,
-                               encryption=self.crypto_ctx)
-        if self.crypto_ctx is not None and mode == "w":
+                               encryption=crypto_ctx)
+        if self.encryptor is not None and mode == "w":
             counter = None
             if kind == AUXILIARY_FILE_INFO:
                 counter = crypto.AES_GCM_IV_CNT_INFOFILE
@@ -606,11 +640,7 @@ class DeltaTar(object):
             raise Exception('Unrecognized extension')
 
         # setup for encrypting payload
-        if self.password is not None:
-            self.crypto_ctx = \
-                    crypto.Encrypt (self.password,
-                                    version=DELTATAR_HEADER_VERSION,
-                                    paramversion=self.crypto_paramversion)
+        self.initialize_encryption (CRYPTO_MODE_ENCRYPT)
 
         # some initialization
         self.vol_no = 0
@@ -643,7 +673,7 @@ class DeltaTar(object):
             tarobj.open_volume(volume_path, encryption=encryption)
 
         # wraps some args from context into the handler
-        new_volume_handler = partial(new_volume_handler, self, cwd, backup_path, self.crypto_ctx)
+        new_volume_handler = partial(new_volume_handler, self, cwd, backup_path, self.encryptor)
 
         index_accu.write(bytes('{"type": "python-delta-tar-index", "version": 1, "backup-type": "full", "extra_data": %s}\n' % extra_data_str, 'UTF-8'))
 
@@ -657,7 +687,7 @@ class DeltaTar(object):
                               mode='w' + self.mode,
                               format=tarfile.GNU_FORMAT,
                               concat_compression='#gz' in self.mode,
-                              encryption=self.crypto_ctx,
+                              encryption=self.encryptor,
                               max_volume_size=max_volume_size,
                               new_volume_handler=new_volume_handler,
                               save_to_members=False,
@@ -773,11 +803,8 @@ class DeltaTar(object):
             raise Exception('Unrecognized extension')
 
         # setup for encrypting payload
-        if self.password is not None:
-            self.crypto_ctx = \
-                    crypto.Encrypt (self.password,
-                                    version=DELTATAR_HEADER_VERSION,
-                                    paramversion=self.crypto_paramversion)
+        self.initialize_encryption (CRYPTO_MODE_ENCRYPT)
+
         # some initialization
         self.vol_no = 0
 
@@ -823,7 +850,7 @@ class DeltaTar(object):
                               mode='w' + self.mode,
                               format=tarfile.GNU_FORMAT,
                               concat_compression='#gz' in self.mode,
-                              encryption=self.crypto_ctx,
+                              encryption=self.encryptor,
                               max_volume_size=max_volume_size,
                               new_volume_handler=new_volume_handler,
                               save_to_members=False,
@@ -1042,7 +1069,7 @@ class DeltaTar(object):
                         mode='r' + self.delta_tar.mode,
                         format=tarfile.GNU_FORMAT,
                         concat_compression='#gz' in self.delta_tar.mode,
-                        encryption=self.delta_tar.crypto_ctx,
+                        encryption=self.delta_tar.decryptor,
                         new_volume_handler=self.new_volume_handler,
                         save_to_members=False,
                         dereference=True)
@@ -1209,19 +1236,18 @@ class DeltaTar(object):
                 volume_path = os.path.join(cwd, volume_path)
             tarobj.open_volume(volume_path, encryption=encryption)
 
-        if self.password is not None:
-            self.crypto_ctx = crypto.Decrypt (self.password)
+        self.initialize_encryption (CRYPTO_MODE_DECRYPT)
 
         backup_path = os.path.dirname(backup_tar_path)
         if not os.path.isabs(backup_path):
             backup_path = os.path.join(cwd, backup_path)
-        new_volume_handler = partial(new_volume_handler, self, cwd, backup_path, self.crypto_ctx)
+        new_volume_handler = partial(new_volume_handler, self, cwd, backup_path, self.decryptor)
 
         tarobj = tarfile.TarFile.open(backup_tar_path,
                             mode='r' + self.mode,
                             format=tarfile.GNU_FORMAT,
                             concat_compression='#gz' in self.mode,
-                            encryption=self.crypto_ctx,
+                            encryption=self.decryptor,
                             new_volume_handler=new_volume_handler,
                             save_to_members=False,
                             dereference=True)
@@ -1318,7 +1344,7 @@ class DeltaTar(object):
 
         # setup for decrypting payload
         if self.password is not None:
-            self.crypto_ctx = crypto.Decrypt (self.password)
+            self.decryptor = crypto.Decrypt (self.password)
 
         if mode == 'tar':
             index_it = self.iterate_tar_path(backup_tar_path)
@@ -1464,7 +1490,7 @@ class RestoreHelper(object):
                     last_lno = 0,
                     new_volume_handler = partial(self.new_volume_handler,
                         self._deltatar, self._cwd, is_full,
-                        os.path.dirname(index), self._deltatar.crypto_ctx)
+                        os.path.dirname(index), self._deltatar.decryptor)
                 )
                 self._data.append(s)
         else:
@@ -1475,7 +1501,7 @@ class RestoreHelper(object):
             # update the new_volume_handler of tar_obj
             tarobj.new_volume_handler = partial(self.new_volume_handler,
                 self._deltatar, self._cwd, True, os.path.dirname(backup_path),
-                self._deltatar.crypto_ctx)
+                self._deltatar.decryptor)
             s = dict(
                 curr_vol_no = None,
                 vol_fd = None,
@@ -1730,7 +1756,7 @@ class RestoreHelper(object):
                     fileobj=index_data['vol_fd'],
                     format=tarfile.GNU_FORMAT,
                     concat_compression='#gz' in self._deltatar.mode,
-                    encryption=self._deltatar.crypto_ctx,
+                    encryption=self._deltatar.decryptor,
                     new_volume_handler=index_data['new_volume_handler'],
                     save_to_members=False)
 
index 508f5a3..e3659a3 100644 (file)
@@ -463,7 +463,7 @@ class _Stream:
                 self.zlib = zlib
                 if mode == "r":
                     self.exception = zlib.error
-                    if concat_stream is True:
+                    if self.encryption is None:
                         self._init_read_gz()
                 elif mode == "w":
                     if self.encryption is None and concat_stream is False:
@@ -2037,7 +2037,7 @@ class TarFile(object):
         elif mode in "aw":
             return cls.taropen(name, mode, fileobj, **kwargs)
 
-        raise ValueError("undiscernible mode")
+        raise ValueError("undiscernible mode %r" % mode)
 
     @classmethod
     def taropen(cls, name, mode="r", fileobj=None, **kwargs):