From addcec4268287dc3c7b6d4771925c5b971623c5a Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Fri, 25 Aug 2017 09:33:49 +0200 Subject: [PATCH] unify construction of secret values --- deltatar/crypto.py | 66 ++++++++++++++++++++++++++++++++++++++++++++------- deltatar/tarfile.py | 7 +---- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/deltatar/crypto.py b/deltatar/crypto.py index d4dd770..843cccf 100755 --- a/deltatar/crypto.py +++ b/deltatar/crypto.py @@ -315,6 +315,8 @@ FMT_I2N_HDR = ("<" # host byte order "16s") # GCM tag # aes+gcm +AES_KEY_SIZE = 16 # b"0123456789abcdef" +AES_KEY_SIZE_B64 = 24 # b'MDEyMzQ1Njc4OWFiY2RlZg==' AES_GCM_MAX_SIZE = (1 << 36) - (1 << 5) # 2^39 - 2^8 b ≅ 64 GB PDTCRYPT_MAX_OBJ_SIZE_DEFAULT = 63 * (1 << 30) # 63 GB PDTCRYPT_MAX_OBJ_SIZE = PDTCRYPT_MAX_OBJ_SIZE_DEFAULT @@ -332,6 +334,10 @@ PDTCRYPT_IV_GEN_MAX_RETRIES = 10 # × PDTCRYPT_IV_FIXEDPART_SIZE = 8 # B PDTCRYPT_IV_COUNTER_SIZE = 4 # B +# secret type: PW of string | KEY of char [16] +PDTCRYPT_SECRET_PW = 0 +PDTCRYPT_SECRET_KEY = 1 + ############################################################################### ## header, trailer ############################################################################### @@ -674,6 +680,51 @@ def reconstruct_offsets (fname, secret): ############################################################################### +## helpers +############################################################################### + +def make_secret (password=None, key=None): + """ + Safely create a “secret” value that consists either of a key or a password. + Inputs are validated: the password is accepted as (UTF-8 encoded) bytes or + string; for the key only a bytes object of the proper size or a base64 + encoded string thereof is accepted. + + If both are provided, the key is preferred over the password; no checks are + performed whether the key is derived from the password. + + :returns: secret value if inputs were acceptable | None otherwise. + """ + if key is not None: + if isinstance (key, str) is True: + key = key.encode ("utf-8") + if isinstance (key, bytes) is True: + if len (key) == AES_KEY_SIZE: + return (PDTCRYPT_SECRET_KEY, key) + if len (key) == AES_KEY_SIZE_B64: + try: + key = base64.b64decode (key) + # the base64 processor is very tolerant and allows for + # arbitrary traling and leading data thus the data obtained + # must be checked for the proper length + if len (key) == AES_KEY_SIZE: + return (PDTCRYPT_SECRET_KEY, key) + except binascii.Error: # “incorrect padding” + pass + elif password is not None: + if isinstance (password, str) is True: + return (PDTCRYPT_SECRET_PW, password) + elif isinstance (password, bytes) is True: + try: + password = password.decode ("utf-8") + return (PDTCRYPT_SECRET_PW, password) + except UnicodeDecodeError: + pass + + return None + + +############################################################################### ## passthrough / null encryption ############################################################################### @@ -1430,9 +1481,6 @@ PDTCRYPT_SUB = \ , "scrypt" : PDTCRYPT_SUB_SCRYPT , "scan" : PDTCRYPT_SUB_SCAN } -PDTCRYPT_SECRET_PW = 0 -PDTCRYPT_SECRET_KEY = 1 - PDTCRYPT_DECRYPT = 1 << 0 # decrypt archive with password PDTCRYPT_SPLIT = 1 << 1 # split archive into individual objects PDTCRYPT_HASH = 1 << 2 # output scrypt hash for file and given password @@ -1938,10 +1986,10 @@ def parse_argv (argv): except StopIteration: bail ("ERROR: argument list incomplete") - def checked_secret (t, arg): + def checked_secret (s): nonlocal secret if secret is None: - secret = (t, arg) + secret = s else: bail ("ERROR: encountered “%s” but secret already given" % arg) @@ -1957,7 +2005,7 @@ def parse_argv (argv): if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypt from %s" % insspec) elif arg in [ "-p", "--password" ]: arg = checked_arg () - checked_secret (PDTCRYPT_SECRET_PW, arg) + checked_secret (make_secret (password=arg)) if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypting with password") else: if subcommand == PDTCRYPT_SUB_PROCESS: @@ -1978,7 +2026,7 @@ def parse_argv (argv): if PDTCRYPT_VERBOSE is True: noise ("PDT: not decrypting") elif arg in [ "-k", "--key" ]: arg = checked_arg () - checked_secret (PDTCRYPT_SECRET_KEY, arg) + checked_secret (make_secret (key=arg)) if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypting with key") else: bail ("ERROR: unexpected positional argument “%s”" % arg) @@ -2011,14 +2059,14 @@ def parse_argv (argv): noise ("ERROR: no password or key specified, trying $PDTCRYPT_PASSWORD") epw = os.getenv ("PDTCRYPT_PASSWORD") if epw is not None: - checked_secret (PDTCRYPT_SECRET_PW, epw.strip ()) + checked_secret (make_secret (password=epw.strip ())) if secret is None: if PDTCRYPT_VERBOSE is True: noise ("ERROR: no password or key specified, trying $PDTCRYPT_KEY") ek = os.getenv ("PDTCRYPT_KEY") if ek is not None: - checked_secret (PDTCRYPT_SECRET_KEY, ek.strip ()) + checked_secret (make_secret (key=ek.strip ())) if secret is None: if subcommand == PDTCRYPT_SUB_SCRYPT: diff --git a/deltatar/tarfile.py b/deltatar/tarfile.py index d44b100..c052fa5 100644 --- a/deltatar/tarfile.py +++ b/deltatar/tarfile.py @@ -3590,12 +3590,7 @@ def idxent_of_tarinfo (tarinfo): def gen_rescue_index (backup_tar_path, mode, password=None, key=None): psidx = [] # pseudo index, return value offsets = None - secret = None - - if password is not None: - secret = (crypto.PDTCRYPT_SECRET_PW, password) - elif key is not None: - secret = (crypto.PDTCRYPT_SECRET_KEY, key) + secret = crypto.make_secret (password=password, key=key) if secret is not None: offsets = crypto.reconstruct_offsets (backup_tar_path, secret) -- 1.7.1