#9393 Refactor ipalib/x509.py for better performance and testing
Opened by rcritten. Modified

Issue

ipalib.x509.py defines an IPACertificate class that builds on python-cryptography but it is incredibly slow by comparison. For example it takes ~2ms to load the IPA CA certificate but cryptography takes .007 ms to load the same one.

Christian took a brief look and identified some obvious issues:

  1. __get_der_field -> __get_der_field for subject, issuser, serial, and extensions decodes than encodes the field with pyasn1
  2. issuer and subject objects have public_bytes() methods that returns the same value

Some of this is likely related to when ipalib/x509.py was written compared to the maturity of python-cryptography now. It is time for some clean up.

Christian is concerned about regressions due to the lack of testing coverage so that will need to be addressed as well.


  • cryptography.x509.name.Name now have a public_bytes method. The code for subject_bytes and issuer_bytes can be replaced by a single line.
  • For serial_number_bytes the class can simply re-encode the Python number as an ASN.1 integer
  • I think san_general_names and san_a_label_dns_names can be re-implemented with Python cryptography extension. (Needs more testing)
  • The current code claims that Python cryptography cannot handle certs with an unknown critical extension. This case also needs more testing.
    @property
    def serial_number_bytes(self):
        return encoder.encode(univ.Integer(self._cert.serial_number))
    @property
    def subject_bytes(self):
        return self._cert.subject.public_bytes()
    @property
    def issuer_bytes(self):
        return self._cert.issuer.public_bytes()
    @property
    def san_general_names(self):
        try:
            ext = self._cert.extensions.get_extension_for_oid(
                crypto_x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME
            )
        except crypto_x509.ExtensionNotFound:
            return []
        else:
            return list(ext.value)
    @property
    def san_a_label_dns_names(self):
        try:
            ext = self._cert.extensions.get_extension_for_oid(
                crypto_x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME
            )
        except crypto_x509.ExtensionNotFound:
            return []
        return ext.value.get_values_for_type(crypto_x509.DNSName)

In case we need to parse SANs with pyasn1:

    def __pyasn1_get_san_general_names(self):
        # get extension value DER from cryptography
        try:
            ext = self._cert.extensions.get_extension_for_oid(
                crypto_x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME
            )
        except crypto_x509.ExtensionNotFound:
            return []
        der = ext.value.public_bytes()
        return decoder.decode(der, asn1Spec=rfc2459.SubjectAltName())[0]
Metadata