#!/usr/bin/python
+import hashlib as _hashlib
import re as _re
import subprocess as _subprocess
import struct as _struct
110: 'private',
}
+ _cipher_block_size = { # in bits
+ 'aes with 128-bit key': 128,
+ 'aes with 192-bit key': 128,
+ 'aes with 256-bit key': 128,
+ 'cast5': 64,
+ }
+
_compression_algorithms = {
0: 'uncompressed',
1: 'zip',
110: 'private',
}
+ _signature_types = {
+ 0x00: 'binary document',
+ 0x01: 'canonical text document',
+ 0x02: 'standalone',
+ 0x10: 'generic user id and public-key packet',
+ 0x11: 'persona user id and public-key packet',
+ 0x12: 'casual user id and public-key packet',
+ 0x13: 'postitive user id and public-key packet',
+ 0x18: 'subkey binding',
+ 0x19: 'primary key binding',
+ 0x1F: 'direct key',
+ 0x20: 'key revocation',
+ 0x28: 'subkey revocation',
+ 0x30: 'certification revocation',
+ 0x40: 'timestamp',
+ 0x50: 'third-party confirmation',
+ }
+
+ _signature_subpacket_types = {
+ 0: 'reserved',
+ 1: 'reserved',
+ 2: 'signature creation time',
+ 3: 'signature expiration time',
+ 4: 'exportable certification',
+ 5: 'trust signature',
+ 6: 'regular expression',
+ 7: 'revocable',
+ 8: 'reserved',
+ 9: 'key expiration time',
+ 10: 'placeholder for backward compatibility',
+ 11: 'preferred symmetric algorithms',
+ 12: 'revocation key',
+ 13: 'reserved',
+ 14: 'reserved',
+ 15: 'reserved',
+ 16: 'issuer',
+ 17: 'reserved',
+ 18: 'reserved',
+ 19: 'reserved',
+ 20: 'notation data',
+ 21: 'preferred hash algorithms',
+ 22: 'preferred compression algorithms',
+ 23: 'key server preferences',
+ 24: 'preferred key server',
+ 25: 'primary user id',
+ 26: 'policy uri',
+ 27: 'key flags',
+ 28: 'signer user id',
+ 29: 'reason for revocation',
+ 30: 'features',
+ 31: 'signature target',
+ 32: 'embedded signature',
+ 100: 'private',
+ 101: 'private',
+ 102: 'private',
+ 103: 'private',
+ 104: 'private',
+ 105: 'private',
+ 106: 'private',
+ 107: 'private',
+ 108: 'private',
+ 109: 'private',
+ 110: 'private',
+ }
+
_clean_type_regex = _re.compile('\W+')
def _clean_type(self):
return self._clean_type_regex.sub('_', self['type'])
+ def __str__(self):
+ method_name = '_str_{}'.format(self._clean_type())
+ method = getattr(self, method_name, None)
+ if not method:
+ return self['type']
+ details = method()
+ return '{}: {}'.format(self['type'], details)
+
+ def _str_public_key_packet(self):
+ return self._str_generic_key_packet()
+
+ def _str_public_subkey_packet(self):
+ return self._str_generic_key_packet()
+
+ def _str_secret_key_packet(self):
+ return self._str_generic_key_packet()
+
+ def _str_secret_subkey_packet(self):
+ return self._str_generic_key_packet()
+
+ def _str_generic_key_packet(self):
+ return self['fingerprint'][-8:].upper()
+
+ def _str_signature_packet(self):
+ lines = [self['signature-type']]
+ if self['unhashed-subpackets']:
+ lines.append(' unhashed subpackets:')
+ for subpacket in self['unhashed-subpackets']:
+ lines.append(' {}'.format(subpacket['type']))
+ return '\n'.join(lines)
+
+ def _str_user_id_packet(self):
+ return self['user']
+
def from_bytes(self, data):
offset = self._parse_header(data=data)
packet = data[offset:offset + self['length']]
raise NotImplementedError(
'algorithm-specific key fields for {}'.format(
self['public-key-algorithm']))
+ fingerprint = _hashlib.sha1()
+ fingerprint.update(b'\x99')
+ fingerprint.update(_struct.pack('>H', len(data)))
+ fingerprint.update(data)
+ self['fingerprint'] = fingerprint.hexdigest()
return offset
+ def _parse_secret_key_packet(self, data):
+ self._parse_generic_secret_key_packet(data=data)
+
+ def _parse_secret_subkey_packet(self, data):
+ self._parse_generic_secret_key_packet(data=data)
+
+ def _parse_generic_secret_key_packet(self, data):
+ offset = self._parse_generic_public_key_packet(data=data)
+ string_to_key_usage = data[offset]
+ offset += 1
+ if string_to_key_usage in [255, 254]:
+ self['symmetric-encryption-algorithm'] = (
+ self._symmetric_key_algorithms[data[offset]])
+ offset += 1
+ offset += self._parse_string_to_key_specifier(data=data[offset:])
+ else:
+ self['symmetric-encryption-algorithm'] = (
+ self._symmetric_key_algorithms[string_to_key_usage])
+ if string_to_key_usage:
+ block_size_bits = self._cipher_block_size.get(
+ self['symmetric-encryption-algorithm'], None)
+ if block_size_bits % 8:
+ raise NotImplementedError(
+ ('{}-bit block size for {} is not an integer number of bytes'
+ ).format(
+ block_size_bits, self['symmetric-encryption-algorithm']))
+ block_size = block_size_bits // 8
+ if not block_size:
+ raise NotImplementedError(
+ 'unknown block size for {}'.format(
+ self['symmetric-encryption-algorithm']))
+ self['initial-vector'] = data[offset: offset + block_size]
+ offset += block_size
+ if string_to_key_usage in [0, 255]:
+ key_end = -2
+ else:
+ key_end = 0
+ self['secret-key'] = data[offset:key_end]
+ if key_end:
+ self['secret-key-checksum'] = data[key_end:]
+
+ def _parse_signature_subpackets(self, data):
+ offset = 0
+ while offset < len(data):
+ o, subpacket = self._parse_signature_subpacket(data=data[offset:])
+ offset += o
+ yield subpacket
+
+ def _parse_signature_subpacket(self, data):
+ subpacket = {}
+ first = data[0]
+ offset = 1
+ if first < 192:
+ length = first
+ elif first >= 192 and first < 255:
+ second = data[offset]
+ offset += 1
+ length = ((first - 192) << 8) + second + 192
+ else:
+ length = _struct.unpack(
+ '>I', data[offset: offset + 4])[0]
+ offset += 4
+ subpacket['type'] = self._signature_subpacket_types[data[offset]]
+ offset += 1
+ subpacket['data'] = data[offset: offset + length - 1]
+ offset += len(subpacket['data'])
+ return (offset, subpacket)
+
+ def _parse_signature_packet(self, data):
+ self['signature-version'] = data[0]
+ offset = 1
+ if self['signature-version'] != 4:
+ raise NotImplementedError(
+ 'signature packet version {}'.format(
+ self['signature-version']))
+ self['signature-type'] = self._signature_types[data[offset]]
+ offset += 1
+ self['public-key-algorithm'] = self._public_key_algorithms[
+ data[offset]]
+ offset += 1
+ self['hash-algorithm'] = self._hash_algorithms[data[offset]]
+ offset += 1
+ hashed_count = _struct.unpack('>H', data[offset: offset + 2])[0]
+ offset += 2
+ self['hashed-subpackets'] = data[offset: offset + hashed_count]
+ offset += hashed_count
+ unhashed_count = _struct.unpack('>H', data[offset: offset + 2])[0]
+ offset += 2
+ self['unhashed-subpackets'] = list(self._parse_signature_subpackets(
+ data=data[offset: offset + unhashed_count]))
+ offset += unhashed_count
+ self['signed-hash-word'] = data[offset: offset + 2]
+ offset += 2
+ self['signature'] = data[offset:]
+
+ def _parse_user_id_packet(self, data):
+ self['user'] = str(data, 'utf-8')
+
def to_bytes(self):
pass
yield packet
+class PGPKey (object):
+ def __init__(self, fingerprint):
+ self.fingerprint = fingerprint
+ self.public_packets = None
+ self.secret_packets = None
+
+ def __str__(self):
+ lines = ['key: {}'.format(self.fingerprint)]
+ if self.public_packets:
+ lines.append(' public:')
+ for packet in self.public_packets:
+ lines.extend(self._str_packet(packet=packet, prefix=' '))
+ if self.secret_packets:
+ lines.append(' secret:')
+ for packet in self.secret_packets:
+ lines.extend(self._str_packet(packet=packet, prefix=' '))
+ return '\n'.join(lines)
+
+ def _str_packet(self, packet, prefix):
+ lines = str(packet).split('\n')
+ return [prefix + line for line in lines]
+
+ def import_from_gpg(self):
+ key_export = _get_stdout(
+ ['gpg', '--export', self.fingerprint])
+ self.public_packets = list(
+ packets_from_bytes(data=key_export))
+ if self.public_packets[0]['type'] != 'public-key packet':
+ raise ValueError(
+ '{} does not start with a public-key packet'.format(
+ self.fingerprint))
+ key_secret_export = _get_stdout(
+ ['gpg', '--export-secret-keys', self.fingerprint])
+ self.secret_packets = list(
+ packets_from_bytes(data=key_secret_export))
+
+ def export_to_gpg(self):
+ raise NotImplemetedError('export to gpg')
+
+ def import_from_key(self, key):
+ """Migrate the (sub)keys into this key"""
+ pass
+
+
def migrate(old_key, new_key):
"""Add the old key and sub-keys to the new key
signatures you'd made. You will lose signature *on* your old key
though, since sub-keys can't be signed (I don't think).
"""
- old_key_export = _get_stdout(
- ['gpg', '--export', old_key])
- old_key_packets = list(
- packets_from_bytes(data=old_key_export))
- if old_key_packets[0]['type'] != 'public-key packet':
- raise ValueError(
- '{} does not start with a public-key packet'.format(
- old_key))
- old_key_secret_export = _get_stdout(
- ['gpg', '--export-secret-keys', old_key])
- old_key_secret_packets = list(
- packets_from_bytes(data=old_key_secret_export))
-
- import pprint
- pprint.pprint(old_key_packets)
- pprint.pprint(old_key_secret_packets)
+ old_key = PGPKey(fingerprint=old_key)
+ old_key.import_from_gpg()
+ new_key = PGPKey(fingerprint=new_key)
+ new_key.import_from_gpg()
+ new_key.import_from_key(key=old_key)
+
+ print(old_key)
+ print(new_key)
if __name__ == '__main__':