Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test_revoked_certificate fails since 2021-10-08 #27

Closed
mweinelt opened this issue Oct 11, 2021 · 1 comment
Closed

test_revoked_certificate fails since 2021-10-08 #27

mweinelt opened this issue Oct 11, 2021 · 1 comment

Comments

@mweinelt
Copy link

Some certificate in the chain expired, which makes the revocation test fail. Can be seen on versions 0.3 and 0.4.

_________________ ValidationTestCase.test_revoked_certificate __________________

self = <signify.x509.context.VerificationContext object at 0x7fffeef62fd0>
certificate = <signify.x509.certificates.Certificate object at 0x7fffeef62f70>

    def verify(self, certificate):
        """Verifies the certificate, and its chain.

        :param Certificate certificate: The certificate to verify
        :return: A valid certificate chain for this certificate.
        :rtype: Iterable[Certificate]
        :raises AuthenticodeVerificationError: When the certificate could not be verified.
        """

        # we keep track of our asn1 objects to make sure we return Certificate objects when we're done
        to_check_asn1cert = certificate.to_asn1crypto
        all_certs = {to_check_asn1cert: certificate}

        # we need to get lists of our intermediates and trusted certificates
        intermediates, trust_roots = [], []
        for store in self.stores:
            for cert in store:
                asn1cert = cert.to_asn1crypto
                # we short-circuit the check here to ensure we do not check too much possibilities
                (trust_roots if store.trusted else intermediates).append(asn1cert)
                all_certs[asn1cert] = cert

        # construct the context and validator for certvalidator
        timestamp = self.timestamp
        context = ValidationContext(
            trust_roots=list(trust_roots),
            moment=timestamp,
            weak_hash_algos=set() if self.allow_legacy else None,
            revocation_mode=self.revocation_mode,
            allow_fetching=self.allow_fetching,
            crl_fetch_params={'timeout': self.fetch_timeout},
            ocsp_fetch_params={'timeout': self.fetch_timeout},
            crls=self.crls,
            ocsps=self.ocsps
        )
        validator = CertificateValidator(
            end_entity_cert=to_check_asn1cert,
            intermediate_certs=list(intermediates),
            validation_context=context
        )

        # verify the chain
        try:
>           chain = validator.validate_usage(
                key_usage=set(self.key_usages) if self.key_usages else set(),
                extended_key_usage=set(self.extended_key_usages) if self.extended_key_usages else set(),
                extended_optional=self.optional_eku
            )

signify/x509/context.py:291:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <certvalidator.CertificateValidator object at 0x7fffee54e4f0>
key_usage = set(), extended_key_usage = set(), extended_optional = True

    def validate_usage(self, key_usage, extended_key_usage=None, extended_optional=False):
        """
        Validates the certificate path and that the certificate is valid for
        the key usage and extended key usage purposes specified.

        :param key_usage:
            A set of unicode strings of the required key usage purposes. Valid
            values include:

             - "digital_signature"
             - "non_repudiation"
             - "key_encipherment"
             - "data_encipherment"
             - "key_agreement"
             - "key_cert_sign"
             - "crl_sign"
             - "encipher_only"
             - "decipher_only"

        :param extended_key_usage:
            A set of unicode strings of the required extended key usage
            purposes. These must be either dotted number OIDs, or one of the
            following extended key usage purposes:

             - "server_auth"
             - "client_auth"
             - "code_signing"
             - "email_protection"
             - "ipsec_end_system"
             - "ipsec_tunnel"
             - "ipsec_user"
             - "time_stamping"
             - "ocsp_signing"
             - "wireless_access_points"

            An example of a dotted number OID:

             - "1.3.6.1.5.5.7.3.1"

        :param extended_optional:
            A bool - if the extended_key_usage extension may be ommited and still
            considered valid

        :raises:
            certvalidator.errors.PathValidationError - when an error occurs validating the path
            certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
            certvalidator.errors.InvalidCertificateError - when the certificate is not valid for the usages specified

        :return:
            A certvalidator.path.ValidationPath object of the validated
            certificate validation path
        """

>       self._validate_path()

