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