tlsfuzzer/tlslite-ng

View on GitHub
tlslite/x509.py

Summary

Maintainability
B
5 hrs
Test Coverage
A
90%
# Authors:
#   Trevor Perrin
#   Google - parsing subject field
#
# See the LICENSE file for legal information regarding use of this file.

"""Class representing an X.509 certificate."""

from ecdsa.keys import VerifyingKey

from .utils.asn1parser import ASN1Parser
from .utils.cryptomath import *
from .utils.keyfactory import _createPublicRSAKey, _create_public_ecdsa_key, \
    _create_public_dsa_key, _create_public_eddsa_key
from .utils.pem import *
from .utils.compat import compatHMAC, b2a_hex
from .constants import AlgorithmOID, RSA_PSS_OID


class X509(object):
    """
    This class represents an X.509 certificate.

    :vartype bytes: bytearray
    :ivar bytes: The DER-encoded ASN.1 certificate

    :vartype publicKey: ~tlslite.utils.rsakey.RSAKey
    :ivar publicKey: The subject public key from the certificate.

    :vartype subject: bytearray
    :ivar subject: The DER-encoded ASN.1 subject distinguished name.

    :vartype certAlg: str
    :ivar certAlg: algorithm of the public key, "rsa" for RSASSA-PKCS#1 v1.5,
        "rsa-pss" for RSASSA-PSS, "ecdsa" for ECDSA
    """

    def __init__(self):
        """Create empty certificate object."""
        self.bytes = bytearray(0)
        self.serial_number = None
        self.subject_public_key = None
        self.publicKey = None
        self.subject = None
        self.certAlg = None
        self.sigalg = None
        self.issuer = None

    def __hash__(self):
        """Calculate hash of object."""
        return hash(bytes(self.bytes))

    def __eq__(self, other):
        """Compare other object for equality."""
        if not hasattr(other, "bytes"):
            return NotImplemented
        return self.bytes == other.bytes

    def __ne__(self, other):
        """Compare with other object for inequality."""
        if not hasattr(other, "bytes"):
            return NotImplemented
        return not self == other

    def parse(self, s):
        """
        Parse a PEM-encoded X.509 certificate.

        :type s: str
        :param s: A PEM-encoded X.509 certificate (i.e. a base64-encoded
            certificate wrapped with "-----BEGIN CERTIFICATE-----" and
            "-----END CERTIFICATE-----" tags).
        """
        bytes = dePem(s, "CERTIFICATE")
        self.parseBinary(bytes)
        return self

    def parseBinary(self, cert_bytes):
        """
        Parse a DER-encoded X.509 certificate.

        :type bytes: L{str} (in python2) or L{bytearray} of unsigned bytes
        :param bytes: A DER-encoded X.509 certificate.
        """
        self.bytes = bytearray(cert_bytes)
        parser = ASN1Parser(self.bytes)

        # Get the SignatureAlgorithm
        signature_algorithm_identifier = parser.getChild(1)
        self.sigalg = bytes(signature_algorithm_identifier.getChildBytes(0))

        # Finally get the (hash, signature) pair coresponding to it
        # If it is rsa-pss we need to check the aditional parameters field
        # to extract the hash algorithm
        if self.sigalg == RSA_PSS_OID:
            sigalg_hash = signature_algorithm_identifier.getChild(1)
            sigalg_hash = bytes(sigalg_hash.getChild(0).value)
            self.sigalg = AlgorithmOID.oid[sigalg_hash]
        else:
            self.sigalg = AlgorithmOID.oid[self.sigalg]

        # Get the tbsCertificate
        tbs_certificate = parser.getChild(0)
        # Is the optional version field present?
        # This determines which index the key is at.
        if tbs_certificate.value[0] == 0xA0:
            serial_number_index = 1
            subject_public_key_info_index = 6
        else:
            serial_number_index = 0
            subject_public_key_info_index = 5

        # Get serial number
        self.serial_number = bytesToNumber(tbs_certificate.getChild(serial_number_index).value)

        # Get the issuer
        self.issuer = tbs_certificate.getChildBytes(
            subject_public_key_info_index - 3)

        # Get the subject
        self.subject = tbs_certificate.getChildBytes(
            subject_public_key_info_index - 1)

        # Get the subjectPublicKeyInfo
        subject_public_key_info = tbs_certificate.getChild(
            subject_public_key_info_index)

        # Get the AlgorithmIdentifier
        alg_identifier = subject_public_key_info.getChild(0)
        alg_identifier_len = alg_identifier.getChildCount()

        # first item of AlgorithmIdentifier is the algorithm
        alg = alg_identifier.getChild(0)
        alg_oid = alg.value
        if list(alg_oid) == [42, 134, 72, 134, 247, 13, 1, 1, 1]:
            self.certAlg = "rsa"
        elif list(alg_oid) == [42, 134, 72, 134, 247, 13, 1, 1, 10]:
            self.certAlg = "rsa-pss"
        elif list(alg_oid) == [42, 134, 72, 206, 56, 4, 1]:
            self.certAlg = "dsa"
        elif list(alg_oid) == [42, 134, 72, 206, 61, 2, 1]:
            self.certAlg = "ecdsa"
        elif list(alg_oid) == [43, 101, 112]:
            self.certAlg = "Ed25519"
        elif list(alg_oid) == [43, 101, 113]:
            self.certAlg = "Ed448"
        else:
            raise SyntaxError("Unrecognized AlgorithmIdentifier")

        # for RSA the parameters of AlgorithmIdentifier shuld be a NULL
        if self.certAlg == "rsa":
            if alg_identifier_len != 2:
                raise SyntaxError("Missing parameters in AlgorithmIdentifier")
            params = alg_identifier.getChild(1)
            if params.value != bytearray(0):
                raise SyntaxError("Unexpected non-NULL parameters in "
                                  "AlgorithmIdentifier")
        elif self.certAlg == "ecdsa":
            self._ecdsa_pubkey_parsing(
                tbs_certificate.getChildBytes(subject_public_key_info_index))
            return
        elif self.certAlg == "dsa":
            self._dsa_pubkey_parsing(subject_public_key_info)
            return
        elif self.certAlg == "Ed25519" or self.certAlg == "Ed448":
            self._eddsa_pubkey_parsing(
                tbs_certificate.getChildBytes(subject_public_key_info_index))
            return
        else:  # rsa-pss
            pass  # ignore parameters, if any - don't apply key restrictions

        self._rsa_pubkey_parsing(subject_public_key_info)

    def _eddsa_pubkey_parsing(self, subject_public_key_info):
        """
        Convert the raw DER encoded EdDSA parameters into public key object.

        :param subject_public_key_info: bytes like object with the DER encoded
            public key in it
        """
        try:
            # python ecdsa knows how to parse curve OIDs so re-use that
            # code
            public_key = VerifyingKey.from_der(compatHMAC(
                subject_public_key_info))
        except Exception:
            raise SyntaxError("Malformed or unsupported public key in "
                              "certificate")
        self.publicKey = _create_public_eddsa_key(public_key)

    def _rsa_pubkey_parsing(self, subject_public_key_info):
        """
        Parse the RSA public key from the certificate.

        :param subject_public_key_info: ASN1Parser object with subject
            public key info of X.509 certificate
        """

        # Get the subjectPublicKey
        subject_public_key = subject_public_key_info.getChild(1)
        self.subject_public_key = subject_public_key_info.getChildBytes(1)
        self.subject_public_key = ASN1Parser(self.subject_public_key).value[1:]

        # Adjust for BIT STRING encapsulation
        if subject_public_key.value[0]:
            raise SyntaxError()
        subject_public_key = ASN1Parser(subject_public_key.value[1:])

        # Get the modulus and exponent
        modulus = subject_public_key.getChild(0)
        public_exponent = subject_public_key.getChild(1)

        # Decode them into numbers
        # pylint: disable=invalid-name
        # 'n' and 'e' are the universally used parameters in RSA algorithm
        # definition
        n = bytesToNumber(modulus.value)
        e = bytesToNumber(public_exponent.value)

        # Create a public key instance
        self.publicKey = _createPublicRSAKey(n, e, self.certAlg)
        # pylint: enable=invalid-name

    def _ecdsa_pubkey_parsing(self, subject_public_key_info):
        """
        Convert the raw DER encoded ECDSA parameters into public key object

        :param subject_public_key_info: bytes like object with DER encoded
            public key in it
        """
        try:
            # python ecdsa knows how to parse curve OIDs so re-use that
            # code
            public_key = VerifyingKey.from_der(compatHMAC(
                subject_public_key_info))
        except Exception:
            raise SyntaxError("Malformed or unsupported public key in "
                              "certificate")
        x = public_key.pubkey.point.x()
        y = public_key.pubkey.point.y()
        curve_name = public_key.curve.name
        self.publicKey = _create_public_ecdsa_key(x, y, curve_name)

    def _dsa_pubkey_parsing(self, subject_public_key_info):
        """
        Convert the raw DER encoded DSA parameters into public key object

        :param subject_public_key_info: bytes like object with DER encoded
          global parameters and public key in it
        """
        global_parameters = (subject_public_key_info.getChild(0)).getChild(1)
        # Get the subjectPublicKey
        public_key = subject_public_key_info.getChild(1)

        # Adjust for BIT STRING encapsulation and get hex value
        if public_key.value[0]:
            raise SyntaxError()
        y = ASN1Parser(public_key.value[1:])

        # Get the {A, p, q}
        p = global_parameters.getChild(0)
        q = global_parameters.getChild(1)
        g = global_parameters.getChild(2)

        # Decode them into numbers
        y = bytesToNumber(y.value)
        p = bytesToNumber(p.value)
        q = bytesToNumber(q.value)
        g = bytesToNumber(g.value)

        # Create a public key instance
        self.publicKey = _create_public_dsa_key(p, q, g, y)

    def getFingerprint(self):
        """
        Get the hex-encoded fingerprint of this certificate.

        :rtype: str
        :returns: A hex-encoded fingerprint.
        """
        return b2a_hex(SHA1(self.bytes))

    def writeBytes(self):
        """Serialise object to a DER encoded string."""
        return self.bytes