diff --git a/src/rust/cryptography-x509-validation/src/lib.rs b/src/rust/cryptography-x509-validation/src/lib.rs index 5308fdf9d984e..686dd27d0945d 100644 --- a/src/rust/cryptography-x509-validation/src/lib.rs +++ b/src/rust/cryptography-x509-validation/src/lib.rs @@ -56,6 +56,7 @@ impl<'a, B: Backend, P: policy::Profile> Validator<'a, B, P> { // Look in the store's root set to see if the working cert is listed. // If it is, we've reached the end. + // // Observe that no issuer connection or signature verification happens // here: inclusion in the root set implies a trust relationship, // even if the working certificate is an EE or intermediate CA. @@ -84,14 +85,20 @@ impl<'a, B: Backend, P: policy::Profile> Validator<'a, B, P> { Err(PolicyError::Other("chain construction exhausted all candidates").into()) } - pub fn validate(&self, ee: &'a Certificate) -> Result, ValidationError> { + pub fn validate(&self, leaf: &'a Certificate) -> Result, ValidationError> { // Before anything else, check whether the given EE cert // is well-formed according to our policy (and its underlying // certificate profile). // // This includes a check against the EE cert's SANs. - self.policy.permits_ee(ee)?; + // + // NOTE: This is correct when the certificate passed in is an EE, + // but there's no reason why a user shouldn't be able to pass in an + // intermediate instead and construct a chain from there. Maybe + // check whether `leaf` is actually an EE and, if not, validate + // it against the policy's CA checks instead? + self.policy.permits_ee(leaf)?; - self.build_chain(ee, 0) + self.build_chain(leaf, 0) } } diff --git a/src/rust/cryptography-x509-validation/src/policy/mod.rs b/src/rust/cryptography-x509-validation/src/policy/mod.rs index 447532bc54ba5..8d9cfa5f2f22d 100644 --- a/src/rust/cryptography-x509-validation/src/policy/mod.rs +++ b/src/rust/cryptography-x509-validation/src/policy/mod.rs @@ -54,8 +54,16 @@ pub struct Policy> { /// this policy. pub max_chain_depth: u8, + /// A DNS name (e.g. `example.com`) that any EE certificates validated + /// by this policy must match. pub name: DNSString, - pub validation_time: asn1::DateTime, + + // NOTE: Conceptually this belongs in the underlying profile instead, + // but doing so introduces another configuration point when virtually + // every profile should have the same validation time semantics. + // So we raise it to the profile instead. + pub validation_time: Option, + // TODO: Real types here, as these get filled in. pub extended_key_usage: (), pub algorithms: (), @@ -73,15 +81,17 @@ impl Policy { // anyways, so we panic rather than pushing the failure states // through. let now = Utc::now(); - let validation_time = asn1::DateTime::new( - now.year() as u16, - now.month() as u8, - now.day() as u8, - now.hour() as u8, - now.minute() as u8, - now.second() as u8, - ) - .expect("unrepresentable system time: malarkey detected"); + let validation_time = Some( + asn1::DateTime::new( + now.year() as u16, + now.month() as u8, + now.day() as u8, + now.hour() as u8, + now.minute() as u8, + now.second() as u8, + ) + .expect("unrepresentable system time: malarkey detected"), + ); Self { profile: RFC5280::default(), @@ -96,11 +106,23 @@ impl Policy { } impl> Policy { - pub fn with_time(mut self, time: asn1::DateTime) -> Self { + /// Configure this policy's validation time, i.e. the time referenced + /// for certificate validity period checks. + /// + /// Passing `None` will disable validity period checks, meaning that + /// no certificates will be considered expired. + pub fn with_validation_time(mut self, time: Option) -> Self { self.validation_time = time; self } + /// Configure this policy's maximum chain building depth, i.e. the + /// longest chain that path construction will attempt before giving up. + pub fn with_max_chain_depth(mut self, depth: u8) -> Self { + self.max_chain_depth = depth; + self + } + fn permits_san(&self, cert: &Certificate) -> Result<(), PolicyError> { // Check that the supplied name matches a DNSName SAN in our EE cert. // TODO: Does this belong under the profile instead? @@ -123,6 +145,7 @@ impl> Policy { } } + /// Checks whether the given CA certificate is compatible with this policy. pub(crate) fn permits_ca(&self, cert: &Certificate) -> Result<(), PolicyError> { self.profile.permits_basic(cert)?; self.profile.permits_ca(cert)?; @@ -132,6 +155,7 @@ impl> Policy { Ok(()) } + /// Checks whether the given EE certificate is compatible with this policy. pub(crate) fn permits_ee(&self, cert: &Certificate) -> Result<(), PolicyError> { // An end entity cert is considered "permitted" under a policy if: // 1. It satisfies the basic (EE and CA) requirements of the underlying profile; @@ -147,6 +171,16 @@ impl> Policy { Ok(()) } + /// Checks whether `issuer` is a valid issuing CA for `child` at a + /// path-building depth of `current_depth`. + /// + /// This checks that `issuer` is permitted under this policy and that + /// it was used to sign for `child`. + /// + /// On success, this function returns the new path-building depth. This + /// may or may not be a higher number than the original depth, depending + /// on the kind of validation performed (e.g., whether the issuer was + /// self-issued). pub(crate) fn valid_issuer( &self, issuer: &Certificate, @@ -164,6 +198,7 @@ impl> Policy { } // Self-issued issuers don't increase the working depth. + // NOTE: This is technically part of the profile's semantics. match issuer.is_self_issued() { true => Ok(current_depth), false => Ok(current_depth + 1),