def pretty_message(string, *params):
    """
    Takes a multi-line string and does the following:

     - dedents
     - converts newlines with text before and after into a single line
     - strips leading and trailing whitespace

    :param string:
        The string to format

    :param *params:
        Params to interpolate into the string

    :return:
        The formatted string
    """

    output = textwrap.dedent(string)

    # Unwrap lines, taking into account bulleted lists, ordered lists and
    # underlines consisting of = signs
    if output.find('\n') != -1:
        output = re.sub('(?<=\\S)\n(?=[^ \n\t\\d\\*\\-=])', ' ', output)

    if params:
        output = output % params

    output = output.strip()

    return output def type_name(value): class ValidationContext():

    # A certvalidator.registry.CertificateRegistry() object
    certificate_registry = None

    # A set of unicode strings of hash algorithms to be considered weak. Valid
    # options include: "md2", "md5", "sha1"
    weak_hash_algos = None

    # A set of byte strings of the SHA-1 hashes of certificates that are whitelisted
    _whitelisted_certs = None

    # A dict with keys being an asn1crypto.x509.Certificate.signature byte
    # string of a certificate. Each value is a
    # certvalidator.path.ValidationPath object of a fully-validated path
    # for that certificate.
    _validate_map = None

    # A dict with keys being an asn1crypto.crl.CertificateList.signature byte
    # string of a CRL. Each value is an asn1crypto.x509.Certificate object of
    # the validated issuer of the CRL.
    _crl_issuer_map = None

    # A dict with keys being an asn1crypto.x509.Certificate.issuer_serial byte
    # string of the certificate the CRLs are for. Each value is a list
    # asn1crypto.crl.CertificateList objects.
    _fetched_crls = None

    # A dict with keys being an asn1crypto.x509.Certificate.issuer_serial byte
    # string of the certificate the responses are for. Each value is a list
    # asn1crypto.ocsp.OCSPResponse objects.
    _fetched_ocsps = None

    # If CRLs or OCSP responses can be fetched from the network
    _allow_fetching = False

    # A list of asn1crypto.crl.CertificateList objects
    _crls = None

    # A list of asn1crypto.ocsp.OCSPResponse objects
    _ocsps = None

    # A dict with keys being an asn1crypto.x509.Certificate.issuer_serial byte
    # string of the certificate, and the value being an
    # asn1crypto.x509.Certificate object containing a certificate from a CRL
    # or OCSP response that was fetched. Only certificates not already part of
    # the .certificate_registry are added to this dict.
    _revocation_certs = None

    # A dict of keyword params to pass to certificates.crl_client.fetch()
    _crl_fetch_params = None

    # A dict of keyword params to pass to certificates.ocsp_client.fetch()
    _ocsp_fetch_params = None

    # Any exceptions that were ignored while the revocation_mode is "soft-fail"
    _soft_fail_exceptions = None

    # A datetime.datetime object to use when checking the validity
    # period of certificates
    moment = None

    # By default, any CRLs or OCSP responses that are passed to the constructor
    # are chccked. If _allow_fetching is True, any CRLs or OCSP responses that
    # can be downloaded will also be checked. The next two attributes change
    # that behavior.

    # A bool - if all CRL and OCSP revocation checks should be skipped, even if
    # provided to the constructor. This is strictly used internally, and if for
    # the purpose of skipping revocation checks on an single-purpose OCSP
    # responder certificate.
    _skip_revocation_checks = None

    # A unicode string of the revocation mode - "soft-fail", "hard-fail",
    # or "require"
    _revocation_mode = None

    def __init__(self, trust_roots=None, extra_trust_roots=None, other_certs=None,
                 whitelisted_certs=None, moment=None, allow_fetching=False, crls=None,
                 crl_fetch_params=None, ocsps=None, ocsp_fetch_params=None,
                 revocation_mode="soft-fail", weak_hash_algos=None):
        """
        :param trust_roots:
            If the operating system's trust list should not be used, instead
            pass a list of byte strings containing DER or PEM-encoded X.509
            certificates, or asn1crypto.x509.Certificate objects. These
            certificates will be used as the trust roots for the path being
            built.

        :param extra_trust_roots:
            If the operating system's trust list should be used, but augmented
            with one or more extra certificates. This should be a list of byte
            strings containing DER or PEM-encoded X.509 certificates, or
            asn1crypto.x509.Certificate objects.

        :param other_certs:
            A list of byte strings containing DER or PEM-encoded X.509
            certificates, or a list of asn1crypto.x509.Certificate objects.
            These other certs are usually provided by the service/item being
            validated. In TLS, these would be intermediate chain certs.

        :param whitelisted_certs:
            None or a list of byte strings or unicode strings of the SHA-1
            fingerprint of one or more certificates. The fingerprint is a hex
            encoding of the SHA-1 byte string, optionally separated into pairs
            by spaces or colons. These whilelisted certificates will not be
            checked for validity dates. If one of the certificates is an
            end-entity certificate in a certificate path, any TLS hostname
            mismatches, key usage errors or extended key usage errors will also
            be ignored.

        :param moment:
            If certificate validation should be performed based on a date and
            time other than right now. A datetime.datetime object with a tzinfo
            value. If this parameter is specified, then the only way to check
            OCSP and CRL responses is to pass them via the crls and ocsps
            parameters. Can not be combined with allow_fetching=True.

        :param crls:
            None or a list/tuple of asn1crypto.crl.CertificateList objects of
            pre-fetched/cached CRLs to be utilized during validation of paths

        :param crl_fetch_params:
            None or a dict of keyword args to pass to
            certvalidator.crl_client.fetch() when fetching CRLs or associated
            certificates. Only applicable when allow_fetching=True.

        :param ocsps:
            None or a list/tuple of asn1crypto.ocsp.OCSPResponse objects of
            pre-fetched/cached OCSP responses to be utilized during validation
            of paths

        :param ocsp_fetch_params:
            None or a dict of keyword args to pass to
            certvalidator.ocsp_client.fetch() when fetching OSCP responses.
            Only applicable when allow_fetching=True.

        :param allow_fetching:
            A bool - if HTTP requests should be made to fetch CRLs and OCSP
            responses. If this is True and certificates contain the location of
            a CRL or OCSP responder, an HTTP request will be made to obtain
            information for revocation checking.

        :param revocation_mode:
            A unicode string of the revocation mode to use: "soft-fail" (the
            default), "hard-fail" or "require". In "soft-fail" mode, any sort of
            error in fetching or locating revocation information is ignored. In
            "hard-fail" mode, if a certificate has a known CRL or OCSP and it
            can not be checked, it is considered a revocation failure. In
            "require" mode, every certificate in the certificate path must have
            a CRL or OCSP.

        :param weak_hash_algos:
            A set of unicode strings of hash algorithms that should be
            considered weak. Valid options include: "md2", "md5", "sha1"
        """

        if crls is not None:
            if not isinstance(crls, (list, tuple)):
                raise TypeError(pretty_message(
                    '''
                    crls must be a list of byte strings or
                    asn1crypto.crl.CertificateList objects, not %s
                    ''',
                    type_name(crls)
                ))
            new_crls = []
            for crl_ in crls:
                if not isinstance(crl_, crl.CertificateList):
                    if not isinstance(crl_, byte_cls):
                        raise TypeError(pretty_message(
                            '''
                            crls must be a list of byte strings or
                            asn1crypto.crl.CertificateList objects, not %s
                            ''',
                            type_name(crl_)
                        ))
                    crl_ = crl.CertificateList.load(crl_)
                new_crls.append(crl_)
            crls = new_crls

        if ocsps is not None:
            if not isinstance(ocsps, list):
                raise TypeError(pretty_message(
                    '''
                    ocsps must be a list of byte strings or
                    asn1crypto.ocsp.OCSPResponse objects, not %s
                    ''',
                    type_name(ocsps)
                ))
            new_ocsps = []
            for ocsp_ in ocsps:
                if not isinstance(ocsp_, ocsp.OCSPResponse):
                    if not isinstance(ocsp_, byte_cls):
                        raise TypeError(pretty_message(
                            '''
                            ocsps must be a list of byte strings or
                            asn1crypto.ocsp.OCSPResponse objects, not %s
                            ''',
                            type_name(ocsp_)
                        ))
                    ocsp_ = ocsp.OCSPResponse.load(ocsp_)
                new_ocsps.append(ocsp_)
            ocsps = new_ocsps

        if moment is not None:
            if allow_fetching:
                raise ValueError(pretty_message(
                    '''
                    allow_fetching must be False when moment is specified
                    '''
                ))

        elif not allow_fetching and crls is None and ocsps is None and revocation_mode != "soft-fail":
            raise ValueError(pretty_message(
                '''
                revocation_mode is "%s" and allow_fetching is False, however
                crls and ocsps are both None, meaning that no validation can
                happen
                '''
            ))

        if crl_fetch_params is not None and not isinstance(crl_fetch_params, dict):
            raise TypeError(pretty_message(
                '''
                crl_fetch_params must be a dict, not %s
                ''',
                type_name(crl_fetch_params)
            ))

        if ocsp_fetch_params is not None and not isinstance(ocsp_fetch_params, dict):
            raise TypeError(pretty_message(
                '''
                ocsp_fetch_params must be a dict, not %s
                ''',
                type_name(ocsp_fetch_params)
            ))

        if moment is None:
            moment = datetime.now(timezone.utc)
        else:
            if not isinstance(moment, datetime):
                raise TypeError(pretty_message(
                    '''
                    moment must be a datetime object, not %s
                    ''',
                    type_name(moment)
                ))

            if moment.utcoffset() is None:
                raise ValueError(pretty_message(
                    '''
                    moment is a naive datetime object, meaning the tzinfo
                    attribute is not set to a valid timezone
                    '''
                ))

        if revocation_mode not in set(['soft-fail', 'hard-fail', 'require']):
            raise ValueError(pretty_message(
                '''
                revocation_mode must be one of "soft-fail", "hard-fail",
                "require", not %s
                ''',
                repr(revocation_mode)
            ))

        self._whitelisted_certs = set()
        if whitelisted_certs is not None:
            for whitelisted_cert in whitelisted_certs:
                if isinstance(whitelisted_cert, byte_cls):
                    whitelisted_cert = whitelisted_cert.decode('ascii')
                if not isinstance(whitelisted_cert, str_cls):
                    raise TypeError(pretty_message(
                        '''
                        whitelisted_certs must contain only byte strings or
                        unicode strings, not %s
                        ''',
                        type_name(whitelisted_cert)
                    ))
                # Allow users to copy from various OS and browser info dialogs,
                # some of which separate the hex char pairs via spaces or colons
                whitelisted_cert = whitelisted_cert.replace(' ', '').replace(':', '')
                self._whitelisted_certs.add(
                    binascii.unhexlify(whitelisted_cert.encode('ascii'))
                )

        if weak_hash_algos is not None:
            if not isinstance(weak_hash_algos, set):
                raise TypeError(pretty_message(
                    '''
                    weak_hash_algos must be a set of unicode strings, not %s
                    ''',
                    type_name(weak_hash_algos)
                ))

            unsupported_hash_algos = weak_hash_algos - set(['md2', 'md5', 'sha1'])
            if unsupported_hash_algos:
                raise ValueError(pretty_message(
                    '''
                    weak_hash_algos must contain only the unicode strings "md2",
                    "md5", "sha1", not %s
                    ''',
                    repr(unsupported_hash_algos)
                ))
        else:
            weak_hash_algos = set(['md2', 'md5'])

        self.certificate_registry = CertificateRegistry(
            trust_roots,
            extra_trust_roots,
            other_certs
        )

        self.moment = moment

        self._validate_map = {}
        self._crl_issuer_map = {}

        self._fetched_crls = {}
        self._fetched_ocsps = {}
        self._revocation_certs = {}

        self._crls = []
        if crls:
            self._crls = crls

        self._ocsps = []
        if ocsps:
            self._ocsps = ocsps
            for ocsp_response in ocsps:
                self._extract_ocsp_certs(ocsp_response)

        self._crl_fetch_params = crl_fetch_params or {}
        self._ocsp_fetch_params = ocsp_fetch_params or {}

        self._allow_fetching = bool(allow_fetching)
        self._skip_revocation_checks = False
        self._revocation_mode = revocation_mode
        self._soft_fail_exceptions = []
        self.weak_hash_algos = weak_hash_algos

    @property
    def crls(self):
        """
        A list of all cached asn1crypto.crl.CertificateList objects
        """

        if not self._allow_fetching:
            return self._crls

        output = []
        for issuer_serial in self._fetched_crls:
            output.extend(self._fetched_crls[issuer_serial])
        return output

    @property
    def ocsps(self):
        """
        A list of all cached asn1crypto.ocsp.OCSPResponse objects
        """

        if not self._allow_fetching:
            return self._ocsps

        output = []
        for issuer_serial in self._fetched_ocsps:
            output.extend(self._fetched_ocsps[issuer_serial])
        return output

    @property
    def new_revocation_certs(self):
        """
        A list of newly-fetched asn1crypto.x509.Certificate objects that were
        obtained from OCSP responses and CRLs
        """

        return list(self._revocation_certs.values())

    @property
    def soft_fail_exceptions(self):
        """
        A list of soft-fail exceptions that were ignored during checks
        """

        return self._soft_fail_exceptions

    @property
    def revocation_mode(self):
        """
        A unicode string of the revocation checking mode: "soft-fail",
        "hard-fail", or "require"
        """

        return self._revocation_mode

    def is_whitelisted(self, cert):
        """
        Checks to see if a certificate has been whitelisted

        :param cert:
            An asn1crypto.x509.Certificate object

        :return:
            A bool - if the certificate is whitelisted
        """

        return cert.sha1 in self._whitelisted_certs

    def retrieve_crls(self, cert):
        """
        :param cert:
            An asn1crypto.x509.Certificate object

        :param path:
            A certvalidator.path.ValidationPath object for the cert

        :return:
            A list of asn1crypto.crl.CertificateList objects
        """

        if not self._allow_fetching:
            return self._crls

        if cert.issuer_serial not in self._fetched_crls:
            try:
                crls = crl_client.fetch(
                    cert,
                    **self._crl_fetch_params
                )
                self._fetched_crls[cert.issuer_serial] = crls
                for crl_ in crls:
                    try:
                        certs = crl_client.fetch_certs(
                            crl_,
                            user_agent=self._crl_fetch_params.get('user_agent'),
                            timeout=self._crl_fetch_params.get('timeout')
                        )
                        for cert_ in certs:
                            if self.certificate_registry.add_other_cert(cert_):
                                self._revocation_certs[cert_.issuer_serial] = cert_
                    except (URLError, socket.error):
                        pass
            except (URLError, socket.error) as e:
                self._fetched_crls[cert.issuer_serial] = []
                if self._revocation_mode == "soft-fail":
                    self._soft_fail_exceptions.append(e)
                    raise SoftFailError()
                else:
                    raise

        return self._fetched_crls[cert.issuer_serial]

    def retrieve_ocsps(self, cert, issuer):
        """
        :param cert:
            An asn1crypto.x509.Certificate object

        :param issuer:
            An asn1crypto.x509.Certificate object of cert's issuer

        :return:
            A list of asn1crypto.ocsp.OCSPResponse objects
        """

        if not self._allow_fetching:
            return self._ocsps

        if cert.issuer_serial not in self._fetched_ocsps:
            try:
                ocsp_response = ocsp_client.fetch(
                    cert,
                    issuer,
                    **self._ocsp_fetch_params
                )

                self._fetched_ocsps[cert.issuer_serial] = [ocsp_response]

                # Responses can contain certificates that are useful in validating the
                # response itself. We can use these since they will be validated using
                # the local trust roots.
                self._extract_ocsp_certs(ocsp_response)
            except (URLError, socket.error) as e:
                self._fetched_ocsps[cert.issuer_serial] = []
                if self._revocation_mode == "soft-fail":
                    self._soft_fail_exceptions.append(e)
                    raise SoftFailError()
                else:
                    raise

        return self._fetched_ocsps[cert.issuer_serial]

    def _extract_ocsp_certs(self, ocsp_response):
        """
        Extracts any certificates included with an OCSP response and adds them
        to the certificate registry

        :param ocsp_response:
            An asn1crypto.ocsp.OCSPResponse object to look for certs inside of
        """

        status = ocsp_response['response_status'].native
        if status == 'successful':
            response_bytes = ocsp_response['response_bytes']
            if response_bytes['response_type'].native == 'basic_ocsp_response':
                response = response_bytes['response'].parsed
                if response['certs']:
                    for other_cert in response['certs']:
                        if self.certificate_registry.add_other_cert(other_cert):
                            self._revocation_certs[other_cert.issuer_serial] = other_cert

    def record_validation(self, cert, path):
        """
        Records that a certificate has been validated, along with the path that
        was used for validation. This helps reduce duplicate work when
        validating a ceritifcate and related resources such as CRLs and OCSPs.

        :param cert:
            An ans1crypto.x509.Certificate object

        :param path:
            A certvalidator.path.ValidationPath object
        """

        self._validate_map[cert.signature] = path

    def check_validation(self, cert):
        """
        Checks to see if a certificate has been validated, and if so, returns
        the ValidationPath used to validate it.

        :param cert:
            An asn1crypto.x509.Certificate object

        :return:
            None if not validated, or a certvalidator.path.ValidationPath
            object of the validation path
        """

        # CA certs are automatically trusted since they are from the trust list
        if self.certificate_registry.is_ca(cert) and cert.signature not in self._validate_map:
            self._validate_map[cert.signature] = ValidationPath(cert)

        return self._validate_map.get(cert.signature)

    def clear_validation(self, cert):
        """
        Clears the record that a certificate has been validated

        :param cert:
            An ans1crypto.x509.Certificate object
        """

        if cert.signature in self._validate_map:
            del self._validate_map[cert.signature]

    def record_crl_issuer(self, certificate_list, cert):
        """
        Records the certificate that issued a certificate list. Used to reduce
        processing code when dealing with self-issued certificates and multiple
        CRLs.

        :param certificate_list:
            An ans1crypto.crl.CertificateList object

        :param cert:
            An ans1crypto.x509.Certificate object
        """

        self._crl_issuer_map[certificate_list.signature] = cert

    def check_crl_issuer(self, certificate_list):
        """
        Checks to see if the certificate that signed a certificate list has
        been found

        :param certificate_list:
            An ans1crypto.crl.CertificateList object

        :return:
            None if not found, or an asn1crypto.x509.Certificate object of the
            issuer
        """

        return self._crl_issuer_map.get(certificate_list.signature) class CRLNoMatchesError(CRLValidationError):

    pass class CRLValidationError(Exception):

    pass class CRLValidationIndeterminateError(CRLValidationError):

    @property
    def failures(self):
        return self.args[1] class InvalidCertificateError(PathValidationError):

    pass class OCSPNoMatchesError(OCSPValidationError):

    pass class OCSPValidationIndeterminateError(OCSPValidationError):

    @property
    def failures(self):
        return self.args[1] class PathValidationError(ValidationError):

    pass class RevokedError(PathValidationError):

    pass class SoftFailError(Exception):

    pass class ValidationPath():
    """
    Represents a path going towards an end-entity certificate
    """

    # A list of asn1crypto.x509.Certificate objects, starting with a trust root
    # and chaining to an end-entity certificate
    _certs = None

    # A set of asn1crypto.x509.Certificate.issuer_serial byte strings of
    # certificates that are already in ._certs
    _cert_hashes = None

    def __init__(self, end_entity_cert=None):
        """
        :param end_entity_cert:
            An asn1crypto.x509.Certificate object for the end-entity certificate
        """

        self._certs = []
        self._cert_hashes = set()
        if end_entity_cert:
            self.prepend(end_entity_cert)

    @property
    def first(self):
        """
        Returns the current beginning of the path - for a path to be complete,
        this certificate should be a trust root

        :return:
            The first asn1crypto.x509.Certificate object in the path
        """

        return self._certs[0]

    def find_issuer(self, cert):
        """
        Return the issuer of the cert specified, as defined by this path

        :param cert:
            An asn1crypto.x509.Certificate object to get the issuer of

        :raises:
            LookupError - when the issuer of the certificate could not be found

        :return:
            An asn1crypto.x509.Certificate object of the issuer
        """

        for entry in self:
            if entry.subject == cert.issuer:
                if entry.key_identifier and cert.authority_key_identifier:
                    if entry.key_identifier == cert.authority_key_identifier:
                        return entry
                else:
                    return entry

        raise LookupError('Unable to find the issuer of the certificate specified')

    def truncate_to(self, cert):
        """
        Remove all certificates in the path after the cert specified

        :param cert:
            An asn1crypto.x509.Certificate object to find

        :raises:
            LookupError - when the certificate could not be found

        :return:
            The current ValidationPath object, for chaining
        """

        cert_index = None
        for index, entry in enumerate(self):
            if entry.issuer_serial == cert.issuer_serial:
                cert_index = index
                break

        if cert_index is None:
            raise LookupError('Unable to find the certificate specified')

        while len(self) > cert_index + 1:
            self.pop()

        return self

    def truncate_to_issuer(self, cert):
        """
        Remove all certificates in the path after the issuer of the cert
        specified, as defined by this path

        :param cert:
            An asn1crypto.x509.Certificate object to find the issuer of

        :raises:
            LookupError - when the issuer of the certificate could not be found

        :return:
            The current ValidationPath object, for chaining
        """

        issuer_index = None
        for index, entry in enumerate(self):
            if entry.subject == cert.issuer:
                if entry.key_identifier and cert.authority_key_identifier:
                    if entry.key_identifier == cert.authority_key_identifier:
                        issuer_index = index
                        break
                else:
                    issuer_index = index
                    break

        if issuer_index is None:
            raise LookupError('Unable to find the issuer of the certificate specified')

        while len(self) > issuer_index + 1:
            self.pop()

        return self

    def copy(self):
        """
        Creates a copy of this path

        :return:
            A ValidationPath object
        """

        copy = self.__class__()
        copy._certs = self._certs[:]
        copy._cert_hashes = self._cert_hashes.copy()
        return copy

    def pop(self):
        """
        Removes the last certificate from the path

        :return:
            The current ValidationPath object, for chaining
        """

        last_cert = self._certs.pop()
        self._cert_hashes.remove(last_cert.issuer_serial)

        return self

    def append(self, cert):
        """
        Appends a cert to the path. This should be a cert issued by the last
        cert in the path.

        :param cert:
            An asn1crypto.x509.Certificate object

        :return:
            The current ValidationPath object, for chaining
        """

        if not isinstance(cert, x509.Certificate):
            if not isinstance(cert, byte_cls):
                raise TypeError(pretty_message(
                    '''
                    cert must be a byte string or an
                    asn1crypto.x509.Certificate object, not %s
                    ''',
                    type_name(cert)
                ))
            if pem.detect(cert):
                _, _, cert = pem.unarmor(cert)
            cert = x509.Certificate.load(cert)

        if cert.issuer_serial in self._cert_hashes:
            raise DuplicateCertificateError()

        self._cert_hashes.add(cert.issuer_serial)
        self._certs.append(cert)

        return self

    def prepend(self, cert):
        """
        Prepends a cert to the path. This should be the issuer of the previously
        prepended cert.

        :param cert:
            An asn1crypto.x509.Certificate object or a byte string

        :return:
            The current ValidationPath object, for chaining
        """

        if not isinstance(cert, x509.Certificate):
            if not isinstance(cert, byte_cls):
                raise TypeError(pretty_message(
                    '''
                    cert must be a byte string or an
                    asn1crypto.x509.Certificate object, not %s
                    ''',
                    type_name(cert)
                ))
            if pem.detect(cert):
                _, _, cert = pem.unarmor(cert)
            cert = x509.Certificate.load(cert)

        if cert.issuer_serial in self._cert_hashes:
            raise DuplicateCertificateError()

        self._cert_hashes.add(cert.issuer_serial)
        self._certs.insert(0, cert)

        return self

    def __len__(self):
        return len(self._certs)

    def __getitem__(self, key):
        return self._certs[key]

    def __iter__(self):
        return iter(self._certs)

    def __eq__(self, other):
        return self._certs == other._certs
