From 2ae46844b09756e6ec6c8b413a0d69aeb783ef23 Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Tue, 28 Mar 2017 17:44:31 +0200 Subject: [PATCH] first draft for making the encryption layer independent MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 | 1 + deltatar/deltatar.py | 39 ++++++++++++------ deltatar/tarfile.py | 111 ++++++------------------------------------------- 3 files changed, 41 insertions(+), 110 deletions(-) diff --git a/deltatar/crypto.py b/deltatar/crypto.py index fa8a2cf..5ae9330 100755 --- a/deltatar/crypto.py +++ b/deltatar/crypto.py @@ -416,6 +416,7 @@ class Crypto (object): return self.stats ["obj"], self.stats ["in"], self.stats ["out"] + class Encrypt (Crypto): curobj = None diff --git a/deltatar/deltatar.py b/deltatar/deltatar.py index 762b70d..b0b83b2 100644 --- a/deltatar/deltatar.py +++ b/deltatar/deltatar.py @@ -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, diff --git a/deltatar/tarfile.py b/deltatar/tarfile.py index 0832d7d..71a50d6 100644 --- a/deltatar/tarfile.py +++ b/deltatar/tarfile.py @@ -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()") - 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(, “%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()") - 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(, “%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) -- 1.7.1