enable strict IV checking by default during decryption
[python-delta-tar] / deltatar / crypto.py
1 #!/usr/bin/env python3
2
3 """
4 Intra2net 2017
5
6 ===============================================================================
7               crypto -- Encryption Layer for the Deltatar Backup
8 ===============================================================================
9
10 Crypto stack:
11
12     - AES-GCM for the symmetric encryption;
13     - Scrypt as KDF.
14
15 References:
16
17     - NIST Recommendation for Block Cipher Modes of Operation: Galois/Counter
18       Mode (GCM) and GMAC
19       http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
20
21     - AES-GCM v1:
22       https://cryptome.org/2014/01/aes-gcm-v1.pdf
23
24     - Authentication weaknesses in GCM
25       http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/comments/CWC-GCM/Ferguson2.pdf
26
27 Errors
28 -------------------------------------------------------------------------------
29
30 Errors fall into roughly three categories:
31
32     - Cryptographical errors or invalid data.
33
34         - ``InvalidGCMTag`` (decryption failed on account of an invalid GCM
35           tag),
36         - ``InvalidIVFixedPart`` (IV fixed part of object not found in list),
37         - ``DuplicateIV`` (the IV of an encrypted object already occurred),
38         - ``DecryptionError`` (used in CLI decryption for presenting error
39           conditions to the user).
40
41     - Incorrect usage of the library.
42
43         - ``InvalidParameter`` (non-conforming user supplied parameter),
44         - ``InvalidHeader`` (data passed for reading not parsable into header),
45         - ``FormatError`` (cannot handle header or parameter version),
46         - ``RuntimeError``.
47
48     - Bad internal state. If one of these is encountered it means that a state
49       was reached that shouldn’t occur during normal processing.
50
51         - ``InternalError``,
52         - ``Unreachable``.
53
54 Also, ``EndOfFile`` is used as a sentinel to communicate that a stream supplied
55 for reading is exhausted.
56
57 Initialization Vectors
58 -------------------------------------------------------------------------------
59
60 Initialization vectors are checked for reuse during the lifetime of a decryptor.
61 The fixed counters for metadata files cannot be reused and attempts to do so
62 will cause a DuplicateIV error. This means the length of objects encrypted with
63 a metadata counter is capped at 63 GB.
64
65 For ordinary, non-metadata payload, there is an optional mode with strict IV
66 checking that causes a crypto context to fail if an IV encountered or created
67 was already used for decrypting or encrypting, respectively, an earlier object.
68 Note that this mode can trigger false positives when decrypting non-linearly,
69 e. g. when traversing the same object multiple times. Since the crypto context
70 has no notion of a position in a PDT encrypted archive, this condition must be
71 sorted out downstream.
72
73 When encrypting with more than one Encrypt context special care must be taken
74 to prevent accidental reuse of IVs. The builtin protection against reuse is
75 only effective for objects encrypted with the same Encrypt handle. If multiple
76 Encrypt handles are used to encrypt with the same combination of password and
77 salt, the encryption becomes susceptible to birthday attacks (bound = 2^32 due
78 to the 64-bit random iv). Thus the use of multiple handles is discouraged.
79
80
81 Command Line Utility
82 -------------------------------------------------------------------------------
83
84 ``crypto.py`` may be invoked as a script for decrypting, validating, and
85 splitting PDT encrypted files. Consult the usage message for details.
86
87 Usage examples:
88
89 Decrypt from stdin using the password ‘foo’: ::
90
91     $ crypto.py process foo -i - -o - <some-file.tar.gz.pdtcrypt >some-file.tar.gz
92
93 Output verbose information about the encrypted objects in the archive: ::
94
95     $ crypto.py process foo -v -i some-file.tar.gz.pdtcrypt -o /dev/null
96     PDT: decrypt from some-file.tar.gz.pdtcrypt
97     PDT: decrypt to /dev/null
98     PDT: source: file some-file.tar.gz.pdtcrypt
99     PDT: sink: file /dev/null
100     PDT: 0 hdr
101     PDT:    · version         = 1                    : 0100
102     PDT:    · paramversion    = 1                    : 0100
103     PDT:    · nacl                                   : d270 b031 00d1 87e2 c946 610d 7b7f 7e5f
104     PDT:    · iv                                     : 02ee 3dd7 a963 1eb1 0100 0000
105     PDT:    · ctsize          = 591                  : 4f02 0000 0000 0000
106     PDT:    · tag                                    : 5b2d 6d8b 8f82 4842 12fd 0b10 b6e3 369b
107     PDT: 64 decrypt obj no. 1,  591 B
108     PDT:    · [64] 0% done, read block (591 B of 591 B remaining)
109     PDT:    · decrypt ciphertext 591 B
110     PDT:    · decrypt plaintext 591 B
111     PDT: 655 finalize
112     …
113
114 Also, the mode *scrypt* allows deriving encryption keys. To calculate the
115 encryption key from the password ‘foo’ and the salt of the first object in a
116 PDT encrypted file: ::
117
118     $ crypto.py scrypt foo -i some-file.pdtcrypt
119     {"paramversion": 1, "salt": "Cqzbk48e3peEjzWto8D0yA==", "key": "JH9EkMwaM4x9F5aim5gK/Q=="}
120
121 The computed 16 byte key is given in hexadecimal notation in the value to
122 ``hash`` and can be fed into Python’s ``binascii.unhexlify()`` to obtain the
123 corresponding binary representation.
124
125 Note that in Scrypt hashing mode, no data integrity checks are being performed.
126 If the wrong password is given, a wrong key will be derived. Whether the password
127 was indeed correct can only be determined by decrypting. Note that since PDT
128 archives essentially consist of a stream of independent objects, the salt and
129 other parameters may change. Thus a key derived using above method from the
130 first object doesn’t necessarily apply to any of the subsequent objects.
131
132 """
133
134 import base64
135 import binascii
136 import bisect
137 import ctypes
138 import io
139 from functools import reduce, partial
140 import mmap
141 import os
142 import struct
143 import stat
144 import sys
145 import time
146 import types
147 import errno
148 try:
149     import enum34
150 except ImportError as exn:
151     pass
152
153 if __name__ == "__main__": ## Work around the import mechanism lest Python’s
154     pwd = os.getcwd()      ## preference for local imports causes a cyclical
155     ## import (crypto → pylibscrypt → […] → ./tarfile → crypto).
156     sys.path = [ p for p in sys.path if p.find ("deltatar") < 0 ]
157
158 import pylibscrypt
159 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
160 from cryptography.hazmat.backends import default_backend
161 import cryptography
162
163
164 __all__ = [ "hdr_make", "hdr_read", "hdr_fmt", "hdr_fmt_pretty"
165           , "scrypt_hashfile"
166           , "PDTCRYPT_HDR_SIZE", "AES_GCM_IV_CNT_DATA"
167           , "AES_GCM_IV_CNT_INFOFILE", "AES_GCM_IV_CNT_INDEX"
168           ]
169
170
171 ###############################################################################
172 ## exceptions
173 ###############################################################################
174
175 class EndOfFile (Exception):
176     """Reached EOF."""
177     remainder = 0
178     msg       = 0
179     def __init__ (self, n=None, msg=None):
180         if n is not None:
181             self.remainder = n
182         self.msg = msg
183
184
185 class InvalidParameter (Exception):
186     """Inputs not valid for PDT encryption."""
187     pass
188
189
190 class InvalidHeader (Exception):
191     """Header not valid."""
192     pass
193
194
195 class InvalidGCMTag (Exception):
196     """
197     The GCM tag calculated during decryption differs from that in the object
198     header.
199     """
200     pass
201
202
203 class InvalidIVFixedPart (Exception):
204     """
205     IV fixed part not in supplied list: either the backup is corrupt or the
206     current object does not belong to it.
207     """
208     pass
209
210
211 class IVFixedPartError (Exception):
212     """
213     Error creating a unique IV fixed part: repeated calls to system RNG yielded
214     the same sequence of bytes as the last IV used.
215     """
216     pass
217
218
219 class InvalidFileCounter (Exception):
220     """
221     When encrypting, an attempted reuse of a dedicated counter (info file,
222     index file) was caught.
223     """
224     pass
225
226
227 class DuplicateIV (Exception):
228     """
229     During encryption, the current IV fixed part is identical to an already
230     existing IV (same prefix and file counter). This indicates tampering or
231     programmer error and cannot be recovered from.
232     """
233     pass
234
235
236 class NonConsecutiveIV (Exception):
237     """
238     IVs not numbered consecutively. This is a hard error with strict IV
239     checking. Precludes random access to the encrypted objects.
240     """
241     pass
242
243
244 class CiphertextTooLong (Exception):
245     """
246     An attempt was made to decrypt more data than the ciphertext size declared
247     in the object header.
248     """
249     pass
250
251
252 class FormatError (Exception):
253     """Unusable parameters in header."""
254     pass
255
256
257 class DecryptionError (Exception):
258     """Error during decryption with ``crypto.py`` on the command line."""
259     pass
260
261
262 class Unreachable (Exception):
263     """
264     Makeshift __builtin_unreachable(); always a programmer error if
265     thrown.
266     """
267     pass
268
269
270 class InternalError (Exception):
271     """Errors not ascribable to bad user inputs or cryptography."""
272     pass
273
274
275 ###############################################################################
276 ## crypto layer version
277 ###############################################################################
278
279 ENCRYPTION_PARAMETERS = \
280     { 0: \
281         { "kdf": ("dummy", 16)
282         , "enc": "passthrough" }
283     , 1: \
284         { "kdf": ( "scrypt"
285                  , { "dkLen"    : 16
286                    , "N"        : 1 << 16
287                    , "r"        : 8
288                    , "p"        : 1
289                    , "NaCl_LEN" : 16 })
290         , "enc": "aes-gcm" } }
291
292 # Mode zero is unencrypted and only provided for testing purposes. nless
293 # the encryptor / decryptor are explicitly instructed to do so.
294 MIN_SECURE_PARAMETERS = 1
295
296 ###############################################################################
297 ## constants
298 ###############################################################################
299
300 PDTCRYPT_HDR_MAGIC = b"PDTCRYPT"
301
302 PDTCRYPT_HDR_SIZE_MAGIC          = 8     #  8
303 PDTCRYPT_HDR_SIZE_VERSION        = 2     # 10
304 PDTCRYPT_HDR_SIZE_PARAMVERSION   = 2     # 12
305 PDTCRYPT_HDR_SIZE_NACL           = 16    # 28
306 PDTCRYPT_HDR_SIZE_IV             = 12    # 40
307 PDTCRYPT_HDR_SIZE_CTSIZE         = 8     # 48
308 PDTCRYPT_HDR_SIZE_TAG            = 16    # 64 GCM auth tag
309
310 PDTCRYPT_HDR_SIZE = PDTCRYPT_HDR_SIZE_MAGIC        + PDTCRYPT_HDR_SIZE_VERSION \
311                   + PDTCRYPT_HDR_SIZE_PARAMVERSION + PDTCRYPT_HDR_SIZE_NACL    \
312                   + PDTCRYPT_HDR_SIZE_IV           + PDTCRYPT_HDR_SIZE_CTSIZE  \
313                   + PDTCRYPT_HDR_SIZE_TAG # = 64
314
315 # precalculate offsets since Python can’t do constant folding over names
316 HDR_OFF_VERSION      = PDTCRYPT_HDR_SIZE_MAGIC
317 HDR_OFF_PARAMVERSION = HDR_OFF_VERSION      + PDTCRYPT_HDR_SIZE_VERSION
318 HDR_OFF_NACL         = HDR_OFF_PARAMVERSION + PDTCRYPT_HDR_SIZE_PARAMVERSION
319 HDR_OFF_IV           = HDR_OFF_NACL         + PDTCRYPT_HDR_SIZE_NACL
320 HDR_OFF_CTSIZE       = HDR_OFF_IV           + PDTCRYPT_HDR_SIZE_IV
321 HDR_OFF_TAG          = HDR_OFF_CTSIZE       + PDTCRYPT_HDR_SIZE_CTSIZE
322
323 FMT_UINT16_LE = "<H"
324 FMT_UINT64_LE = "<Q"
325 FMT_I2N_IV    = "<8sL"   # 8 random bytes ‖ 32 bit counter
326 FMT_I2N_HDR   = ("<"     # host byte order
327                  "8s"    # magic
328                  "H"     # version
329                  "H"     # paramversion
330                  "16s"   # sodium chloride
331                  "12s"   # iv
332                  "Q"     # size
333                  "16s")  # GCM tag
334
335 # aes+gcm
336 AES_KEY_SIZE                  = 16 # b"0123456789abcdef"
337 AES_KEY_SIZE_B64              = 24 # b'MDEyMzQ1Njc4OWFiY2RlZg=='
338
339 AES_GCM_MAX_SIZE              = (1 << 36) - (1 << 5) # 2^39 - 2^8 b ≅ 64 GB.
340                                 # Source: NIST SP 800-38D section 5.2.1.1
341 # https://crypto.stackexchange.com/questions/31793/plain-text-size-limits-for-aes-gcm-mode-just-64gb
342
343 PDTCRYPT_MAX_OBJ_SIZE_DEFAULT = 63 * (1 << 30)       #                63 GB
344 PDTCRYPT_MAX_OBJ_SIZE         = PDTCRYPT_MAX_OBJ_SIZE_DEFAULT
345
346 # index and info files are written on-the fly while encrypting so their
347 # counters must be available in advance
348 AES_GCM_IV_CNT_INFOFILE     = 1 # constant
349 AES_GCM_IV_CNT_INDEX        = AES_GCM_IV_CNT_INFOFILE + 1
350 AES_GCM_IV_CNT_DATA         = AES_GCM_IV_CNT_INDEX    + 1 # also for multivolume
351 AES_GCM_IV_CNT_MAX_DEFAULT  = 0xffFFffFF
352 AES_GCM_IV_CNT_MAX          = AES_GCM_IV_CNT_MAX_DEFAULT
353
354 # IV structure and generation
355 PDTCRYPT_IV_GEN_MAX_RETRIES = 10 # ×
356 PDTCRYPT_IV_FIXEDPART_SIZE  =  8 # B
357 PDTCRYPT_IV_COUNTER_SIZE    =  4 # B
358
359 # secret type: PW of string | KEY of char [16]
360 PDTCRYPT_SECRET_PW   = 0
361 PDTCRYPT_SECRET_KEY  = 1
362
363 ###############################################################################
364 ## header, trailer
365 ###############################################################################
366 #
367 # Interface:
368 #
369 #    struct hdrinfo
370 #      { version      : u16
371 #      , paramversion : u16
372 #      , nacl         : [u8; 16]
373 #      , iv           : [u8; 12]
374 #      , ctsize       : usize
375 #      , tag          : [u8; 16] }
376 #
377 #    fn hdr_read (f : handle) -> hdrinfo;
378 #    fn hdr_make (f : handle, h : hdrinfo) -> IOResult<usize>;
379 #    fn hdr_fmt (h : hdrinfo) -> String;
380 #
381
382 def hdr_read (data):
383     """
384     Read bytes as header structure.
385
386     If the input could not be interpreted as a header, fail with
387     ``InvalidHeader``.
388     """
389
390     try:
391         mag, version, paramversion, nacl, iv, ctsize, tag = \
392             struct.unpack (FMT_I2N_HDR, data)
393     except Exception as exn:
394         raise InvalidHeader ("error unpacking header from [%r]: %s"
395                              % (binascii.hexlify (data), str (exn)))
396
397     if mag != PDTCRYPT_HDR_MAGIC:
398         raise InvalidHeader ("bad magic in header: expected [%s], got [%s]"
399                              % (PDTCRYPT_HDR_MAGIC, mag))
400
401     return \
402         {      "version" : version
403         , "paramversion" : paramversion
404         ,         "nacl" : nacl
405         ,           "iv" : iv
406         ,       "ctsize" : ctsize
407         ,          "tag" : tag
408         }
409
410
411 def hdr_read_stream (instr):
412     """
413     Read header from stream at the current position.
414
415     Fail with ``InvalidHeader`` if insufficient bytes were read from the
416     stream, or if the content could not be interpreted as a header.
417     """
418     data = instr.read(PDTCRYPT_HDR_SIZE)
419     ldata = len (data)
420     if ldata == 0:
421         raise EndOfFile
422     elif ldata != PDTCRYPT_HDR_SIZE:
423         raise InvalidHeader ("hdr_read_stream: expected %d B, received %d B"
424                              % (PDTCRYPT_HDR_SIZE, ldata))
425     return hdr_read (data)
426
427
428 def hdr_from_params (version, paramversion, nacl, iv, ctsize, tag):
429     """
430     Assemble the necessary values into a PDTCRYPT header.
431
432     :type      version: int to fit   uint16_t
433     :type paramversion: int to fit   uint16_t
434     :type         nacl: bytes to fit uint8_t[16]
435     :type           iv: bytes to fit uint8_t[12]
436     :type         size: int to fit   uint64_t
437     :type          tag: bytes to fit uint8_t[16]
438     """
439     buf  = bytearray (PDTCRYPT_HDR_SIZE)
440     bufv = memoryview (buf)
441
442     try:
443         struct.pack_into (FMT_I2N_HDR, bufv, 0,
444                           PDTCRYPT_HDR_MAGIC,
445                           version, paramversion, nacl, iv, ctsize, tag)
446     except Exception as exn:
447         return False, "error assembling header: %s" % str (exn)
448
449     return True, bytes (buf)
450
451
452 def hdr_make_dummy (s):
453     """
454     Create a header sized block of bytes initialized to a value derived from a
455     string. Used to verify we’ve jumped back correctly to the actual position
456     of the object header.
457     """
458     c = reduce (lambda a, c: a + ord(c), s, 0) % 0xFF
459     return bytes (bytearray (struct.pack ("B", c)) * PDTCRYPT_HDR_SIZE)
460
461
462 def hdr_make (hdr):
463     """
464     Assemble a header from the given header structure.
465     """
466     return hdr_from_params (version=hdr.get("version"),
467                             paramversion=hdr.get("paramversion"),
468                             nacl=hdr.get("nacl"), iv=hdr.get("iv"),
469                             ctsize=hdr.get("ctsize"), tag=hdr.get("tag"))
470
471
472 HDR_FMT = "I2n_header { version: %d, paramversion: %d, nacl: %s[%d]," \
473                       " iv: %s[%d], ctsize: %d, tag: %s[%d] }"
474
475 def hdr_fmt (h):
476     """Format a header structure into readable output."""
477     return HDR_FMT % (h["version"], h["paramversion"],
478                       binascii.hexlify (h["nacl"]), len(h["nacl"]),
479                       binascii.hexlify (h["iv"]), len(h["iv"]),
480                       h["ctsize"],
481                       binascii.hexlify (h["tag"]), len(h["tag"]))
482
483
484 def hex_spaced_of_bytes (b):
485     """Format bytes object, hexdump style."""
486     return " ".join ([ "%.2x%.2x" % (c1, c2)
487                        for c1, c2 in zip (b[0::2], b[1::2]) ]) \
488          + (len (b) | 1 == len (b) and " %.2x" % b[-1] or "") # odd lengths
489
490
491 def hdr_iv_counter (h):
492     """Extract the variable part of the IV of the given header."""
493     _fixed, cnt = struct.unpack (FMT_I2N_IV, h ["iv"])
494     return cnt
495
496
497 def hdr_iv_fixed (h):
498     """Extract the fixed part of the IV of the given header."""
499     fixed, _cnt = struct.unpack (FMT_I2N_IV, h ["iv"])
500     return fixed
501
502
503 hdr_dump = hex_spaced_of_bytes
504
505
506 HDR_FMT_PRETTY = \
507 """version         = %-4d                 : %s
508 paramversion    = %-4d                 : %s
509 nacl                                   : %s
510 iv                                     : %s
511 ctsize          = %-20d : %s
512 tag                                    : %s
513 """
514
515 def hdr_fmt_pretty (h):
516     """
517     Format header structure into multi-line representation of its contents and
518     their raw representation. (Omit the implicit “PDTCRYPT” magic bytes that
519     precede every header.)
520     """
521     return HDR_FMT_PRETTY \
522                 % (h["version"],
523                    hex_spaced_of_bytes (struct.pack (FMT_UINT16_LE, h["version"])),
524                    h["paramversion"],
525                    hex_spaced_of_bytes (struct.pack (FMT_UINT16_LE, h["paramversion"])),
526                    hex_spaced_of_bytes (h["nacl"]),
527                    hex_spaced_of_bytes (h["iv"]),
528                    h["ctsize"],
529                    hex_spaced_of_bytes (struct.pack (FMT_UINT64_LE, h["ctsize"])),
530                    hex_spaced_of_bytes (h["tag"]))
531
532 IV_FMT = "((f %s) (c %d))"
533
534 def iv_fmt (iv):
535     """Format the two components of an IV in a readable fashion."""
536     fixed, cnt = struct.unpack (FMT_I2N_IV, iv)
537     return IV_FMT % (binascii.hexlify (fixed), cnt)
538
539
540 ###############################################################################
541 ## restoration
542 ###############################################################################
543
544 class Location (object):
545     n = 0
546     offset = 0
547
548 def restore_loc_fmt (loc):
549     return "%d off:%d" \
550         % (loc.n, loc.offset)
551
552 def locate_hdr_candidates (fd):
553     """
554     Walk over instances of the magic string in the payload, collecting their
555     positions. If the offset of the first found instance is not zero, the file
556     begins with leading garbage. Used by desaster recovery.
557
558     :return:    The list of offsets in the file.
559     """
560     cands = []
561
562     mm = mmap.mmap(fd, 0, mmap.MAP_SHARED, mmap.PROT_READ)
563     pos = 0
564     while True:
565         pos = mm.find (PDTCRYPT_HDR_MAGIC, pos)
566         if pos == -1:
567             break
568         cands.append (pos)
569         pos += 1
570
571     return cands
572
573
574 HDR_CAND_GOOD       = 0 # header marks begin of valid object
575 HDR_CAND_FISHY      = 1 # inconclusive (tag mismatch, obj overlap etc.)
576 HDR_CAND_JUNK       = 2 # not a header / object unreadable
577
578 HDR_VERDICT_NAME = \
579     { HDR_CAND_GOOD  : "valid"
580     , HDR_CAND_FISHY : "fishy"
581     , HDR_CAND_JUNK  : "junk"
582     }
583
584
585 def verdict_fmt (vdt):
586     return HDR_VERDICT_NAME [vdt]
587
588
589 def inspect_hdr (fd, off):
590     """
591     Attempt to parse a header in *fd* at position *off*.
592
593     Returns a verdict about the quality of that header plus the parsed header
594     when readable.
595     """
596
597     _ = os.lseek (fd, off, os.SEEK_SET)
598
599     if os.lseek (fd, 0, os.SEEK_CUR) != off:
600         if PDTCRYPT_VERBOSE is True:
601             noise ("PDT: %d → dismissed (lseek() past EOF)" % off)
602         return HDR_CAND_JUNK, None
603
604     raw = os.read (fd, PDTCRYPT_HDR_SIZE)
605     if len (raw) != PDTCRYPT_HDR_SIZE:
606         if PDTCRYPT_VERBOSE is True:
607             noise ("PDT: %d → dismissed (EOF inside header)" % off)
608         return HDR_CAND_JUNK, None
609
610     try:
611         hdr = hdr_read (raw)
612     except InvalidHeader as exn:
613         if PDTCRYPT_VERBOSE is True:
614             noise ("PDT: %d → dismissed (invalid: [%s])" % (off, str (exn)))
615         return HDR_CAND_JUNK, None
616
617     obj0 = off + PDTCRYPT_HDR_SIZE
618     objX = obj0 + hdr ["ctsize"]
619
620     eof  = os.lseek (fd, 0, os.SEEK_END)
621     if eof < objX:
622         if PDTCRYPT_VERBOSE is True:
623             noise ("PDT: %d → EOF inside object (%d≤%d≤%d); adjusting size to "
624                    "%d" % (off, obj0, eof, objX, (eof - obj0)))
625         # try reading up to the end
626         hdr ["ctsize"] = eof - obj0
627         return HDR_CAND_FISHY, hdr
628
629     return HDR_CAND_GOOD, hdr
630
631
632 def try_decrypt (ifd, off, hdr, secret, ofd=-1):
633     """
634     Attempt to decrypt the object in the (seekable) descriptor *ifd* starting
635     at *off* using the metadata in *hdr* and *secret*. An output fd can be
636     specified with *ofd*; if it is *-1* – the default –, the decrypted payload
637     will be discarded.
638
639     Always creates a fresh decryptor, so validation steps across objects don’t
640     apply.
641
642     Errors during GCM tag validation are ignored. Used by desaster recovery.
643     """
644     ctleft = hdr ["ctsize"]
645     pos    = off
646
647     ks = secret [0]
648     if ks == PDTCRYPT_SECRET_PW:
649         decr = Decrypt (password=secret [1])
650     elif ks == PDTCRYPT_SECRET_KEY:
651         key = secret [1]
652         decr = Decrypt (key=key)
653     else:
654         raise RuntimeError
655
656     decr.next (hdr)
657
658     try:
659         os.lseek (ifd, pos, os.SEEK_SET)
660         pt = b""
661         while ctleft > 0:
662             cnksiz = min (ctleft, PDTCRYPT_BLOCKSIZE)
663             cnk    = os.read (ifd, cnksiz)
664             ctleft -= cnksiz
665             pos    += cnksiz
666             pt     = decr.process (cnk)
667             if ofd != -1:
668                 os.write (ofd, pt)
669         try:
670             pt = decr.done ()
671         except InvalidGCMTag:
672             noise ("PDT: GCM tag mismatch for object %d–%d"
673                    % (off, off + hdr ["ctsize"]))
674         if len (pt) > 0 and ofd != -1:
675             os.write (ofd, pt)
676
677     except Exception as exn:
678         noise ("PDT: error decrypting object %d–%d@%d, %d B remaining [%s]"
679                % (off, off + hdr ["ctsize"], pos, ctleft, exn))
680         raise
681
682     return pos - off
683
684
685 def readable_objects_offsets (ifd, secret, cands):
686     """
687     From a list of candidates, locate the ones that mark the start of actual
688     readable PDTCRYPT objects.
689     """
690     good = []
691
692     for i, cand in enumerate (cands):
693         vdt, hdr = inspect_hdr (ifd, cand)
694         if vdt == HDR_CAND_JUNK:
695             pass # ignore unreadable ones
696         elif vdt in [HDR_CAND_GOOD, HDR_CAND_FISHY]:
697             ctsize =  hdr ["ctsize"]
698             off0 = cand + PDTCRYPT_HDR_SIZE
699             ok = try_decrypt (ifd, off0, hdr, secret) == ctsize
700             if ok is True:
701                 good.append ((cand, off0 + ctsize))
702
703     overlap = find_overlaps (good)
704
705     return [ g [0] for g in good ]
706
707
708 def reconstruct_offsets (fname, secret):
709     ifd = os.open (fname, os.O_RDONLY)
710
711     try:
712         cands = locate_hdr_candidates (ifd)
713         return readable_objects_offsets (ifd, secret, cands)
714     finally:
715         os.close (ifd)
716
717
718 ###############################################################################
719 ## helpers
720 ###############################################################################
721
722 def make_secret (password=None, key=None):
723     """
724     Safely create a “secret” value that consists either of a key or a password.
725     Inputs are validated: the password is accepted as (UTF-8 encoded) bytes or
726     string; for the key only a bytes object of the proper size or a base64
727     encoded string thereof is accepted.
728
729     If both are provided, the key is preferred over the password; no checks are
730     performed whether the key is derived from the password.
731
732     :returns: secret value if inputs were acceptable | None otherwise.
733     """
734     if key is not None:
735         if isinstance (key, str) is True:
736             key = key.encode ("utf-8")
737         if isinstance (key, bytes) is True:
738             if len (key) == AES_KEY_SIZE:
739                 return (PDTCRYPT_SECRET_KEY, key)
740             if len (key) == AES_KEY_SIZE * 2:
741                 try:
742                     key = binascii.unhexlify (key)
743                     return (PDTCRYPT_SECRET_KEY, key)
744                 except binascii.Error: # garbage in string
745                     pass
746             if len (key) == AES_KEY_SIZE_B64:
747                 try:
748                     key = base64.b64decode (key)
749                     # the base64 processor is very tolerant and allows for
750                     # arbitrary trailing and leading data thus the data obtained
751                     # must be checked for the proper length
752                     if len (key) == AES_KEY_SIZE:
753                         return (PDTCRYPT_SECRET_KEY, key)
754                 except binascii.Error: # “incorrect padding”
755                     pass
756     elif password is not None:
757         if isinstance (password, str) is True:
758             return (PDTCRYPT_SECRET_PW, password)
759         elif isinstance (password, bytes) is True:
760             try:
761                 password = password.decode ("utf-8")
762                 return (PDTCRYPT_SECRET_PW, password)
763             except UnicodeDecodeError:
764                 pass
765
766     return None
767
768
769 ###############################################################################
770 ## passthrough / null encryption
771 ###############################################################################
772
773 class PassthroughCipher (object):
774
775     tag = struct.pack ("<QQ", 0, 0)
776
777     def __init__          (self)    : pass
778
779     def update            (self, b) : return b
780
781     def finalize          (self)    : return b""
782
783     def finalize_with_tag (self, _) : return b""
784
785 ###############################################################################
786 ## convenience wrapper
787 ###############################################################################
788
789
790 def kdf_dummy (klen, password, _nacl):
791     """
792     Fake KDF for testing purposes that is called when parameter version zero is
793     encountered.
794     """
795     q, r = divmod (klen, len (password))
796     if isinstance (password, bytes) is False:
797         password = password.encode ()
798     return password * q + password [:r], b""
799
800
801 SCRYPT_KEY_MEMO = { } # static because needed for both the info file and the archive
802
803
804 def kdf_scrypt (params, password, nacl):
805     """
806     Wrapper for the Scrypt KDF, corresponds to parameter version one. The
807     computation result is memoized based on the inputs to facilitate spawning
808     multiple encryption contexts.
809     """
810     N = params["N"]
811     r = params["r"]
812     p = params["p"]
813     dkLen = params["dkLen"]
814
815     if nacl is None:
816         nacl = os.urandom (params["NaCl_LEN"])
817
818     key_parms = (password, nacl, N, r, p, dkLen)
819     global SCRYPT_KEY_MEMO
820     if key_parms not in SCRYPT_KEY_MEMO:
821         SCRYPT_KEY_MEMO [key_parms] = \
822             pylibscrypt.scrypt (password, nacl, N, r, p, dkLen)
823     return SCRYPT_KEY_MEMO [key_parms], nacl
824
825
826 def kdf_by_version (paramversion=None, defs=None):
827     """
828     Pick the KDF handler corresponding to the parameter version or the
829     definition set.
830
831     :rtype: function (password : str, nacl : str) -> str
832     """
833     if paramversion is not None:
834         defs = ENCRYPTION_PARAMETERS.get(paramversion, None)
835     if defs is None:
836         raise InvalidParameter ("no encryption parameters for version %r"
837                                 % paramversion)
838     (kdf, params) = defs["kdf"]
839     fn = None
840     if kdf == "scrypt" : fn = kdf_scrypt
841     elif kdf == "dummy"  : fn = kdf_dummy
842     if fn is None:
843         raise ValueError ("key derivation method %r unknown" % kdf)
844     return partial (fn, params)
845
846
847 ###############################################################################
848 ## SCRYPT hashing
849 ###############################################################################
850
851 def scrypt_hashsource (pw, ins):
852     """
853     Calculate the SCRYPT hash from the password and the information contained
854     in the first header found in ``ins``.
855
856     This does not validate whether the first object is encrypted correctly.
857     """
858     if isinstance (pw, str) is True:
859         pw = str.encode (pw)
860     elif isinstance (pw, bytes) is False:
861         raise InvalidParameter ("password must be a string, not %s"
862                                 % type (pw))
863     if isinstance (ins, io.BufferedReader) is False and \
864             isinstance (ins, io.FileIO) is False:
865         raise InvalidParameter ("file to hash must be opened in “binary” mode")
866     hdr = None
867     try:
868         hdr = hdr_read_stream (ins)
869     except EndOfFile as exn:
870         noise ("PDT: malformed input: end of file reading first object header")
871         noise ("PDT:")
872         return 1
873
874     nacl = hdr ["nacl"]
875     pver = hdr ["paramversion"]
876     if PDTCRYPT_VERBOSE is True:
877         noise ("PDT: salt of first object          : %s" % binascii.hexlify (nacl))
878         noise ("PDT: parameter version of archive  : %d" % pver)
879
880     try:
881         defs = ENCRYPTION_PARAMETERS.get(pver, None)
882         kdfname, params = defs ["kdf"]
883         if kdfname != "scrypt":
884             noise ("PDT: input is not an SCRYPT archive")
885             noise ("")
886             return 1
887         kdf = kdf_by_version (None, defs)
888     except ValueError as exn:
889         noise ("PDT: object has unknown parameter version %d" % pver)
890
891     hsh, _void = kdf (pw, nacl)
892
893     return hsh, nacl, hdr ["version"], pver
894
895
896 def scrypt_hashfile (pw, fname):
897     """
898     Calculate the SCRYPT hash from the password and the information contained
899     in the first header found in the given file. The header is read only at
900     offset zero.
901     """
902     with deptdcrypt_mk_stream (PDTCRYPT_SOURCE, fname  or "-") as ins:
903         hsh, _void, _void, _void = scrypt_hashsource (pw, ins)
904         return hsh
905
906
907 ###############################################################################
908 ## AES-GCM context
909 ###############################################################################
910
911 class Crypto (object):
912     """
913     Encryption context to remain alive throughout an entire tarfile pass.
914     """
915     enc  = None
916     nacl = None
917     key  = None
918     cnt  = None # file counter (uint32_t != 0)
919     iv   = None # current IV
920     fixed        = None  # accu for 64 bit fixed parts of IV
921     used_ivs     = None  # tracks IVs
922     strict_ivs   = False # if True, panic on duplicate or non-consecutive object IV
923     password     = None
924     paramversion = None
925     insecure     = False # allow plaintext parameters
926     stats = { "in"  : 0
927             , "out" : 0
928             , "obj" : 0 }
929
930     ctsize  = -1
931     ptsize  = -1
932     info_counter_used  = False
933     index_counter_used = False
934
935     def __init__ (self, *al, **akv):
936         self.used_ivs = set ()
937         self.set_parameters (*al, **akv)
938
939
940     def next_fixed (self):
941         # NOP for decryption
942         pass
943
944
945     def set_object_counter (self, cnt=None):
946         """
947         Safely set the internal counter of encrypted objects. Numerous
948         constraints apply:
949
950         The same counter may not be reused in combination with one IV fixed
951         part. This is validated elsewhere in the IV handling.
952
953         Counter zero is invalid. The first two counters are reserved for
954         metadata. The implementation does not allow for splitting metadata
955         files over multiple encrypted objects. (This would be possible by
956         assigning new fixed parts.) Thus in a Deltatar backup there is at most
957         one object with a counter value of one and two. On creation of a
958         context, the initial counter may be chosen. The globals
959         ``AES_GCM_IV_CNT_INFOFILE`` and ``AES_GCM_IV_CNT_INDEX`` can be used to
960         request one of the reserved values. If one of these values has been
961         used, any further attempt of setting the counter to that value will
962         be rejected with an ``InvalidFileCounter`` exception.
963
964         Out of bounds values (i. e. below one and more than the maximum of 2³²)
965         cause an ``InvalidParameter`` exception to be thrown.
966         """
967         if cnt is None:
968             self.cnt = AES_GCM_IV_CNT_DATA
969             return
970         if cnt == 0 or cnt > AES_GCM_IV_CNT_MAX + 1:
971             raise InvalidParameter ("invalid counter value %d requested: "
972                                     "acceptable values are from 1 to %d"
973                                     % (cnt, AES_GCM_IV_CNT_MAX))
974         if cnt == AES_GCM_IV_CNT_INFOFILE:
975             if self.info_counter_used is True:
976                 raise InvalidFileCounter ("attempted to reuse info file "
977                                           "counter %d: must be unique" % cnt)
978             self.info_counter_used = True
979         elif cnt == AES_GCM_IV_CNT_INDEX:
980             if self.index_counter_used is True:
981                 raise InvalidFileCounter ("attempted to reuse index file "
982                                           "counter %d: must be unique" % cnt)
983             self.index_counter_used = True
984         if cnt <= AES_GCM_IV_CNT_MAX:
985             self.cnt = cnt
986             return
987         # cnt == AES_GCM_IV_CNT_MAX + 1 → wrap
988         self.cnt = AES_GCM_IV_CNT_DATA
989         self.next_fixed ()
990
991
992     def set_parameters (self, password=None, key=None, paramversion=None,
993                         nacl=None, counter=None, strict_ivs=False,
994                         insecure=False):
995         """
996         Configure the internal state of a crypto context. Not intended for
997         external use.
998
999         A parameter version indicating passthrough (plaintext) mode is rejected
1000         with an ``InvalidParameter`` unless ``insecure`` is set.
1001         """
1002         self.next_fixed ()
1003         self.set_object_counter (counter)
1004         self.strict_ivs = strict_ivs
1005
1006         self.insecure = insecure
1007
1008         if paramversion is not None:
1009             if self.insecure is False \
1010                     and paramversion < MIN_SECURE_PARAMETERS:
1011                 raise InvalidParameter \
1012                     ("set_parameters: requested parameter version %d but "
1013                      "plaintext encryption disallowed in secure context!"
1014                      % paramversion)
1015             self.paramversion = paramversion
1016
1017         if key is not None:
1018             self.key, self.nacl = key, nacl
1019             return
1020
1021         if password is not None:
1022             if isinstance (password, bytes) is False:
1023                 password = str.encode (password)
1024             self.password = password
1025             if paramversion is None and nacl is None:
1026                 # postpone key setup until first header is available
1027                 return
1028             kdf = kdf_by_version (paramversion)
1029             if kdf is not None:
1030                 self.key, self.nacl = kdf (password, nacl)
1031
1032
1033     def process (self, buf):
1034         """
1035         Encrypt / decrypt a buffer. Invokes the ``.update()`` method on the
1036         wrapped encryptor or decryptor, respectively.
1037
1038         The Cryptography exception ``AlreadyFinalized`` is translated to an
1039         ``InternalError`` at this point. It may occur in sound code when the GC
1040         closes an encrypting stream after an error. Everywhere else it must be
1041         treated as a bug.
1042         """
1043         if self.enc is None:
1044             raise RuntimeError ("process: context not initialized")
1045         self.stats ["in"] += len (buf)
1046         try:
1047             out = self.enc.update (buf)
1048         except cryptography.exceptions.AlreadyFinalized as exn:
1049             raise InternalError (exn)
1050         self.stats ["out"] += len (out)
1051         return out
1052
1053
1054     def next (self, password, paramversion, nacl):
1055         """
1056         Prepare for encrypting another object: Reset the data counters and
1057         change the configuration in case one of the variable parameters differs
1058         from the last object.
1059         """
1060         self.ctsize = 0
1061         self.ptsize = 0
1062         self.stats ["obj"] += 1
1063
1064         if (   self.paramversion != paramversion
1065             or self.password     != password
1066             or self.nacl         != nacl):
1067             self.set_parameters (password=password, paramversion=paramversion,
1068                                  nacl=nacl, strict_ivs=self.strict_ivs,
1069                                  insecure=self.insecure)
1070
1071
1072     def counters (self):
1073         """
1074         Access the data counters.
1075         """
1076         return self.stats ["obj"], self.stats ["in"], self.stats ["out"]
1077
1078
1079     def drop (self):
1080         """
1081         Clear the current context regardless of its finalization state. The
1082         next operation must be ``.next()``.
1083         """
1084         self.enc = None
1085
1086
1087     def get_used_ivs (self):
1088         """
1089         Get the set of IVs that were used so far during the lifetime of
1090         this context. Useful to check for IV reuse if multiple encryption
1091         contexts were used independently.
1092         """
1093         return self.used_ivs
1094
1095
1096     def reset_last_iv (self):
1097         """
1098         Implemented only for decryptor; no-op otherwise.
1099         """
1100         pass
1101
1102
1103 class Encrypt (Crypto):
1104
1105     lastinfo     = None
1106     version      = None
1107     paramenc     = None
1108
1109     def __init__ (self, version, paramversion, password=None, key=None, nacl=None,
1110                   counter=AES_GCM_IV_CNT_DATA, strict_ivs=False, insecure=False):
1111         """
1112         The ctor will throw immediately if one of the parameters does not conform
1113         to our expectations.
1114
1115         :type      version: int to fit   uint16_t
1116         :type paramversion: int to fit   uint16_t
1117         :param    password: mutually exclusive with ``key``
1118         :type     password: bytes
1119         :param         key: mutually exclusive with ``password``
1120         :type          key: bytes
1121         :type         nacl: bytes
1122         :type      counter: initial object counter the values
1123                             ``AES_GCM_IV_CNT_INFOFILE`` and
1124                             ``AES_GCM_IV_CNT_INDEX`` are unique in each backup set
1125                             and cannot be reused even with different fixed parts.
1126         :type   strict_ivs: bool
1127         :param  strict_ivs: Enable paranoid tracking of IVs.
1128         :type     insecure: bool
1129         :param    insecure: whether to permit passthrough mode
1130
1131         *Security considerations*: The ``class Encrypt`` handle guarantees that
1132         all random parts (first eight bytes) of the IVs used for encrypting
1133         objects are unique. This guarantee does *not* apply across handles if
1134         multiple handles are used with the same combination of password and
1135         salt. Thus, use of multiple handles with the same combination of password
1136         and salt is subject to birthday attacks with a bound of 2^32. To avoid
1137         collisions, the application should keep the number of handles as low
1138         as possible and check for reuse by comparing the set of IVs used of all
1139         handles that were created (accessible using the ``get_used_ivs`` method).
1140         """
1141         if         password is     None and key is     None \
1142                 or password is not None and key is not None :
1143             raise InvalidParameter ("__init__: need either key or password")
1144
1145         if key is not None:
1146             if isinstance (key, bytes) is False:
1147                 raise InvalidParameter ("__init__: key must be provided as "
1148                                         "bytes, not %s" % type (key))
1149             if nacl is None:
1150                 raise InvalidParameter ("__init__: salt must be provided along "
1151                                         "with encryption key")
1152         else: # password, no key
1153             if isinstance (password, str) is False:
1154                 raise InvalidParameter ("__init__: password must be a string, not %s"
1155                                         % type (password))
1156             if len (password) == 0:
1157                 raise InvalidParameter ("__init__: supplied empty password but not "
1158                                         "permitted for PDT encrypted files")
1159         # version
1160         if isinstance (version, int) is False:
1161             raise InvalidParameter ("__init__: version number must be an "
1162                                     "integer, not %s" % type (version))
1163         if version < 0:
1164             raise InvalidParameter ("__init__: version number must be a "
1165                                     "nonnegative integer, not %d" % version)
1166         # paramversion
1167         if isinstance (paramversion, int) is False:
1168             raise InvalidParameter ("__init__: crypto parameter version number "
1169                                     "must be an integer, not %s"
1170                                     % type (paramversion))
1171         if paramversion < 0:
1172             raise InvalidParameter ("__init__: crypto parameter version number "
1173                                     "must be a nonnegative integer, not %d"
1174                                     % paramversion)
1175         # salt
1176         if nacl is not None:
1177             if isinstance (nacl, bytes) is False:
1178                 raise InvalidParameter ("__init__: salt given, but of type %s "
1179                                         "instead of bytes" % type (nacl))
1180             # salt length would depend on the actual encryption so it can’t be
1181             # validated at this point
1182         self.fixed        = [ ]
1183         self.version      = version
1184         self.paramenc     = ENCRYPTION_PARAMETERS.get (paramversion) ["enc"]
1185
1186         super().__init__ (password, key, paramversion, nacl, counter=counter,
1187                           strict_ivs=strict_ivs, insecure=insecure)
1188
1189
1190     def next_fixed (self, retries=PDTCRYPT_IV_GEN_MAX_RETRIES):
1191         """
1192         Generate the next IV fixed part by reading eight bytes from
1193         ``/dev/urandom``. The buffer so obtained is tested against the fixed
1194         parts used so far to prevent accidental reuse of IVs. After a
1195         configurable number of attempts to create a unique fixed part, it will
1196         refuse to continue with an ``IVFixedPartError``. This is unlikely to
1197         ever happen on a normal system but may detect an issue with the random
1198         generator.
1199
1200         The list of fixed parts that were used by the context at hand can be
1201         accessed through the ``.fixed`` list. Its last element is the fixed
1202         part currently in use.
1203         """
1204         i = 0
1205         while i < retries:
1206             fp = os.urandom (PDTCRYPT_IV_FIXEDPART_SIZE)
1207             if fp not in self.fixed:
1208                 self.fixed.append (fp)
1209                 return
1210             i += 1
1211         raise IVFixedPartError ("error obtaining a unique IV fixed part from "
1212                                 "/dev/urandom; giving up after %d tries" % i)
1213
1214
1215     def iv_make (self):
1216         """
1217         Construct a 12-bytes IV from the current fixed part and the object
1218         counter.
1219         """
1220         return struct.pack(FMT_I2N_IV, self.fixed [-1], self.cnt)
1221
1222
1223     def next (self, filename=None, counter=None):
1224         """
1225         Prepare for encrypting the next incoming object. Update the counter
1226         and put together the IV, possibly changing prefixes. Then create the
1227         new encryptor.
1228
1229         The argument ``counter`` can be used to specify a file counter for this
1230         object. Unless it is one of the reserved values, the counter of
1231         subsequent objects will be computed from this one.
1232
1233         If this is the first object in a series, ``filename`` is required,
1234         otherwise it is reused if not present. The value is used to derive a
1235         header sized placeholder to use until after encryption when all the
1236         inputs to construct the final header are available. This is then
1237         matched in ``.done()`` against the value found at the position of the
1238         header. The motivation for this extra check is primarily to assist
1239         format debugging: It makes stray headers easy to spot in malformed
1240         PDTCRYPT files.
1241         """
1242         if filename is None:
1243             if self.lastinfo is None:
1244                 raise InvalidParameter ("next: filename is mandatory for "
1245                                         "first object")
1246             filename, _dummy = self.lastinfo
1247         else:
1248             if isinstance (filename, str) is False:
1249                 raise InvalidParameter ("next: filename must be a string, no %s"
1250                                         % type (filename))
1251         if counter is not None:
1252             if isinstance (counter, int) is False:
1253                 raise InvalidParameter ("next: the supplied counter is of "
1254                                         "invalid type %s; please pass an "
1255                                         "integer instead" % type (counter))
1256             self.set_object_counter (counter)
1257
1258         self.iv = self.iv_make ()
1259         if self.paramenc == "aes-gcm":
1260             self.enc = Cipher \
1261                             ( algorithms.AES (self.key)
1262                             , modes.GCM (self.iv)
1263                             , backend = default_backend ()) \
1264                             .encryptor ()
1265         elif self.paramenc == "passthrough":
1266             self.enc = PassthroughCipher ()
1267         else:
1268             raise InvalidParameter ("next: parameter version %d not known"
1269                                     % self.paramversion)
1270         hdrdum = hdr_make_dummy (filename)
1271         self.lastinfo = (filename, hdrdum)
1272
1273         self.check_duplicate_iv (self.iv)
1274
1275         super().next (self.password, self.paramversion, self.nacl)
1276
1277         self.set_object_counter (self.cnt + 1)
1278         return hdrdum
1279
1280
1281     def check_duplicate_iv (self, iv):
1282         """
1283         Add an IV (the 12 byte representation as in the header) to the list. With
1284         strict checking enabled, this will throw a ``DuplicateIV``. Depending on
1285         the context, this may indicate a serious error (IV reuse).
1286
1287         IVs are only tracked in strict_ivs mode.
1288         """
1289         if self.strict_ivs is False:
1290             return
1291
1292         if iv in self.used_ivs:
1293             raise DuplicateIV ("iv %s was reused" % iv_fmt (iv))
1294         # vi has not been used before; add to collection
1295         self.used_ivs.add (iv)
1296
1297
1298     def done (self, cmpdata):
1299         """
1300         Complete encryption of an object. After this has been called, attempts
1301         of encrypting further data will cause an error until ``.next()`` is
1302         invoked properly.
1303
1304         Returns a 64 bytes buffer containing the object header including all
1305         values including the “late” ones e. g. the ciphertext size and the
1306         GCM tag.
1307         """
1308         if isinstance (cmpdata, bytes) is False:
1309             raise InvalidParameter ("done: comparison input expected as bytes, "
1310                                     "not %s" % type (cmpdata))
1311         if self.lastinfo is None:
1312             raise RuntimeError ("done: encryption context not initialized")
1313         filename, hdrdum = self.lastinfo
1314         if cmpdata != hdrdum:
1315             raise RuntimeError ("done: bad sync of header for object %d: "
1316                                 "preliminary data does not match; this likely "
1317                                 "indicates a wrongly repositioned stream"
1318                                 % self.cnt)
1319         data = self.enc.finalize ()
1320         self.stats ["out"] += len (data)
1321         self.ctsize += len (data)
1322         ok, hdr = hdr_from_params (self.version, self.paramversion, self.nacl,
1323                                    self.iv, self.ctsize, self.enc.tag)
1324         if ok is False:
1325             raise InternalError ("error constructing header: %r" % hdr)
1326         return data, hdr, self.fixed
1327
1328
1329     def process (self, buf):
1330         """
1331         Encrypt a chunk of plaintext with the active encryptor. Returns the
1332         size of the input consumed. This **must** be checked downstream. If the
1333         maximum possible object size has been reached, the current context must
1334         be finalized and a new one established before any further data can be
1335         encrypted. The second argument is the remainder of the plaintext that
1336         was not encrypted for the caller to use immediately after the new
1337         context is ready.
1338         """
1339         if isinstance (buf, bytes) is False:
1340             raise InvalidParameter ("process: expected byte buffer, not %s"
1341                                     % type (buf))
1342         bsize = len (buf)
1343         newptsize = self.ptsize + bsize
1344         diff = newptsize - PDTCRYPT_MAX_OBJ_SIZE
1345         if diff > 0:
1346             bsize -= diff
1347             newptsize = PDTCRYPT_MAX_OBJ_SIZE
1348         self.ptsize = newptsize
1349         data = super().process (buf [:bsize])
1350         self.ctsize += len (data)
1351         return bsize, data
1352
1353
1354 class Decrypt (Crypto):
1355
1356     tag        = None   # GCM tag, part of header
1357     last_iv    = None   # check consecutive ivs in strict mode
1358     hdr_ctsize = -1
1359
1360     def __init__ (self, password=None, key=None, counter=None, fixedparts=None,
1361                   strict_ivs=True, insecure=False):
1362         """
1363         Sanitizing ctor for the decryption context. ``fixedparts`` specifies a
1364         list of IV fixed parts accepted during decryption. If a fixed part is
1365         encountered that is not in the list, decryption will fail.
1366
1367         :param    password: mutually exclusive with ``key``
1368         :type     password: bytes
1369         :param         key: mutually exclusive with ``password``
1370         :type          key: bytes
1371         :type      counter: initial object counter the values
1372                             ``AES_GCM_IV_CNT_INFOFILE`` and
1373                             ``AES_GCM_IV_CNT_INDEX`` are unique in each backup set
1374                             and cannot be reused even with different fixed parts.
1375         :type   fixedparts: bytes list
1376         :type   strict_ivs: bool
1377         :param  strict_ivs: fail if IVs of decrypted objects are not linearly
1378                             increasing
1379         :type     insecure: bool
1380         :param    insecure: whether to process objects encrypted in
1381                             passthrough mode (*``paramversion`` < 1*)
1382
1383         *Security considerations*: The ``strict_ivs`` setting protects against
1384         ciphertext reordering and injection attacks. For this to work it relies
1385         on a property of how the object counters are created during encryption.
1386         If multiple ``Encrypt`` handles have been used during encryption, this
1387         is property is unlikely to apply as it would require manual management
1388         of counters across Encrypt handles. In these cases it may thus be
1389         necessary to disable the ```strict_ivs`` protection.
1390         """
1391         if         password is     None and key is     None \
1392                 or password is not None and key is not None :
1393             raise InvalidParameter ("__init__: need either key or password")
1394
1395         if key is not None:
1396             if isinstance (key, bytes) is False:
1397                 raise InvalidParameter ("__init__: key must be provided as "
1398                                         "bytes, not %s" % type (key))
1399         else: # password, no key
1400             if isinstance (password, str) is False:
1401                 raise InvalidParameter ("__init__: password must be a string, not %s"
1402                                         % type (password))
1403             if len (password) == 0:
1404                 raise InvalidParameter ("__init__: supplied empty password but not "
1405                                         "permitted for PDT encrypted files")
1406         # fixed parts
1407         if fixedparts is not None:
1408             if isinstance (fixedparts, list) is False:
1409                 raise InvalidParameter ("__init__: IV fixed parts must be "
1410                                         "supplied as list, not %s"
1411                                         % type (fixedparts))
1412             self.fixed = fixedparts
1413             self.fixed.sort ()
1414
1415         super().__init__ (password=password, key=key, counter=counter,
1416                           strict_ivs=strict_ivs, insecure=insecure)
1417
1418
1419     def valid_fixed_part (self, iv):
1420         """
1421         Check if a fixed part was already seen.
1422         """
1423         # check if fixed part is known
1424         fixed, _cnt = struct.unpack (FMT_I2N_IV, iv)
1425         i = bisect.bisect_left (self.fixed, fixed)
1426         return i != len (self.fixed) and self.fixed [i] == fixed
1427
1428
1429     def reset_last_iv (self):
1430         """
1431         Force a new IV sequence start. The last IV counter will be set from the
1432         next IV encountered and the check for consecutive IVs will be suppressed.
1433
1434         The intended use is backup volume boundaries or handling batches of
1435         objects encrypted with ``Encrypt`` handles initialized with different
1436         initial counter values.
1437         """
1438         self.last_iv = None
1439
1440     def check_consecutive_iv (self, iv):
1441         """
1442         Check whether the counter part of the given IV is indeed the successor
1443         of the currently present counter. This should always be the case for
1444         the objects in a well formed PDT archive but should not be enforced
1445         when decrypting out-of-order.
1446         """
1447         fixed, cnt = struct.unpack (FMT_I2N_IV, iv)
1448         if self.strict_ivs is True \
1449                 and self.last_iv is not None \
1450                 and self.last_iv [0] == fixed \
1451                 and self.last_iv [1] + 1 != cnt:
1452             raise NonConsecutiveIV ("iv %s counter not successor of "
1453                                     "last object (expected %d, found %d)"
1454                                     % (iv_fmt (iv), self.last_iv [1] + 1, cnt))
1455         self.last_iv = (fixed, cnt)
1456
1457
1458     def next (self, hdr):
1459         """
1460         Start decrypting the next object. The PDTCRYPT header for the object
1461         can be given either as already parsed object or as bytes.
1462         """
1463         if isinstance (hdr, bytes) is True:
1464             hdr = hdr_read (hdr)
1465         elif isinstance (hdr, dict) is False:
1466             # this won’t catch malformed specs though
1467             raise InvalidParameter ("next: wrong type of parameter hdr: "
1468                                     "expected bytes or spec, got %s"
1469                                     % type (hdr))
1470         try:
1471             paramversion = hdr ["paramversion"]
1472             nacl         = hdr ["nacl"]
1473             iv           = hdr ["iv"]
1474             tag          = hdr ["tag"]
1475             ctsize       = hdr ["ctsize"]
1476         except KeyError:
1477             raise InvalidHeader ("next: not a header %r" % hdr)
1478
1479         if ctsize > PDTCRYPT_MAX_OBJ_SIZE:
1480             raise InvalidHeader ("next: ciphertext size %d exceeds maximum "
1481                                  "object size (%d)"
1482                                  % (ctsize, PDTCRYPT_MAX_OBJ_SIZE))
1483
1484         self.hdr_ctsize = ctsize
1485
1486         super().next (self.password, paramversion, nacl)
1487         if self.fixed is not None and self.valid_fixed_part (iv) is False:
1488             raise InvalidIVFixedPart ("iv %s has invalid fixed part"
1489                                       % iv_fmt (iv))
1490
1491         self.check_consecutive_iv (iv)
1492
1493         self.tag = tag
1494         defs = ENCRYPTION_PARAMETERS.get (paramversion, None)
1495         if defs is None:
1496             raise FormatError ("header contains unknown parameter version %d; "
1497                                "maybe the file was created by a more recent "
1498                                "version of Deltatar" % paramversion)
1499         enc = defs ["enc"]
1500         if enc == "aes-gcm":
1501             self.enc = Cipher \
1502                             ( algorithms.AES (self.key)
1503                             , modes.GCM (iv, tag=self.tag)
1504                             , backend = default_backend ()) \
1505                             . decryptor ()
1506         elif enc == "passthrough":
1507             self.enc = PassthroughCipher ()
1508         else:
1509             raise InternalError ("encryption parameter set %d refers to unknown "
1510                                  "mode %r" % (paramversion, enc))
1511         self.set_object_counter (self.cnt + 1)
1512
1513
1514     def done (self, tag=None):
1515         """
1516         Stop decryption of the current object and finalize it with the active
1517         context. This will throw an *InvalidGCMTag* exception to indicate that
1518         the authentication tag does not match the data. If the tag is correct,
1519         the rest of the plaintext is returned.
1520         """
1521         data = b""
1522         try:
1523             if tag is None:
1524                 data = self.enc.finalize ()
1525             else:
1526                 if isinstance (tag, bytes) is False:
1527                     raise InvalidParameter ("done: wrong type of parameter "
1528                                             "tag: expected bytes, got %s"
1529                                             % type (tag))
1530                 data = self.enc.finalize_with_tag (self.tag)
1531         except cryptography.exceptions.InvalidTag:
1532             raise InvalidGCMTag ("done: tag mismatch of object %d: %s "
1533                                   "rejected by finalize ()"
1534                                   % (self.cnt, binascii.hexlify (self.tag)))
1535         self.ptsize += len (data)
1536         self.stats ["out"] += len (data)
1537
1538         assert self.ctsize == self.ptsize == self.hdr_ctsize
1539
1540         return data
1541
1542
1543     def process (self, buf):
1544         """
1545         Decrypt the bytes object *buf* with the active decryptor.
1546         """
1547         if isinstance (buf, bytes) is False:
1548             raise InvalidParameter ("process: expected byte buffer, not %s"
1549                                     % type (buf))
1550         self.ctsize += len (buf)
1551         if self.ctsize > self.hdr_ctsize:
1552             raise CiphertextTooLong ("process: object length exceeded: got "
1553                                      "%d B but header specfiies %d B"
1554                                      % (self.ctsize, self.hdr_ctsize))
1555
1556         data = super().process (buf)
1557         self.ptsize += len (data)
1558         return data
1559
1560
1561 ###############################################################################
1562 ## testing helpers
1563 ###############################################################################
1564
1565 def _patch_global (glob, vow, n=None):
1566     """
1567     Adapt upper file counter bound for testing IV logic. Completely unsafe.
1568     """
1569     assert vow == "I am fully aware that this will void my warranty."
1570     r = globals () [glob]
1571     if n is None:
1572         n = globals () [glob + "_DEFAULT"]
1573     globals () [glob] = n
1574     return r
1575
1576 _testing_set_AES_GCM_IV_CNT_MAX = \
1577         partial (_patch_global, "AES_GCM_IV_CNT_MAX")
1578
1579 _testing_set_PDTCRYPT_MAX_OBJ_SIZE = \
1580         partial (_patch_global, "PDTCRYPT_MAX_OBJ_SIZE")
1581
1582 def open2_dump_file (fname, dir_fd, force=False):
1583     outfd = -1
1584
1585     oflags = os.O_CREAT | os.O_WRONLY
1586     if force is True:
1587         oflags |= os.O_TRUNC
1588     else:
1589         oflags |= os.O_EXCL
1590
1591     try:
1592         outfd = os.open (fname, oflags,
1593                          stat.S_IRUSR | stat.S_IWUSR, dir_fd=dir_fd)
1594     except FileExistsError as exn:
1595         noise ("PDT: refusing to overwrite existing file %s" % fname)
1596         noise ("")
1597         raise RuntimeError ("destination file %s already exists" % fname)
1598     if PDTCRYPT_VERBOSE is True:
1599         noise ("PDT: new output file %s (fd=%d)" % (fname, outfd))
1600
1601     return outfd
1602
1603 ###############################################################################
1604 ## freestanding invocation
1605 ###############################################################################
1606
1607 PDTCRYPT_SUB_PROCESS = 0
1608 PDTCRYPT_SUB_SCRYPT  = 1
1609 PDTCRYPT_SUB_SCAN    = 2
1610
1611 PDTCRYPT_SUB = \
1612         { "process" : PDTCRYPT_SUB_PROCESS
1613         , "scrypt"  : PDTCRYPT_SUB_SCRYPT
1614         , "scan"    : PDTCRYPT_SUB_SCAN }
1615
1616 PDTCRYPT_DECRYPT   = 1 << 0 # decrypt archive with password
1617 PDTCRYPT_SPLIT     = 1 << 1 # split archive into individual objects
1618 PDTCRYPT_HASH      = 1 << 2 # output scrypt hash for file and given password
1619
1620 PDTCRYPT_SPLITNAME  = "pdtcrypt-object-%d.bin"
1621 PDTCRYPT_RESCUENAME = "pdtcrypt-rescue-object-%0.5d.bin"
1622
1623 PDTCRYPT_VERBOSE   = False
1624 PDTCRYPT_STRICTIVS = False
1625 PDTCRYPT_OVERWRITE = False
1626 PDTCRYPT_BLOCKSIZE = 1 << 12
1627 PDTCRYPT_SINK      = 0
1628 PDTCRYPT_SOURCE    = 1
1629 SELF               = None
1630
1631 PDTCRYPT_DEFAULT_VER  = 1
1632 PDTCRYPT_DEFAULT_PVER = 1
1633
1634 # scrypt hashing output control
1635 PDTCRYPT_SCRYPT_INTRANATOR = 0
1636 PDTCRYPT_SCRYPT_PARAMETERS = 1
1637 PDTCRYPT_SCRYPT_DEFAULT    = PDTCRYPT_SCRYPT_INTRANATOR
1638
1639 PDTCRYPT_SCRYPT_FORMAT = \
1640     { "i2n"    : PDTCRYPT_SCRYPT_INTRANATOR
1641     , "params" : PDTCRYPT_SCRYPT_PARAMETERS }
1642
1643 PDTCRYPT_TT_COLUMNS = 80 # assume standard terminal
1644
1645 class PDTDecryptionError (Exception):
1646     """Decryption failed."""
1647
1648 class PDTSplitError (Exception):
1649     """Decryption failed."""
1650
1651
1652 def noise (*a, **b):
1653     print (file=sys.stderr, *a, **b)
1654
1655
1656 class PassthroughDecryptor (object):
1657
1658     curhdr = None # write current header on first data write
1659
1660     def __init__ (self):
1661         if PDTCRYPT_VERBOSE is True:
1662             noise ("PDT: no encryption; data passthrough")
1663
1664     def next (self, hdr):
1665         ok, curhdr = hdr_make (hdr)
1666         if ok is False:
1667             raise PDTDecryptionError ("bad header %r" % hdr)
1668         self.curhdr = curhdr
1669
1670     def done (self):
1671         if self.curhdr is not None:
1672             return self.curhdr
1673         return b""
1674
1675     def process (self, d):
1676         if self.curhdr is not None:
1677             d = self.curhdr + d
1678             self.curhdr = None
1679         return d
1680
1681
1682 def depdtcrypt (mode, secret, ins, outs):
1683     """
1684     Remove PDTCRYPT layer from all objects encrypted with the secret. Used on a
1685     Deltatar backup this will yield a (possibly Gzip compressed) tarball.
1686     """
1687     ctleft     = -1              # length of ciphertext to consume
1688     ctcurrent  = 0               # total ciphertext of current object
1689     total_obj  = 0               # total number of objects read
1690     total_pt   = 0               # total plaintext bytes
1691     total_ct   = 0               # total ciphertext bytes
1692     total_read = 0               # total bytes read
1693     outfile    = None            # Python file object for output
1694
1695     if mode & PDTCRYPT_DECRYPT:  # decryptor
1696         ks = secret [0]
1697         if ks == PDTCRYPT_SECRET_PW:
1698             decr = Decrypt (password=secret [1], strict_ivs=PDTCRYPT_STRICTIVS)
1699         elif ks == PDTCRYPT_SECRET_KEY:
1700             key = secret [1]
1701             decr = Decrypt (key=key, strict_ivs=PDTCRYPT_STRICTIVS)
1702         else:
1703             raise InternalError ("‘%d’ does not specify a valid kind of secret"
1704                                  % ks)
1705     else:
1706         decr = PassthroughDecryptor ()
1707
1708     def nextout (_):
1709         """Dummy for non-split mode: output file does not vary."""
1710         return outs
1711
1712     if mode & PDTCRYPT_SPLIT:
1713         def nextout (outfile):
1714             """
1715             We were passed an fd as outs for accessing the destination
1716             directory where extracted archive components are supposed
1717             to end up in.
1718             """
1719
1720             if outfile is None:
1721                 if PDTCRYPT_VERBOSE is True:
1722                     noise ("PDT: no output file to close at this point")
1723             else:
1724                 if PDTCRYPT_VERBOSE is True:
1725                     noise ("PDT: release output file %r" % outfile)
1726                 # cleanup happens automatically by the GC; the next
1727                 # line will error out on account of an invalid fd
1728                 #outfile.close ()
1729
1730             assert total_obj > 0
1731             fname = PDTCRYPT_SPLITNAME % total_obj
1732             try:
1733                 outfd = open2_dump_file (fname, outs, force=PDTCRYPT_OVERWRITE)
1734             except RuntimeError as exn:
1735                 raise PDTSplitError (exn)
1736             return os.fdopen (outfd, "wb", closefd=True)
1737
1738
1739     def tell (s):
1740         """ESPIPE is normal on non-seekable stdio stream."""
1741         try:
1742             return s.tell ()
1743         except OSError as exn:
1744             if exn.errno == errno.ESPIPE:
1745                 return -1
1746
1747     def out (pt, outfile):
1748         npt = len (pt)
1749         nonlocal total_pt
1750         total_pt += npt
1751         if PDTCRYPT_VERBOSE is True:
1752             noise ("PDT:\t· decrypt plaintext %d B" % (npt))
1753         try:
1754             nn = outfile.write (pt)
1755         except OSError as exn: # probably ENOSPC
1756             raise DecryptionError ("error (%s)" % exn)
1757         if nn != npt:
1758             raise DecryptionError ("write aborted after %d of %d B" % (nn, npt))
1759
1760     while True:
1761         if ctleft <= 0:
1762             # current object completed; in a valid archive this marks either
1763             # the start of a new header or the end of the input
1764             if ctleft == 0: # current object requires finalization
1765                 if PDTCRYPT_VERBOSE is True:
1766                     noise ("PDT: %d finalize" % tell (ins))
1767                 try:
1768                     pt = decr.done ()
1769                 except InvalidGCMTag as exn:
1770                     raise DecryptionError ("error finalizing object %d (%d B): "
1771                                            "%r" % (total_obj, len (pt), exn)) \
1772                           from exn
1773                 out (pt, outfile)
1774                 if PDTCRYPT_VERBOSE is True:
1775                     noise ("PDT:\t· object validated")
1776
1777             if PDTCRYPT_VERBOSE is True:
1778                 noise ("PDT: %d hdr" % tell (ins))
1779             try:
1780                 hdr = hdr_read_stream (ins)
1781                 total_read += PDTCRYPT_HDR_SIZE
1782             except EndOfFile as exn:
1783                 total_read += exn.remainder
1784                 if total_ct + total_obj * PDTCRYPT_HDR_SIZE != total_read:
1785                     raise PDTDecryptionError ("ciphertext processed (%d B) plus "
1786                                               "overhead (%d × %d B) does not match "
1787                                               "the number of bytes read (%d )"
1788                                               % (total_ct, total_obj, PDTCRYPT_HDR_SIZE,
1789                                                  total_read))
1790                 # the single good exit
1791                 return total_read, total_obj, total_ct, total_pt
1792             except InvalidHeader as exn:
1793                 raise PDTDecryptionError ("invalid header at position %d in %r "
1794                                           "(%s)" % (tell (ins), exn, ins))
1795             if PDTCRYPT_VERBOSE is True:
1796                 pretty = hdr_fmt_pretty (hdr)
1797                 noise (reduce (lambda a, e: (a + "\n" if a else "") + "PDT:\t· " + e,
1798                                pretty.splitlines (), ""))
1799             ctcurrent = ctleft = hdr ["ctsize"]
1800
1801             decr.next (hdr)
1802
1803             total_obj += 1 # used in file counter with split mode
1804
1805             # finalization complete or skipped in case of first object in
1806             # stream; create a new output file if necessary
1807             outfile = nextout (outfile)
1808
1809             if PDTCRYPT_VERBOSE is True:
1810                 noise ("PDT: %d decrypt obj no. %d,  %d B"
1811                        % (tell (ins), total_obj, ctleft))
1812
1813         # always allocate a new buffer since python-cryptography doesn’t allow
1814         # passing a bytearray :/
1815         nexpect = min (ctleft, PDTCRYPT_BLOCKSIZE)
1816         if PDTCRYPT_VERBOSE is True:
1817             noise ("PDT:\t· [%d] %d%% done, read block (%d B of %d B remaining)"
1818                    % (tell (ins),
1819                       100 - ctleft * 100 / (ctcurrent > 0 and ctcurrent or 1),
1820                       nexpect, ctleft))
1821         ct      = ins.read (nexpect)
1822         nct     = len (ct)
1823         if nct < nexpect:
1824             off = tell (ins)
1825             raise EndOfFile (nct,
1826                              "hit EOF after %d of %d B in block [%d:%d); "
1827                              "%d B ciphertext remaining for object no %d"
1828                              % (nct, nexpect, off, off + nexpect, ctleft,
1829                                 total_obj))
1830         ctleft     -= nct
1831         total_ct   += nct
1832         total_read += nct
1833
1834         if PDTCRYPT_VERBOSE is True:
1835             noise ("PDT:\t· decrypt ciphertext %d B" % (nct))
1836         pt = decr.process (ct)
1837         out (pt, outfile)
1838
1839
1840 def deptdcrypt_mk_stream (kind, path):
1841     """Create stream from file or stdio descriptor."""
1842     if kind == PDTCRYPT_SINK:
1843         if path == "-":
1844             if PDTCRYPT_VERBOSE is True: noise ("PDT: sink: stdout")
1845             return sys.stdout.buffer
1846         else:
1847             if PDTCRYPT_VERBOSE is True: noise ("PDT: sink: file %s" % path)
1848             return io.FileIO (path, "w")
1849     if kind == PDTCRYPT_SOURCE:
1850         if path == "-":
1851             if PDTCRYPT_VERBOSE is True: noise ("PDT: source: stdin")
1852             return sys.stdin.buffer
1853         else:
1854             if PDTCRYPT_VERBOSE is True: noise ("PDT: source: file %s" % path)
1855             return io.FileIO (path, "r")
1856
1857     raise ValueError ("bogus stream “%s” / %s" % (kind, path))
1858
1859
1860 def mode_depdtcrypt (mode, secret, ins, outs):
1861     try:
1862         total_read, total_obj, total_ct, total_pt = \
1863             depdtcrypt (mode, secret, ins, outs)
1864     except DecryptionError as exn:
1865         noise ("PDT: Decryption failed:")
1866         noise ("PDT:")
1867         noise ("PDT:    “%s”" % exn)
1868         noise ("PDT:")
1869         noise ("PDT: Did you specify the correct key / password?")
1870         noise ("")
1871         return 1
1872     except PDTSplitError as exn:
1873         noise ("PDT: Split operation failed:")
1874         noise ("PDT:")
1875         noise ("PDT:    “%s”" % exn)
1876         noise ("PDT:")
1877         noise ("PDT: Hint: target directory should be empty.")
1878         noise ("")
1879         return 1
1880
1881     if PDTCRYPT_VERBOSE is True:
1882         noise ("PDT: decryption successful"                 )
1883         noise ("PDT:   %.10d bytes read"        % total_read)
1884         noise ("PDT:   %.10d objects decrypted" % total_obj )
1885         noise ("PDT:   %.10d bytes ciphertext"  % total_ct  )
1886         noise ("PDT:   %.10d bytes plaintext"   % total_pt  )
1887         noise (""                                           )
1888
1889     return 0
1890
1891
1892 def mode_scrypt (pw, ins=None, nacl=None, fmt=PDTCRYPT_SCRYPT_INTRANATOR):
1893     hsh = None
1894     paramversion = PDTCRYPT_DEFAULT_PVER
1895     if ins is not None:
1896         hsh, nacl, version, paramversion = scrypt_hashsource (pw, ins)
1897         defs = ENCRYPTION_PARAMETERS.get(paramversion, None)
1898     else:
1899         nacl    = binascii.unhexlify (nacl)
1900         defs    = ENCRYPTION_PARAMETERS.get(paramversion, None)
1901         version = PDTCRYPT_DEFAULT_VER
1902
1903     kdfname, params = defs ["kdf"]
1904     if hsh is None:
1905         kdf = kdf_by_version (None, defs)
1906         hsh, _void = kdf (pw, nacl)
1907
1908     import json
1909
1910     if fmt == PDTCRYPT_SCRYPT_INTRANATOR:
1911         out = json.dumps ({ "salt"          : base64.b64encode (nacl).decode ()
1912                           , "key"           : base64.b64encode (hsh) .decode ()
1913                           , "paramversion"  : paramversion })
1914     elif fmt == PDTCRYPT_SCRYPT_PARAMETERS:
1915         out = json.dumps ({ "salt"          : binascii.hexlify (nacl).decode ()
1916                           , "key"           : binascii.hexlify (hsh) .decode ()
1917                           , "version"       : version
1918                           , "scrypt_params" : { "N"     : params ["N"]
1919                                               , "r"     : params ["r"]
1920                                               , "p"     : params ["p"]
1921                                               , "dkLen" : params ["dkLen"] } })
1922     else:
1923         raise RuntimeError ("bad scrypt output scheme %r" % fmt)
1924
1925     print (out)
1926
1927
1928 def noise_output_candidates (cands, indent=8, cols=PDTCRYPT_TT_COLUMNS):
1929     """
1930     Print a list of offsets without garbling the terminal too much.
1931
1932     The indent is counted from column zero; if it is wide enough, the “PDT: ”
1933     marker will be prepended, considered part of the indentation.
1934     """
1935     wd   = cols - 1
1936     nc   = len (cands)
1937     idt  = " " * indent if indent < 5 else "PDT: " + " " * (indent - 5)
1938     line = idt
1939     lpos = indent
1940     sep  = ","
1941     lsep = len (sep)
1942     init = True # prevent leading separator
1943
1944     if indent >= wd:
1945         raise ValueError ("the requested indentation exceeds the line "
1946                           "width by %d" % (indent - wd))
1947
1948     for n in cands:
1949         ns  = "%d" % n
1950         lns = len (ns)
1951         if init is False:
1952             line += sep
1953             lpos += lsep
1954
1955         lpos += lns
1956         if lpos > wd: # line break
1957             noise (line)
1958             line = idt
1959             lpos = indent + lns
1960         elif init is True:
1961             init = False
1962         else: # space
1963             line += ' '
1964             lpos += 1
1965
1966         line += ns
1967
1968     if lpos != indent:
1969         noise (line)
1970
1971
1972 SLICE_START = 1 # ordering is important to have starts of intervals
1973 SLICE_END   = 0 # sorted before equal ends
1974
1975 def find_overlaps (slices):
1976     """
1977     Find overlapping slices: iterate open/close points of intervals, tracking
1978     the ones open at any time.
1979     """
1980     bounds = []
1981     inside = set () # of indices into bounds
1982     ovrlp  = set () # of indices into bounds
1983
1984     for i, s in enumerate (slices):
1985         bounds.append ((s [0], SLICE_START, i))
1986         bounds.append ((s [1], SLICE_END  , i))
1987     bounds = sorted (bounds)
1988
1989     for val in bounds:
1990         i = val [2]
1991         if val [1] == SLICE_START:
1992             inside.add (i)
1993         else:
1994             if len (inside) > 1: # closing one that overlapped
1995                 ovrlp |= inside
1996             inside.remove (i)
1997
1998     return [ slices [i] for i in ovrlp ]
1999
2000
2001 def mode_scan (secret, fname, outs=None, nacl=None):
2002     """
2003     Dissect a binary file, looking for PDTCRYPT headers and objects.
2004
2005     If *outs* is supplied, recoverable data will be dumped into the specified
2006     directory.
2007     """
2008     try:
2009         ifd = os.open (fname, os.O_RDONLY)
2010     except FileNotFoundError:
2011         noise ("PDT: failed to open %s readonly" % fname)
2012         noise ("")
2013         usage (err=True)
2014
2015     try:
2016         if PDTCRYPT_VERBOSE is True:
2017             noise ("PDT: scan for potential sync points")
2018         cands = locate_hdr_candidates (ifd)
2019         if len (cands) == 0:
2020             noise ("PDT: scan complete: input does not contain potential PDT "
2021                    "headers; giving up.")
2022             return -1
2023         if PDTCRYPT_VERBOSE is True:
2024             noise ("PDT: scan complete: found %d candidates:" % len (cands))
2025             noise_output_candidates (cands)
2026     except:
2027         os.close (ifd)
2028         raise
2029
2030     junk, todo, slices = [], [], []
2031     try:
2032         nobj = 0
2033         for cand in cands:
2034             nobj += 1
2035             vdt, hdr = inspect_hdr (ifd, cand)
2036
2037             vdts = verdict_fmt (vdt)
2038
2039             if vdt == HDR_CAND_JUNK:
2040                 noise ("PDT: obj %d: %s object: bad header, skipping" % vdts)
2041                 junk.append (cand)
2042             else:
2043                 off0 = cand + PDTCRYPT_HDR_SIZE
2044                 if PDTCRYPT_VERBOSE is True:
2045                     noise ("PDT: obj %d: read payload @%d" % (nobj, off0))
2046                     pretty = hdr_fmt_pretty (hdr)
2047                     noise (reduce (lambda a, e: (a + "\n" if a else "") + "PDT:\t· " + e,
2048                                 pretty.splitlines (), ""))
2049
2050                 ofd = -1
2051                 if outs is not None:
2052                     ofname = PDTCRYPT_RESCUENAME % nobj
2053                     ofd = open2_dump_file (ofname, outs, force=PDTCRYPT_OVERWRITE)
2054
2055                 ctsize = hdr ["ctsize"]
2056                 try:
2057                     l = try_decrypt (ifd, off0, hdr, secret, ofd=ofd)
2058                     ok = l == ctsize
2059                     slices.append ((off0, off0 + l))
2060                 finally:
2061                     if ofd != -1:
2062                         os.close (ofd)
2063                 if vdt == HDR_CAND_GOOD and ok is True:
2064                     noise ("PDT: %d → ✓ %s object %d–%d"
2065                            % (cand, vdts, off0, off0 + ctsize))
2066                 elif vdt == HDR_CAND_FISHY and ok is True:
2067                     noise ("PDT: %d → × %s object %d–%d, corrupt header"
2068                            % (cand, vdts, off0, off0 + ctsize))
2069                 elif vdt == HDR_CAND_GOOD and ok is False:
2070                     noise ("PDT: %d → × %s object %d–%d, problematic payload"
2071                            % (cand, vdts, off0, off0 + ctsize))
2072                 elif vdt == HDR_CAND_FISHY and ok is False:
2073                     noise ("PDT: %d → × %s object %d–%d, corrupt header, problematic "
2074                            "ciphertext" % (cand, vdts, off0, off0 + ctsize))
2075                 else:
2076                     raise Unreachable
2077     finally:
2078         os.close (ifd)
2079
2080     if len (junk) == 0:
2081         noise ("PDT: all headers ok")
2082     else:
2083         noise ("PDT: %d candidates not parseable as headers:" % len (junk))
2084         noise_output_candidates (junk)
2085
2086     overlap = find_overlaps (slices)
2087     if len (overlap) > 0:
2088         noise ("PDT: %d objects overlapping others" % len (overlap))
2089         for slice in overlap:
2090             noise ("PDT:    × %d→%d" % (slice [0], slice [1]))
2091
2092 def usage (err=False):
2093     out = print
2094     if err is True:
2095         out = noise
2096     indent = ' ' * len (SELF)
2097     out ("usage: %s SUBCOMMAND { --help" % SELF)
2098     out ("       %s            | [ -v ] { -p PASSWORD | -k KEY }" % indent)
2099     out ("       %s              [ { -i | --in }  { - | SOURCE } ]" % indent)
2100     out ("       %s              [ { -n | --nacl } { SALT } ]" % indent)
2101     out ("       %s              [ { -o | --out } { - | DESTINATION } ]" % indent)
2102     out ("       %s              [ -D | --no-decrypt ] [ -S | --split ]" % indent)
2103     out ("       %s              [ -f | --format ]" % indent)
2104     out ("")
2105     out ("\twhere")
2106     out ("\t\tSUBCOMMAND      main mode: { process | scrypt }")
2107     out ("\t\t                where:")
2108     out ("\t\t                   process: extract objects from PDT archive")
2109     out ("\t\t                   scrypt:  calculate hash from password and first object")
2110     out ("\t\t-p PASSWORD     password to derive the encryption key from")
2111     out ("\t\t-k KEY          encryption key as 16 bytes in hexadecimal notation")
2112     out ("\t\t-s              enforce strict handling of initialization vectors")
2113     out ("\t\t-i SOURCE       file name to read from")
2114     out ("\t\t-o DESTINATION  file to write output to")
2115     out ("\t\t-n SALT         provide salt for scrypt mode in hex encoding")
2116     out ("\t\t-v              print extra info")
2117     out ("\t\t-S              split into files at object boundaries; this")
2118     out ("\t\t                requires DESTINATION to refer to directory")
2119     out ("\t\t-D              PDT header and ciphertext passthrough")
2120     out ("\t\t-f              format of SCRYPT hash output (“default” or “parameters”)")
2121     out ("")
2122     out ("\tinstead of filenames, “-” may used to specify stdin / stdout")
2123     out ("")
2124     sys.exit ((err is True) and 42 or 0)
2125
2126
2127 def bail (msg):
2128     noise (msg)
2129     noise ("")
2130     usage (err=True)
2131     raise Unreachable
2132
2133
2134 def parse_argv (argv):
2135     global PDTCRYPT_OVERWRITE
2136     global SELF
2137     mode          = PDTCRYPT_DECRYPT
2138     secret        = None
2139     insspec       = None
2140     outsspec      = None
2141     outs          = None
2142     nacl          = None
2143     scrypt_format = PDTCRYPT_SCRYPT_DEFAULT
2144
2145     argvi = iter (argv)
2146     SELF  = os.path.basename (next (argvi))
2147
2148     try:
2149         rawsubcmd  = next (argvi)
2150         subcommand = PDTCRYPT_SUB [rawsubcmd]
2151     except StopIteration:
2152         bail ("ERROR: subcommand required")
2153     except KeyError:
2154         bail ("ERROR: invalid subcommand “%s” specified" % rawsubcmd)
2155
2156     def checked_arg ():
2157         nonlocal argvi
2158         try:
2159             return next (argvi)
2160         except StopIteration:
2161             bail ("ERROR: argument list incomplete")
2162
2163     def checked_secret (s):
2164         nonlocal secret
2165         if secret is None:
2166             secret = s
2167         else:
2168             bail ("ERROR: encountered “%s” but secret already given" % arg)
2169
2170     for arg in argvi:
2171         if arg in [ "-h", "--help" ]:
2172             usage ()
2173             raise Unreachable
2174         elif arg in [ "-v", "--verbose", "--wtf" ]:
2175             global PDTCRYPT_VERBOSE
2176             PDTCRYPT_VERBOSE = True
2177         elif arg in [ "-i", "--in", "--source" ]:
2178             insspec = checked_arg ()
2179             if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypt from %s" % insspec)
2180         elif arg in [ "-p", "--password" ]:
2181             arg = checked_arg ()
2182             checked_secret (make_secret (password=arg))
2183             if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypting with password")
2184         else:
2185             if subcommand == PDTCRYPT_SUB_PROCESS:
2186                 if arg in [ "-s", "--strict-ivs" ]:
2187                     global PDTCRYPT_STRICTIVS
2188                     PDTCRYPT_STRICTIVS = True
2189                 elif arg in [ "-o", "--out", "--dest", "--sink" ]:
2190                     outsspec = checked_arg ()
2191                     if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypt to %s" % outsspec)
2192                 elif arg in [ "-f", "--force" ]:
2193                     PDTCRYPT_OVERWRITE = True
2194                     if PDTCRYPT_VERBOSE is True: noise ("PDT: overwrite existing files")
2195                 elif arg in [ "-S", "--split" ]:
2196                     mode |= PDTCRYPT_SPLIT
2197                     if PDTCRYPT_VERBOSE is True: noise ("PDT: split files")
2198                 elif arg in [ "-D", "--no-decrypt" ]:
2199                     mode &= ~PDTCRYPT_DECRYPT
2200                     if PDTCRYPT_VERBOSE is True: noise ("PDT: not decrypting")
2201                 elif arg in [ "-k", "--key" ]:
2202                     arg = checked_arg ()
2203                     checked_secret (make_secret (key=arg))
2204                     if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypting with key")
2205                 else:
2206                     bail ("ERROR: unexpected positional argument “%s”" % arg)
2207             elif subcommand == PDTCRYPT_SUB_SCRYPT:
2208                 if arg in [ "-n", "--nacl", "--salt" ]:
2209                     nacl = checked_arg ()
2210                     if PDTCRYPT_VERBOSE is True: noise ("PDT: salt key with %s" % nacl)
2211                 elif arg in [ "-f", "--format" ]:
2212                     arg = checked_arg ()
2213                     try:
2214                         scrypt_format = PDTCRYPT_SCRYPT_FORMAT [arg]
2215                     except KeyError:
2216                         bail ("ERROR: invalid scrypt output format %s" % arg)
2217                     if PDTCRYPT_VERBOSE is True:
2218                         noise ("PDT: scrypt output format “%s”" % scrypt_format)
2219                 else:
2220                     bail ("ERROR: unexpected positional argument “%s”" % arg)
2221             elif subcommand == PDTCRYPT_SUB_SCAN:
2222                 if arg in [ "-o", "--out", "--dest", "--sink" ]:
2223                     outsspec = checked_arg ()
2224                     if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypt to %s" % outsspec)
2225                 elif arg in [ "-f", "--force" ]:
2226                     PDTCRYPT_OVERWRITE = True
2227                     if PDTCRYPT_VERBOSE is True: noise ("PDT: overwrite existing files")
2228                 else:
2229                     bail ("ERROR: unexpected positional argument “%s”" % arg)
2230
2231     if secret is None:
2232         if PDTCRYPT_VERBOSE is True:
2233             noise ("ERROR: no password or key specified, trying $PDTCRYPT_PASSWORD")
2234         epw = os.getenv ("PDTCRYPT_PASSWORD")
2235         if epw is not None:
2236             checked_secret (make_secret (password=epw.strip ()))
2237
2238     if secret is None:
2239         if PDTCRYPT_VERBOSE is True:
2240             noise ("ERROR: no password or key specified, trying $PDTCRYPT_KEY")
2241         ek = os.getenv ("PDTCRYPT_KEY")
2242         if ek is not None:
2243             checked_secret (make_secret (key=ek.strip ()))
2244
2245     if secret is None:
2246         if subcommand == PDTCRYPT_SUB_SCRYPT:
2247             bail ("ERROR: scrypt hash mode requested but no password given")
2248         elif mode & PDTCRYPT_DECRYPT:
2249             bail ("ERROR: decryption requested but no password given")
2250
2251     if mode & PDTCRYPT_SPLIT and outsspec is None:
2252         bail ("ERROR: split mode is incompatible with stdout sink "
2253               "(the default)")
2254
2255     if subcommand == PDTCRYPT_SUB_SCAN and outsspec is None:
2256         pass # no output by default in scan mode
2257     elif mode & PDTCRYPT_SPLIT or subcommand == PDTCRYPT_SUB_SCAN:
2258         # destination must be directory
2259         if outsspec == "-":
2260             bail ("ERROR: mode is incompatible with stdout sink")
2261         try:
2262             try:
2263                 os.makedirs (outsspec, 0o700)
2264             except FileExistsError:
2265                 # if it’s a directory with appropriate perms, everything is
2266                 # good; otherwise, below invocation of open(2) will fail
2267                 pass
2268             outs = os.open (outsspec, os.O_DIRECTORY, 0o600)
2269         except FileNotFoundError as exn:
2270             bail ("ERROR: cannot create target directory “%s”" % outsspec)
2271         except NotADirectoryError as exn:
2272             bail ("ERROR: target path “%s” is not a directory" % outsspec)
2273     else:
2274         outs = deptdcrypt_mk_stream (PDTCRYPT_SINK, outsspec or "-")
2275
2276     if subcommand == PDTCRYPT_SUB_SCAN:
2277         if insspec is None:
2278             bail ("ERROR: please supply an input file for scanning")
2279         if insspec == '-':
2280             bail ("ERROR: input must be seekable; please specify a file")
2281         return True, partial (mode_scan, secret, insspec, outs, nacl=nacl)
2282
2283     if subcommand == PDTCRYPT_SUB_SCRYPT:
2284         if secret [0] == PDTCRYPT_SECRET_KEY:
2285             bail ("ERROR: scrypt mode requires a password")
2286         if     insspec is not None and nacl is not None \
2287             or insspec is     None and nacl is     None :
2288                 bail ("ERROR: please supply either an input file or "
2289                       "the salt")
2290
2291     # default to stdout
2292     ins = None
2293     if insspec is not None or subcommand != PDTCRYPT_SUB_SCRYPT:
2294         ins = deptdcrypt_mk_stream (PDTCRYPT_SOURCE, insspec or "-")
2295
2296     if subcommand == PDTCRYPT_SUB_SCRYPT:
2297         return True, partial (mode_scrypt, secret [1].encode (), ins, nacl,
2298                               fmt=scrypt_format)
2299
2300     return True, partial (mode_depdtcrypt, mode, secret, ins, outs)
2301
2302
2303 def main (argv):
2304     ok, runner = parse_argv (argv)
2305
2306     if ok is True: return runner ()
2307
2308     return 1
2309
2310
2311 if __name__ == "__main__":
2312     sys.exit (main (sys.argv))
2313