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