import mmap
import os
import struct
+import stat
import sys
import time
import types
return HDR_CAND_GOOD, hdr
-def try_decrypt (fd, off, hdr, secret, fname=None):
+def try_decrypt (ifd, off, hdr, secret, ofd=-1):
"""
- Attempt to decrypt the object in the (seekable) descriptor *fd* starting at
- *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.
+ Attempt to decrypt the object in the (seekable) descriptor *ifd* starting
+ at *off* using the metadata in *hdr* and *secret*. An output fd can be
+ specified with *ofd*; if it is *-1* – the default –, the decrypted payload
+ will be discarded.
Always creates a fresh decryptor, so validation steps across objects don’t
apply.
else:
raise RuntimeError
- if fname is not None: raise NotImplementedError
-
decr.next (hdr)
try:
- os.lseek (fd, pos, os.SEEK_SET)
+ os.lseek (ifd, pos, os.SEEK_SET)
while ctleft > 0:
cnksiz = min (ctleft, PDTCRYPT_BLOCKSIZE)
- cnk = os.read (fd, cnksiz)
+ cnk = os.read (ifd, cnksiz)
ctleft -= cnksiz
pos += cnksiz
- _pt = decr.process (cnk)
+ pt = decr.process (cnk)
+ if ofd != -1:
+ os.write (ofd, pt)
+ pt = decr.done ()
+ if len (pt) > 0 and ofd != -1:
+ os.write (ofd, pt)
- _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))
_testing_set_PDTCRYPT_MAX_OBJ_SIZE = \
partial (_patch_global, "PDTCRYPT_MAX_OBJ_SIZE")
+def open2_dump_file (fname, dir_fd, force=False):
+ outfd = -1
+
+ oflags = os.O_CREAT | os.O_WRONLY
+ if PDTCRYPT_OVERWRITE is True:
+ oflags |= os.O_TRUNC
+ else:
+ oflags |= os.O_EXCL
+
+ try:
+ outfd = os.open (fname, oflags,
+ stat.S_IRUSR | stat.S_IWUSR, dir_fd=dir_fd)
+ except FileExistsError as exn:
+ noise ("PDT: refusing to overwrite existing file %s" % fname)
+ noise ("")
+ raise RuntimeError ("destination file %s already exists" % fname)
+ if PDTCRYPT_VERBOSE is True:
+ noise ("PDT: new output file %s (fd=%d)" % (fname, outfd))
+
+ return outfd
+
###############################################################################
## freestanding invocation
###############################################################################
PDTCRYPT_SPLIT = 1 << 1 # split archive into individual objects
PDTCRYPT_HASH = 1 << 2 # output scrypt hash for file and given password
-PDTCRYPT_SPLITNAME = "pdtcrypt-object-%d.bin"
+PDTCRYPT_SPLITNAME = "pdtcrypt-object-%d.bin"
+PDTCRYPT_RESCUENAME = "pdtcrypt-rescue-object-%0.5d.bin"
PDTCRYPT_VERBOSE = False
PDTCRYPT_STRICTIVS = False
assert total_obj > 0
fname = PDTCRYPT_SPLITNAME % total_obj
try:
- oflags = os.O_CREAT | os.O_WRONLY
- if PDTCRYPT_OVERWRITE is True:
- oflags |= os.O_TRUNC
- else:
- oflags |= os.O_EXCL
- outfd = os.open (fname, oflags, 0o600, dir_fd=outs)
- if PDTCRYPT_VERBOSE is True:
- noise ("PDT: new output file %s → %d" % (fname, outfd))
- except FileExistsError as exn:
- noise ("PDT: refusing to overwrite existing file %s" % fname)
- noise ("")
- raise PDTSplitError ("destination file %s already exists"
- % fname)
-
+ outfd = open2_dump_file (fname, outs, force=PDTCRYPT_OVERWRITE)
+ except RuntimeError as exn:
+ raise PDTSplitError (exn)
return os.fdopen (outfd, "wb", closefd=True)
noise (line)
-def mode_scan (secret, fname, nacl=None):
+def mode_scan (secret, fname, outs=None, nacl=None):
"""
Dissect a binary file, looking for PDTCRYPT headers and objects.
+
+ If *outs* is supplied, recoverable data will be dumped into the specified
+ directory.
"""
try:
- fd = os.open (fname, os.O_RDONLY)
+ ifd = os.open (fname, os.O_RDONLY)
except FileNotFoundError:
noise ("PDT: failed to open %s readonly" % fname)
noise ("")
try:
if PDTCRYPT_VERBOSE is True:
noise ("PDT: scan for potential sync points")
- cands = locate_hdr_candidates (fd)
+ cands = locate_hdr_candidates (ifd)
if len (cands) == 0:
noise ("PDT: scan complete: input does not contain potential PDT "
"headers; giving up.")
noise ("PDT: scan complete: found %d candidates:" % len (cands))
noise_output_candidates (cands)
except:
- os.close (fd)
+ os.close (ifd)
raise
junk, todo = [], []
try:
+ nobj = 0
for cand in cands:
- vdt, hdr = inspect_hdr (fd, cand)
+ nobj += 1
+ vdt, hdr = inspect_hdr (ifd, cand)
if vdt == HDR_CAND_JUNK:
junk.append (cand)
else:
off0 = cand + PDTCRYPT_HDR_SIZE
if PDTCRYPT_VERBOSE is True:
- noise ("PDT: read payload @%d" % off0)
+ noise ("PDT: obj %d: read payload @%d" % (nobj, off0))
pretty = hdr_fmt_pretty (hdr)
noise (reduce (lambda a, e: (a + "\n" if a else "") + "PDT:\t· " + e,
pretty.splitlines (), ""))
- ok = try_decrypt (fd, off0, hdr, secret) == hdr ["ctsize"]
+ ofd = -1
+ if outs is not None:
+ ofname = PDTCRYPT_RESCUENAME % nobj
+ ofd = open2_dump_file (ofname, outs, force=PDTCRYPT_OVERWRITE)
+
+ try:
+ ok = try_decrypt (ifd, off0, hdr, secret, ofd=ofd) == hdr ["ctsize"]
+ finally:
+ if ofd != -1:
+ os.close (ofd)
if vdt == HDR_CAND_GOOD and ok is True:
noise ("PDT: %d → ✓ valid object %d–%d"
% (cand, off0, off0 + hdr ["ctsize"]))
else:
raise Unreachable
finally:
- os.close (fd)
+ os.close (ifd)
if len (junk) == 0:
noise ("PDT: all headers ok")
secret = None
insspec = None
outsspec = None
+ outs = None
nacl = None
scrypt_format = PDTCRYPT_SCRYPT_DEFAULT
else:
bail ("ERROR: unexpected positional argument “%s”" % arg)
elif subcommand == PDTCRYPT_SUB_SCAN:
- pass
+ if arg in [ "-o", "--out", "--dest", "--sink" ]:
+ outsspec = checked_arg ()
+ if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypt to %s" % outsspec)
+ elif arg in [ "-f", "--force" ]:
+ global PDTCRYPT_OVERWRITE
+ PDTCRYPT_OVERWRITE = True
+ if PDTCRYPT_VERBOSE is True: noise ("PDT: overwrite existing files")
+ else:
+ bail ("ERROR: unexpected positional argument “%s”" % arg)
if secret is None:
if PDTCRYPT_VERBOSE is True:
elif mode & PDTCRYPT_DECRYPT:
bail ("ERROR: encryption requested but no password given")
+ if mode & PDTCRYPT_SPLIT and outsspec is None:
+ bail ("ERROR: split mode is incompatible with stdout sink "
+ "(the default)")
+
+ if subcommand == PDTCRYPT_SUB_SCAN and outsspec is None:
+ pass # no output by default in scan mode
+ elif mode & PDTCRYPT_SPLIT or subcommand == PDTCRYPT_SUB_SCAN:
+ # destination must be directory
+ if outsspec == "-":
+ bail ("ERROR: mode is incompatible with stdout sink")
+ try:
+ try:
+ os.makedirs (outsspec, 0o700)
+ except FileExistsError:
+ # if it’s a directory with appropriate perms, everything is
+ # good; otherwise, below invocation of open(2) will fail
+ pass
+ outs = os.open (outsspec, os.O_DIRECTORY, 0o600)
+ except FileNotFoundError as exn:
+ bail ("ERROR: cannot create target directory “%s”" % outsspec)
+ except NotADirectoryError as exn:
+ bail ("ERROR: target path “%s” is not a directory" % outsspec)
+ else:
+ outs = deptdcrypt_mk_stream (PDTCRYPT_SINK, outsspec or "-")
+
if subcommand == PDTCRYPT_SUB_SCAN:
if insspec is None:
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, insspec, nacl)
+ return True, partial (mode_scan, secret, insspec, outs, nacl=nacl)
if subcommand == PDTCRYPT_SUB_SCRYPT:
if secret [0] == PDTCRYPT_SECRET_KEY:
return True, partial (mode_scrypt, secret [1].encode (), ins, nacl,
fmt=scrypt_format)
- if mode & PDTCRYPT_SPLIT: # destination must be directory
- if outsspec is None or outsspec == "-":
- bail ("ERROR: split mode is incompatible with stdout sink")
-
- try:
- try:
- os.makedirs (outsspec, 0o700)
- except FileExistsError:
- # if it’s a directory with appropriate perms, everything is
- # good; otherwise, below invocation of open(2) will fail
- pass
- outs = os.open (outsspec, os.O_DIRECTORY, 0o600)
- except FileNotFoundError as exn:
- bail ("ERROR: cannot create target directory “%s”" % outsspec)
- except NotADirectoryError as exn:
- bail ("ERROR: target path “%s” is not a directory" % outsspec)
-
- else:
- outs = deptdcrypt_mk_stream (PDTCRYPT_SINK, outsspec or "-")
-
return True, partial (mode_depdtcrypt, mode, secret, ins, outs)