first draft for making the encryption layer independent
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 28 Mar 2017 15:44:31 +0000 (17:44 +0200)
committerPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 28 Mar 2017 15:45:56 +0000 (17:45 +0200)
WIP.

In Deltatar, we cannot use the ctor itself to set up the
encryption because it is neutral wrt. reading / writing.
Only once one of the entry points:

- ``.list_backup()``,
- ``.create_full_backup()``, ``.create_diff_backup()``, or
- ``.restore_backup()``.

are invoked do we know what the object’s intended use is.
Thus we hook the encryption handler somewhere in there.

deltatar/crypto.py
deltatar/deltatar.py
deltatar/tarfile.py

index fa8a2cf..5ae9330 100755 (executable)
@@ -416,6 +416,7 @@ class Crypto (object):
         return self.stats ["obj"], self.stats ["in"], self.stats ["out"]
 
 
+
 class Encrypt (Crypto):
 
     curobj = None
index 60f3030..9455643 100644 (file)
@@ -33,6 +33,7 @@ import json
 from functools import partial
 
 from . import tarfile
+from . import crypto
 
 class NullHandler(logging.Handler):
     def emit(self, record):
@@ -82,6 +83,10 @@ class DeltaTar(object):
     # used together with aes modes to encrypt and decrypt backups.
     password = None
 
+    # when encrypting or decrypting, this holds crypto handler; created before
+    # establishing the Tarfile stream iff a password is supplied.
+    crypto_ctx = None
+
     # When encrypting, the salt is created by the first crypto operation, i. e.
     # opening the index for writing. The subsequently opened archive uses the
     # same salt.
@@ -92,7 +97,7 @@ class DeltaTar(object):
 
     # specifies the index mode in the same format as @param mode, but without
     # the ':', '|' or '#' at the begining. It doesn't make sense to specify
-    # that the index is encrypted if no no password is given in the constructor.
+    # that the index is encrypted if no password is given in the constructor.
     index_mode = None
 
     # current time for this backup. Used for file names and file creation checks
@@ -208,7 +213,9 @@ class DeltaTar(object):
         if logger:
             self.logger.addHandler(logger)
         self.mode = mode
-        self.password = password
+
+        if password is not None:
+            self.password = password
 
         # generate index_mode
         if index_mode is None:
@@ -497,8 +504,6 @@ class DeltaTar(object):
                             layer.
         :type  kind:        str
         '''
-        filemode = None
-
         if self.index_mode.startswith('gz'):
             comptype = 'gz'
         elif self.index_mode.startswith('bz2'):
@@ -508,16 +513,12 @@ class DeltaTar(object):
 
         encver = None
         counter = None
-        if 'aes' in self.index_mode:
-            encver = I2N_XXX_ENCRYPTION_VERSION
-            counter = None
-            if kind == "info": # fixed counter
-                counter = crypto.AES_GCM_IV_CNT_INFOFILE
+        if self.crypto_ctx is not None and kind == "info":
+            counter = crypto.AES_GCM_IV_CNT_INFOFILE
 
         return tarfile._Stream(name=path, mode=mode, comptype=comptype,
                                bufsize=tarfile.RECORDSIZE, fileobj=None,
-                               encver=encver, enccounter=counter,
-                               password=self.password)
+                               encryption=self.crypto_ctx, enccounter=counter)
 
     def create_full_backup(self, source_path, backup_path,
                            max_volume_size=None, extra_data=dict()):
@@ -580,6 +581,11 @@ class DeltaTar(object):
         if self.mode not in self.__file_extensions_dict:
             raise Exception('Unrecognized extension')
 
+        # setup for encrypting payload
+        if self.password is not None:
+            self.crypto_ctx = crypto.Encrypt (self.password,
+                                              paramversion=I2N_XXX_ENCRYPTION_VERSION)
+
         # some initialization
         self.vol_no = 0
 
@@ -630,8 +636,7 @@ class DeltaTar(object):
                               mode='w' + self.mode,
                               format=tarfile.GNU_FORMAT,
                               concat_compression='#gz' in self.mode,
-                              password=self.password,
-                              nacl=self.nacl,
+                              encryption=self.crypto_ctx,
                               max_volume_size=max_volume_size,
                               new_volume_handler=new_volume_handler,
                               save_to_members=False,
@@ -740,6 +745,10 @@ class DeltaTar(object):
         if self.mode not in self.__file_extensions_dict:
             raise Exception('Unrecognized extension')
 
+        # setup for encrypting payload
+        if self.password is not None:
+            self.crypto_ctx = crypto.Encrypt (self.password,
+                                              paramversion=I2N_XXX_ENCRYPTION_VERSION)
         # some initialization
         self.vol_no = 0
 
@@ -1273,6 +1282,10 @@ class DeltaTar(object):
         cwd = os.getcwd()
         os.chdir(target_path)
 
+        # setup for decrypting payload
+        if self.password is not None:
+            self.crypto_ctx = crypto.Decrypt (self.password)
+
         if mode == 'tar':
             index_it = self.iterate_tar_path(backup_tar_path)
             helper = RestoreHelper(self, cwd, backup_path=backup_tar_path,
index 0832d7d..71a50d6 100644 (file)
@@ -413,8 +413,8 @@ class _Stream:
 
     def __init__(self, name, mode, comptype, fileobj, bufsize,
                  concat_stream=False,
-                 encver=None, enccounter=None, password=None,
-                 nacl=None, compresslevel=9):
+                 encryption=None, enccounter=None,
+                 compresslevel=9):
         """Construct a _Stream object.
         """
         self._extfileobj = True
@@ -447,9 +447,7 @@ class _Stream:
         self.compresslevel = compresslevel
         self.bytes_written = 0
         # crypto parameters
-        self.encver     = encver
-        self.password   = password
-        self.encryption = None
+        self.encryption = encryption
         self.lasthdr    = None
 
         enccounter = enccounter or crypto.AES_GCM_IV_CNT_DATA
@@ -463,42 +461,13 @@ class _Stream:
                 self.zlib = zlib
                 if mode == "r":
                     self._init_read_gz()
-                    if self.encver is not None:
-                        if password is None:
-                            raise InvalidEncryptionError \
-                                    ("encryption requested (v=%d) but no password given"
-                                     % encver)
-                        try:
-                            enc = crypto.Decrypt (password)
-                        except ValueError as exn:
-                            raise InvalidEncryptionError \
-                                    ("ctor failed crypto.Decrypt(<PASSWORD>)")
-                        self.encryption = enc
-                else:
-                    if self.encver is not None:
-                        # Layers are stacked differently: initialization is
-                        # necessary per file.
-                        if password is None:
-                            raise InvalidEncryptionError \
-                                    ("encryption requested (v=%d) but no password given"
-                                     % encver)
-                        try:
-                            enc = crypto.Encrypt (password,
-                                                  I2N_XXX_ENCRYPTION_VERSION, nacl,
-                                                  counter=enccounter)
-                        except ValueError as exn:
-                            raise InvalidEncryptionError \
-                                    ("ctor failed crypto.Encrypt(<PASSWORD>, “%s”, %r)"
-                                     % (nacl, 1))
-                        self.encryption = enc
                 self.exception = zlib.error # XXX what for? seems unused
                 self.crc = zlib.crc32(b"") & 0xFFFFffff
 
             elif comptype == "bz2":
-                if self.encver is not None:
-                    raise InvalidEncryptionError("encryption version %r not "
-                                                 "available for compression %s"
-                                                 % (encver, comptype))
+                if self.encryption is not None:
+                    raise InvalidEncryptionError("encryption not available for "
+                                                 "compression %s" % comptype)
                 try:
                     import bz2
                 except ImportError:
@@ -511,10 +480,9 @@ class _Stream:
                     self.cmp = bz2.BZ2Compressor()
 
             elif comptype == 'xz':
-                if self.encver is not None:
-                    raise InvalidEncryptionError("encryption version %r not "
-                                                 "available for compression %s"
-                                                 % (encver, comptype))
+                if self.encryption is not None:
+                    raise InvalidEncryptionError("encryption not available for "
+                                                 "compression %s" % comptype)
                 try:
                     import lzma
                 except ImportError:
@@ -527,39 +495,11 @@ class _Stream:
                     self.cmp = lzma.LZMACompressor()
 
             elif comptype != "tar":
-                if self.encver is not None:
-                    raise InvalidEncryptionError("encryption version %r not "
-                                                 "available for compression %s"
-                                                 % (encver, comptype))
+                if self.encryption is not None:
+                    raise InvalidEncryptionError("encryption not available for "
+                                                 "compression %s" % comptype)
                 raise CompressionError("unknown compression type %r" % comptype)
 
-            else: # no compression
-                if mode == "r":
-                    if password is None:
-                        raise InvalidEncryptionError \
-                                ("encryption requested (v=%d) but no password given"
-                                 % encver)
-                    try:
-                        enc = crypto.Decrypt (password)
-                    except ValueError as exn:
-                        raise InvalidEncryptionError \
-                                ("ctor failed crypto.Decrypt(<PASSWORD>)")
-                    self.encryption = enc
-                elif mode == "w":
-                    if password is None:
-                        raise InvalidEncryptionError \
-                                ("encryption requested (v=%d) but no password given"
-                                 % encver)
-                    try:
-                        enc = crypto.Encrypt (password,
-                                              I2N_XXX_ENCRYPTION_VERSION, nacl,
-                                              counter=enccounter)
-                    except ValueError as exn:
-                        raise InvalidEncryptionError \
-                                ("ctor failed crypto.Encrypt(<PASSWORD>, “%s”, %r)"
-                                 % (nacl, 1))
-                    self.encryption = enc
-
         except: # XXX seriously?
             if not self._extfileobj:
                 self.fileobj.close()
@@ -2023,7 +1963,7 @@ class TarFile(object):
 
     @classmethod
     def open(cls, name=None, mode="r", fileobj=None, bufsize=RECORDSIZE,
-             compresslevel=9, **kwargs):
+             encryption=None, compresslevel=9, **kwargs):
         """Open a tar archive for reading, writing or appending. Return
            an appropriate TarFile class.
 
@@ -2120,37 +2060,14 @@ class TarFile(object):
             filemode, comptype = mode.split("#", 1)
             filemode = filemode or "r"
             password = ''
-            nacl = None
-            encver = None
 
             if filemode not in "rw":
                 raise ValueError("mode must be 'r' or 'w'")
 
-            # XXX add equivalent check for defined modes
-            #if comptype not in ["gz", "gz.aes128", "gz.aes256", 'aes128',
-            #                    'aes256']:
-            #    raise ValueError("comptype must be 'gz' or 'aes'")
-
-            # encryption gz.aes128 or gz.aes256
-            if "." in comptype:
-                comptype, _ = comptype.split(".", 1)
-                encver = I2N_XXX_ENCRYPTION_VERSION # XXX set dynamically
-                password = kwargs.get('password', '')
-                if not password:
-                    raise ValueError("you should give a password for encryption")
-
-            if comptype.startswith("aes"):
-                comptype = 'tar'
-                encver = I2N_XXX_ENCRYPTION_VERSION # XXX set dynamically
-                password = kwargs.get ("password")
-                if password is None:
-                    raise ValueError("you should give a password for encryption")
-
             kwargs['concat_compression'] = True
 
             stream = _Stream(name, filemode, comptype, fileobj, bufsize,
-                             concat_stream=True, encver=encver,
-                             password=password, nacl=nacl,
+                             concat_stream=True, encryption=encryption,
                              compresslevel=compresslevel)
             try:
                 t = cls(name, filemode, stream, **kwargs)