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