implement decryption for tolerant mode
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 22 Aug 2017 11:29:45 +0000 (13:29 +0200)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Mon, 2 Apr 2018 11:34:09 +0000 (13:34 +0200)
Not possible to reuse the existing CLI decryption since we’re
operating with fds in scan mode.

deltatar/crypto.py

index 268bbbf..7528281 100755 (executable)
@@ -590,14 +590,48 @@ def inspect_hdr (fd, off):
     return HDR_CAND_GOOD, hdr
 
 
-def try_decrypt (fd, off, hdr, fname=None):
+def try_decrypt (fd, off, hdr, secret, fname=None):
     """
     Attempt to decrypt the object in the (seekable) descriptor *fd* starting at
-    *off* using the metadata in *hdr*. An output file can be specified with
-    *fname*; if it is *None*, the decrypted payload will be discarded.
+    *off* using the metadata in *hdr* and *secret*. An output file can be
+    specified with *fname*; if it is *None*, the decrypted payload will be
+    discarded.
+
+    Always creates a fresh decryptor, so validation steps across objects don’t
+    apply.
     """
+    ctleft = hdr ["ctsize"]
+    pos    = off
+
+    ks = secret [0]
+    if ks == PDTCRYPT_SECRET_PW:
+        decr = Decrypt (password=secret [1])
+    elif ks == PDTCRYPT_SECRET_KEY:
+        key = binascii.unhexlify (secret [1])
+        decr = Decrypt (key=key)
+    else:
+        raise RuntimeError
+
+    if fname is not None: raise NotImplementedError
+
+    decr.next (hdr)
+
+    try:
+        os.lseek (fd, pos, os.SEEK_SET)
+        while ctleft > 0:
+            cnksiz = min (ctleft, PDTCRYPT_BLOCKSIZE)
+            cnk    = os.read (fd, cnksiz)
+            ctleft -= cnksiz
+            pos    += cnksiz
+            _pt    = decr.process (cnk)
+
+        _pt = decr.done ()
+    except Exception as exn:
+        noise ("PDT: error decrypting object %d–%d@%d, %d B remaining [%s]"
+               % (off, off + hdr ["ctsize"], pos, ctleft, exn))
+        raise
 
-    raise NotImplementedError
+    return pos - off
 
 
 ###############################################################################
@@ -1705,7 +1739,7 @@ def noise_output_candidates (cands, indent=8, cols=PDTCRYPT_TT_COLUMNS):
         noise (line)
 
 
-def mode_scan (pw, fname, nacl=None):
+def mode_scan (secret, fname, nacl=None):
     """
     Dissect a binary file, looking for PDTCRYPT headers and objects.
     """
@@ -1740,19 +1774,22 @@ def mode_scan (pw, fname, nacl=None):
             else:
                 off0 = cand + PDTCRYPT_HDR_SIZE
                 if PDTCRYPT_VERBOSE is True:
-                    noise ("PDT: read payload @%d: %s" % (off0, hdr_fmt (hdr)))
-                vdtt = try_decrypt (fd, off0, hdr)
+                    noise ("PDT: read payload @%d" % off0)
+                    pretty = hdr_fmt_pretty (hdr)
+                    noise (reduce (lambda a, e: (a + "\n" if a else "") + "PDT:\t· " + e,
+                                pretty.splitlines (), ""))
 
-                if vdt == HDR_CAND_GOOD and vdtt == HDR_CAND_GOOD:
+                ok = try_decrypt (fd, off0, hdr, secret) == hdr ["ctsize"]
+                if vdt == HDR_CAND_GOOD and ok is True:
                     noise ("PDT: %d → ✓ valid object %d–%d"
                            % (cand, off0, off0 + hdr ["ctsize"]))
-                elif vdt == HDR_CAND_FISHY and vdtt == HDR_CAND_GOOD:
+                elif vdt == HDR_CAND_FISHY and ok is True:
                     noise ("PDT: %d → × object %d–%d, corrupt header"
                            % (cand, off0, off0 + hdr ["ctsize"]))
-                elif vdt == HDR_CAND_GOOD and vdtt == HDR_CAND_FISHY:
+                elif vdt == HDR_CAND_GOOD and ok is False:
                     noise ("PDT: %d → × object %d–%d, problematic payload"
                            % (cand, off0, off0 + hdr ["ctsize"]))
-                elif vdt == HDR_CAND_FISHY and vdtt == HDR_CAND_FISHY:
+                elif vdt == HDR_CAND_FISHY and ok is False:
                     noise ("PDT: %d → × object %d–%d, corrupt header, problematic "
                            "ciphertext" % (cand, off0, off0 + hdr ["ctsize"]))
                 else:
@@ -1760,6 +1797,12 @@ def mode_scan (pw, fname, nacl=None):
     finally:
         os.close (fd)
 
+    if len (junk) == 0:
+        noise ("PDT: all headers ok")
+    else:
+        noise ("PDT: %d candidates not parseable as headers:" % len (junk))
+        noise_output_candidates (junk)
+
 def usage (err=False):
     out = print
     if err is True:
@@ -1916,7 +1959,7 @@ def parse_argv (argv):
             bail ("ERROR: please supply an input file for scanning")
         if insspec == '-':
             bail ("ERROR: input must be seekable; please specify a file")
-        return True, partial (mode_scan, secret [1].encode (), insspec, nacl)
+        return True, partial (mode_scan, secret, insspec, nacl)
 
     if subcommand == PDTCRYPT_SUB_SCRYPT:
         if secret [0] == PDTCRYPT_SECRET_KEY: