Stub out a PGPPacket._key_size dictionary
[gpg-migrate.git] / gpg-migrate.py
1 #!/usr/bin/python
2
3 import getpass as _getpass
4 import hashlib as _hashlib
5 import math as _math
6 import re as _re
7 import subprocess as _subprocess
8 import struct as _struct
9
10 import Crypto.Cipher.AES as _crypto_cipher_aes
11 import Crypto.Cipher.Blowfish as _crypto_cipher_blowfish
12 import Crypto.Cipher.CAST as _crypto_cipher_cast
13 import Crypto.Cipher.DES3 as _crypto_cipher_des3
14
15
16 def _get_stdout(args, stdin=None):
17     stdin_pipe = None
18     if stdin is not None:
19         stdin_pipe = _subprocess.PIPE
20     p = _subprocess.Popen(args, stdin=stdin_pipe, stdout=_subprocess.PIPE)
21     stdout, stderr = p.communicate(stdin)
22     status = p.wait()
23     if status != 0:
24         raise RuntimeError(status)
25     return stdout
26
27
28 class PGPPacket (dict):
29     # http://tools.ietf.org/search/rfc4880
30     _old_format_packet_length_type = {  # type: (bytes, struct type)
31         0: (1, 'B'),  # 1-byte unsigned integer
32         1: (2, 'H'),  # 2-byte unsigned integer
33         2: (4, 'I'),  # 4-byte unsigned integer
34         3: (None, None),
35         }
36
37     _packet_types = {
38         0: 'reserved',
39         1: 'public-key encrypted session key packet',
40         2: 'signature packet',
41         3: 'symmetric-key encrypted session key packet',
42         4: 'one-pass signature packet',
43         5: 'secret-key packet',
44         6: 'public-key packet',
45         7: 'secret-subkey packet',
46         8: 'compressed data packet',
47         9: 'symmetrically encrypted data packet',
48         10: 'marker packet',
49         11: 'literal data packet',
50         12: 'trust packet',
51         13: 'user id packet',
52         14: 'public-subkey packet',
53         17: 'user attribute packet',
54         18: 'sym. encrypted and integrity protected data packet',
55         19: 'modification detection code packet',
56         60: 'private',
57         61: 'private',
58         62: 'private',
59         63: 'private',
60         }
61
62     _public_key_algorithms = {
63         1: 'rsa (encrypt or sign)',
64         2: 'rsa encrypt-only',
65         3: 'rsa sign-only',
66         16: 'elgamal (encrypt-only)',
67         17: 'dsa (digital signature algorithm)',
68         18: 'reserved for elliptic curve',
69         19: 'reserved for ecdsa',
70         20: 'reserved (formerly elgamal encrypt or sign)',
71         21: 'reserved for diffie-hellman',
72         100: 'private',
73         101: 'private',
74         102: 'private',
75         103: 'private',
76         104: 'private',
77         105: 'private',
78         106: 'private',
79         107: 'private',
80         108: 'private',
81         109: 'private',
82         110: 'private',
83         }
84
85     _symmetric_key_algorithms = {
86         0: 'plaintext or unencrypted data',
87         1: 'idea',
88         2: 'tripledes',
89         3: 'cast5',
90         4: 'blowfish',
91         5: 'reserved',
92         6: 'reserved',
93         7: 'aes with 128-bit key',
94         8: 'aes with 192-bit key',
95         9: 'aes with 256-bit key',
96         10: 'twofish',
97         100: 'private',
98         101: 'private',
99         102: 'private',
100         103: 'private',
101         104: 'private',
102         105: 'private',
103         106: 'private',
104         107: 'private',
105         108: 'private',
106         109: 'private',
107         110: 'private',
108         }
109
110     _cipher_block_size = {  # in bits
111         'aes with 128-bit key': 128,
112         'aes with 192-bit key': 128,
113         'aes with 256-bit key': 128,
114         'cast5': 64,
115         }
116
117     _crypto_module = {
118         'aes with 128-bit key': _crypto_cipher_aes,
119         'aes with 192-bit key': _crypto_cipher_aes,
120         'aes with 256-bit key': _crypto_cipher_aes,
121         'blowfish': _crypto_cipher_blowfish,
122         'cast5': _crypto_cipher_cast,
123         'tripledes': _crypto_cipher_des3,
124         }
125
126     _key_size = {  # in bits
127         'aes with 128-bit key': 128,
128         'aes with 192-bit key': 192,
129         'aes with 256-bit key': 256,
130         }
131
132     _compression_algorithms = {
133         0: 'uncompressed',
134         1: 'zip',
135         2: 'zlib',
136         3: 'bzip2',
137         100: 'private',
138         101: 'private',
139         102: 'private',
140         103: 'private',
141         104: 'private',
142         105: 'private',
143         106: 'private',
144         107: 'private',
145         108: 'private',
146         109: 'private',
147         110: 'private',
148         }
149
150     _hash_algorithms = {
151         1: 'md5',
152         2: 'sha-1',
153         3: 'ripe-md/160',
154         4: 'reserved',
155         5: 'reserved',
156         6: 'reserved',
157         7: 'reserved',
158         8: 'sha256',
159         9: 'sha384',
160         10: 'sha512',
161         11: 'sha224',
162         100: 'private',
163         101: 'private',
164         102: 'private',
165         103: 'private',
166         104: 'private',
167         105: 'private',
168         106: 'private',
169         107: 'private',
170         108: 'private',
171         109: 'private',
172         110: 'private',
173         }
174
175     _string_to_key_types = {
176         0: 'simple',
177         1: 'salted',
178         2: 'reserved',
179         3: 'iterated and salted',
180         100: 'private',
181         101: 'private',
182         102: 'private',
183         103: 'private',
184         104: 'private',
185         105: 'private',
186         106: 'private',
187         107: 'private',
188         108: 'private',
189         109: 'private',
190         110: 'private',
191         }
192
193     _string_to_key_expbias = 6
194
195     _signature_types = {
196         0x00: 'binary document',
197         0x01: 'canonical text document',
198         0x02: 'standalone',
199         0x10: 'generic user id and public-key packet',
200         0x11: 'persona user id and public-key packet',
201         0x12: 'casual user id and public-key packet',
202         0x13: 'postitive user id and public-key packet',
203         0x18: 'subkey binding',
204         0x19: 'primary key binding',
205         0x1F: 'direct key',
206         0x20: 'key revocation',
207         0x28: 'subkey revocation',
208         0x30: 'certification revocation',
209         0x40: 'timestamp',
210         0x50: 'third-party confirmation',
211         }
212
213     _signature_subpacket_types = {
214         0: 'reserved',
215         1: 'reserved',
216         2: 'signature creation time',
217         3: 'signature expiration time',
218         4: 'exportable certification',
219         5: 'trust signature',
220         6: 'regular expression',
221         7: 'revocable',
222         8: 'reserved',
223         9: 'key expiration time',
224         10: 'placeholder for backward compatibility',
225         11: 'preferred symmetric algorithms',
226         12: 'revocation key',
227         13: 'reserved',
228         14: 'reserved',
229         15: 'reserved',
230         16: 'issuer',
231         17: 'reserved',
232         18: 'reserved',
233         19: 'reserved',
234         20: 'notation data',
235         21: 'preferred hash algorithms',
236         22: 'preferred compression algorithms',
237         23: 'key server preferences',
238         24: 'preferred key server',
239         25: 'primary user id',
240         26: 'policy uri',
241         27: 'key flags',
242         28: 'signer user id',
243         29: 'reason for revocation',
244         30: 'features',
245         31: 'signature target',
246         32: 'embedded signature',
247         100: 'private',
248         101: 'private',
249         102: 'private',
250         103: 'private',
251         104: 'private',
252         105: 'private',
253         106: 'private',
254         107: 'private',
255         108: 'private',
256         109: 'private',
257         110: 'private',
258         }
259
260     _clean_type_regex = _re.compile('\W+')
261
262     def _clean_type(self, type=None):
263         if type is None:
264             type = self['type']
265         return self._clean_type_regex.sub('_', type)
266
267     @staticmethod
268     def _reverse(dict, value):
269         """Reverse lookups in dictionaries
270
271         >>> PGPPacket._reverse(PGPPacket._packet_types, 'public-key packet')
272         6
273         """
274         return [k for k,v in dict.items() if v == value][0]
275
276     def __str__(self):
277         method_name = '_str_{}'.format(self._clean_type())
278         method = getattr(self, method_name, None)
279         if not method:
280             return self['type']
281         details = method()
282         return '{}: {}'.format(self['type'], details)
283
284     def _str_public_key_packet(self):
285         return self._str_generic_key_packet()
286
287     def _str_public_subkey_packet(self):
288         return self._str_generic_key_packet()
289
290     def _str_secret_key_packet(self):
291         return self._str_generic_key_packet()
292
293     def _str_secret_subkey_packet(self):
294         return self._str_generic_key_packet()
295
296     def _str_generic_key_packet(self):
297         return self['fingerprint'][-8:].upper()
298
299     def _str_signature_packet(self):
300         lines = [self['signature-type']]
301         if self['hashed-subpackets']:
302             lines.append('  hashed subpackets:')
303             lines.extend(self._str_signature_subpackets(
304                 self['hashed-subpackets'], prefix='    '))
305         if self['unhashed-subpackets']:
306             lines.append('  unhashed subpackets:')
307             lines.extend(self._str_signature_subpackets(
308                 self['unhashed-subpackets'], prefix='    '))
309         return '\n'.join(lines)
310
311     def _str_signature_subpackets(self, subpackets, prefix):
312         lines = []
313         for subpacket in subpackets:
314             method_name = '_str_{}_signature_subpacket'.format(
315                 self._clean_type(type=subpacket['type']))
316             method = getattr(self, method_name, None)
317             if method:
318                 lines.append('    {}: {}'.format(
319                     subpacket['type'],
320                     method(subpacket=subpacket)))
321             else:
322                 lines.append('    {}'.format(subpacket['type']))
323         return lines
324
325     def _str_signature_creation_time_signature_subpacket(self, subpacket):
326         return str(subpacket['signature-creation-time'])
327
328     def _str_issuer_signature_subpacket(self, subpacket):
329         return subpacket['issuer'][-8:].upper()
330
331     def _str_key_expiration_time_signature_subpacket(self, subpacket):
332         return str(subpacket['key-expiration-time'])
333
334     def _str_preferred_symmetric_algorithms_signature_subpacket(
335             self, subpacket):
336         return ', '.join(
337             algo for algo in subpacket['preferred-symmetric-algorithms'])
338
339     def _str_preferred_hash_algorithms_signature_subpacket(
340             self, subpacket):
341         return ', '.join(
342             algo for algo in subpacket['preferred-hash-algorithms'])
343
344     def _str_preferred_compression_algorithms_signature_subpacket(
345             self, subpacket):
346         return ', '.join(
347             algo for algo in subpacket['preferred-compression-algorithms'])
348
349     def _str_key_server_preferences_signature_subpacket(self, subpacket):
350         return ', '.join(
351             x for x in sorted(subpacket['key-server-preferences']))
352
353     def _str_primary_user_id_signature_subpacket(self, subpacket):
354         return str(subpacket['primary-user-id'])
355
356     def _str_key_flags_signature_subpacket(self, subpacket):
357         return ', '.join(x for x in sorted(subpacket['key-flags']))
358
359     def _str_features_signature_subpacket(self, subpacket):
360         return ', '.join(x for x in sorted(subpacket['features']))
361
362     def _str_embedded_signature_signature_subpacket(self, subpacket):
363         return subpacket['embedded']['signature-type']
364
365     def _str_user_id_packet(self):
366         return self['user']
367
368     def from_bytes(self, data):
369         offset = self._parse_header(data=data)
370         packet = data[offset:offset + self['length']]
371         if len(packet) < self['length']:
372             raise ValueError('packet too short ({} < {})'.format(
373                 len(packet), self['length']))
374         offset += self['length']
375         method_name = '_parse_{}'.format(self._clean_type())
376         method = getattr(self, method_name, None)
377         if not method:
378             raise NotImplementedError(
379                 'cannot parse packet type {!r}'.format(self['type']))
380         method(data=packet)
381         return offset
382
383     def _parse_header(self, data):
384         packet_tag = data[0]
385         offset = 1
386         always_one = packet_tag & 1 << 7
387         if not always_one:
388             raise ValueError('most significant packet tag bit not set')
389         self['new-format'] = packet_tag & 1 << 6
390         if self['new-format']:
391             type_code = packet_tag & 0b111111
392             raise NotImplementedError('new-format packet length')
393         else:
394             type_code = packet_tag >> 2 & 0b1111
395             self['length-type'] = packet_tag & 0b11
396             length_bytes, length_type = self._old_format_packet_length_type[
397                 self['length-type']]
398             if not length_bytes:
399                 raise NotImplementedError(
400                     'old-format packet of indeterminate length')
401             length_format = '>{}'.format(length_type)
402             length_data = data[offset: offset + length_bytes]
403             offset += length_bytes
404             self['length'] = _struct.unpack(length_format, length_data)[0]
405         self['type'] = self._packet_types[type_code]
406         return offset
407
408     @staticmethod
409     def _parse_multiprecision_integer(data):
410         r"""Parse RFC 4880's multiprecision integers
411
412         >>> PGPPacket._parse_multiprecision_integer(b'\x00\x01\x01')
413         (3, 1)
414         >>> PGPPacket._parse_multiprecision_integer(b'\x00\x09\x01\xff')
415         (4, 511)
416         """
417         bits = _struct.unpack('>H', data[:2])[0]
418         offset = 2
419         length = (bits + 7) // 8
420         value = 0
421         for i in range(length):
422             value += data[offset + i] * 1 << (8 * (length - i - 1))
423         offset += length
424         return (offset, value)
425
426     @classmethod
427     def _decode_string_to_key_count(cls, data):
428         r"""Decode RFC 4880's string-to-key count
429
430         >>> PGPPacket._decode_string_to_key_count(b'\x97'[0])
431         753664
432         """
433         return (16 + (data & 15)) << ((data >> 4) + cls._string_to_key_expbias)
434
435     def _parse_string_to_key_specifier(self, data):
436         self['string-to-key-type'] = self._string_to_key_types[data[0]]
437         offset = 1
438         if self['string-to-key-type'] == 'simple':
439             self['string-to-key-hash-algorithm'] = self._hash_algorithms[
440                 data[offset]]
441             offset += 1
442         elif self['string-to-key-type'] == 'salted':
443             self['string-to-key-hash-algorithm'] = self._hash_algorithms[
444                 data[offset]]
445             offset += 1
446             self['string-to-key-salt'] = data[offset: offset + 8]
447             offset += 8
448         elif self['string-to-key-type'] == 'iterated and salted':
449             self['string-to-key-hash-algorithm'] = self._hash_algorithms[
450                 data[offset]]
451             offset += 1
452             self['string-to-key-salt'] = data[offset: offset + 8]
453             offset += 8
454             self['string-to-key-count'] = self._decode_string_to_key_count(
455                 data=data[offset])
456             offset += 1
457         else:
458             raise NotImplementedError(
459                 'string-to-key type {}'.format(self['string-to-key-type']))
460         return offset
461
462     def _parse_public_key_packet(self, data):
463         self._parse_generic_public_key_packet(data=data)
464
465     def _parse_public_subkey_packet(self, data):
466         self._parse_generic_public_key_packet(data=data)
467
468     def _parse_generic_public_key_packet(self, data):
469         self['key-version'] = data[0]
470         offset = 1
471         if self['key-version'] != 4:
472             raise NotImplementedError(
473                 'public (sub)key packet version {}'.format(
474                     self['key-version']))
475         length = 5
476         self['creation-time'], algorithm = _struct.unpack(
477             '>IB', data[offset: offset + length])
478         offset += length
479         self['public-key-algorithm'] = self._public_key_algorithms[algorithm]
480         if self['public-key-algorithm'].startswith('rsa '):
481             o, self['public-modulus'] = self._parse_multiprecision_integer(
482                 data[offset:])
483             offset += o
484             o, self['public-exponent'] = self._parse_multiprecision_integer(
485                 data[offset:])
486             offset += o
487         elif self['public-key-algorithm'].startswith('dsa '):
488             o, self['prime'] = self._parse_multiprecision_integer(
489                 data[offset:])
490             offset += o
491             o, self['group-order'] = self._parse_multiprecision_integer(
492                 data[offset:])
493             offset += o
494             o, self['group-generator'] = self._parse_multiprecision_integer(
495                 data[offset:])
496             offset += o
497             o, self['public-key'] = self._parse_multiprecision_integer(
498                 data[offset:])
499             offset += o
500         elif self['public-key-algorithm'].startswith('elgamal '):
501             o, self['prime'] = self._parse_multiprecision_integer(
502                 data[offset:])
503             offset += o
504             o, self['group-generator'] = self._parse_multiprecision_integer(
505                 data[offset:])
506             offset += o
507             o, self['public-key'] = self._parse_multiprecision_integer(
508                 data[offset:])
509             offset += o
510         else:
511             raise NotImplementedError(
512                 'algorithm-specific key fields for {}'.format(
513                     self['public-key-algorithm']))
514         fingerprint = _hashlib.sha1()
515         fingerprint.update(b'\x99')
516         fingerprint.update(_struct.pack('>H', len(data)))
517         fingerprint.update(data)
518         self['fingerprint'] = fingerprint.hexdigest()
519         return offset
520
521     def _parse_secret_key_packet(self, data):
522         self._parse_generic_secret_key_packet(data=data)
523
524     def _parse_secret_subkey_packet(self, data):
525         self._parse_generic_secret_key_packet(data=data)
526
527     def _parse_generic_secret_key_packet(self, data):
528         offset = self._parse_generic_public_key_packet(data=data)
529         string_to_key_usage = data[offset]
530         offset += 1
531         if string_to_key_usage in [255, 254]:
532             self['symmetric-encryption-algorithm'] = (
533                 self._symmetric_key_algorithms[data[offset]])
534             offset += 1
535             offset += self._parse_string_to_key_specifier(data=data[offset:])
536         else:
537             self['symmetric-encryption-algorithm'] = (
538                 self._symmetric_key_algorithms[string_to_key_usage])
539         if string_to_key_usage:
540             block_size_bits = self._cipher_block_size.get(
541                 self['symmetric-encryption-algorithm'], None)
542             if block_size_bits % 8:
543                 raise NotImplementedError(
544                     ('{}-bit block size for {} is not an integer number of bytes'
545                      ).format(
546                          block_size_bits, self['symmetric-encryption-algorithm']))
547             block_size = block_size_bits // 8
548             if not block_size:
549                 raise NotImplementedError(
550                     'unknown block size for {}'.format(
551                         self['symmetric-encryption-algorithm']))
552             self['initial-vector'] = data[offset: offset + block_size]
553             offset += block_size
554             ciphertext = data[offset:]
555             offset += len(ciphertext)
556             decrypted_data = self.decrypt_symmetric_encryption(data=ciphertext)
557         else:
558             decrypted_data = data[offset:key_end]
559         if string_to_key_usage in [0, 255]:
560             key_end = -2
561         elif string_to_key_usage == 254:
562             key_end = -20
563         else:
564             key_end = 0
565         secret_key = decrypted_data[:key_end]
566         if key_end:
567             secret_key_checksum = decrypted_data[key_end:]
568             if key_end == -2:
569                 calculated_checksum = sum(secret_key) % 65536
570             else:
571                 checksum_hash = _hashlib.sha1()
572                 checksum_hash.update(secret_key)
573                 calculated_checksum = checksum_hash.digest()
574             if secret_key_checksum != calculated_checksum:
575                 raise ValueError(
576                     'corrupt secret key (checksum {} != expected {})'.format(
577                         secret_key_checksum, calculated_checksum))
578         self['secret-key'] = secret_key
579
580     def _parse_signature_subpackets(self, data):
581         offset = 0
582         while offset < len(data):
583             o, subpacket = self._parse_signature_subpacket(data=data[offset:])
584             offset += o
585             yield subpacket
586
587     def _parse_signature_subpacket(self, data):
588         subpacket = {}
589         first = data[0]
590         offset = 1
591         if first < 192:
592             length = first
593         elif first >= 192 and first < 255:
594             second = data[offset]
595             offset += 1
596             length = ((first - 192) << 8) + second + 192
597         else:
598             length = _struct.unpack(
599                 '>I', data[offset: offset + 4])[0]
600             offset += 4
601         subpacket['type'] = self._signature_subpacket_types[data[offset]]
602         offset += 1
603         subpacket_data = data[offset: offset + length - 1]
604         offset += len(subpacket_data)
605         method_name = '_parse_{}_signature_subpacket'.format(
606             self._clean_type(type=subpacket['type']))
607         method = getattr(self, method_name, None)
608         if not method:
609             raise NotImplementedError(
610                 'cannot parse signature subpacket type {!r}'.format(
611                     subpacket['type']))
612         method(data=subpacket_data, subpacket=subpacket)
613         return (offset, subpacket)
614
615     def _parse_signature_packet(self, data):
616         self['signature-version'] = data[0]
617         offset = 1
618         if self['signature-version'] != 4:
619             raise NotImplementedError(
620                 'signature packet version {}'.format(
621                     self['signature-version']))
622         self['signature-type'] = self._signature_types[data[offset]]
623         offset += 1
624         self['public-key-algorithm'] = self._public_key_algorithms[
625             data[offset]]
626         offset += 1
627         self['hash-algorithm'] = self._hash_algorithms[data[offset]]
628         offset += 1
629         hashed_count = _struct.unpack('>H', data[offset: offset + 2])[0]
630         offset += 2
631         self['hashed-subpackets'] = list(self._parse_signature_subpackets(
632             data[offset: offset + hashed_count]))
633         offset += hashed_count
634         unhashed_count = _struct.unpack('>H', data[offset: offset + 2])[0]
635         offset += 2
636         self['unhashed-subpackets'] = list(self._parse_signature_subpackets(
637             data=data[offset: offset + unhashed_count]))
638         offset += unhashed_count
639         self['signed-hash-word'] = data[offset: offset + 2]
640         offset += 2
641         self['signature'] = data[offset:]
642
643     def _parse_signature_creation_time_signature_subpacket(
644             self, data, subpacket):
645         subpacket['signature-creation-time'] = _struct.unpack('>I', data)[0]
646
647     def _parse_issuer_signature_subpacket(self, data, subpacket):
648         subpacket['issuer'] = ''.join('{:02x}'.format(byte) for byte in data)
649
650     def _parse_key_expiration_time_signature_subpacket(
651             self, data, subpacket):
652         subpacket['key-expiration-time'] = _struct.unpack('>I', data)[0]
653
654     def _parse_preferred_symmetric_algorithms_signature_subpacket(
655             self, data, subpacket):
656         subpacket['preferred-symmetric-algorithms'] = [
657             self._symmetric_key_algorithms[d] for d in data]
658
659     def _parse_preferred_hash_algorithms_signature_subpacket(
660             self, data, subpacket):
661         subpacket['preferred-hash-algorithms'] = [
662             self._hash_algorithms[d] for d in data]
663
664     def _parse_preferred_compression_algorithms_signature_subpacket(
665             self, data, subpacket):
666         subpacket['preferred-compression-algorithms'] = [
667             self._compression_algorithms[d] for d in data]
668
669     def _parse_key_server_preferences_signature_subpacket(
670             self, data, subpacket):
671         subpacket['key-server-preferences'] = set()
672         if data[0] & 0x80:
673             subpacket['key-server-preferences'].add('no-modify')
674
675     def _parse_primary_user_id_signature_subpacket(self, data, subpacket):
676         subpacket['primary-user-id'] = bool(data[0])
677
678     def _parse_key_flags_signature_subpacket(self, data, subpacket):
679         subpacket['key-flags'] = set()
680         if data[0] & 0x1:
681             subpacket['key-flags'].add('can certify')
682         if data[0] & 0x2:
683             subpacket['key-flags'].add('can sign')
684         if data[0] & 0x4:
685             subpacket['key-flags'].add('can encrypt communications')
686         if data[0] & 0x8:
687             subpacket['key-flags'].add('can encrypt storage')
688         if data[0] & 0x10:
689             subpacket['key-flags'].add('private split')
690         if data[0] & 0x20:
691             subpacket['key-flags'].add('can authenticate')
692         if data[0] & 0x80:
693             subpacket['key-flags'].add('private shared')
694
695     def _parse_features_signature_subpacket(self, data, subpacket):
696         subpacket['features'] = set()
697         if data[0] & 0x1:
698             subpacket['features'].add('modification detection')
699
700     def _parse_embedded_signature_signature_subpacket(self, data, subpacket):
701         subpacket['embedded'] = PGPPacket()
702         subpacket['embedded']._parse_signature_packet(data=data)
703
704     def _parse_user_id_packet(self, data):
705         self['user'] = str(data, 'utf-8')
706
707     def to_bytes(self):
708         method_name = '_serialize_{}'.format(self._clean_type())
709         method = getattr(self, method_name, None)
710         if not method:
711             raise NotImplementedError(
712                 'cannot serialize packet type {!r}'.format(self['type']))
713         body = method()
714         self['length'] = len(body)
715         return b''.join([
716             self._serialize_header(),
717             body,
718             ])
719
720     def _serialize_header(self):
721         always_one = 1
722         new_format = 0
723         type_code = self._reverse(self._packet_types, self['type'])
724         packet_tag = (
725             always_one * (1 << 7) |
726             new_format * (1 << 6) |
727             type_code * (1 << 2) |
728             self['length-type']
729             )
730         length_bytes, length_type = self._old_format_packet_length_type[
731             self['length-type']]
732         length_format = '>{}'.format(length_type)
733         length_data = _struct.pack(length_format, self['length'])
734         return b''.join([
735             bytes([packet_tag]),
736             length_data,
737             ])
738
739     @staticmethod
740     def _serialize_multiprecision_integer(integer):
741         r"""Serialize RFC 4880's multipricision integers
742
743         >>> PGPPacket._serialize_multiprecision_integer(1)
744         b'\x00\x01\x01'
745         >>> PGPPacket._serialize_multiprecision_integer(511)
746         b'\x00\t\x01\xff'
747         """
748         bit_length = int(_math.log(integer, 2)) + 1
749         chunks = [
750             _struct.pack('>H', bit_length),
751             ]
752         while integer > 0:
753             chunks.insert(1, bytes([integer & 0xff]))
754             integer = integer >> 8
755         return b''.join(chunks)
756
757     @classmethod
758     def _encode_string_to_key_count(cls, count):
759         r"""Encode RFC 4880's string-to-key count
760
761         >>> PGPPacket._encode_string_to_key_count(753664)
762         b'\x97'
763         """
764         coded_count = 0
765         count = count >> cls._string_to_key_expbias
766         while not count & 1:
767             count = count >> 1
768             coded_count += 1 << 4
769         coded_count += count & 15
770         return bytes([coded_count])
771
772     def _serialize_string_to_key_specifier(self):
773         string_to_key_type = bytes([
774             self._reverse(
775                 self._string_to_key_types, self['string-to-key-type']),
776             ])
777         chunks = [string_to_key_type]
778         if self['string-to-key-type'] == 'simple':
779             chunks.append(bytes([self._reverse(
780                 self._hash_algorithms, self['string-to-key-hash-algorithm'])]))
781         elif self['string-to-key-type'] == 'salted':
782             chunks.append(bytes([self._reverse(
783                 self._hash_algorithms, self['string-to-key-hash-algorithm'])]))
784             chunks.append(self['string-to-key-salt'])
785         elif self['string-to-key-type'] == 'iterated and salted':
786             chunks.append(bytes([self._reverse(
787                 self._hash_algorithms, self['string-to-key-hash-algorithm'])]))
788             chunks.append(self['string-to-key-salt'])
789             chunks.append(self._encode_string_to_key_count(
790                 count=self['string-to-key-count']))
791         else:
792             raise NotImplementedError(
793                 'string-to-key type {}'.format(self['string-to-key-type']))
794         return offset
795         return b''.join(chunks)
796
797     def _serialize_public_key_packet(self):
798         return self._serialize_generic_public_key_packet()
799
800     def _serialize_public_subkey_packet(self):
801         return self._serialize_generic_public_key_packet()
802
803     def _serialize_generic_public_key_packet(self):
804         key_version = bytes([self['key-version']])
805         chunks = [key_version]
806         if self['key-version'] != 4:
807             raise NotImplementedError(
808                 'public (sub)key packet version {}'.format(
809                     self['key-version']))
810         chunks.append(_struct.pack('>I', self['creation-time']))
811         chunks.append(bytes([self._reverse(
812             self._public_key_algorithms, self['public-key-algorithm'])]))
813         if self['public-key-algorithm'].startswith('rsa '):
814             chunks.append(self._serialize_multiprecision_integer(
815                 self['public-modulus']))
816             chunks.append(self._serialize_multiprecision_integer(
817                 self['public-exponent']))
818         elif self['public-key-algorithm'].startswith('dsa '):
819             chunks.append(self._serialize_multiprecision_integer(
820                 self['prime']))
821             chunks.append(self._serialize_multiprecision_integer(
822                 self['group-order']))
823             chunks.append(self._serialize_multiprecision_integer(
824                 self['group-generator']))
825             chunks.append(self._serialize_multiprecision_integer(
826                 self['public-key']))
827         elif self['public-key-algorithm'].startswith('elgamal '):
828             chunks.append(self._serialize_multiprecision_integer(
829                 self['prime']))
830             chunks.append(self._serialize_multiprecision_integer(
831                 self['group-generator']))
832             chunks.append(self._serialize_multiprecision_integer(
833                 self['public-key']))
834         else:
835             raise NotImplementedError(
836                 'algorithm-specific key fields for {}'.format(
837                     self['public-key-algorithm']))
838         return b''.join(chunks)
839
840     def decrypt_symmetric_encryption(self, data):
841         """Decrypt OpenPGP's Cipher Feedback mode"""
842         algorithm = self['symmetric-encryption-algorithm']
843         module = self._crypto_module[algorithm]
844         key_size = self._key_size[algorithm]
845         segment_size_bits = self._cipher_block_size[algorithm]
846         if segment_size_bits % 8:
847             raise NotImplementedError(
848                 ('{}-bit segment size for {} is not an integer number of bytes'
849                  ).format(segment_size_bits, algorithm))
850         segment_size_bytes = segment_size_bits // 8
851         padding = segment_size_bytes - len(data) % segment_size_bytes
852         if padding:
853             data += b'\x00' * padding
854         passphrase = _getpass.getpass(
855             'passphrase for {}: '.format(self['fingerprint'][-8:]))
856         passphrase = passphrase.encode('ascii')
857         cipher = module.new(
858             key=passphrase,
859             mode=module.MODE_CFB,
860             IV=self['initial-vector'],
861             segment_size=segment_size_bits)
862         plaintext = cipher.decrypt(data)
863         if padding:
864             plaintext = plaintext[:-padding]
865         return plaintext
866
867
868 def packets_from_bytes(data):
869     offset = 0
870     while offset < len(data):
871         packet = PGPPacket()
872         offset += packet.from_bytes(data=data[offset:])
873         yield packet
874
875
876 class PGPKey (object):
877     """An OpenPGP key with public and private parts.
878
879     From RFC 4880 [1]:
880
881       OpenPGP users may transfer public keys.  The essential elements
882       of a transferable public key are as follows:
883
884       - One Public-Key packet
885       - Zero or more revocation signatures
886       - One or more User ID packets
887       - After each User ID packet, zero or more Signature packets
888         (certifications)
889       - Zero or more User Attribute packets
890       - After each User Attribute packet, zero or more Signature
891         packets (certifications)
892       - Zero or more Subkey packets
893       - After each Subkey packet, one Signature packet, plus
894         optionally a revocation
895
896     Secret keys have a similar packet stream [2]:
897
898       OpenPGP users may transfer secret keys.  The format of a
899       transferable secret key is the same as a transferable public key
900       except that secret-key and secret-subkey packets are used
901       instead of the public key and public-subkey packets.
902       Implementations SHOULD include self-signatures on any user IDs
903       and subkeys, as this allows for a complete public key to be
904       automatically extracted from the transferable secret key.
905       Implementations MAY choose to omit the self-signatures,
906       especially if a transferable public key accompanies the
907       transferable secret key.
908
909     [1]: http://tools.ietf.org/search/rfc4880#section-11.1
910     [2]: http://tools.ietf.org/search/rfc4880#section-11.2
911     """
912     def __init__(self, fingerprint):
913         self.fingerprint = fingerprint
914         self.public_packets = None
915         self.secret_packets = None
916
917     def __str__(self):
918         lines = ['key: {}'.format(self.fingerprint)]
919         if self.public_packets:
920             lines.append('  public:')
921             for packet in self.public_packets:
922                 lines.extend(self._str_packet(packet=packet, prefix='    '))
923         if self.secret_packets:
924             lines.append('  secret:')
925             for packet in self.secret_packets:
926                 lines.extend(self._str_packet(packet=packet, prefix='    '))
927         return '\n'.join(lines)
928
929     def _str_packet(self, packet, prefix):
930         lines = str(packet).split('\n')
931         return [prefix + line for line in lines]
932
933     def import_from_gpg(self):
934         key_export = _get_stdout(
935             ['gpg', '--export', self.fingerprint])
936         self.public_packets = list(
937             packets_from_bytes(data=key_export))
938         if self.public_packets[0]['type'] != 'public-key packet':
939             raise ValueError(
940                 '{} does not start with a public-key packet'.format(
941                     self.fingerprint))
942         key_secret_export = _get_stdout(
943             ['gpg', '--export-secret-keys', self.fingerprint])
944         self.secret_packets = list(
945             packets_from_bytes(data=key_secret_export))
946         if self.secret_packets[0]['type'] != 'secret-key packet':
947             raise ValueError(
948                 '{} does not start with a secret-key packet'.format(
949                     self.fingerprint))
950
951     def export_to_gpg(self):
952         raise NotImplemetedError('export to gpg')
953
954     def import_from_key(self, key):
955         """Migrate the (sub)keys into this key"""
956         pass
957
958
959 def migrate(old_key, new_key):
960     """Add the old key and sub-keys to the new key
961
962     For example, to upgrade your master key, while preserving old
963     signatures you'd made.  You will lose signature *on* your old key
964     though, since sub-keys can't be signed (I don't think).
965     """
966     old_key = PGPKey(fingerprint=old_key)
967     old_key.import_from_gpg()
968     new_key = PGPKey(fingerprint=new_key)
969     new_key.import_from_gpg()
970     new_key.import_from_key(key=old_key)
971
972     print(old_key)
973     print(new_key)
974
975
976 if __name__ == '__main__':
977     import sys as _sys
978
979     old_key, new_key = _sys.argv[1:3]
980     migrate(old_key=old_key, new_key=new_key)