/nix/store/qxzqxa6k9rznbidjcnjj1ra1ascizy7n-python3.9-certvalidator-0.11.1/lib/python3.9/site-packages/certvalidator/__init__.py:193:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <certvalidator.CertificateValidator object at 0x7fffee54e4f0>

    def _validate_path(self):
        """
        Builds possible certificate paths and validates them until a valid one
        is found, or all fail.

        :raises:
            certvalidator.errors.PathValidationError - when an error occurs validating the path
            certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
        """

        if self._path is not None:
            return

        exceptions = []

        if self._certificate.hash_algo in self._context.weak_hash_algos:
            raise InvalidCertificateError(pretty_message(
                '''
                The X.509 certificate provided has a signature using the weak
                hash algorithm %s
                ''',
                self._certificate.hash_algo
            ))

        try:
            paths = self._context.certificate_registry.build_paths(self._certificate)
        except (PathBuildingError) as e:
            if self._certificate.self_signed in set(['yes', 'maybe']):
                raise InvalidCertificateError(pretty_message(
                    '''
                    The X.509 certificate provided is self-signed - "%s"
                    ''',
                    self._certificate.subject.human_friendly
                ))
            raise

        for candidate_path in paths:
            try:
                validate_path(self._context, candidate_path)
                self._path = candidate_path
                return
            except (ValidationError) as e:
                exceptions.append(e)

        if len(exceptions) == 1:
>           raise exceptions[0]

/nix/store/qxzqxa6k9rznbidjcnjj1ra1ascizy7n-python3.9-certvalidator-0.11.1/lib/python3.9/site-packages/certvalidator/__init__.py:128:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <certvalidator.CertificateValidator object at 0x7fffee54e4f0>

    def _validate_path(self):
        """
        Builds possible certificate paths and validates them until a valid one
        is found, or all fail.

        :raises:
            certvalidator.errors.PathValidationError - when an error occurs validating the path
            certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
        """

        if self._path is not None:
            return

        exceptions = []

        if self._certificate.hash_algo in self._context.weak_hash_algos:
            raise InvalidCertificateError(pretty_message(
                '''
                The X.509 certificate provided has a signature using the weak
                hash algorithm %s
                ''',
                self._certificate.hash_algo
            ))

        try:
            paths = self._context.certificate_registry.build_paths(self._certificate)
        except (PathBuildingError) as e:
            if self._certificate.self_signed in set(['yes', 'maybe']):
                raise InvalidCertificateError(pretty_message(
                    '''
                    The X.509 certificate provided is self-signed - "%s"
                    ''',
                    self._certificate.subject.human_friendly
                ))
            raise

        for candidate_path in paths:
            try:
>               validate_path(self._context, candidate_path)

/nix/store/qxzqxa6k9rznbidjcnjj1ra1ascizy7n-python3.9-certvalidator-0.11.1/lib/python3.9/site-packages/certvalidator/__init__.py:121:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

validation_context = <certvalidator.context.ValidationContext object at 0x7fffee4df6d0>
path = <certvalidator.path.ValidationPath object at 0x7fffee534880>

    def validate_path(validation_context, path):
        """
        Validates the path using the algorithm from
        https://tools.ietf.org/html/rfc5280#section-6.1, with the exception
        that name constraints are not checked or enforced.

        Critical extensions on the end-entity certificate are not validated
        and are left up to the consuming application to process and/or fail on.

        :param validation_context:
            A certvalidator.context.ValidationContext object to use for
            configuring validation behavior

        :param path:
            A certvalidator.path.ValidationPath object of the path to validate

        :raises:
            certvalidator.errors.PathValidationError - when an error occurs validating the path
            certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked

        :return:
            The final certificate in the path - an instance of
            asn1crypto.x509.Certificate
        """

>       return _validate_path(validation_context, path)

/nix/store/qxzqxa6k9rznbidjcnjj1ra1ascizy7n-python3.9-certvalidator-0.11.1/lib/python3.9/site-packages/certvalidator/validate.py:50:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