from asn1crypto import x509, crl
from oscrypto import asymmetric
from ._errors import pretty_message
from ._types import str_cls, type_name
from .context import ValidationContext
from .errors import (
    CRLNoMatchesError,
    CRLValidationError,
    CRLValidationIndeterminateError,
    InvalidCertificateError,
    OCSPNoMatchesError,
    OCSPValidationIndeterminateError,
    PathValidationError,
    RevokedError,
    SoftFailError,
)
from .path import ValidationPath
import oscrypto.errors
    valid_reasons = set([
        'key_compromise',
        'ca_compromise',
        'affiliation_changed',
        'superseded',
        'cessation_of_operation',
        'certificate_hold',
        'privilege_withdrawn',
        'aa_compromise',
    ])

    known_extensions = set([
        'issuer_alt_name',
        'crl_number',
        'delta_crl_indicator',
        'issuing_distribution_point',
        'authority_key_identifier',
        'freshest_crl',
        'authority_information_access',
    ])

    checked_reasons = set()

    failures = []
    issuer_failures = 0

    while len(crls_to_process) > 0:
        certificate_list = crls_to_process.pop(0)
        crl_idp = certificate_list.issuing_distribution_point_value
        delta_certificate_list = None
        delta_crl_idp = None

        interim_reasons = set()

        crl_issuer = None
        crl_issuer_name = None
        is_indirect = False

        if crl_idp and crl_idp['indirect_crl'].native:
            is_indirect = True
            crl_idp_name = crl_idp['distribution_point']
            if crl_idp_name:
                if crl_idp_name.name == 'full_name':
                    crl_issuer_name = crl_idp_name.chosen[0].chosen
                else:
                    crl_issuer_name = cert_issuer.subject.copy().chosen.append(
                        crl_idp_name.chosen
                    )
            elif certificate_list.authority_key_identifier:
                tmp_crl_issuer = certificate_registry.retrieve_by_key_identifier(
                    certificate_list.authority_key_identifier
                )
                crl_issuer_name = tmp_crl_issuer.subject
            else:
                failures.append((
                    'CRL is marked as an indirect CRL, but provides no '
                    'mechanism for locating the CRL issuer certificate',
                    certificate_list
                ))
                continue
        else:
            crl_issuer_name = certificate_list.issuer

        if not crl_issuer:
            crl_issuer = validation_context.check_crl_issuer(certificate_list)

        if not crl_issuer:
            candidate_crl_issuers = certificate_registry.retrieve_by_name(crl_issuer_name, cert_issuer)
            candidates_skipped = 0
            signatures_failed = 0
            unauthorized_certs = 0

            if not candidate_crl_issuers and crl_issuer_name != certificate_list.issuer:
                candidate_crl_issuers = certificate_registry.retrieve_by_name(certificate_list.issuer, cert_issuer)

            for candidate_crl_issuer in candidate_crl_issuers:
                direct_issuer = candidate_crl_issuer.subject == cert_issuer.subject

                # In some cases an indirect CRL issuer is a certificate issued
                # by the certificate issuer. However, we need to ensure that
                # the candidate CRL issuer is not the certificate being checked,
                # otherwise we may be checking an incorrect CRL and produce
                # incorrect results.
                indirect_issuer = candidate_crl_issuer.issuer == cert_issuer.subject
                indirect_issuer = indirect_issuer and candidate_crl_issuer.sha256 != cert.sha256

                if not direct_issuer and not indirect_issuer and not is_indirect:
                    candidates_skipped += 1
                    continue

                # Step f
                candidate_crl_issuer_path = None

                if validation_context:
                    candidate_crl_issuer_path = validation_context.check_validation(candidate_crl_issuer)

                if candidate_crl_issuer_path is None:
                    candidate_crl_issuer_path = path.copy().truncate_to_issuer(candidate_crl_issuer)
                    candidate_crl_issuer_path.append(candidate_crl_issuer)
                    try:
                        # Pre-emptively mark a path as validated to prevent recursion
                        if validation_context:
                            validation_context.record_validation(candidate_crl_issuer, candidate_crl_issuer_path)

                        temp_override = end_entity_name_override
                        if temp_override is None and candidate_crl_issuer.sha256 != cert_issuer.sha256:
                            temp_override = cert_description + ' CRL issuer'
                        _validate_path(
                            validation_context,
                            candidate_crl_issuer_path,
                            end_entity_name_override=temp_override
                        )

                    except (PathValidationError) as e:
                        # If the validation did not work out, clear it
                        if validation_context:
                            validation_context.clear_validation(candidate_crl_issuer)

                        # We let a revoked error fall through since step k will catch
                        # it with a correct error message

                        if isinstance(e, RevokedError):