rustls / webpki

WebPKI X.509 Certificate Validation in Rust

Home Page:https://docs.rs/rustls-webpki/latest/webpki/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow path building to return the validated path

cpu opened this issue · comments

Presently the validate_cert.rs implementation of build_path used for path-building only returns an indication of whether the operation failed or not:

pub(crate) fn build_chain(
required_eku_if_present: KeyPurposeId,
supported_sig_algs: &[&SignatureAlgorithm],
trust_anchors: &[TrustAnchor],
intermediate_certs: &[&[u8]],
cert: &Cert,
time: time::Time,
sub_ca_count: usize,
) -> Result<(), Error> {

CRL validation also requires building a path from the issuer of the CRL to a trust anchor. Further, RFC 5280 6.3.3 says the path must be built to the same trust anchor used to validate the target end-entity certificate for which we're considering revocation status:

(f)  Obtain and validate the certification path for the issuer of
     the complete CRL.  The trust anchor for the certification
     path MUST be the same as the trust anchor used to validate
     the target certificate.  ....

At a minimum, the build_path implementation should yield the final TrustAnchor that was used to build the validated path so that we can ensure this requirement is met.

To avoid having to build a path from scratch from the issuer to that TrustAnchor it would be best if build_path could yield the full validated path including the intermediate certificates used. That would allow the CRL validation to proceed using the same validation path to the same root. This is the approach used by the gRPC implementation of CRL validation based on providing the implementation the verifiedChains [][]*x509.Certificate produced by path building.

Changing the implementation to yield just a TrustAnchor is fairly straight forward. Producing the full chain that was validated without making it require alloc is likely to be more difficult.

Related issue: briansmith/webpki#264

Changing the implementation to yield just a TrustAnchor is fairly straight forward. Producing the full chain that was validated without making it require alloc is likely to be more difficult.

Reading the existing cert.rs and verify_cert.rs code more carefully I realized we already have a way to represent this to look at for inspiration. The Cert.ee_or_ca field's Ca variant holds a &'a Cert<'a> ref that's used to track the child cert of the CA Cert in-hand. The verify_cert::check_signatures fn uses this to walk the chain built in build_chain backwards from the selected trust anchor to the leaf end entity certificate.

I thought I'd be able to smuggle out a &Cert from build_chain to use to reconstruct the path through ee_or_ca but the snag I'm hitting is that build_chain builds a Cert from intermediate DER internal to the second loop_while_non_fatal_error body while processing intermediates. I can't return a value from build_chain that references the value owned by that function:

let potential_issuer =
cert::parse_cert(untrusted::Input::from(cert_der), EndEntityOrCa::Ca(cert))?;

I can't change build_chain to take an owned Cert instead of &Cert because the consumers of build_chain are &self and can't move the inner Cert out. I'm going to continue trying to figure something out and will push a broken branch for some help if I stay stuck.

Can you replace the potential_issuer part of Cert with a Cow-like thing that would allow us to do either?

I experimented with that and I think the catch there was that Cert isn't Clone and some of its fields aren't either. I don't have the error in front of me at the moment. I might have been holding it wrong.

I also thought about having the caller transform the slice of DER references into Cert's up-front but that can't be done easily either (I thought I'd need to map + Collect into a vec which requires alloc, and there's the catch of needing to set the ee_or_ca field for the intermediates with pointers to the child certs). It might be doable but wasn't coming easy and front-loads parsing work we defer until later otherwise.

I had to log off for the day but my thought for a next idea is to try to return references to the DER of the built chain intermediates instead of the parsed Cert at the cost of having to re-build Cert from the DER elsewhere. The DER comes from the caller and has a lifetime that would encapsulate the built chain.

I think I'm going to take a different approach. I don't want to heavily refactor the existing path building just to support CRLs and haven't been able to arrive at a minimal change that's workable. I think it will be easier to consider the CRL path building and signature validation separate from the existing implementation to start.

I need to add test coverage but I think I've figured out a way forward. I was making life harder for myself trying to handle CRL validation separately from path building. Threading the CRL data into the existing validation works out better and obviates the requirement to pull the validated path out. It's also a better match to how 5280 + friends recommend this be handled.

Closing this since the approach I used in #66 doesn't require this and implementing it will require more significant changes to the core path building than I'm keen to bite off right now.