Beispiel #1
0
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"),
    }
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
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"),
    }
Beispiel #6
0
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
Beispiel #7
0
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
Beispiel #8
0
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")
  }