Stub out signature subpacket parsing in PGPPacket
authorW. Trevor King <wking@tremily.us>
Fri, 20 Dec 2013 21:09:25 +0000 (13:09 -0800)
committerW. Trevor King <wking@tremily.us>
Mon, 23 Dec 2013 02:31:20 +0000 (18:31 -0800)
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

gpg-migrate.py

index 8e2e4fb3d384f9944fc9bdb58a130692b6a99c6a..08ba42402e36467bf1c8d70fd37196380141ec2e 100755 (executable)
@@ -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)