4 import subprocess as _subprocess
5 import struct as _struct
8 def _get_stdout(args, stdin=None):
11 stdin_pipe = _subprocess.PIPE
12 p = _subprocess.Popen(args, stdin=stdin_pipe, stdout=_subprocess.PIPE)
13 stdout, stderr = p.communicate(stdin)
16 raise RuntimeError(status)
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
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',
41 11: 'literal data 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',
54 _public_key_algorithms = {
55 1: 'rsa (encrypt or sign)',
56 2: 'rsa encrypt-only',
58 16: 'elgamal (encrypt-only)',
59 17: 'dsa (digital signature algorithm)',
60 18: 'reserved for elliptic curve',
61 19: 'reserved for ecdsa',
62 20: 'reserved (formerly elgamal encrypt or sign)',
63 21: 'reserved for diffie-hellman',
77 _clean_type_regex = _re.compile('\W+')
79 def _clean_type(self):
80 return self._clean_type_regex.sub('_', self['type'])
82 def from_bytes(self, data):
83 offset = self._parse_header(data=data)
84 packet = data[offset:offset + self['length']]
85 if len(packet) < self['length']:
86 raise ValueError('packet too short ({} < {})'.format(
87 len(packet), self['length']))
88 offset += self['length']
89 method_name = '_parse_{}'.format(self._clean_type())
90 method = getattr(self, method_name, None)
92 raise NotImplementedError(
93 'cannot parse packet type {!r}'.format(self['type']))
97 def _parse_header(self, data):
100 always_one = packet_tag & 1 << 7
102 raise ValueError('most significant packet tag bit not set')
103 self['new-format'] = packet_tag & 1 << 6
104 if self['new-format']:
105 type_code = packet_tag & 0b111111
106 raise NotImplementedError('new-format packet length')
108 type_code = packet_tag >> 2 & 0b1111
109 self['length-type'] = packet_tag & 0b11
110 length_bytes, length_type = self._old_format_packet_length_type[
113 raise NotImplementedError(
114 'old-format packet of indeterminate length')
115 length_format = '>{}'.format(length_type)
116 length_data = data[offset: offset + length_bytes]
117 offset += length_bytes
118 self['length'] = _struct.unpack(length_format, length_data)[0]
119 self['type'] = self._packet_types[type_code]
122 def _parse_public_key_packet(self, data):
123 self._parse_generic_public_key_packet(data=data)
125 def _parse_public_subkey_packet(self, data):
126 self._parse_generic_public_key_packet(data=data)
128 def _parse_generic_public_key_packet(self, data):
129 self['key-version'] = data[0]
131 if self['key-version'] != 4:
132 raise NotImplementedError(
133 'public (sub)key packet version {}'.format(
134 self['key-version']))
136 self['creation_time'], self['public-key-algorithm'] = _struct.unpack(
137 '>IB', data[offset: offset + length])
139 self['key'] = data[offset:]
145 def packets_from_bytes(data):
147 while offset < len(data):
149 offset += packet.from_bytes(data=data[offset:])
153 def migrate(old_key, new_key):
154 """Add the old key and sub-keys to the new key
156 For example, to upgrade your master key, while preserving old
157 signatures you'd made. You will lose signature *on* your old key
158 though, since sub-keys can't be signed (I don't think).
160 old_key_export = _get_stdout(
161 ['gpg', '--export', old_key])
162 old_key_packets = list(
163 packets_from_bytes(data=old_key_export))
164 if old_key_packets[0]['type'] != 'public-key packet':
166 '{} does not start with a public-key packet'.format(
168 old_key_secret_export = _get_stdout(
169 ['gpg', '--export-secret-keys', old_key])
170 old_key_secret_packets = list(
171 packets_from_bytes(data=old_key_secret_export))
174 pprint.pprint(old_key_packets)
175 pprint.pprint(old_key_secret_packets)
178 if __name__ == '__main__':
181 old_key, new_key = _sys.argv[1:3]
182 migrate(old_key=old_key, new_key=new_key)