validation_context = <certvalidator.context.ValidationContext object at 0x7fffee4df6d0>
path = <certvalidator.path.ValidationPath object at 0x7fffee534880>
end_entity_name_override = None

    def _validate_path(validation_context, path, end_entity_name_override=None):
        """
        Internal copy of validate_path() that allows overriding the name of the
        end-entity certificate as used in exception messages. This functionality is
        used during chain validation when dealing with indirect CRLs issuer or
        OCSP responder certificates.

        :param validation_context:
            A certvalidator.context.ValidationContext object to use for
            configuring validation behavior

        :param path:
            A certvalidator.path.ValidationPath object of the path to validate

        :param end_entity_name_override:
            A unicode string of the name to use for the final certificate in the
            path. This is necessary when dealing with indirect CRL issuers or
            OCSP responder certificates.

        :return:
            The final certificate in the path - an instance of
            asn1crypto.x509.Certificate
        """

        if not isinstance(path, ValidationPath):
            raise TypeError(pretty_message(
                '''
                path must be an instance of certvalidator.path.ValidationPath,
                not %s
                ''',
                type_name(path)
            ))

        if not isinstance(validation_context, ValidationContext):
            raise TypeError(pretty_message(
                '''
                validation_context must be an instance of
                certvalidator.context.ValidationContext, not %s
                ''',
                type_name(validation_context)
            ))

        moment = validation_context.moment

        if end_entity_name_override is not None and not isinstance(end_entity_name_override, str_cls):
            raise TypeError(pretty_message(
                '''
                end_entity_name_override must be a unicode string, not %s
                ''',
                type_name(end_entity_name_override)
            ))

        # Inputs

        trust_anchor = path.first

        # We skip the trust anchor when measuring the path since technically
        # the trust anchor is not part of the path
        path_length = len(path) - 1

        # We don't accept any certificate policy or name constraint values as input
        # and instead just start allowing everything during initialization

        # Step 1: initialization

        # Step 1 a
        valid_policy_tree = PolicyTreeRoot('any_policy', set(), set(['any_policy']))

        # Steps 1 b-c skipped since they relate to name constraints

        # Steps 1 d-f
        # We do not use initial-explicit-policy, initial-any-policy-inhibit or
        # initial-policy-mapping-inhibit, so they are all set to the path length + 1
        explicit_policy = path_length + 1
        inhibit_any_policy = path_length + 1
        policy_mapping = path_length + 1

        # Steps 1 g-i
        working_public_key = trust_anchor.public_key
        # Step 1 j
        working_issuer_name = trust_anchor.subject
        # Step 1 k
        max_path_length = path_length
        if trust_anchor.max_path_length is not None:
            max_path_length = trust_anchor.max_path_length

        # Step 2: basic processing
        index = 1
        last_index = len(path) - 1

        completed_path = ValidationPath(trust_anchor)
        validation_context.record_validation(trust_anchor, completed_path)

        cert = trust_anchor
        while index <= last_index:
            cert = path[index]

            # Step 2 a 1
            signature_algo = cert['signature_algorithm'].signature_algo
            hash_algo = cert['signature_algorithm'].hash_algo

            if hash_algo in validation_context.weak_hash_algos:
                raise PathValidationError(pretty_message(
                    '''
                    The path could not be validated because the signature of %s
                    uses the weak hash algorithm %s
                    ''',
                    _cert_type(index, last_index, end_entity_name_override, definite=True),
                    hash_algo
                ))

            if signature_algo == 'rsassa_pkcs1v15':
                verify_func = asymmetric.rsa_pkcs1v15_verify
            elif signature_algo == 'dsa':
                verify_func = asymmetric.dsa_verify
            elif signature_algo == 'ecdsa':
                verify_func = asymmetric.ecdsa_verify
            else:
                raise PathValidationError(pretty_message(
                    '''
                    The path could not be validated because the signature of %s
                    uses the unsupported algorithm %s
                    ''',
                    _cert_type(index, last_index, end_entity_name_override, definite=True),
                    signature_algo
                ))

            try:
                key_object = asymmetric.load_public_key(working_public_key)
                verify_func(key_object, cert['signature_value'].native, cert['tbs_certificate'].dump(), hash_algo)

            except (oscrypto.errors.SignatureError):
                raise PathValidationError(pretty_message(
                    '''
                    The path could not be validated because the signature of %s
                    could not be verified
                    ''',
                    _cert_type(index, last_index, end_entity_name_override, definite=True)
                ))

            # Step 2 a 2
            if not validation_context.is_whitelisted(cert):
                validity = cert['tbs_certificate']['validity']
                if moment < validity['not_before'].native:
                    raise PathValidationError(pretty_message(
                        '''
                        The path could not be validated because %s is not valid
                        until %s
                        ''',
                        _cert_type(index, last_index, end_entity_name_override, definite=True),
                        validity['not_before'].native.strftime('%Y-%m-%d %H:%M:%SZ')
                    ))
                if moment > validity['not_after'].native:
