diff --git a/.github/actions/fetch-vectors/action.yml b/.github/actions/fetch-vectors/action.yml index bd7923531ead..f5f9771c301c 100644 --- a/.github/actions/fetch-vectors/action.yml +++ b/.github/actions/fetch-vectors/action.yml @@ -30,5 +30,5 @@ runs: env: # Latest commit on the wycheproof main branch, as of Aug 19, 2026. WYCHEPROOF_REF: "dac1dd4729fd1f8dd9e1e9f3dce51d783da6c166" # wycheproof-ref - # Latest commit on the x509-limbo main branch, as of Aug 28, 2026. - X509_LIMBO_REF: "972626160c26b45426bbd8c935a605219bd93207" # x509-limbo-ref + # Latest commit on the x509-limbo main branch, as of Sep 02, 2026. + X509_LIMBO_REF: "21cc053f7edbd22e0e8d8a98a7fe13918912af9d" # x509-limbo-ref diff --git a/src/rust/cryptography-x509-verification/src/policy/extension.rs b/src/rust/cryptography-x509-verification/src/policy/extension.rs index 17d694bd094c..fc5a142419d6 100644 --- a/src/rust/cryptography-x509-verification/src/policy/extension.rs +++ b/src/rust/cryptography-x509-verification/src/policy/extension.rs @@ -739,6 +739,32 @@ mod ca { ))); } + // `GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree`, + // so a subtree field that is present must not be empty. This + // matters most for permittedSubtrees: RFC 5280 6.1.4 (g)(1) + // intersects it with its previous value, and 6.1.3 (b) requires + // each name to lie within it, so an empty permittedSubtrees admits + // no name at all. Accepting it here would instead permit every + // name, because the loop over the subtrees never runs. + if name_constraints + .permitted_subtrees + .as_ref() + .is_some_and(|pst| pst.is_empty()) + { + return Err(ValidationError::new(ValidationErrorKind::Other( + "nameConstraints permittedSubtrees must not be empty".to_string(), + ))); + } + if name_constraints + .excluded_subtrees + .as_ref() + .is_some_and(|est| est.is_empty()) + { + return Err(ValidationError::new(ValidationErrorKind::Other( + "nameConstraints excludedSubtrees must not be empty".to_string(), + ))); + } + // NOTE: Both RFC 5280 and CABF require each `GeneralSubtree` // to have `minimum=0` and `maximum=NULL`, but experimentally // not many validators check for this. @@ -798,9 +824,9 @@ mod tests { use asn1::{ObjectIdentifier, SimpleAsn1Writable}; use cryptography_x509::extensions::{BasicConstraints, Extension}; - use cryptography_x509::oid::BASIC_CONSTRAINTS_OID; + use cryptography_x509::oid::{BASIC_CONSTRAINTS_OID, NAME_CONSTRAINTS_OID}; - use super::{Criticality, ExtensionValidator}; + use super::{ca, Criticality, ExtensionValidator}; use crate::certificate::tests::PublicKeyErrorOps; use crate::ops::tests::{cert, epoch, v1_cert_pem}; use crate::ops::{CryptoOps, VerificationCertificate}; @@ -1056,4 +1082,40 @@ mod tests { ) .is_err()); } + + fn name_constraints_policy() -> PolicyDefinition<'static, PublicKeyErrorOps> { + PolicyDefinition::server( + PublicKeyErrorOps {}, + Subject::DNS(DNSName::new("example.com").unwrap()), + epoch(), + None, + None, + None, + ) + .expect("failed to create policy definition") + } + + #[test] + fn test_ca_name_constraints_empty_excluded_subtrees() { + let cert_pem = v1_cert_pem(); + let cert = cert(&cert_pem); + let verification_cert = VerificationCertificate::new(&cert, ()); + let policy_def = name_constraints_policy(); + let policy = Policy::new(&policy_def, ()); + + // SEQUENCE { + // [0] { SEQUENCE { [2] "ok.example" }} -- permittedSubtrees + // [1] {} -- excludedSubtrees + // } + let extn_value: &[u8] = &[ + 0x30, 0x12, 0xa0, 0x0e, 0x30, 0x0c, 0x82, 0x0a, b'o', b'k', b'.', b'e', b'x', b'a', + b'm', b'p', b'l', b'e', 0xa1, 0x00, + ]; + let extn = Extension { + extn_id: NAME_CONSTRAINTS_OID, + critical: true, + extn_value, + }; + assert!(ca::name_constraints(&policy, &verification_cert, Some(&extn)).is_err()); + } }