Extract signature packet targets
authorW. Trevor King <wking@tremily.us>
Mon, 23 Dec 2013 03:28:59 +0000 (19:28 -0800)
committerW. Trevor King <wking@tremily.us>
Mon, 23 Dec 2013 21:41:06 +0000 (13:41 -0800)
We need to know what we're signing.  From RFC 4880 [1]:

  The concatenation of the data being signed and the signature data
  from the version number through the hashed subpacket data
  (inclusive) is hashed.  The resulting hash value is what is signed.

It's not always clear what the target bytes should be.  RFC 4880 is
clear for some types, and ambiguous for others.  For example, it's
clear that a standalone signature has no target [2]:

  0x02: Standalone signature.
    This signature is a signature of only its own subpacket contents.
    It is calculated identically to a signature over a zero-length
    binary document.

It is less clear how to serialize the targets for a
user-id-and-public-key signature [2]:

  0x10: Generic certification of a User ID and Public-Key packet.
    The issuer of this certification does not make any particular
    assertion as to how well the certifier has checked that the owner
    of the key is in fact the person described by the User ID.

There is a bit more guidance later on, showing that the key packet
should indeed come before the user id packet [3]:

  When a signature is made over a key, the hash data starts with the
  octet 0x99, followed by a two-octet length of the key, and then body
  of the key packet.  (Note that this is an old-style packet header
  for a key packet with two-octet length.)  A subkey binding signature
  (type 0x18) or primary key binding signature (type 0x19) then hashes
  the subkey using the same format as the main key (also using 0x99 as
  the first octet).  Key revocation signatures (types 0x20 and 0x28)
  hash only the key being revoked.

  A certification signature (type 0x10 through 0x13) hashes the User
  ID being bound to the key into the hash context after the above
  data.  A V3 certification hashes the contents of the User ID or
  attribute packet packet, without any header.  A V4 certification
  hashes the constant 0xB4 for User ID certifications or the constant
  0xD1 for User Attribute certifications, followed by a four-octet
  number giving the length of the User ID or User Attribute data, and
  then the User ID or User Attribute data.

  ...

  V4 signatures also hash in a final trailer of six octets: the
  version of the Signature packet, i.e., 0x04; 0xFF; and a four-octet,
  big-endian number that is the length of the hashed data from the
  Signature packet (note that this number does not include these final
  six octets).

  After all this has been hashed in a single hash context, the
  resulting hash field is used in the signature algorithm and placed
  at the end of the Signature packet.

[1]: http://tools.ietf.org/search/rfc4880#section-5.2.3
[2]: http://tools.ietf.org/search/rfc4880#section-5.2.1
[3]: http://tools.ietf.org/search/rfc4880#section-5.2.4

gpg-migrate.py

index 95e34c03ac870b4237017584992d2a32fbd42c16..f5a757c2aa84eb4432ce9ac1233c4d0ba0cbe0de 100755 (executable)
@@ -725,6 +725,16 @@ class PGPPacket (dict):
         self['signed-hash-word'] = data[offset: offset + 2]
         offset += 2
         self['signature'] = data[offset:]
+        if self['signature-type'] == 'standalone':
+            self['target'] = None
+        elif self['signature-type'].endswith(' user id and public-key packet'):
+            self['target'] = [
+                [p for p in self.key.public_packets if p['type'] == 'public-key packet'][-1],
+                [p for p in self.key.public_packets if p['type'] == 'user id packet'][-1],
+                ]
+        else:
+            raise NotImplementedError(
+                'target for {}'.format(self['signature-type']))
 
     def _parse_signature_creation_time_signature_subpacket(
             self, data, subpacket):