unify construction of secret values
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Fri, 25 Aug 2017 07:33:49 +0000 (09:33 +0200)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Mon, 2 Apr 2018 11:34:09 +0000 (13:34 +0200)
deltatar/crypto.py
deltatar/tarfile.py

index d4dd770..843cccf 100755 (executable)
@@ -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:
index d44b100..c052fa5 100644 (file)
@@ -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)