def get_pubkey_params(data): """ <Purpose> Parse the public-key parameters as multi-precision-integers. <Arguments> data: the RFC4880-encoded public key parameters data buffer as described in the fifth paragraph of section 5.5.2. <Exceptions> securesystemslib.gpg.exceptions.PacketParsingError: if the public key parameters are malformed <Side Effects> None. <Returns> The parsed DSA public key in the format securesystemslib.formats.GPG_DSA_PUBKEY_SCHEMA. """ ptr = 0 prime_p_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 prime_p = data[ptr:ptr + prime_p_length] if len(prime_p) != prime_p_length: # pragma: no cover raise PacketParsingError("This MPI was truncated!") ptr += prime_p_length group_order_q_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 group_order_q = data[ptr:ptr + group_order_q_length] if len(group_order_q) != group_order_q_length: # pragma: no cover raise PacketParsingError("This MPI has been truncated!") ptr += group_order_q_length generator_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 generator = data[ptr:ptr + generator_length] if len(generator) != generator_length: # pragma: no cover raise PacketParsingError("This MPI has been truncated!") ptr += generator_length value_y_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 value_y = data[ptr:ptr + value_y_length] if len(value_y) != value_y_length: # pragma: no cover raise PacketParsingError("This MPI has been truncated!") return { "y": binascii.hexlify(value_y).decode('ascii'), "p": binascii.hexlify(prime_p).decode("ascii"), "g": binascii.hexlify(generator).decode("ascii"), "q": binascii.hexlify(group_order_q).decode("ascii"), }
def parse_subpacket_header(data): """ Parse out subpacket header as per RFC4880 5.2.3.1. Signature Subpacket Specification. """ # NOTE: Although the RFC does not state it explicitly, the length encoded # in the header must be greater equal 1, as it includes the mandatory # subpacket type octet. # Hence, passed bytearrays like [0] or [255, 0, 0, 0, 0], which encode a # subpacket length 0 are invalid. # The caller has to deal with the resulting IndexError. if data[0] < 192: length_len = 1 length = data[0] elif data[0] >= 192 and data[0] < 255: length_len = 2 length = ((data[0] - 192 << 8) + (data[1] + 192)) elif data[0] == 255: length_len = 5 length = struct.unpack(">I", data[1:length_len])[0] else: # pragma: no cover (unreachable) raise PacketParsingError("Invalid subpacket header") return data[length_len], length_len + 1, length - 1, length_len + length
def get_signature_params(data): """ <Purpose> Parse the signature parameters as multi-precision-integers. <Arguments> data: the RFC4880-encoded signature data buffer as described in the third paragraph of section 5.2.2. <Exceptions> securesystemslib.gpg.exceptions.PacketParsingError: if the public key parameters are malformed <Side Effects> None. <Returns> The decoded signature buffer """ ptr = 0 signature_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 signature = data[ptr:ptr + signature_length] if len(signature) != signature_length: # pragma: no cover raise PacketParsingError("This signature was truncated!") return signature
def get_signature_params(data): """ <Purpose> Parse the signature parameters as multi-precision-integers. <Arguments> data: the RFC4880-encoded signature data buffer as described in the fourth paragraph of section 5.2.2 <Exceptions> securesystemslib.gpg.exceptions.PacketParsingError: if the public key parameters are malformed securesystemslib.exceptions.UnsupportedLibraryError: if the cryptography module is not available <Side Effects> None. <Returns> The decoded signature buffer """ if not CRYPTO: # pragma: no cover return exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG) ptr = 0 r_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 r = data[ptr:ptr + r_length] if len(r) != r_length: # pragma: no cover raise PacketParsingError("r-value truncated in signature") ptr += r_length s_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 s = data[ptr:ptr + s_length] if len(s) != s_length: # pragma: no cover raise PacketParsingError("s-value truncated in signature") s = int(binascii.hexlify(s), 16) r = int(binascii.hexlify(r), 16) signature = dsautils.encode_dss_signature(r, s) return signature
def get_pubkey_params(data): """ <Purpose> Parse the public key parameters as multi-precision-integers. <Arguments> data: the RFC4880-encoded public key parameters data buffer as described in the fifth paragraph of section 5.5.2. <Exceptions> securesystemslib.gpg.exceptions.PacketParsingError: if the public key parameters are malformed <Side Effects> None. <Returns> The parsed RSA public key in the format securesystemslib.formats.GPG_RSA_PUBKEY_SCHEMA. """ ptr = 0 modulus_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 modulus = data[ptr:ptr + modulus_length] if len(modulus) != modulus_length: # pragma: no cover raise PacketParsingError("This modulus MPI was truncated!") ptr += modulus_length exponent_e_length = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 exponent_e = data[ptr:ptr + exponent_e_length] if len(exponent_e) != exponent_e_length: # pragma: no cover raise PacketParsingError("This e MPI has been truncated!") return { "e": binascii.hexlify(exponent_e).decode('ascii'), "n": binascii.hexlify(modulus).decode("ascii"), }
def parse_pubkey_bundle(data): """ <Purpose> Parse packets from passed gpg public key data, associating self-signatures with the packets they correspond to, based on the structure of V4 keys defined in RFC4880 12.1 Key Structures. The returned raw key bundle may be used to further enrich the master key, with certified information (e.g. key expiration date) taken from self-signatures, and/or to verify that the parsed subkeys are bound to the primary key via signatures. <Arguments> data: Public key data as written to stdout by GPG_EXPORT_PUBKEY_COMMAND. <Exceptions> securesystemslib.gpg.exceptions.PacketParsingError If data is empty. If data cannot be parsed. <Side Effects> None. <Returns> A raw public key bundle where self-signatures are associated with their corresponding packets. See `key_bundle` for details. """ if not data: raise PacketParsingError("Cannot parse keys from empty gpg data.") # Temporary data structure to hold parsed gpg packets key_bundle = { PACKET_TYPE_PRIMARY_KEY: { "key": {}, "packet": None, "signatures": [] }, PACKET_TYPE_USER_ID: collections.OrderedDict(), PACKET_TYPE_USER_ATTR: collections.OrderedDict(), PACKET_TYPE_SUB_KEY: collections.OrderedDict() } # Iterate over gpg data and parse out packets of different types position = 0 while position < len(data): try: packet_type, header_len, body_len, packet_length = \ securesystemslib.gpg.util.parse_packet_header(data[position:]) packet = data[position:position+packet_length] payload = packet[header_len:] # The first (and only the first) packet in the bundle must be the master # key. See RFC4880 12.1 Key Structures, V4 version keys # TODO: Do we need additional key structure assertions? e.g. # - there must be least one User ID packet, or # - order and type of signatures, or # - disallow duplicate packets if packet_type != PACKET_TYPE_PRIMARY_KEY and \ not key_bundle[PACKET_TYPE_PRIMARY_KEY]["key"]: raise PacketParsingError("First packet must be a primary key ('{}'), " "got '{}'.".format(PACKET_TYPE_PRIMARY_KEY, packet_type)) elif packet_type == PACKET_TYPE_PRIMARY_KEY and \ key_bundle[PACKET_TYPE_PRIMARY_KEY]["key"]: raise PacketParsingError("Unexpected primary key.") # Fully parse master key to fail early, e.g. if key is malformed # or not supported, but also retain original packet for subkey binding # signature verification elif packet_type == PACKET_TYPE_PRIMARY_KEY: key_bundle[PACKET_TYPE_PRIMARY_KEY] = { "key": parse_pubkey_payload(bytearray(payload)), "packet": packet, "signatures": [] } # Other non-signature packets in the key bundle include User IDs and User # Attributes, required to verify primary key certificates, and subkey # packets. For each packet we create a new ordered dictionary entry. We # use a dictionary to aggregate signatures by packet below, # and it must be ordered because each signature packet belongs to the # most recently parsed packet of a type. elif packet_type in {PACKET_TYPE_USER_ID, PACKET_TYPE_USER_ATTR, PACKET_TYPE_SUB_KEY}: key_bundle[packet_type][packet] = { "header_len": header_len, "body_len": body_len, "signatures": [] } # The remaining relevant packets are signatures, required to bind subkeys # to the primary key, or to gather additional information about the # primary key, e.g. expiration date. # A signature corresponds to the most recently parsed packet of a type, # where the type is given by the availability of respective packets. # We test availability and assign accordingly as per the order of packet # types defined in RFC4880 12.1 (bottom-up). elif packet_type == PACKET_TYPE_SIGNATURE: for _type in [PACKET_TYPE_SUB_KEY, PACKET_TYPE_USER_ATTR, PACKET_TYPE_USER_ID]: if key_bundle[_type]: # Add to most recently added packet's signatures of matching type key_bundle[_type][next(reversed(key_bundle[_type]))]\ ["signatures"].append(packet) break else: # If no packets are available for any of above types (yet), the # signature belongs to the primary key key_bundle[PACKET_TYPE_PRIMARY_KEY]["signatures"].append(packet) else: log.info("Ignoring gpg key packet '{}', we only handle packets of " "types '{}' (see RFC4880 4.3. Packet Tags).".format(packet_type, [PACKET_TYPE_PRIMARY_KEY, PACKET_TYPE_USER_ID, PACKET_TYPE_USER_ATTR, PACKET_TYPE_SUB_KEY, PACKET_TYPE_SIGNATURE])) # Both errors might be raised in parse_packet_header and in this loop except (PacketParsingError, IndexError) as e: raise PacketParsingError("Invalid public key data at position {}: {}." .format(position, e)) # Go to next packet position += packet_length return key_bundle
def parse_packet_header(data, expected_type=None): """ <Purpose> Parse out packet type and header and body lengths from an RFC4880 packet. <Arguments> data: An RFC4880 packet as described in section 4.2 of the rfc. expected_type: (optional) Used to error out if the packet does not have the expected type. See securesystemslib.gpg.constants.PACKET_TYPE_* for available types. <Exceptions> securesystemslib.gpg.exceptions.PacketParsingError If the new format packet length encodes a partial body length If the old format packet length encodes an indeterminate length If header or body length could not be determined If the expected_type was passed and does not match the packet type IndexError If the passed data is incomplete <Side Effects> None. <Returns> A tuple of packet type, header length, body length and packet length. (see RFC4880 4.3. for the list of available packet types) """ data = bytearray(data) header_len = None body_len = None # If Bit 6 of 1st octet is set we parse a New Format Packet Length, and # an Old Format Packet Lengths otherwise if data[0] & 0b01000000: # In new format packet lengths the packet type is encoded in Bits 5-0 of # the 1st octet of the packet packet_type = data[0] & 0b00111111 # The rest of the packet header is the body length header, which may # consist of one, two or five octets. To disambiguate the RFC, the first # octet of the body length header is the second octet of the packet. if data[1] < 192: header_len = 2 body_len = data[1] elif data[1] >= 192 and data[1] <= 223: header_len = 3 body_len = (data[1] - 192 << 8) + data[2] + 192 elif data[1] >= 224 and data[1] < 255: raise PacketParsingError( "New length " "format packets of partial body lengths are not supported") elif data[1] == 255: header_len = 6 body_len = data[2] << 24 | data[3] << 16 | data[4] << 8 | data[5] else: # pragma: no cover # Unreachable: octet must be between 0 and 255 raise PacketParsingError("Invalid new length") else: # In old format packet lengths the packet type is encoded in Bits 5-2 of # the 1st octet and the length type in Bits 1-0 packet_type = (data[0] & 0b00111100) >> 2 length_type = data[0] & 0b00000011 # The body length is encoded using one, two, or four octets, starting # with the second octet of the packet if length_type == 0: body_len = data[1] header_len = 2 elif length_type == 1: header_len = 3 body_len = struct.unpack(">H", data[1:header_len])[0] elif length_type == 2: header_len = 5 body_len = struct.unpack(">I", data[1:header_len])[0] elif length_type == 3: raise PacketParsingError( "Old length " "format packets of indeterminate length are not supported") else: # pragma: no cover (unreachable) # Unreachable: bits 1-0 must be one of 0 to 3 raise PacketParsingError("Invalid old length") if header_len is None or body_len is None: # pragma: no cover # Unreachable: One of above must have assigned lengths or raised error raise PacketParsingError("Could not determine packet length") if expected_type is not None and packet_type != expected_type: raise PacketParsingError("Expected packet " "{}, but got {} instead!".format( expected_type, packet_type)) return packet_type, header_len, body_len, header_len + body_len
def get_pubkey_params(data): """ <Purpose> Parse algorithm-specific part for EdDSA public keys See RFC4880-bis8 sections 5.6.5. Algorithm-Specific Part for EdDSA Keys, 9.2. ECC Curve OID and 13.3. EdDSA Point Format for more details. <Arguments> data: The EdDSA public key data AFTER the one-octet number denoting the public-key algorithm of this key. <Exceptions> securesystemslib.gpg.exceptions.PacketParsingError or IndexError: if the public key data is malformed. <Side Effects> None. <Returns> A dictionary with an element "q" that holds the ascii hex representation of the MPI of an EC point representing an EdDSA public key that conforms with securesystemslib.formats.GPG_ED25519_PUBKEY_SCHEMA. """ ptr = 0 curve_oid_len = data[ptr] ptr += 1 curve_oid = data[ptr:ptr + curve_oid_len] ptr += curve_oid_len # See 9.2. ECC Curve OID if curve_oid != ED25519_PUBLIC_KEY_OID: raise PacketParsingError( "bad ed25519 curve OID '{}', expected {}'".format( curve_oid, ED25519_PUBLIC_KEY_OID)) # See 13.3. EdDSA Point Format public_key_len = gpg_util.get_mpi_length(data[ptr:ptr + 2]) ptr += 2 if public_key_len != ED25519_PUBLIC_KEY_LENGTH: raise PacketParsingError( "bad ed25519 MPI length '{}', expected {}'".format( public_key_len, ED25519_PUBLIC_KEY_LENGTH)) public_key_prefix = data[ptr] ptr += 1 if public_key_prefix != ED25519_PUBLIC_KEY_PREFIX: raise PacketParsingError( "bad ed25519 MPI prefix '{}', expected '{}'".format( public_key_prefix, ED25519_PUBLIC_KEY_PREFIX)) public_key = data[ptr:ptr + public_key_len - 1] return { "q": binascii.hexlify(public_key).decode("ascii") }