Skip to content

Instantly share code, notes, and snippets.

@woodruffw
Created December 18, 2023 19:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save woodruffw/776153088e0df3fc2f0675c5e835f7b8 to your computer and use it in GitHub Desktop.
Save woodruffw/776153088e0df3fc2f0675c5e835f7b8 to your computer and use it in GitHub Desktop.
diff --git b/src/rust/cryptography-x509-validation/src/lib.rs a/src/rust/cryptography-x509-validation/src/lib.rs
index 778ad4962..0f6ba7d6c 100644
--- b/src/rust/cryptography-x509-validation/src/lib.rs
+++ a/src/rust/cryptography-x509-validation/src/lib.rs
@@ -163,8 +163,23 @@ impl<'a> AccumulatedNameConstraints<'a> {
pub struct Intermediates<'a>(HashSet<Certificate<'a>>);
impl<'a> Intermediates<'a> {
- fn new(intermediates: impl IntoIterator<Item = Certificate<'a>>) -> Self {
- Self(intermediates.into_iter().collect())
+ fn new<B: CryptoOps>(
+ intermediates: impl IntoIterator<Item = Certificate<'a>>,
+ policy: &Policy<'_, B>,
+ ) -> Result<Self, ValidationError> {
+ Ok(Self(
+ intermediates
+ .into_iter()
+ .map(
+ |intermediate| match cert_is_self_signed(&intermediate, &policy.ops) {
+ true => Err(ValidationError::Other(
+ "self-signed certificate cannot be an intermediate".to_string(),
+ )),
+ false => Ok(intermediate),
+ },
+ )
+ .collect::<Result<_, _>>()?,
+ ))
}
}
@@ -176,7 +191,7 @@ pub fn verify<'a, 'chain, B: CryptoOps>(
policy: &Policy<'_, B>,
store: &'a Store<'chain>,
) -> Result<Chain<'chain>, ValidationError> {
- let builder = ChainBuilder::new(Intermediates::new(intermediates), policy, store);
+ let builder = ChainBuilder::new(Intermediates::new(intermediates, policy)?, policy, store);
builder.build_chain(leaf)
}
@@ -237,15 +252,29 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
&self,
working_cert: &'a Certificate<'chain>,
current_depth: u8,
+ is_leaf: bool,
working_cert_extensions: &'a Extensions<'chain>,
accumulated_constraints: &'a mut AccumulatedNameConstraints<'chain>,
) -> Result<Chain<'chain>, ValidationError> {
+ if current_depth > self.policy.max_chain_depth {
+ return Err(ValidationError::Other(
+ "chain construction exceeds max depth".into(),
+ ));
+ }
+
// Per RFC 5280: Name constraints are not applied
// to subjects in self-issued certificates, *unless* the
// certificate is the final certificate in the path.
//
+ // Naively we'd check `current_depth == 0` to determine
+ // if we're checking the final certificate, but this
+ // isn't sufficient: self-issued certificates don't
+ // increase the depth, so we pass in a special-purpose
+ // `is_leaf` state that's only true on the first chain
+ // building step.
+ //
// See: RFC 5280 4.2.1.10
- let skip_name_constraints = cert_is_self_issued(working_cert) && current_depth != 0;
+ let skip_name_constraints = cert_is_self_issued(working_cert) && !is_leaf;
if !skip_name_constraints {
accumulated_constraints.accumulate_san(working_cert_extensions)?;
}
@@ -258,15 +287,6 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
return Ok(vec![working_cert.clone()]);
}
- // Check that our current depth does not exceed our policy-configured
- // max depth. We do this after the root set check, since the depth
- // only measures the intermediate chain's length, not the root or leaf.
- if current_depth > self.policy.max_chain_depth {
- return Err(ValidationError::Other(
- "chain construction exceeds max depth".into(),
- ));
- }
-
// Otherwise, we collect a list of potential issuers for this cert,
// and continue with the first that verifies.
let mut last_err: Option<ValidationError> = None;
@@ -281,26 +301,11 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
current_depth,
&issuer_extensions,
) {
- Ok(_) => {
+ Ok(next_depth) => {
match self.build_chain_inner(
issuing_cert_candidate,
- // NOTE(ww): According to RFC 5280, we should only
- // increase the chain depth when the certificate is **not**
- // self-issued. In practice however, implementations widely
- // ignore this requirement, and unconditionally increment
- // the depth with every chain member. We choose to do the same;
- // see `pathlen::self-issued-certs-pathlen` from x509-limbo
- // for the testcase we intentionally fail.
- //
- // Implementation note for someone looking to change this in the future:
- // care should be taken to avoid infinite recursion with self-signed
- // certificates in the intermediate set; changing this behavior will
- // also require a "is not self-signed" check on intermediate candidates.
- current_depth.checked_add(1).ok_or_else(|| {
- ValidationError::Other(
- "current depth calculation overflowed".to_string(),
- )
- })?,
+ next_depth,
+ false,
&issuer_extensions,
accumulated_constraints,
) {
@@ -345,6 +350,7 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> {
let result = self.build_chain_inner(
leaf,
0,
+ true,
&leaf_extensions,
&mut AccumulatedNameConstraints::default(),
);
diff --git b/src/rust/cryptography-x509-validation/src/policy/mod.rs a/src/rust/cryptography-x509-validation/src/policy/mod.rs
index 6bd0aeaca..6b8e7d63b 100644
--- b/src/rust/cryptography-x509-validation/src/policy/mod.rs
+++ a/src/rust/cryptography-x509-validation/src/policy/mod.rs
@@ -27,6 +27,7 @@ use cryptography_x509::oid::{
};
use self::extension::{ca, common, ee, Criticality, ExtensionPolicy};
+use crate::certificate::cert_is_self_issued;
use crate::ops::CryptoOps;
use crate::types::{DNSName, DNSPattern, IPAddress};
use crate::ValidationError;
@@ -525,7 +526,7 @@ impl<'a, B: CryptoOps> Policy<'a, B> {
child: &Certificate<'_>,
current_depth: u8,
issuer_extensions: &Extensions<'_>,
- ) -> Result<(), ValidationError> {
+ ) -> Result<u8, ValidationError> {
// The issuer needs to be a valid CA at the current depth.
self.permits_ca(issuer, current_depth, issuer_extensions)?;
@@ -561,7 +562,13 @@ impl<'a, B: CryptoOps> Policy<'a, B> {
));
}
- Ok(())
+ // Self-issued issuers don't increase the working depth.
+ match cert_is_self_issued(issuer) {
+ true => Ok(current_depth),
+ false => Ok(current_depth.checked_add(1).ok_or_else(|| {
+ ValidationError::Other("current depth calculation overflowed".to_string())
+ })?),
+ }
}
}
diff --git b/tests/x509/verification/test_limbo.py a/tests/x509/verification/test_limbo.py
index 24cc3bdf2..99adf922e 100644
--- b/tests/x509/verification/test_limbo.py
+++ a/tests/x509/verification/test_limbo.py
@@ -37,12 +37,6 @@ LIMBO_UNSUPPORTED_FEATURES = {
}
LIMBO_SKIP_TESTCASES = {
- # We unconditionally count intermediate certificates for pathlen and max
- # depth constraint purposes, even when self-issued.
- # This is a violation of RFC 5280, but is consistent with Go's crypto/x509
- # and Rust's webpki crate do.
- "pathlen::self-issued-certs-pathlen",
- "pathlen::max-chain-depth-1-self-issued",
# We allow certificates with serial numbers of zero. This is
# invalid under RFC 5280 but is widely violated by certs in common
# trust stores.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment