From: W. Trevor King Date: Mon, 23 Dec 2013 21:22:15 +0000 (-0800) Subject: Add PGPPacket._serialize_signature_packet X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=c6425eb301821a306ad954456d3d16b716979c4c;p=gpg-migrate.git Add PGPPacket._serialize_signature_packet This is mostly an inverse of the current signature packet parsing code, except that there's some new stuff in _serialize_signature_packet_target for serializing signature targets. PGP 4880 adds a bunch of extra logic, headers, trailers, etc. [1]: For binary document signatures (type 0x00), the document data is hashed directly. For text document signatures (type 0x01), the document is canonicalized by converting line endings to , and the resulting data is hashed. 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. When a signature is made over a Signature packet (type 0x50), the hash data starts with the octet 0x88, followed by the four-octet length of the signature, and then the body of the Signature packet. (Note that this is an old-style packet header for a Signature packet with the length-of-length set to zero.) The unhashed subpacket data of the Signature packet being hashed is not included in the hash, and the unhashed subpacket data length value is set to zero. Once the data body is hashed, then a trailer is hashed. A V3 signature hashes five octets of the packet body, starting from the signature type field. This data is the signature type, followed by the four-octet signature time. A V4 signature hashes the packet body starting from its first field, the version number, through the end of the hashed subpacket data. Thus, the fields hashed are the signature version, the signature type, the public-key algorithm, the hash algorithm, the hashed subpacket length, and the hashed subpacket body. 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. We don't handle everything in there, but we do handle the key packet headers (0x99, 2-byte length, packet body) and certification packets (0x99, 2-byte key body length, key body, 0xb4, user id body). We also handle the v4 signature trailer (signature_version, 0xff, 4-byte hashed signature packet length). [1]: http://tools.ietf.org/search/rfc4880#section-5.2.4 --- diff --git a/gpg-migrate.py b/gpg-migrate.py index 3a3c7e9..21bbc8c 100755 --- a/gpg-migrate.py +++ b/gpg-migrate.py @@ -946,6 +946,168 @@ class PGPPacket (dict): self['public-key-algorithm'])) return b''.join(chunks) + def _serialize_signature_subpackets(self, subpackets): + return b''.join( + self._serialize_signature_subpacket(subpacket=subpacket) + for subpacket in subpackets) + + def _serialize_signature_subpacket(self, subpacket): + method_name = '_serialize_{}_signature_subpacket'.format( + self._clean_type(type=subpacket['type'])) + method = getattr(self, method_name, None) + if not method: + raise NotImplementedError( + 'cannot serialize signature subpacket type {!r}'.format( + subpacket['type'])) + body = method(subpacket=subpacket) + length = len(body) + 1 + chunks = [] + if length < 192: + chunks.append(bytes([length])) + else: + first = ((length - 192) >> 8) + 192 + if first < 255: + chunks.extend([ + first, + (length - 192) % 256, + ]) + else: + chunks.append(_struct.pack('>I', length)) + chunks.append(bytes([self._reverse( + self._signature_subpacket_types, subpacket['type'])])) + chunks.append(body) + return b''.join(chunks) + + def _serialize_signature_packet_target(self, target): + if target is None: + return b'' + elif isinstance(target, bytes): + return target + elif isinstance(target, PGPPacket): + serialized = target._serialize_body() + if target['type'] in [ + 'public-key packet', + 'public-subkey packet' + 'secret-key packet', + 'secret-subkey packet' + ]: + serialized = b''.join([ + b'\x99', + _struct.pack('>H', len(serialized)), + serialized, + ]) + elif target['type'] == 'user id packet': + serialized = b''.join([ + b'\xb4', + _struct.pack('>I', len(serialized)), + serialized, + ]) + elif target['type'] == 'user attribute packet': + serialized = b''.join([ + b'\xd1', + _struct.pack('>I', len(serialized)), + serialized, + ]) + return serialized + elif isinstance(target, list): + return b''.join( + self._serialize_signature_packet_target(target=x) + for x in target) + + def _serialize_signature_packet(self): + if self['signature-version'] != 4: + raise NotImplementedError( + 'signature packet version {}'.format( + self['signature-version'])) + signature_version = bytes([self['signature-version']]) + chunks = [signature_version] + chunks.append(bytes([self._reverse( + self._signature_types, self['signature-type'])])) + chunks.append(bytes([self._reverse( + self._public_key_algorithms, self['public-key-algorithm'])])) + chunks.append(bytes([self._reverse( + self._hash_algorithms, self['hash-algorithm'])])) + hashed_subpackets = self._serialize_signature_subpackets( + self['hashed-subpackets']) + chunks.append(_struct.pack('>H', len(hashed_subpackets))) + chunks.append(hashed_subpackets) + hashed_signature_data = b''.join(chunks) + unhashed_subpackets = self._serialize_signature_subpackets( + self['unhashed-subpackets']) + chunks.append(_struct.pack('>H', len(unhashed_subpackets))) + chunks.append(unhashed_subpackets) + target = self._serialize_signature_packet_target(target=self['target']) + signed_data = b''.join([ + target, + hashed_signature_data, + signature_version, + b'\xff', + _struct.pack('>I', len(hashed_signature_data)), + ]) + digest, signature = self.key.sign( + data=signed_data, hash_algorithm=self['hash-algorithm'], + signature_algorithm=self['public-key-algorithm']) + chunks.extend([digest[:2], signature]) + return b''.join(chunks) + + def _serialize_signature_creation_time_signature_subpacket( + self, subpacket): + return _struct.pack('>I', subpacket['signature-creation-time']) + + def _serialize_issuer_signature_subpacket(self, subpacket): + return string_bytes(data=subpacket['issuer'], sep='') + + def _serialize_key_expiration_time_signature_subpacket(self, subpacket): + return _struct.pack('>I', subpacket['key-expiration-time']) + + def _serialize_preferred_symmetric_algorithms_signature_subpacket( + self, subpacket): + return bytes( + self._reverse(self._symmetric_key_algorithms, a) + for a in subpacket['preferred-symmetric-algorithms']) + + def _serialize_preferred_hash_algorithms_signature_subpacket( + self, subpacket): + return bytes( + self._reverse(self._hash_algorithms, a) + for a in subpacket['preferred-hash-algorithms']) + + def _serialize_preferred_compression_algorithms_signature_subpacket( + self, subpacket): + return bytes( + self._reverse(self._compression_algorithms, a) + for a in subpacket['preferred-compression-algorithms']) + + def _serialize_key_server_preferences_signature_subpacket(self, subpacket): + return bytes([ + 0x80 * ('no-modify' in subpacket['key-server-preferences']) | + 0, + ]) + + def _serialize_primary_user_id_signature_subpacket(self, subpacket): + return bytes([0x1 * subpacket['primary-user-id']]) + + def _serialize_key_flags_signature_subpacket(self, subpacket): + return bytes([ + 0x1 * ('can certify' in subpacket['key-flags']) | + 0x2 * ('can sign' in subpacket['key-flags']) | + 0x4 * ('can encrypt communications' in subpacket['key-flags']) | + 0x8 * ('can encrypt storage' in subpacket['key-flags']) | + 0x10 * ('private split' in subpacket['key-flags']) | + 0x20 * ('can authenticate' in subpacket['key-flags']) | + 0x80 * ('private shated' in subpacket['key-flags']) | + 0, + ]) + + def _serialize_features_signature_subpacket(self, subpacket): + return bytes([ + 0x1 * ('modification detection' in subpacket['features']) | + 0, + ]) + + def _serialize_embedded_signature_signature_subpacket(self, subpacket): + return subpacket['embedded'].to_bytes() + def _serialize_user_id_packet(self): return self['user'].encode('utf-8')