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

Allow verifying an x509 cert chain without making assertions about the subject name #10276

Closed
Tracked by #10034
kislyuk opened this issue Jan 28, 2024 · 21 comments · Fixed by #10345
Closed
Tracked by #10034

Allow verifying an x509 cert chain without making assertions about the subject name #10276

kislyuk opened this issue Jan 28, 2024 · 21 comments · Fixed by #10345

Comments

@kislyuk
Copy link

kislyuk commented Jan 28, 2024

Thanks to all who worked on the X.509 verification support in version 42.

I am trying to use this API for verifying a signing certificate, and realizing that the API requires me to assert a subject name (DNS name or IP address) to get the validation output. The subject name is not defined/not relevant in this application.

How can I verify that a certificate is in the chain of trust without asserting on the subject name?

@alex
Copy link
Member

alex commented Jan 28, 2024

We don't yet support this use case. We intend to in the future.

@woodruffw
Copy link
Contributor

woodruffw commented Jan 28, 2024

I've been thinking a bit about how to do this (since I'll also likely need it at some point for sigstore-python). Some ideas:

  1. Make the current build_server_verifier(subject: Subject) take an optional subject instead (maybe explicitly None?), which would then disable any SAN checks during chain building.
  2. Add a new build_generic_verifier(subject: Subject | None), which would then be an explicit path for chain building operations that are more "generic" than server authentication (presumed by build_server_identifier).

@alex
Copy link
Member

alex commented Jan 28, 2024

Well, we should not be changing the build_server_verifier function. You should never verify a server cert without checking the subject is valid.

This needs to be a new type of verifier.

@woodruffw
Copy link
Contributor

That makes sense. How does build_generic_verifier/GenericVerifier sound?

(Other meh names: AnonymousVerifier/SubjectlessVerifier for explicit non-subject checking.)

@alex
Copy link
Member

alex commented Jan 28, 2024

What's teh difference between that and a client cert verifier?

@woodruffw
Copy link
Contributor

Hmm, not sure. My idea of a ClientVerifier was something that takes a nonempty list[Subject] to match against so it wouldn't work for the "explicitly not checking the subject" case. But maybe I'm thinking about that the wrong way.

@alex
Copy link
Member

alex commented Jan 28, 2024

I don't think that's how a clientverifier would work -- imagine a webserver using client certs for auth.

@woodruffw
Copy link
Contributor

So in that case ClientVerifier.verify() would return the Subject for subsequent AuthZ/handling, right? In that case yeah, I think this is indistinguishable from the client cert verifier case and can be handled through it.

@alex
Copy link
Member

alex commented Jan 28, 2024

Yes, I think that's what you'd want, indeed.

@woodruffw
Copy link
Contributor

Just to give a public update: I've offered to do the work here. I'll try and get a PR up for this tonight.

@exFalso
Copy link

exFalso commented Feb 16, 2024

Just noting: there are other x509 certificates that don't actually have anything to do with webPKI. In our case we're looking for a generic way to verify certificate chains, with various constraints on the contents. It'd be nice if the project had a generic verifier as well. As-is, we're stuck with oscrypto/certvalidator or a fork.

@woodruffw
Copy link
Contributor

Just noting: there are other x509 certificates that don't actually have anything to do with webPKI. In our case we're looking for a generic way to verify certificate chains, with various constraints on the contents. It'd be nice if the project had a generic verifier as well. As-is, we're stuck with oscrypto/certvalidator or a fork.

Could you say a bit more about your use case? There isn't exactly anything called "generic" chain verification; all chain verifiers perform verification modulo some kind of certificate profile (sometimes that profile is poorly defined or not well documented, but it's always there).

In other words: are you verifying code-signing certs, S/MIME, etc.? Each of these has a profile (or profiles), and supporting each without exposing footguns requires some design consideration 🙂

@vEpiphyte
Copy link

If you look at the PyOpenSSL X509Store / X509StoreContext APIs ( https://www.pyopenssl.org/en/latest/api/crypto.html#x509store-objects ) those are probably the most flexible examples of asking "Does this certificate validate with this CA/CRL chain using the [openssl] polices I choose to set?". That level of flexibility does mean its full of footguns for someone to use and potentially whiff on ( for example, validating a server certificate is signed but not taking the extra step to validate the name is as expected ).

@exFalso If you've got good examples you can share I bet that would be helpful for the PYCA devs in their feature planning :)

@exFalso
Copy link

exFalso commented Feb 17, 2024

@woodruffw @vEpiphyte sure I can expand. We're working with Intel SGX remote attestation, which requires a client of a remotely executing code to verify certain cryptographic evidence (it's essentially Intel signing off on the fact that a piece of code has been isolated and is running using encrypted memory). The remote code will present a certificate chain with a signature over a byteblob which contains further data about that running code. Part of the verification is the chain verification (there are quite a few more steps to it).

Example API: https://api.portal.trustedservices.intel.com/content/documentation.html#pcs-certificate-v4-response

Example certvalidator/oscrypto chain verification (obv. partial code):

        trust_roots = [spec_dcap.dcapRootCaDer]
        validation_context = ValidationContext(trust_roots)
        validator = CertificateValidator(
            pck_certs[0].as_bytes(),
            intermediate_certs=map(lambda cert: cert.as_bytes(), pck_certs[1:]),
            validation_context=validation_context,
        )
        validator.validate_usage({"digital_signature"})

