f754aef8b6498935d8e1f43c4e672ba19c39a669
[gpg-migrate.git] / gpg-migrate.py
1 #!/usr/bin/python
2
3 import re as _re
4 import subprocess as _subprocess
5 import struct as _struct
6
7
8 def _get_stdout(args, stdin=None):
9     stdin_pipe = None
10     if stdin is not None:
11         stdin_pipe = _subprocess.PIPE
12     p = _subprocess.Popen(args, stdin=stdin_pipe, stdout=_subprocess.PIPE)
13     stdout, stderr = p.communicate(stdin)
14     status = p.wait()
15     if status != 0:
16         raise RuntimeError(status)
17     return stdout
18
19
20 class PGPPacket (dict):
21     # http://tools.ietf.org/search/rfc4880
22     _old_format_packet_length_type = {  # type: (bytes, struct type)
23         0: (1, 'B'),  # 1-byte unsigned integer
24         1: (2, 'H'),  # 2-byte unsigned integer
25         2: (4, 'I'),  # 4-byte unsigned integer
26         3: (None, None),
27         }
28
29     _packet_types = {
30         0: 'reserved',
31         1: 'public-key encrypted session key packet',
32         2: 'signature packet',
33         3: 'symmetric-key encrypted session key packet',
34         4: 'one-pass signature packet',
35         5: 'secret-key packet',
36         6: 'public-key packet',
37         7: 'secret-subkey packet',
38         8: 'compressed data packet',
39         9: 'symmetrically encrypted data packet',
40         10: 'marker packet',
41         11: 'literal data packet',
42         12: 'trust packet',
43         13: 'user id packet',
44         14: 'public-subkey packet',
45         17: 'user attribute packet',
46         18: 'sym. encrypted and integrity protected data packet',
47         19: 'modification detection code packet',
48         60: 'private',
49         61: 'private',
50         62: 'private',
51         63: 'private',
52         }
53
54     _clean_type_regex = _re.compile('\W+')
55
56     def _clean_type(self):
57         return self._clean_type_regex.sub('_', self['type'])
58
59     def from_bytes(self, data):
60         offset = self._parse_header(data=data)
61         packet = data[offset:offset + self['length']]
62         if len(packet) < self['length']:
63             raise ValueError('packet too short ({} < {})'.format(
64                 len(packet), self['length']))
65         offset += self['length']
66         method_name = '_parse_{}'.format(self._clean_type())
67         method = getattr(self, method_name, None)
68         if not method:
69             raise NotImplementedError(
70                 'cannot parse packet type {!r}'.format(self['type']))
71         method(data=packet)
72         return offset
73
74     def _parse_header(self, data):
75         packet_tag = data[0]
76         offset = 1
77         always_one = packet_tag & 1 << 7
78         if not always_one:
79             raise ValueError('most significant packet tag bit not set')
80         self['new-format'] = packet_tag & 1 << 6
81         if self['new-format']:
82             type_code = packet_tag & 0b111111
83             raise NotImplementedError('new-format packet length')
84         else:
85             type_code = packet_tag >> 2 & 0b1111
86             self['length-type'] = packet_tag & 0b11
87             length_bytes, length_type = self._old_format_packet_length_type[
88                 self['length-type']]
89             if not length_bytes:
90                 raise NotImplementedError(
91                     'old-format packet of indeterminate length')
92             length_format = '>{}'.format(length_type)
93             length_data = data[offset: offset + length_bytes]
94             offset += length_bytes
95             self['length'] = _struct.unpack(length_format, length_data)[0]
96         self['type'] = self._packet_types[type_code]
97         return offset
98
99     def to_bytes(self):
100         pass
101
102
103 def packets_from_bytes(data):
104     offset = 0
105     while offset < len(data):
106         packet = PGPPacket()
107         offset += packet.from_bytes(data=data[offset:])
108         yield packet
109
110
111 def migrate(old_key, new_key):
112     """Add the old key and sub-keys to the new key
113
114     For example, to upgrade your master key, while preserving old
115     signatures you'd made.  You will lose signature *on* your old key
116     though, since sub-keys can't be signed (I don't think).
117     """
118     old_key_export = _get_stdout(
119         ['gpg', '--export', old_key])
120     old_key_packets = list(
121         packets_from_bytes(data=old_key_export))
122     old_key_secret_export = _get_stdout(
123         ['gpg', '--export-secret-keys', old_key])
124     old_key_secret_packets = list(
125         packets_from_bytes(data=old_key_secret_export))
126
127     import pprint
128     pprint.pprint(old_key_packets)
129     pprint.pprint(old_key_secret_packets)
130
131
132 if __name__ == '__main__':
133     import sys as _sys
134
135     old_key, new_key = _sys.argv[1:3]
136     migrate(old_key=old_key, new_key=new_key)