From fac2cfe1b177177d5b106ef186bd79a46b828be3 Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Thu, 11 May 2017 17:40:21 +0200 Subject: [PATCH] distinguish auxiliary file errors Auxiliary files that grow larger than the maximum defined encrypted file size cause an irrecoverable error because their fixed IV is being reused. Add a new exception to distinguish this specific case. Encrypted auxiliary files thus never consist of more than one object, no on-the-fly continuation is permitted like with ordinary files. --- deltatar/crypto.py | 19 ++++++++++++++----- deltatar/tarfile.py | 7 ++++++- testing/test_deltatar.py | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/deltatar/crypto.py b/deltatar/crypto.py index f2dd19c..782d4af 100755 --- a/deltatar/crypto.py +++ b/deltatar/crypto.py @@ -133,6 +133,11 @@ class InvalidIVFixedPart (Exception): pass +class InvalidFileCounter (Exception): + """IV fixed part not in supplied list.""" + pass + + class DuplicateIV (Exception): """IV reused.""" pass @@ -534,13 +539,13 @@ class Crypto (object): % (cnt, AES_GCM_IV_CNT_MAX)) if cnt == AES_GCM_IV_CNT_INFOFILE: if self.info_counter_used is True: - raise InvalidParameter ("attempted to reuse info file counter " - "%d: must be unique" % cnt) + raise InvalidFileCounter ("attempted to reuse info file " + "counter %d: must be unique" % cnt) self.info_counter_used = True elif cnt == AES_GCM_IV_CNT_INDEX: if self.index_counter_used is True: - raise InvalidParameter ("attempted to reuse index file counter " - "%d: must be unique" % cnt) + raise InvalidFileCounter ("attempted to reuse index file " + " counter %d: must be unique" % cnt) self.index_counter_used = True if cnt <= AES_GCM_IV_CNT_MAX: self.cnt = cnt @@ -579,7 +584,10 @@ class Crypto (object): if self.enc is None: raise RuntimeError ("process: context not initialized") self.stats ["in"] += len (buf) - out = self.enc.update (buf) + try: + out = self.enc.update (buf) + except cryptography.exceptions.AlreadyFinalized as exn: + raise InternalError (exn) self.stats ["out"] += len (out) return out @@ -687,6 +695,7 @@ class Encrypt (Crypto): "invalid type %s; please pass an " "integer instead" % type (counter)) self.set_object_counter (counter) + self.iv = self.iv_make () if self.paramenc == "aes-gcm": self.enc = Cipher \ diff --git a/deltatar/tarfile.py b/deltatar/tarfile.py index ff4b239..4bfc2a9 100644 --- a/deltatar/tarfile.py +++ b/deltatar/tarfile.py @@ -577,7 +577,12 @@ class _Stream: def __del__(self): if hasattr(self, "closed") and not self.closed: - self.close() + try: + self.close() + except crypto.InternalError: + # context already finalized due to abort but close() tried + # to use it + pass def next (self, name): diff --git a/testing/test_deltatar.py b/testing/test_deltatar.py index 9e903db..a7b3d92 100644 --- a/testing/test_deltatar.py +++ b/testing/test_deltatar.py @@ -218,6 +218,44 @@ class DeltaTarTest(BaseTest): assert value == self.md5sum(key) + def test_create_backup_index_max_file_length (self): + """ + Creates a full backup including one file that exceeds the (purposely + lowered) upper bound on GCM encrypted objects. This will yield two + encrypted objects for one plaintext file. + + Success is verified by splitting the archive at object boundaries and + counting the parts. + """ + if self.MODE_COMPRESSES is True: + raise SkipTest ("GCM file length test not meaningful with compression.") + if self.ENCRYPTION is None: + raise SkipTest ("GCM file length applies only to encrypted backups.") + + new_max = 5000 + crypto._testing_set_PDTCRYPT_MAX_OBJ_SIZE \ + ("I am fully aware that this will void my warranty.", + new_max) + + password, paramversion = self.ENCRYPTION + deltatar = DeltaTar (mode=self.MODE, password=password, + crypto_paramversion=paramversion, + logger=self.consoleLogger) + + self.hash = dict () + os.makedirs ("source_dir2") + for i in range (42): + f = "source_dir2/dummy_%rd" % i + self.hash [f] = self.create_file (f, i) + + try: + deltatar.create_full_backup \ + (source_path="source_dir2", backup_path="backup_dir") + except crypto.InvalidFileCounter: + pass + shutil.rmtree ("source_dir2") + + def test_check_index_checksum(self): ''' Creates a full backup and checks the index' checksum of files -- 1.7.1