>                   raise PathValidationError(pretty_message(
                        '''
                        The path could not be validated because %s expired %s
                        ''',
                        _cert_type(index, last_index, end_entity_name_override, definite=True),
                        validity['not_after'].native.strftime('%Y-%m-%d %H:%M:%SZ')
                    ))
E                   certvalidator.errors.PathValidationError: The path could not be validated because the end-entity certificate expired 2021-10-08 12:00:00Z

/nix/store/qxzqxa6k9rznbidjcnjj1ra1ascizy7n-python3.9-certvalidator-0.11.1/lib/python3.9/site-packages/certvalidator/validate.py:358: PathValidationError

During handling of the above exception, another exception occurred:

self = <tests.test_context.ValidationTestCase testMethod=test_revoked_certificate>

    def test_revoked_certificate(self):
        root = FileSystemCertificateStore(root_dir / "certs" / 'digicert-global-root-ca.pem', trusted=True)
        intermediate = FileSystemCertificateStore(root_dir / "certs" / 'digicert-sha2-secure-server-ca.pem')
        with open(str(root_dir / "certs" / 'revoked.badssl.com.pem'), "rb") as f:
            cert = Certificate.from_pem(f.read())

        # check that when we do not verify the CRL it does not fail
        context = VerificationContext(root, intermediate)
>       context.verify(cert)

tests/test_context.py:42:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <signify.x509.context.VerificationContext object at 0x7fffeef62fd0>
certificate = <signify.x509.certificates.Certificate object at 0x7fffeef62f70>

    def verify(self, certificate):
        """Verifies the certificate, and its chain.

        :param Certificate certificate: The certificate to verify
        :return: A valid certificate chain for this certificate.
        :rtype: Iterable[Certificate]
        :raises AuthenticodeVerificationError: When the certificate could not be verified.
        """

        # we keep track of our asn1 objects to make sure we return Certificate objects when we're done
        to_check_asn1cert = certificate.to_asn1crypto
        all_certs = {to_check_asn1cert: certificate}

        # we need to get lists of our intermediates and trusted certificates
        intermediates, trust_roots = [], []
        for store in self.stores:
            for cert in store:
                asn1cert = cert.to_asn1crypto
                # we short-circuit the check here to ensure we do not check too much possibilities
                (trust_roots if store.trusted else intermediates).append(asn1cert)
                all_certs[asn1cert] = cert

        # construct the context and validator for certvalidator
        timestamp = self.timestamp
        context = ValidationContext(
            trust_roots=list(trust_roots),
            moment=timestamp,
            weak_hash_algos=set() if self.allow_legacy else None,
            revocation_mode=self.revocation_mode,
            allow_fetching=self.allow_fetching,
            crl_fetch_params={'timeout': self.fetch_timeout},
            ocsp_fetch_params={'timeout': self.fetch_timeout},
            crls=self.crls,
            ocsps=self.ocsps
        )
        validator = CertificateValidator(
            end_entity_cert=to_check_asn1cert,
            intermediate_certs=list(intermediates),
            validation_context=context
        )

        # verify the chain
        try:
            chain = validator.validate_usage(
                key_usage=set(self.key_usages) if self.key_usages else set(),
                extended_key_usage=set(self.extended_key_usages) if self.extended_key_usages else set(),
                extended_optional=self.optional_eku
            )
        except Exception as e:
>           raise CertificateVerificationError("Chain verification from %s failed: %s" % (certificate, e))
E           signify.exceptions.CertificateVerificationError: Chain verification from CN=revoked.badssl.com, O=Lucas Garron Torres, L=Walnut Creek, ST=California, C=US (serial:4578095623763233818958520798617405692, sha1:23fc13ba6f27bf8dd1761bda7bb41921f59c21f7) failed: The path could not be validated because the end-entity certificate expired 2021-10-08 12:00:00Z

signify/x509/context.py:297: CertificateVerificationError
@ralphje
Copy link
Owner

ralphje commented Oct 31, 2021

Addressed this issue with a temporary skip, now waiting for chromium/badssl.com#477 to get a new certificate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants