From: W. Trevor King Date: Fri, 20 Dec 2013 21:09:25 +0000 (-0800) Subject: Stub out signature subpacket parsing in PGPPacket X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=8eea0dbc87dca1fa72e0d2defb08b094a52137fb;p=gpg-migrate.git Stub out signature subpacket parsing in PGPPacket From RFC 4880 [1]: Each subpacket consists of a subpacket header and a body. The header consists of: - the subpacket length (1, 2, or 5 octets), - the subpacket type (1 octet), and is followed by the subpacket-specific data. The length includes the type octet but not this length. Its format is similar to the "new" format packet header lengths, but cannot have Partial Body Lengths. That is: if the 1st octet < 192, then lengthOfLength = 1 subpacketLen = 1st_octet if the 1st octet >= 192 and < 255, then lengthOfLength = 2 subpacketLen = ((1st_octet - 192) << 8) + (2nd_octet) + 192 if the 1st octet = 255, then lengthOfLength = 5 subpacket length = [four-octet scalar starting at 2nd_octet] The value of the subpacket type octet may be: 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's User ID 29 = Reason for Revocation 30 = Features 31 = Signature Target 32 = Embedded Signature 100 To 110 = Private or experimental [1]: http://tools.ietf.org/search/rfc4880#section-5.2.3.1 --- diff --git a/gpg-migrate.py b/gpg-migrate.py index 8e2e4fb..08ba424 100755 --- a/gpg-migrate.py +++ b/gpg-migrate.py @@ -186,6 +186,53 @@ class PGPPacket (dict): 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): @@ -215,7 +262,12 @@ class PGPPacket (dict): return self['fingerprint'][-8:].upper() def _str_signature_packet(self): - return self['signature-type'] + 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'] @@ -404,6 +456,33 @@ class PGPPacket (dict): 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 @@ -424,7 +503,8 @@ class PGPPacket (dict): offset += hashed_count unhashed_count = _struct.unpack('>H', data[offset: offset + 2])[0] offset += 2 - self['unhashed-subpackets'] = data[offset: offset + unhashed_count] + 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 @@ -456,13 +536,17 @@ class PGPKey (object): if self.public_packets: lines.append(' public:') for packet in self.public_packets: - lines.append(' {}'.format(packet)) + lines.extend(self._str_packet(packet=packet, prefix=' ')) if self.secret_packets: lines.append(' secret:') for packet in self.secret_packets: - lines.append(' {}'.format(packet)) + 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]) @@ -480,6 +564,10 @@ class PGPKey (object): 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 @@ -492,6 +580,7 @@ def migrate(old_key, new_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)