(sidenote: I know the camelcase field is an eyesore, that comes from generated code ;) )

Example root certificate, this is the Intel SGX DCAP root:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            d1:07:76:5d:32:a3:b0:94
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, ST = CA, L = Santa Clara, O = Intel Corporation, CN = Intel SGX Attestation Report Signing CA
        Validity
            Not Before: Nov 14 15:37:31 2016 GMT
            Not After : Dec 31 23:59:59 2049 GMT
        Subject: C = US, ST = CA, L = Santa Clara, O = Intel Corporation, CN = Intel SGX Attestation Report Signing CA
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (3072 bit)
                Modulus:
                    00:9f:3c:64:7e:b5:77:3c:bb:51:2d:27:32:c0:d7:
                    ...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 CRL Distribution Points:
                Full Name:
                  URI:http://trustedservices.intel.com/content/CRL/SGX/AttestationReportSigningCA.crl
            X509v3 Subject Key Identifier:
                78:43:7B:76:A6:7E:BC:D0:AF:7E:42:37:EB:35:7C:3B:87:01:51:3C
            X509v3 Authority Key Identifier:
                78:43:7B:76:A6:7E:BC:D0:AF:7E:42:37:EB:35:7C:3B:87:01:51:3C
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        78:5f:2d:60:c5:c8:0a:f4:2a:79:76:10:21:39:15:da:82:c9:
        ...

The full verification requires a lot more steps.

As you can see, this is a highly specialized use case, and implementing a separate specialized validator in an x509 library is unwarranted. Instead, a verifier for the overall chain integrity would suffice, and any additional checks we want to implement would be separate. However, this is currently not possible with cryptography iiuc.

Another completely different use-case is SPIFFE: https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md, where again, the webPKI-specialized validator will not work. I would highly recommend not implementing these verifiers, but rather exposing the primitives (probably under hazmat) that allow library users to verify.

@woodruffw
Copy link
Contributor

Thanks for the additional context @exFalso!

It looks like the Intel SGX certificate profile is defined here: https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/SGX_PCK_Certificate_CRL_Spec-1.4.pdf

However, I agree that it probably doesn't make a ton of sense to expose a dedicated API for every X.509 profile on the planet 🙂. Having a building block validator under hazmat (or extending the existing configuration knobs in a way that doesn't encourage user misuse) would probably be more straightforward.

@exFalso
Copy link

exFalso commented Feb 19, 2024

If you look at the PyOpenSSL X509Store / X509StoreContext APIs ( https://www.pyopenssl.org/en/latest/api/crypto.html#x509store-objects ) those are probably the most flexible examples of asking "Does this certificate validate with this CA/CRL chain using the [openssl] polices I choose to set?". That level of flexibility does mean its full of footguns for someone to use and potentially whiff on ( for example, validating a server certificate is signed but not taking the extra step to validate the name is as expected ).

@exFalso If you've got good examples you can share I bet that would be helpful for the PYCA devs in their feature planning :)

This worked, thank you!

@vEpiphyte
Copy link

@exFalso If you're able to share some of the code you used from pyopenssl that could be useful for the devs here. I shared an example here #10393 for their reference purposes.

@exFalso
Copy link

exFalso commented Feb 20, 2024

It's very similar to that code.

        root_x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, spec_dcap.dcapRootCaDer)
        trust_store = OpenSSL.crypto.X509Store()
        trust_store.add_cert(root_x509)
        pck_x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pck_certs[0].as_bytes())
        intermediate_certs = map(lambda cert: OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert.as_bytes()), pck_certs[1:])
        context = OpenSSL.crypto.X509StoreContext(trust_store, pck_x509, list(intermediate_certs))
        context.verify_certificate()

It is a lot more cumbersome to do additional checks like on keyUsage, effectively need to deserialize from the ASN1-level

@shane-kearns
Copy link
Contributor

Another non web use case is in verification of TPM certificates (e.g. Endorsement Key, which is signed by the TPM manufacturer; and Attestation Keys signed by the system manufacturer or owner).
https://trustedcomputinggroup.org/wp-content/uploads/TPM-2p0-Keys-for-Device-Identity-and-Attestation_v1_r12_pub10082021.pdf

I think similar to the cases described by exFalso, path validation is relatively standard but the application needs to do additional checks on the leaf certificate e.g. checking the certificate policy and key usage extensions for the appropriate OIDs.

The primitives needed to implement path validation on top of cryptography are all there, but there are a lot of opportunities to get it wrong too.

@kislyuk
Copy link
Author

kislyuk commented Mar 7, 2024

FYI, for completeness, my use case for this functionality is certificate verification for XML Signature (which is used in SAML 2.0 and XAdES), and TSP (RFC 3161), also used in XAdES and other applications.

@woodruffw
Copy link
Contributor

Just to give a brief update here: the work in #10345 is pretty much done, and just needs to be deconflicted (and reviewed, of course). Once merged, that will expose Python APIs for "client" verification (i.e. path validation without subject assertions, with the expectation that the validating party will check the subjects or otherwise handle the chain appropriately subsequently).

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

Successfully merging a pull request may close this issue.

6 participants