Skip to main content

xml_sec/xmldsig/
x509.rs

1//! X.509 certificate path and revocation validation.
2
3use std::{
4    collections::HashSet,
5    time::{SystemTime, UNIX_EPOCH},
6};
7
8use x509_parser::{
9    certificate::X509Certificate,
10    extensions::{GeneralName, NameConstraints, ParsedExtension},
11    prelude::FromDer,
12    revocation_list::CertificateRevocationList,
13    time::ASN1Time,
14    x509::AlgorithmIdentifier,
15};
16
17use super::{
18    X509DataInfo,
19    parse::{distinguished_name_within_subtree, distinguished_names_equal, x509_name_to_rfc4514},
20};
21use crate::{
22    policy::{DsaKeyPolicy, ExtendedKeyPurpose, RsaKeyPolicy},
23    provider::X509SignatureAlgorithm,
24};
25
26/// Inputs controlling X.509 certificate-chain validation.
27#[derive(Debug, Clone)]
28pub struct X509ChainOptions<'a> {
29    /// DER-encoded certificates accepted as trust anchors.
30    pub trusted_certs: &'a [Vec<u8>],
31    /// Time used for certificate, CRL, and revocation checks.
32    pub verification_time: SystemTime,
33    /// Maximum number of certificates in the validated path, including the anchor.
34    pub max_chain_depth: usize,
35    /// Whether parsed `<X509CRL>` entries are enforced.
36    pub check_crls: bool,
37    /// Purposes accepted when any path certificate carries ExtendedKeyUsage.
38    pub allowed_extended_key_usages: Option<&'a HashSet<ExtendedKeyPurpose>>,
39    /// RSA strength requirements for every issuer key used by the path.
40    pub rsa_keys: RsaKeyPolicy,
41    /// DSA strength requirements for every issuer key used by the path.
42    pub dsa_keys: DsaKeyPolicy,
43}
44
45/// Certificate-chain validation failure.
46#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
47#[non_exhaustive]
48pub enum X509ChainError {
49    /// The selected cryptographic provider rejected path authentication.
50    #[error("cryptographic provider rejected X.509 authentication: {0}")]
51    Provider(#[from] crate::provider::ProviderError),
52    /// The configured path limit cannot contain a certificate.
53    #[error("maximum certificate chain depth must be greater than zero")]
54    InvalidDepth,
55    /// A certificate or CRL is malformed DER.
56    #[error("invalid {kind} DER: {message}")]
57    InvalidDer {
58        /// Object type being parsed.
59        kind: &'static str,
60        /// Parser diagnostic.
61        message: String,
62    },
63    /// A certificate repeats an extension OID, which RFC 5280 forbids.
64    #[error("certificate at chain position {position} repeats extension {oid}")]
65    DuplicateExtension {
66        /// Position of the malformed certificate in the candidate path.
67        position: usize,
68        /// Repeated extension object identifier.
69        oid: String,
70    },
71    /// The ordered embedded path cannot be completed to a configured anchor.
72    #[error("certificate chain does not terminate at a trusted certificate")]
73    UntrustedRoot,
74    /// The path contains more certificates than allowed.
75    #[error("certificate chain exceeds maximum depth of {0}")]
76    DepthExceeded(usize),
77    /// A certificate is outside its validity period.
78    #[error("certificate at chain position {0} is expired or not yet valid")]
79    CertificateNotValid(usize),
80    /// An issuer certificate is not authorized to issue certificates.
81    #[error("certificate at chain position {0} is not a CA")]
82    IssuerNotCa(usize),
83    /// A CA path-length constraint is violated.
84    #[error("certificate at chain position {position} exceeds path length constraint {limit}")]
85    PathLengthExceeded {
86        /// Position of the constraining CA certificate.
87        position: usize,
88        /// Maximum permitted subordinate CA count.
89        limit: u32,
90    },
91    /// A subordinate certificate is outside a CA's permitted name space.
92    #[error(
93        "certificate at chain position {position} violates name constraints from position {constraining_position}"
94    )]
95    NameConstraintViolation {
96        /// Position of the subordinate certificate.
97        position: usize,
98        /// Position of the CA carrying NameConstraints.
99        constraining_position: usize,
100    },
101    /// A critical certificate extension is not implemented by path validation.
102    #[error("certificate at chain position {position} has unsupported critical extension {oid}")]
103    UnsupportedCriticalExtension {
104        /// Position of the certificate in the validated path.
105        position: usize,
106        /// Extension object identifier.
107        oid: String,
108    },
109    /// NameConstraints is not a critical CA extension as required by RFC 5280.
110    #[error("certificate at chain position {position} has invalid NameConstraints placement")]
111    InvalidNameConstraints {
112        /// Position of the certificate carrying the invalid extension.
113        position: usize,
114    },
115    /// A certificate key usage extension forbids the required operation.
116    #[error("certificate at chain position {position} does not permit {required}")]
117    InvalidKeyUsage {
118        /// Position of the certificate in the validated path.
119        position: usize,
120        /// RFC 5280 key usage required for the operation.
121        required: &'static str,
122    },
123    /// A certificate signature does not verify under its issuer key.
124    #[error("certificate signature at chain position {0} is invalid or unsupported")]
125    InvalidSignature(usize),
126    /// An issuer key violates the active key-strength policy.
127    #[error("certificate issuer key at chain position {position} is rejected by policy: {source}")]
128    KeyPolicy {
129        /// Position of the issuer certificate in the candidate path.
130        position: usize,
131        /// Typed key-policy rejection.
132        source: crate::policy::PolicyViolation,
133    },
134    /// The certificate or CRL declares an algorithm this build cannot verify.
135    #[error("unsupported X.509 signature algorithm: {oid}")]
136    UnsupportedSignatureAlgorithm {
137        /// AlgorithmIdentifier object identifier.
138        oid: String,
139    },
140    /// A CRL is not valid for the selected verification time or issuer.
141    #[error("CRL {0} is invalid or cannot be authenticated")]
142    InvalidCrl(usize),
143    /// A path certificate was revoked by an applicable CRL.
144    #[error("certificate at chain position {0} is revoked")]
145    Revoked(usize),
146}
147
148/// Verify the ordered certificate path parsed from one `<X509Data>` element.
149pub fn verify_x509_certificate_chain(
150    info: &X509DataInfo,
151    options: &X509ChainOptions<'_>,
152) -> Result<(), X509ChainError> {
153    verify_x509_certificate_chain_with_provider(info, options, crate::provider::default_provider())
154}
155
156pub(crate) fn verify_x509_certificate_chain_with_provider(
157    info: &X509DataInfo,
158    options: &X509ChainOptions<'_>,
159    provider: &dyn crate::provider::CryptoProvider,
160) -> Result<(), X509ChainError> {
161    if options.max_chain_depth == 0 {
162        return Err(X509ChainError::InvalidDepth);
163    }
164    if info.certificate_chain.is_empty() {
165        return Err(X509ChainError::UntrustedRoot);
166    }
167
168    let path_der = info
169        .certificate_chain
170        .iter()
171        .map(|&idx| {
172            info.certificates
173                .get(idx)
174                .map(Vec::as_slice)
175                .ok_or(X509ChainError::UntrustedRoot)
176        })
177        .collect::<Result<Vec<_>, _>>()?;
178
179    let last = parse_certificate(
180        path_der
181            .last()
182            .copied()
183            .ok_or(X509ChainError::UntrustedRoot)?,
184    )?;
185    let trusted_anchors = options
186        .trusted_certs
187        .iter()
188        .map(|der| parse_certificate(der).map(|cert| (der.as_slice(), cert)))
189        .collect::<Result<Vec<_>, _>>()?;
190    let verification_time = system_time_to_asn1(options.verification_time)?;
191    let embedded_anchor = trusted_anchors.iter().any(|(der, _)| *der == last.as_raw());
192    if embedded_anchor {
193        return validate_path(&path_der, info, options, verification_time, provider);
194    }
195
196    // Use the path-edge verifier here too: x509-parser does not verify legacy
197    // DSA-SHA1 roots, while our fallback must recognize them for rollover.
198    let replace_untrusted_root = if path_der.len() > 1
199        && certificate_names_equal(last.subject(), last.issuer())
200        && verify_certificate_signature_with_provider(&last, &last, provider)?
201    {
202        let child = parse_certificate(path_der[path_der.len() - 2])?;
203        certificate_names_equal(child.issuer(), last.subject())
204            && verify_certificate_signature_with_provider(&child, &last, provider)?
205    } else {
206        false
207    };
208    let candidate_base = if replace_untrusted_root {
209        &path_der[..path_der.len() - 1]
210    } else {
211        path_der.as_slice()
212    };
213    let candidate_child = parse_certificate(
214        candidate_base
215            .last()
216            .copied()
217            .ok_or(X509ChainError::UntrustedRoot)?,
218    )?;
219
220    let mut first_validation_error = None;
221    for (anchor_der, cert) in &trusted_anchors {
222        if !certificate_names_equal(cert.subject(), candidate_child.issuer())
223            || !verify_certificate_signature_with_provider(&candidate_child, cert, provider)?
224        {
225            continue;
226        }
227        let mut candidate_path = candidate_base.to_vec();
228        candidate_path.push(anchor_der);
229        match validate_path(&candidate_path, info, options, verification_time, provider) {
230            Ok(()) => return Ok(()),
231            Err(error) => first_validation_error.get_or_insert(error),
232        };
233    }
234
235    Err(first_validation_error.unwrap_or(X509ChainError::UntrustedRoot))
236}
237
238fn validate_path(
239    path_der: &[&[u8]],
240    info: &X509DataInfo,
241    options: &X509ChainOptions<'_>,
242    verification_time: ASN1Time,
243    provider: &dyn crate::provider::CryptoProvider,
244) -> Result<(), X509ChainError> {
245    if path_der.len() > options.max_chain_depth {
246        return Err(X509ChainError::DepthExceeded(options.max_chain_depth));
247    }
248
249    let path = path_der
250        .iter()
251        .map(|der| parse_certificate(der))
252        .collect::<Result<Vec<_>, _>>()?;
253    let mut effective_extended_key_usages = options.allowed_extended_key_usages.cloned();
254
255    for (position, cert) in path.iter().enumerate() {
256        validate_certificate_serial(cert)?;
257        validate_unique_extensions(cert, position)?;
258        if !cert.validity().is_valid_at(verification_time) {
259            return Err(X509ChainError::CertificateNotValid(position));
260        }
261        if position == 0 {
262            validate_leaf_key_usage(cert)?;
263        } else {
264            validate_ca_constraints(cert, position)?;
265        }
266        validate_extended_key_usage(cert, position, &mut effective_extended_key_usages)?;
267        validate_subject_identity(cert)?;
268        validate_critical_extensions(cert, position)?;
269    }
270    validate_path_length_constraints(&path)?;
271    validate_name_constraints(&path)?;
272
273    for (position, pair) in path.windows(2).enumerate() {
274        let [child, issuer] = pair else {
275            unreachable!()
276        };
277        validate_issuer_key_policy(issuer, position + 1, options.rsa_keys, options.dsa_keys)?;
278        if !certificate_names_equal(child.issuer(), issuer.subject())
279            || !verify_certificate_signature_with_provider(child, issuer, provider)?
280        {
281            return Err(X509ChainError::InvalidSignature(position));
282        }
283    }
284
285    if options.check_crls {
286        verify_crls(&path, &info.crls, verification_time, provider)?;
287    }
288    Ok(())
289}
290
291fn validate_issuer_key_policy(
292    issuer: &X509Certificate<'_>,
293    position: usize,
294    rsa_keys: RsaKeyPolicy,
295    dsa_keys: DsaKeyPolicy,
296) -> Result<(), X509ChainError> {
297    match issuer.public_key().parsed() {
298        Ok(x509_parser::public_key::PublicKey::RSA(key)) => rsa_keys
299            .validate_components("X.509 issuer verification", key.modulus, key.exponent)
300            .map(|_| ())
301            .map_err(|source| X509ChainError::KeyPolicy { position, source }),
302        Ok(x509_parser::public_key::PublicKey::DSA(_)) => {
303            match super::signature::validate_dsa_signature_spki_with_minimum(
304                issuer.public_key().raw,
305                dsa_keys.minimum_modulus_bits,
306            ) {
307                Ok(()) => Ok(()),
308                Err(super::SignatureVerificationError::KeyPolicy(source)) => {
309                    Err(X509ChainError::KeyPolicy { position, source })
310                }
311                Err(_) => Err(X509ChainError::InvalidDer {
312                    kind: "DSA issuer SubjectPublicKeyInfo",
313                    message: "invalid DSA key parameters".into(),
314                }),
315            }
316        }
317        Ok(_) => Ok(()),
318        Err(error) => Err(X509ChainError::InvalidDer {
319            kind: "issuer SubjectPublicKeyInfo",
320            message: error.to_string(),
321        }),
322    }
323}
324
325fn validate_certificate_serial(cert: &X509Certificate<'_>) -> Result<(), X509ChainError> {
326    validate_positive_serial_bytes(cert.raw_serial(), "certificate serial number")
327}
328
329fn validate_positive_serial_bytes(serial: &[u8], kind: &'static str) -> Result<(), X509ChainError> {
330    let magnitude = serial.strip_prefix(&[0]).unwrap_or(serial);
331    if serial.is_empty()
332        || serial[0] & 0x80 != 0
333        || magnitude.is_empty()
334        || magnitude.len() > 20
335        || magnitude.iter().all(|byte| *byte == 0)
336    {
337        return Err(X509ChainError::InvalidDer {
338            kind,
339            message: "RFC 5280 requires a positive, non-zero value of at most 20 octets".into(),
340        });
341    }
342    Ok(())
343}
344
345fn validate_unique_extensions(
346    cert: &X509Certificate<'_>,
347    position: usize,
348) -> Result<(), X509ChainError> {
349    let mut seen = std::collections::HashSet::with_capacity(cert.extensions().len());
350    for extension in cert.extensions() {
351        let oid = extension.oid.to_id_string();
352        if !seen.insert(oid.clone()) {
353            return Err(X509ChainError::DuplicateExtension { position, oid });
354        }
355    }
356    Ok(())
357}
358
359fn validate_subject_identity(cert: &X509Certificate<'_>) -> Result<(), X509ChainError> {
360    for attribute in cert.subject().iter_email() {
361        let email = attribute
362            .as_str()
363            .map_err(|error| X509ChainError::InvalidDer {
364                kind: "certificate subject emailAddress",
365                message: error.to_string(),
366            })?;
367        if !mailbox_has_valid_syntax(email) {
368            return Err(X509ChainError::InvalidDer {
369                kind: "certificate subject emailAddress",
370                message: format!("invalid RFC 5280 mailbox syntax: {email:?}"),
371            });
372        }
373    }
374
375    let subject_is_empty = cert.subject().iter().next().is_none();
376    let mut san_extensions = cert
377        .extensions()
378        .iter()
379        .filter(|extension| extension.oid.to_id_string() == "2.5.29.17");
380    let Some(extension) = san_extensions.next() else {
381        return if subject_is_empty {
382            Err(invalid_subject_identity(
383                "an empty subject requires a critical SubjectAlternativeName",
384            ))
385        } else {
386            Ok(())
387        };
388    };
389    if san_extensions.next().is_some() {
390        return Err(invalid_subject_identity(
391            "an empty subject must not contain duplicate SubjectAlternativeName extensions",
392        ));
393    }
394    let ParsedExtension::SubjectAlternativeName(names) = extension.parsed_extension() else {
395        return Err(invalid_subject_identity(
396            "SubjectAlternativeName could not be parsed",
397        ));
398    };
399    for name in &names.general_names {
400        validate_subject_alternative_name(name)?;
401    }
402    if subject_is_empty && (!extension.critical || names.general_names.is_empty()) {
403        return Err(invalid_subject_identity(
404            "an empty subject requires a critical, non-empty SubjectAlternativeName",
405        ));
406    }
407    Ok(())
408}
409
410fn validate_subject_alternative_name(name: &GeneralName<'_>) -> Result<(), X509ChainError> {
411    match name {
412        GeneralName::RFC822Name(value) => validate_rfc5280_mailbox(value),
413        GeneralName::DNSName(value) => {
414            // RFC 5280 section 4.2.1.6 requires RFC 1034/1123 preferred-name
415            // syntax here. RFC 9525 wildcard matching is an application-level
416            // TLS identity rule, not certificate-path profile validation.
417            validate_rfc5280_dns_name(value)
418        }
419        GeneralName::URI(value) => validate_rfc5280_uri(value),
420        GeneralName::IPAddress(value) if !matches!(value.len(), 4 | 16) => {
421            Err(invalid_subject_identity(
422                "SubjectAlternativeName iPAddress must contain 4 or 16 octets",
423            ))
424        }
425        GeneralName::Invalid(..) => Err(invalid_subject_identity(
426            "SubjectAlternativeName contains a malformed GeneralName",
427        )),
428        _ => Ok(()),
429    }
430}
431
432fn validate_rfc5280_mailbox(value: &str) -> Result<(), X509ChainError> {
433    if !mailbox_has_valid_syntax(value) {
434        return Err(invalid_subject_identity(
435            "SubjectAlternativeName rfc822Name has invalid RFC 5280 mailbox syntax",
436        ));
437    }
438    Ok(())
439}
440
441fn mailbox_has_valid_syntax(value: &str) -> bool {
442    value.rsplit_once('@').is_some_and(|(local, domain)| {
443        mailbox_local_part_has_valid_syntax(local) && mailbox_domain_has_valid_syntax(domain)
444    })
445}
446
447fn mailbox_domain_has_valid_syntax(domain: &str) -> bool {
448    let Some(literal) = domain
449        .strip_prefix('[')
450        .and_then(|value| value.strip_suffix(']'))
451    else {
452        return dns_name_has_valid_syntax(domain, false);
453    };
454    if literal
455        .get(..5)
456        .is_some_and(|prefix| prefix.eq_ignore_ascii_case("IPv6:"))
457    {
458        literal[5..].parse::<std::net::Ipv6Addr>().is_ok()
459    } else {
460        literal.parse::<std::net::Ipv4Addr>().is_ok()
461    }
462}
463
464fn mailbox_local_part_has_valid_syntax(local: &str) -> bool {
465    if let Some(quoted) = local
466        .strip_prefix('"')
467        .and_then(|value| value.strip_suffix('"'))
468    {
469        if quoted.is_empty() {
470            return false;
471        }
472        let mut escaped = false;
473        for byte in quoted.bytes() {
474            if escaped {
475                if !(0x20..=0x7e).contains(&byte) {
476                    return false;
477                }
478                escaped = false;
479            } else if byte == b'\\' {
480                escaped = true;
481            } else if byte == b'"' || !(0x20..=0x7e).contains(&byte) {
482                return false;
483            }
484        }
485        return !escaped;
486    }
487
488    local.split('.').all(|atom| {
489        !atom.is_empty()
490            && atom.bytes().all(|byte| {
491                byte.is_ascii_alphanumeric()
492                    || matches!(
493                        byte,
494                        b'!' | b'#'
495                            | b'$'
496                            | b'%'
497                            | b'&'
498                            | b'\''
499                            | b'*'
500                            | b'+'
501                            | b'-'
502                            | b'/'
503                            | b'='
504                            | b'?'
505                            | b'^'
506                            | b'_'
507                            | b'`'
508                            | b'{'
509                            | b'|'
510                            | b'}'
511                            | b'~'
512                    )
513            })
514    })
515}
516
517fn validate_rfc5280_uri(value: &str) -> Result<(), X509ChainError> {
518    let Some((scheme, scheme_specific)) = value.split_once(':') else {
519        return Err(invalid_subject_identity(
520            "SubjectAlternativeName URI must be absolute",
521        ));
522    };
523    if scheme.is_empty()
524        || !scheme.as_bytes()[0].is_ascii_alphabetic()
525        || !scheme
526            .bytes()
527            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.'))
528        || scheme_specific.is_empty()
529        || !uri_scheme_specific_part_has_valid_syntax(scheme_specific)
530    {
531        return Err(invalid_subject_identity(
532            "SubjectAlternativeName URI has invalid RFC 3986 syntax",
533        ));
534    }
535
536    if let Some(authority_and_path) = scheme_specific.strip_prefix("//") {
537        let authority = authority_and_path
538            .split(['/', '?', '#'])
539            .next()
540            .unwrap_or_default();
541        if !uri_authority_has_rfc5280_host(authority) {
542            return Err(invalid_subject_identity(
543                "SubjectAlternativeName URI authority requires a fully qualified host",
544            ));
545        }
546    }
547    Ok(())
548}
549
550fn uri_scheme_specific_part_has_valid_syntax(value: &str) -> bool {
551    if value.bytes().any(|byte| {
552        !byte.is_ascii()
553            || byte.is_ascii_control()
554            || byte == b' '
555            || !matches!(
556                byte,
557                b'A'..=b'Z'
558                    | b'a'..=b'z'
559                    | b'0'..=b'9'
560                    | b'-'
561                    | b'.'
562                    | b'_'
563                    | b'~'
564                    | b':'
565                    | b'/'
566                    | b'?'
567                    | b'#'
568                    | b'['
569                    | b']'
570                    | b'@'
571                    | b'!'
572                    | b'$'
573                    | b'&'
574                    | b'\''
575                    | b'('
576                    | b')'
577                    | b'*'
578                    | b'+'
579                    | b','
580                    | b';'
581                    | b'='
582                    | b'%'
583            )
584    }) || value.matches('#').count() > 1
585    {
586        return false;
587    }
588
589    let bytes = value.as_bytes();
590    let mut index = 0;
591    while index < bytes.len() {
592        if bytes[index] == b'%'
593            && (index + 2 >= bytes.len()
594                || !bytes[index + 1].is_ascii_hexdigit()
595                || !bytes[index + 2].is_ascii_hexdigit())
596        {
597            return false;
598        }
599        index += if bytes[index] == b'%' { 3 } else { 1 };
600    }
601    true
602}
603
604fn uri_authority_has_rfc5280_host(authority: &str) -> bool {
605    parse_uri_authority_host(authority).is_some()
606}
607
608#[derive(Clone, Copy)]
609enum UriAuthorityHost<'a> {
610    Dns(&'a str),
611    Ip,
612}
613
614fn parse_uri_authority_host(authority: &str) -> Option<UriAuthorityHost<'_>> {
615    let host_port = match authority.split_once('@') {
616        Some((userinfo, host_port))
617            if !host_port.contains('@') && uri_userinfo_has_valid_syntax(userinfo) =>
618        {
619            host_port
620        }
621        Some(_) => return None,
622        None => authority,
623    };
624    if let Some(bracketed) = host_port.strip_prefix('[') {
625        let (host, port) = bracketed.split_once(']')?;
626        return (host.parse::<std::net::Ipv6Addr>().is_ok() && uri_port_has_valid_syntax(port))
627            .then_some(UriAuthorityHost::Ip);
628    }
629    let (host, port) = host_port
630        .split_once(':')
631        .map_or((host_port, None), |(host, port)| (host, Some(port)));
632    if port.is_some_and(|port| port.is_empty() || !port.bytes().all(|byte| byte.is_ascii_digit())) {
633        return None;
634    }
635    if host.parse::<std::net::Ipv4Addr>().is_ok() {
636        Some(UriAuthorityHost::Ip)
637    } else if dns_name_has_valid_syntax(host, false) {
638        Some(UriAuthorityHost::Dns(host))
639    } else {
640        None
641    }
642}
643
644fn uri_port_has_valid_syntax(suffix: &str) -> bool {
645    suffix.is_empty()
646        || suffix
647            .strip_prefix(':')
648            .is_some_and(|port| !port.is_empty() && port.bytes().all(|byte| byte.is_ascii_digit()))
649}
650
651fn uri_userinfo_has_valid_syntax(userinfo: &str) -> bool {
652    let bytes = userinfo.as_bytes();
653    let mut index = 0;
654    while index < bytes.len() {
655        let byte = bytes[index];
656        if byte == b'%' {
657            if index + 2 >= bytes.len()
658                || !bytes[index + 1].is_ascii_hexdigit()
659                || !bytes[index + 2].is_ascii_hexdigit()
660            {
661                return false;
662            }
663            index += 3;
664            continue;
665        }
666        if !(byte.is_ascii_alphanumeric()
667            || matches!(
668                byte,
669                b'-' | b'.'
670                    | b'_'
671                    | b'~'
672                    | b'!'
673                    | b'$'
674                    | b'&'
675                    | b'\''
676                    | b'('
677                    | b')'
678                    | b'*'
679                    | b'+'
680                    | b','
681                    | b';'
682                    | b'='
683                    | b':'
684            ))
685        {
686            return false;
687        }
688        index += 1;
689    }
690    true
691}
692
693fn invalid_subject_identity(message: &str) -> X509ChainError {
694    X509ChainError::InvalidDer {
695        kind: "certificate subject identity",
696        message: message.into(),
697    }
698}
699
700#[cfg(test)]
701fn verify_certificate_signature(
702    certificate: &X509Certificate<'_>,
703    issuer: &X509Certificate<'_>,
704) -> bool {
705    verify_certificate_signature_with_provider(
706        certificate,
707        issuer,
708        crate::provider::default_provider(),
709    )
710    .unwrap_or(false)
711}
712
713fn verify_certificate_signature_with_provider(
714    certificate: &X509Certificate<'_>,
715    issuer: &X509Certificate<'_>,
716    provider: &dyn crate::provider::CryptoProvider,
717) -> Result<bool, X509ChainError> {
718    // RFC 5280 sections 4.1.1.2 and 4.1.2.3 require the outer and signed
719    // AlgorithmIdentifier values to be identical. Enforce this independently
720    // of the backend so the legacy DSA path cannot bypass the invariant.
721    if certificate.signature_algorithm != certificate.tbs_certificate.signature {
722        return Ok(false);
723    }
724    verify_x509_signature_with_provider(
725        &certificate.signature_algorithm,
726        &certificate.signature_value.data,
727        certificate.tbs_certificate.as_ref(),
728        issuer.public_key().raw,
729        provider,
730    )
731}
732
733/// Test a candidate certificate-path edge without assigning trust to either
734/// certificate. Path construction uses this only to distinguish certificates
735/// that share an issuer subject name; full policy validation still happens
736/// after the complete path has been assembled.
737#[cfg(test)]
738pub(crate) fn certificate_signature_matches(certificate_der: &[u8], issuer_der: &[u8]) -> bool {
739    certificate_signature_matches_with_provider(
740        certificate_der,
741        issuer_der,
742        crate::provider::default_provider(),
743    )
744    .unwrap_or(false)
745}
746
747pub(crate) fn certificate_signature_matches_with_provider(
748    certificate_der: &[u8],
749    issuer_der: &[u8],
750    provider: &dyn crate::provider::CryptoProvider,
751) -> Result<bool, X509ChainError> {
752    let (Ok(certificate), Ok(issuer)) = (
753        parse_certificate(certificate_der),
754        parse_certificate(issuer_der),
755    ) else {
756        return Ok(false);
757    };
758    verify_certificate_signature_with_provider(&certificate, &issuer, provider)
759}
760
761fn certificate_names_equal(
762    left: &x509_parser::x509::X509Name<'_>,
763    right: &x509_parser::x509::X509Name<'_>,
764) -> bool {
765    let (Ok(left), Ok(right)) = (x509_name_to_rfc4514(left), x509_name_to_rfc4514(right)) else {
766        return false;
767    };
768    distinguished_names_equal(&left, &right)
769}
770
771#[cfg(test)]
772fn verify_crl_signature(crl: &CertificateRevocationList<'_>, issuer: &X509Certificate<'_>) -> bool {
773    verify_crl_signature_with_provider(crl, issuer, crate::provider::default_provider())
774        .unwrap_or(false)
775}
776
777fn verify_crl_signature_with_provider(
778    crl: &CertificateRevocationList<'_>,
779    issuer: &X509Certificate<'_>,
780    provider: &dyn crate::provider::CryptoProvider,
781) -> Result<bool, X509ChainError> {
782    // RFC 5280 sections 5.1.1.2 and 5.1.2.2 impose the same equality rule on
783    // CRLs as certificates.
784    if crl.signature_algorithm != crl.tbs_cert_list.signature {
785        return Ok(false);
786    }
787    verify_x509_signature_with_provider(
788        &crl.signature_algorithm,
789        &crl.signature_value.data,
790        crl.tbs_cert_list.as_ref(),
791        issuer.public_key().raw,
792        provider,
793    )
794}
795
796fn verify_x509_signature_with_provider(
797    algorithm_identifier: &AlgorithmIdentifier<'_>,
798    signature_der: &[u8],
799    signed_data: &[u8],
800    issuer_spki_der: &[u8],
801    provider: &dyn crate::provider::CryptoProvider,
802) -> Result<bool, X509ChainError> {
803    let algorithm = x509_signature_algorithm(algorithm_identifier)?;
804    provider
805        .require_capability(crate::provider::ProviderCapability::VerifyCertificate(
806            algorithm,
807        ))
808        .map_err(X509ChainError::from)?;
809    provider
810        .verify_x509_signature(algorithm, signed_data, signature_der, issuer_spki_der)
811        .map_err(Into::into)
812}
813
814fn x509_signature_algorithm(
815    identifier: &AlgorithmIdentifier<'_>,
816) -> Result<X509SignatureAlgorithm, X509ChainError> {
817    let oid = identifier.algorithm.to_id_string();
818    let algorithm = match oid.as_str() {
819        "1.2.840.10040.4.3" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha1),
820        "2.16.840.1.101.3.4.3.2" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha256),
821        "2.16.840.1.101.3.4.3.3" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha384),
822        "2.16.840.1.101.3.4.3.4" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha512),
823        "1.2.840.113549.1.1.5" | "1.3.14.3.2.29" => {
824            X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha1)
825        }
826        "1.2.840.113549.1.1.11" => {
827            X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha256)
828        }
829        "1.2.840.113549.1.1.12" => {
830            X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha384)
831        }
832        "1.2.840.113549.1.1.13" => {
833            X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha512)
834        }
835        "1.2.840.113549.1.1.10" => parse_rsa_pss_algorithm(identifier)?,
836        "1.2.840.10045.4.1" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha1),
837        "1.2.840.10045.4.3.2" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha256),
838        "1.2.840.10045.4.3.3" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha384),
839        "1.2.840.10045.4.3.4" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha512),
840        "1.3.101.112" => X509SignatureAlgorithm::Ed25519,
841        _ => return Err(X509ChainError::UnsupportedSignatureAlgorithm { oid }),
842    };
843    match &algorithm {
844        X509SignatureAlgorithm::Dsa(_)
845        | X509SignatureAlgorithm::Ecdsa(_)
846        | X509SignatureAlgorithm::Ed25519 => require_absent_signature_parameters(identifier)?,
847        X509SignatureAlgorithm::RsaPkcs1v15(_) => {
848            require_null_or_absent_signature_parameters(identifier)?;
849        }
850        X509SignatureAlgorithm::RsaPss { .. } => {}
851    }
852    Ok(algorithm)
853}
854
855fn require_absent_signature_parameters(
856    identifier: &AlgorithmIdentifier<'_>,
857) -> Result<(), X509ChainError> {
858    if identifier.parameters.is_some() {
859        return Err(invalid_signature_parameters(
860            identifier,
861            "parameters must be absent",
862        ));
863    }
864    Ok(())
865}
866
867fn require_null_or_absent_signature_parameters(
868    identifier: &AlgorithmIdentifier<'_>,
869) -> Result<(), X509ChainError> {
870    if identifier
871        .parameters
872        .as_ref()
873        .is_some_and(|parameters| parameters.tag() != x509_parser::asn1_rs::Tag::Null)
874    {
875        return Err(invalid_signature_parameters(
876            identifier,
877            "parameters must be NULL or absent",
878        ));
879    }
880    Ok(())
881}
882
883fn invalid_signature_parameters(
884    identifier: &AlgorithmIdentifier<'_>,
885    requirement: &str,
886) -> X509ChainError {
887    X509ChainError::InvalidDer {
888        kind: "X.509 signature AlgorithmIdentifier parameters",
889        message: format!("{}: {requirement}", identifier.algorithm),
890    }
891}
892
893fn parse_rsa_pss_algorithm(
894    identifier: &AlgorithmIdentifier<'_>,
895) -> Result<X509SignatureAlgorithm, X509ChainError> {
896    let parameters = identifier
897        .parameters
898        .as_ref()
899        .ok_or_else(|| X509ChainError::InvalidDer {
900            kind: "RSASSA-PSS parameters",
901            message: "missing parameters".into(),
902        })?;
903    let parameters = x509_parser::signature_algorithm::RsaSsaPssParams::try_from(parameters)
904        .map_err(|error| X509ChainError::InvalidDer {
905            kind: "RSASSA-PSS parameters",
906            message: error.to_string(),
907        })?;
908    if parameters.trailer_field() != 1 {
909        return Err(X509ChainError::InvalidDer {
910            kind: "RSASSA-PSS parameters",
911            message: "trailerField must be 1".into(),
912        });
913    }
914    let digest = x509_digest_algorithm(&parameters.hash_algorithm_oid().to_id_string())?;
915    let mask = parameters
916        .mask_gen_algorithm()
917        .map_err(|error| X509ChainError::InvalidDer {
918            kind: "RSASSA-PSS parameters",
919            message: error.to_string(),
920        })?;
921    if mask.mgf.to_id_string() != "1.2.840.113549.1.1.8" {
922        return Err(X509ChainError::UnsupportedSignatureAlgorithm {
923            oid: mask.mgf.to_id_string(),
924        });
925    }
926    let mgf_digest = x509_digest_algorithm(&mask.hash.to_id_string())?;
927    let salt_len =
928        usize::try_from(parameters.salt_length()).map_err(|_| X509ChainError::InvalidDer {
929            kind: "RSASSA-PSS parameters",
930            message: "saltLength does not fit this platform".into(),
931        })?;
932    Ok(X509SignatureAlgorithm::RsaPss {
933        digest,
934        mgf_digest,
935        salt_len,
936    })
937}
938
939fn x509_digest_algorithm(oid: &str) -> Result<super::DigestAlgorithm, X509ChainError> {
940    match oid {
941        "1.3.14.3.2.26" => Ok(super::DigestAlgorithm::Sha1),
942        "2.16.840.1.101.3.4.2.1" => Ok(super::DigestAlgorithm::Sha256),
943        "2.16.840.1.101.3.4.2.2" => Ok(super::DigestAlgorithm::Sha384),
944        "2.16.840.1.101.3.4.2.3" => Ok(super::DigestAlgorithm::Sha512),
945        _ => Err(X509ChainError::UnsupportedSignatureAlgorithm {
946            oid: oid.to_owned(),
947        }),
948    }
949}
950
951fn validate_leaf_key_usage(cert: &X509Certificate<'_>) -> Result<(), X509ChainError> {
952    // RFC 5280 section 4.2.1.3 restricts key purpose only when KeyUsage is present.
953    if cert
954        .key_usage()
955        .map_err(|error| X509ChainError::InvalidDer {
956            kind: "certificate KeyUsage",
957            message: error.to_string(),
958        })?
959        .is_some_and(|usage| !usage.value.digital_signature() && !usage.value.non_repudiation())
960    {
961        return Err(X509ChainError::InvalidKeyUsage {
962            position: 0,
963            required: "digitalSignature or nonRepudiation",
964        });
965    }
966    Ok(())
967}
968
969fn validate_extended_key_usage(
970    cert: &X509Certificate<'_>,
971    position: usize,
972    effective_extended_key_usages: &mut Option<HashSet<ExtendedKeyPurpose>>,
973) -> Result<(), X509ChainError> {
974    let Some(usage) = cert
975        .extended_key_usage()
976        .map_err(|error| X509ChainError::InvalidDer {
977            kind: "certificate ExtendedKeyUsage",
978            message: error.to_string(),
979        })?
980    else {
981        return Ok(());
982    };
983    if usage.value.any {
984        return Ok(());
985    }
986    if let Some(effective) = effective_extended_key_usages {
987        effective.retain(|purpose| extended_key_usage_contains(usage.value, purpose));
988        if !effective.is_empty() {
989            return Ok(());
990        }
991    }
992    Err(X509ChainError::InvalidKeyUsage {
993        position,
994        required: "an approved extended key usage",
995    })
996}
997
998fn extended_key_usage_contains(
999    usage: &x509_parser::extensions::ExtendedKeyUsage<'_>,
1000    purpose: &ExtendedKeyPurpose,
1001) -> bool {
1002    match purpose {
1003        ExtendedKeyPurpose::ServerAuth => usage.server_auth,
1004        ExtendedKeyPurpose::ClientAuth => usage.client_auth,
1005        ExtendedKeyPurpose::CodeSigning => usage.code_signing,
1006        ExtendedKeyPurpose::EmailProtection => usage.email_protection,
1007        ExtendedKeyPurpose::TimeStamping => usage.time_stamping,
1008        ExtendedKeyPurpose::OcspSigning => usage.ocsp_signing,
1009        ExtendedKeyPurpose::Other(arcs) => usage.other.iter().any(|oid| {
1010            let oid = oid.to_id_string();
1011            arcs.iter()
1012                .map(u64::to_string)
1013                .collect::<Vec<_>>()
1014                .join(".")
1015                == oid
1016        }),
1017    }
1018}
1019
1020fn parse_certificate(der: &[u8]) -> Result<X509Certificate<'_>, X509ChainError> {
1021    let (rest, cert) =
1022        X509Certificate::from_der(der).map_err(|error| X509ChainError::InvalidDer {
1023            kind: "certificate",
1024            message: error.to_string(),
1025        })?;
1026    if !rest.is_empty() {
1027        return Err(X509ChainError::InvalidDer {
1028            kind: "certificate",
1029            message: "trailing data".into(),
1030        });
1031    }
1032    Ok(cert)
1033}
1034
1035fn system_time_to_asn1(time: SystemTime) -> Result<ASN1Time, X509ChainError> {
1036    let seconds = time
1037        .duration_since(UNIX_EPOCH)
1038        .map_err(|_| X509ChainError::CertificateNotValid(0))?
1039        .as_secs();
1040    let timestamp = i64::try_from(seconds).map_err(|_| X509ChainError::CertificateNotValid(0))?;
1041    ASN1Time::from_timestamp(timestamp).map_err(|error| X509ChainError::InvalidDer {
1042        kind: "verification time",
1043        message: error.to_string(),
1044    })
1045}
1046
1047fn validate_ca_constraints(
1048    cert: &X509Certificate<'_>,
1049    position: usize,
1050) -> Result<(), X509ChainError> {
1051    let extension = cert
1052        .extensions()
1053        .iter()
1054        .find(|extension| {
1055            matches!(
1056                extension.parsed_extension(),
1057                ParsedExtension::BasicConstraints(_)
1058            )
1059        })
1060        .ok_or(X509ChainError::IssuerNotCa(position))?;
1061    let ParsedExtension::BasicConstraints(constraints) = extension.parsed_extension() else {
1062        unreachable!("extension was selected by parsed type")
1063    };
1064    if !constraints.ca {
1065        return Err(X509ChainError::IssuerNotCa(position));
1066    }
1067    // RFC 5280 section 4.2.1.9 requires conforming issuers to mark CA
1068    // BasicConstraints critical, but the path-validation algorithm requires
1069    // the cA assertion and does not turn issuer non-conformance into a path
1070    // failure. OpenSSL/xmlsec1 accepts historical non-critical CA extensions.
1071
1072    if cert
1073        .key_usage()
1074        .map_err(|error| X509ChainError::InvalidDer {
1075            kind: "certificate KeyUsage",
1076            message: error.to_string(),
1077        })?
1078        .is_some_and(|usage| !usage.value.key_cert_sign())
1079    {
1080        return Err(X509ChainError::InvalidKeyUsage {
1081            position,
1082            required: "keyCertSign",
1083        });
1084    }
1085
1086    Ok(())
1087}
1088
1089fn basic_constraints(
1090    cert: &X509Certificate<'_>,
1091) -> Option<x509_parser::extensions::BasicConstraints> {
1092    cert.extensions()
1093        .iter()
1094        .find_map(|extension| match extension.parsed_extension() {
1095            ParsedExtension::BasicConstraints(value) => Some(value.clone()),
1096            _ => None,
1097        })
1098}
1099
1100fn validate_path_length_constraints(path: &[X509Certificate<'_>]) -> Result<(), X509ChainError> {
1101    for (position, cert) in path.iter().enumerate().skip(1) {
1102        let Some(limit) = basic_constraints(cert).and_then(|value| value.path_len_constraint)
1103        else {
1104            continue;
1105        };
1106        let subordinate_ca_count = path[1..position]
1107            .iter()
1108            .filter(|subordinate| {
1109                basic_constraints(subordinate).is_some_and(|value| value.ca)
1110                    && !certificate_names_equal(subordinate.subject(), subordinate.issuer())
1111            })
1112            .count();
1113        if subordinate_ca_count > limit as usize {
1114            return Err(X509ChainError::PathLengthExceeded { position, limit });
1115        }
1116    }
1117    Ok(())
1118}
1119
1120fn validate_critical_extensions(
1121    cert: &X509Certificate<'_>,
1122    position: usize,
1123) -> Result<(), X509ChainError> {
1124    for extension in cert
1125        .extensions()
1126        .iter()
1127        .filter(|extension| extension.critical)
1128    {
1129        let oid = extension.oid.to_id_string();
1130        if !matches!(
1131            oid.as_str(),
1132            "2.5.29.15" | "2.5.29.17" | "2.5.29.19" | "2.5.29.30" | "2.5.29.37"
1133        ) {
1134            return Err(X509ChainError::UnsupportedCriticalExtension { position, oid });
1135        }
1136        if matches!(
1137            extension.parsed_extension(),
1138            ParsedExtension::UnsupportedExtension { .. }
1139                | ParsedExtension::ParseError { .. }
1140                | ParsedExtension::Unparsed
1141        ) {
1142            return Err(X509ChainError::UnsupportedCriticalExtension { position, oid });
1143        }
1144    }
1145    Ok(())
1146}
1147
1148fn validate_name_constraints(path: &[X509Certificate<'_>]) -> Result<(), X509ChainError> {
1149    for (position, certificate) in path.iter().enumerate() {
1150        if let Some(extension) = certificate
1151            .extensions()
1152            .iter()
1153            .find(|extension| extension.oid.to_id_string() == "2.5.29.30")
1154            && (position == 0 || !extension.critical)
1155        {
1156            return Err(X509ChainError::InvalidNameConstraints { position });
1157        }
1158    }
1159    for (constraining_position, issuer) in path.iter().enumerate().skip(1) {
1160        let Some(extension) = issuer
1161            .extensions()
1162            .iter()
1163            .find(|extension| extension.oid.to_id_string() == "2.5.29.30")
1164        else {
1165            continue;
1166        };
1167        let ParsedExtension::NameConstraints(constraints) = extension.parsed_extension() else {
1168            continue;
1169        };
1170        validate_name_constraints_der(extension.value, constraining_position)?;
1171        ensure_supported_name_constraints(constraints, constraining_position)?;
1172        for (position, subordinate) in path[..constraining_position].iter().enumerate() {
1173            // The target certificate is always checked. Self-issued CA rollover
1174            // certificates between it and the constraint issuer are exempt.
1175            if position != 0 && certificate_names_equal(subordinate.subject(), subordinate.issuer())
1176            {
1177                continue;
1178            }
1179            validate_certificate_names(subordinate, constraints, position, constraining_position)?;
1180        }
1181    }
1182    Ok(())
1183}
1184
1185fn validate_name_constraints_der(
1186    extension_der: &[u8],
1187    position: usize,
1188) -> Result<(), X509ChainError> {
1189    use der::Decode as _;
1190
1191    // x509-parser intentionally omits GeneralSubtree distance fields from its
1192    // public model. Decode the raw extension as well so they cannot silently
1193    // acquire the zero-minimum, unbounded semantics implemented below.
1194    let constraints =
1195        x509_cert::ext::pkix::NameConstraints::from_der(extension_der).map_err(|error| {
1196            X509ChainError::InvalidDer {
1197                kind: "NameConstraints",
1198                message: error.to_string(),
1199            }
1200        })?;
1201    if constraints.permitted_subtrees.is_none() && constraints.excluded_subtrees.is_none()
1202        || constraints
1203            .permitted_subtrees
1204            .as_ref()
1205            .is_some_and(Vec::is_empty)
1206        || constraints
1207            .excluded_subtrees
1208            .as_ref()
1209            .is_some_and(Vec::is_empty)
1210    {
1211        return Err(X509ChainError::InvalidNameConstraints { position });
1212    }
1213    let unsupported = constraints
1214        .permitted_subtrees
1215        .iter()
1216        .flatten()
1217        .chain(constraints.excluded_subtrees.iter().flatten())
1218        .any(|subtree| subtree.minimum != 0 || subtree.maximum.is_some());
1219    if unsupported {
1220        return Err(X509ChainError::InvalidNameConstraints { position });
1221    }
1222    Ok(())
1223}
1224
1225fn ensure_supported_name_constraints(
1226    constraints: &NameConstraints<'_>,
1227    position: usize,
1228) -> Result<(), X509ChainError> {
1229    for subtree in constraints
1230        .permitted_subtrees
1231        .iter()
1232        .flatten()
1233        .chain(constraints.excluded_subtrees.iter().flatten())
1234    {
1235        match &subtree.base {
1236            GeneralName::DNSName(value) | GeneralName::URI(value) => {
1237                validate_dns_name_constraint(value)?;
1238            }
1239            GeneralName::RFC822Name(value) => validate_email_name_constraint(value)?,
1240            GeneralName::IPAddress(bytes) => {
1241                validate_ip_name_constraint(bytes)?;
1242            }
1243            _ => {}
1244        }
1245        if matches!(
1246            subtree.base,
1247            GeneralName::OtherName(..)
1248                | GeneralName::X400Address(..)
1249                | GeneralName::EDIPartyName(..)
1250                | GeneralName::RegisteredID(..)
1251                | GeneralName::Invalid(..)
1252        ) {
1253            return Err(X509ChainError::UnsupportedCriticalExtension {
1254                position,
1255                oid: "2.5.29.30".into(),
1256            });
1257        }
1258    }
1259    Ok(())
1260}
1261
1262fn validate_email_name_constraint(value: &str) -> Result<(), X509ChainError> {
1263    if value.contains('@') {
1264        if !mailbox_has_valid_syntax(value) {
1265            return Err(invalid_string_name_constraint(value));
1266        }
1267        Ok(())
1268    } else {
1269        validate_dns_name_constraint(value)
1270    }
1271}
1272
1273fn validate_dns_name_constraint(value: &str) -> Result<(), X509ChainError> {
1274    if !dns_name_has_valid_syntax(value, true) {
1275        return Err(invalid_string_name_constraint(value));
1276    }
1277    Ok(())
1278}
1279
1280fn validate_rfc5280_dns_name(value: &str) -> Result<(), X509ChainError> {
1281    if !dns_name_has_valid_syntax(value, false) {
1282        return Err(X509ChainError::InvalidDer {
1283            kind: "certificate DNS name",
1284            message: format!("invalid RFC 5280 dNSName: {value:?}"),
1285        });
1286    }
1287    Ok(())
1288}
1289
1290fn dns_name_has_valid_syntax(value: &str, allow_leading_dot: bool) -> bool {
1291    let domain = if allow_leading_dot {
1292        value.strip_prefix('.').unwrap_or(value)
1293    } else {
1294        value
1295    };
1296    if domain.is_empty()
1297        || domain.len() > 253
1298        || domain.split('.').any(|label| {
1299            label.is_empty()
1300                || label.len() > 63
1301                || !label
1302                    .bytes()
1303                    .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
1304                || !label
1305                    .as_bytes()
1306                    .first()
1307                    .is_some_and(u8::is_ascii_alphanumeric)
1308                || !label
1309                    .as_bytes()
1310                    .last()
1311                    .is_some_and(u8::is_ascii_alphanumeric)
1312        })
1313    {
1314        return false;
1315    }
1316    true
1317}
1318
1319fn invalid_string_name_constraint(value: &str) -> X509ChainError {
1320    X509ChainError::InvalidDer {
1321        kind: "string name constraint",
1322        message: format!("invalid RFC 5280 string name constraint: {value:?}"),
1323    }
1324}
1325
1326fn validate_certificate_names(
1327    certificate: &X509Certificate<'_>,
1328    constraints: &NameConstraints<'_>,
1329    position: usize,
1330    constraining_position: usize,
1331) -> Result<(), X509ChainError> {
1332    if certificate.subject().iter().next().is_some() {
1333        let subject = GeneralName::DirectoryName(certificate.subject().clone());
1334        validate_general_name(&subject, constraints, position, constraining_position)?;
1335    }
1336    for attribute in certificate.subject().iter_email() {
1337        let email = attribute
1338            .as_str()
1339            .map_err(|error| X509ChainError::InvalidDer {
1340                kind: "certificate subject emailAddress",
1341                message: error.to_string(),
1342            })?;
1343        validate_general_name(
1344            &GeneralName::RFC822Name(email),
1345            constraints,
1346            position,
1347            constraining_position,
1348        )?;
1349    }
1350    if let Some(names) =
1351        certificate
1352            .extensions()
1353            .iter()
1354            .find_map(|extension| match extension.parsed_extension() {
1355                ParsedExtension::SubjectAlternativeName(value) => Some(&value.general_names),
1356                _ => None,
1357            })
1358    {
1359        for name in names {
1360            validate_general_name(name, constraints, position, constraining_position)?;
1361        }
1362    }
1363    Ok(())
1364}
1365
1366fn validate_general_name(
1367    name: &GeneralName<'_>,
1368    constraints: &NameConstraints<'_>,
1369    position: usize,
1370    constraining_position: usize,
1371) -> Result<(), X509ChainError> {
1372    let permitted = constraints
1373        .permitted_subtrees
1374        .iter()
1375        .flatten()
1376        .filter(|subtree| general_names_have_same_form(name, &subtree.base));
1377    let mut has_permitted_form = false;
1378    let mut matches_permitted = false;
1379    for subtree in permitted {
1380        has_permitted_form = true;
1381        matches_permitted |=
1382            general_name_within_subtree(name, &subtree.base)? == NameConstraintMatch::Match;
1383    }
1384    let excluded = constraints
1385        .excluded_subtrees
1386        .iter()
1387        .flatten()
1388        .filter(|subtree| general_names_have_same_form(name, &subtree.base))
1389        .try_fold(false, |rejected, subtree| {
1390            general_name_within_subtree(name, &subtree.base)
1391                .map(|current| rejected || current != NameConstraintMatch::NoMatch)
1392        })?;
1393    if excluded || (has_permitted_form && !matches_permitted) {
1394        return Err(X509ChainError::NameConstraintViolation {
1395            position,
1396            constraining_position,
1397        });
1398    }
1399    Ok(())
1400}
1401
1402fn general_names_have_same_form(left: &GeneralName<'_>, right: &GeneralName<'_>) -> bool {
1403    matches!(
1404        (left, right),
1405        (GeneralName::RFC822Name(_), GeneralName::RFC822Name(_))
1406            | (GeneralName::DNSName(_), GeneralName::DNSName(_))
1407            | (GeneralName::DirectoryName(_), GeneralName::DirectoryName(_))
1408            | (GeneralName::URI(_), GeneralName::URI(_))
1409            | (GeneralName::IPAddress(_), GeneralName::IPAddress(_))
1410    )
1411}
1412
1413#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1414enum NameConstraintMatch {
1415    Match,
1416    NoMatch,
1417    Unevaluable,
1418}
1419
1420impl From<bool> for NameConstraintMatch {
1421    fn from(matched: bool) -> Self {
1422        if matched { Self::Match } else { Self::NoMatch }
1423    }
1424}
1425
1426fn general_name_within_subtree(
1427    name: &GeneralName<'_>,
1428    subtree: &GeneralName<'_>,
1429) -> Result<NameConstraintMatch, X509ChainError> {
1430    Ok(match (name, subtree) {
1431        (GeneralName::DNSName(name), GeneralName::DNSName(subtree)) => {
1432            dns_name_within_subtree(name, subtree, true).into()
1433        }
1434        (GeneralName::RFC822Name(name), GeneralName::RFC822Name(subtree)) => {
1435            email_within_subtree(name, subtree).into()
1436        }
1437        (GeneralName::DirectoryName(name), GeneralName::DirectoryName(subtree)) => {
1438            let name = x509_name_to_rfc4514(name).map_err(|error| X509ChainError::InvalidDer {
1439                kind: "certificate name constraint",
1440                message: error.to_string(),
1441            })?;
1442            let subtree =
1443                x509_name_to_rfc4514(subtree).map_err(|error| X509ChainError::InvalidDer {
1444                    kind: "certificate name constraint",
1445                    message: error.to_string(),
1446                })?;
1447            distinguished_name_within_subtree(&name, &subtree).into()
1448        }
1449        (GeneralName::URI(name), GeneralName::URI(subtree)) => uri_host(name)
1450            .map_or(NameConstraintMatch::Unevaluable, |host| {
1451                dns_name_within_subtree(host, subtree, false).into()
1452            }),
1453        (GeneralName::IPAddress(name), GeneralName::IPAddress(subtree)) => {
1454            ip_address_within_subtree(name, subtree)?.into()
1455        }
1456        _ => NameConstraintMatch::NoMatch,
1457    })
1458}
1459
1460fn dns_name_within_subtree(name: &str, subtree: &str, include_subdomains: bool) -> bool {
1461    let name = name.trim_end_matches('.');
1462    let subtree = subtree.trim_end_matches('.');
1463    if let Some(domain) = subtree.strip_prefix('.') {
1464        return name.len() > domain.len()
1465            && name.as_bytes()[name.len() - domain.len() - 1] == b'.'
1466            && name[name.len() - domain.len()..].eq_ignore_ascii_case(domain);
1467    }
1468    name.eq_ignore_ascii_case(subtree)
1469        || (include_subdomains
1470            && name.len() > subtree.len()
1471            && name.as_bytes()[name.len() - subtree.len() - 1] == b'.'
1472            && name[name.len() - subtree.len()..].eq_ignore_ascii_case(subtree))
1473}
1474
1475fn email_within_subtree(name: &str, subtree: &str) -> bool {
1476    let Some((local, domain)) = name.rsplit_once('@') else {
1477        return false;
1478    };
1479    if let Some((expected_local, expected_domain)) = subtree.rsplit_once('@') {
1480        return local == expected_local && domain.eq_ignore_ascii_case(expected_domain);
1481    }
1482    dns_name_within_subtree(domain, subtree, false)
1483}
1484
1485fn uri_host(uri: &str) -> Option<&str> {
1486    let authority = uri.split_once("://")?.1;
1487    let authority = authority.split(['/', '?', '#']).next()?;
1488    match parse_uri_authority_host(authority)? {
1489        UriAuthorityHost::Dns(host) => Some(host),
1490        UriAuthorityHost::Ip => None,
1491    }
1492}
1493
1494fn ip_address_within_subtree(address: &[u8], subtree: &[u8]) -> Result<bool, X509ChainError> {
1495    if !matches!(address.len(), 4 | 16) {
1496        return Err(X509ChainError::InvalidDer {
1497            kind: "IP subject alternative name",
1498            message: format!("expected 4 or 16 octets, got {}", address.len()),
1499        });
1500    }
1501    let (network, mask) = validate_ip_name_constraint(subtree)?;
1502    if network.len() != address.len() {
1503        return Ok(false);
1504    }
1505    Ok(address
1506        .iter()
1507        .zip(network)
1508        .zip(mask)
1509        .all(|((address, network), mask)| address & mask == network & mask))
1510}
1511
1512fn validate_ip_name_constraint(subtree: &[u8]) -> Result<(&[u8], &[u8]), X509ChainError> {
1513    if !matches!(subtree.len(), 8 | 32) {
1514        return Err(X509ChainError::InvalidDer {
1515            kind: "IP name constraint",
1516            message: format!("expected 8 or 32 octets, got {}", subtree.len()),
1517        });
1518    }
1519    let (network, mask) = subtree.split_at(subtree.len() / 2);
1520    if !ip_mask_is_contiguous(mask) {
1521        return Err(X509ChainError::InvalidDer {
1522            kind: "IP name constraint",
1523            message: "network mask is not contiguous".into(),
1524        });
1525    }
1526    Ok((network, mask))
1527}
1528
1529fn ip_mask_is_contiguous(mask: &[u8]) -> bool {
1530    let mut zero_seen = false;
1531    for byte in mask {
1532        for bit in (0..8).rev() {
1533            let set = byte & (1 << bit) != 0;
1534            if zero_seen && set {
1535                return false;
1536            }
1537            zero_seen |= !set;
1538        }
1539    }
1540    true
1541}
1542
1543fn certificate_subject_key_identifier<'a>(
1544    certificate: &'a X509Certificate<'a>,
1545) -> Option<&'a [u8]> {
1546    certificate
1547        .extensions()
1548        .iter()
1549        .find_map(|extension| match extension.parsed_extension() {
1550            ParsedExtension::SubjectKeyIdentifier(identifier) => Some(identifier.0),
1551            _ => None,
1552        })
1553}
1554
1555fn crl_authority_key_matches(
1556    crl: &CertificateRevocationList<'_>,
1557    issuer: &X509Certificate<'_>,
1558) -> Result<Option<bool>, X509ChainError> {
1559    let authority_key = crl
1560        .extensions()
1561        .iter()
1562        .find(|extension| extension.oid.to_id_string() == "2.5.29.35")
1563        .map(|extension| match extension.parsed_extension() {
1564            ParsedExtension::AuthorityKeyIdentifier(identifier) => {
1565                Ok(identifier.key_identifier.as_ref().map(|key| key.0))
1566            }
1567            _ => Err(X509ChainError::InvalidDer {
1568                kind: "CRL AuthorityKeyIdentifier",
1569                message: "extension could not be decoded".into(),
1570            }),
1571        })
1572        .transpose()?
1573        .flatten();
1574    Ok(authority_key
1575        .zip(certificate_subject_key_identifier(issuer))
1576        .map(|(authority, subject)| authority == subject))
1577}
1578
1579fn validate_crl_extensions(
1580    crl: &CertificateRevocationList<'_>,
1581    crl_index: usize,
1582) -> Result<(), X509ChainError> {
1583    validate_crl_extension_uniqueness(crl, crl_index)?;
1584    validate_crl_extension_semantics(crl, crl_index)
1585}
1586
1587fn validate_crl_extension_uniqueness(
1588    crl: &CertificateRevocationList<'_>,
1589    crl_index: usize,
1590) -> Result<(), X509ChainError> {
1591    crl.tbs_cert_list
1592        .extensions_map()
1593        .map_err(|_| X509ChainError::InvalidCrl(crl_index))?;
1594    for revoked in crl.iter_revoked_certificates() {
1595        validate_positive_serial_bytes(revoked.raw_serial(), "CRL revoked certificate serial")
1596            .map_err(|_| X509ChainError::InvalidCrl(crl_index))?;
1597        revoked
1598            .extensions_map()
1599            .map_err(|_| X509ChainError::InvalidCrl(crl_index))?;
1600    }
1601    Ok(())
1602}
1603
1604fn validate_crl_extension_semantics(
1605    crl: &CertificateRevocationList<'_>,
1606    crl_index: usize,
1607) -> Result<(), X509ChainError> {
1608    for extension in crl.extensions() {
1609        let oid = extension.oid.to_id_string();
1610        // IssuingDistributionPoint changes which certificates and issuers a CRL
1611        // covers. Delta CRLs also cannot be treated as complete CRLs: in particular,
1612        // removeFromCRL has the opposite meaning from a complete-list revocation.
1613        if matches!(oid.as_str(), "2.5.29.27" | "2.5.29.28")
1614            || (extension.critical && oid != "2.5.29.35")
1615        {
1616            return Err(X509ChainError::InvalidCrl(crl_index));
1617        }
1618        if oid == "2.5.29.35"
1619            && !matches!(
1620                extension.parsed_extension(),
1621                ParsedExtension::AuthorityKeyIdentifier(_)
1622            )
1623        {
1624            return Err(X509ChainError::InvalidCrl(crl_index));
1625        }
1626    }
1627    for revoked in crl.iter_revoked_certificates() {
1628        for extension in revoked.extensions() {
1629            let oid = extension.oid.to_id_string();
1630            // certificateIssuer carries the issuer identity for indirect CRLs.
1631            // removeFromCRL is meaningful only in a delta CRL, which this
1632            // complete-CRL validator rejects above.
1633            let invalid_reason = oid == "2.5.29.21"
1634                && !matches!(
1635                    extension.parsed_extension(),
1636                    ParsedExtension::ReasonCode(code)
1637                        if *code != x509_parser::x509::ReasonCode::RemoveFromCRL
1638                );
1639            if oid == "2.5.29.29" || extension.critical || invalid_reason {
1640                return Err(X509ChainError::InvalidCrl(crl_index));
1641            }
1642        }
1643    }
1644    Ok(())
1645}
1646
1647fn verify_crls(
1648    path: &[X509Certificate<'_>],
1649    crl_der: &[Vec<u8>],
1650    verification_time: ASN1Time,
1651    provider: &dyn crate::provider::CryptoProvider,
1652) -> Result<(), X509ChainError> {
1653    let crls = crl_der
1654        .iter()
1655        .enumerate()
1656        .map(|(idx, der)| {
1657            let (rest, crl) = CertificateRevocationList::from_der(der).map_err(|error| {
1658                X509ChainError::InvalidDer {
1659                    kind: "CRL",
1660                    message: error.to_string(),
1661                }
1662            })?;
1663            if !rest.is_empty() {
1664                return Err(X509ChainError::InvalidDer {
1665                    kind: "CRL",
1666                    message: "trailing data".into(),
1667                });
1668            }
1669            Ok((idx, crl))
1670        })
1671        .collect::<Result<Vec<_>, _>>()?;
1672
1673    for (position, cert) in path.iter().enumerate().take(path.len().saturating_sub(1)) {
1674        let issuer = &path[position + 1];
1675        for (crl_index, crl) in crls
1676            .iter()
1677            .filter(|(_, crl)| certificate_names_equal(crl.issuer(), cert.issuer()))
1678        {
1679            // Duplicate OIDs make first-match AKI filtering ambiguous, so this
1680            // structural invariant must hold before key applicability is tested.
1681            validate_crl_extension_uniqueness(crl, *crl_index)?;
1682            let authority_key_match = crl_authority_key_matches(crl, issuer)?;
1683            if authority_key_match == Some(false) {
1684                continue;
1685            }
1686            if !verify_crl_signature_with_provider(crl, issuer, provider)? {
1687                if authority_key_match == Some(true) {
1688                    return Err(X509ChainError::InvalidCrl(*crl_index));
1689                }
1690                continue;
1691            }
1692            // Extension semantics can reject an applicable CRL, but unrelated
1693            // untrusted CRL material must not influence the selected path.
1694            validate_crl_extensions(crl, *crl_index)?;
1695            if issuer
1696                .key_usage()
1697                .map_err(|error| X509ChainError::InvalidDer {
1698                    kind: "certificate KeyUsage",
1699                    message: error.to_string(),
1700                })?
1701                .is_some_and(|usage| !usage.value.crl_sign())
1702            {
1703                return Err(X509ChainError::InvalidKeyUsage {
1704                    position: position + 1,
1705                    required: "cRLSign",
1706                });
1707            }
1708            // RFC 5280 requires conforming CRL issuers to provide nextUpdate;
1709            // without it this verifier cannot establish a bounded freshness window.
1710            let time_valid = crl.next_update().is_some_and(|next| {
1711                crl.last_update() <= verification_time && verification_time <= next
1712            });
1713            if !time_valid {
1714                return Err(X509ChainError::InvalidCrl(*crl_index));
1715            }
1716            if crl.iter_revoked_certificates().any(|revoked| {
1717                revoked.raw_serial() == cert.raw_serial()
1718                    && revoked.revocation_date <= verification_time
1719            }) {
1720                return Err(X509ChainError::Revoked(position));
1721            }
1722        }
1723    }
1724    Ok(())
1725}
1726
1727#[cfg(test)]
1728mod tests {
1729    use std::str::FromStr as _;
1730
1731    use super::*;
1732    use crate::xmldsig::{KeyInfoSource, parse::XMLDSIG_NS, parse_key_info};
1733    use p256::pkcs8::EncodePublicKey;
1734    use roxmltree::Document;
1735    use sha2::{Digest, Sha256, Sha384};
1736    use signature::hazmat::PrehashSigner;
1737    use std::time::Duration;
1738    use x509_parser::oid_registry::{OID_SIG_ECDSA_WITH_SHA256, OID_SIG_ECDSA_WITH_SHA384, Oid};
1739
1740    fn generated_certificate_params(common_name: &str, is_ca: bool) -> rcgen::CertificateParams {
1741        let mut params = rcgen::CertificateParams::new(Vec::new())
1742            .expect("empty SAN list should produce valid certificate parameters");
1743        params
1744            .distinguished_name
1745            .push(rcgen::DnType::CommonName, common_name);
1746        if is_ca {
1747            params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1748            params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
1749        }
1750        params
1751    }
1752
1753    fn verify_generated_path(
1754        certificates: Vec<Vec<u8>>,
1755        trusted_anchor: Vec<u8>,
1756    ) -> Result<(), X509ChainError> {
1757        verify_generated_path_with_eku(certificates, trusted_anchor, None)
1758    }
1759
1760    fn verify_generated_path_with_eku(
1761        certificates: Vec<Vec<u8>>,
1762        trusted_anchor: Vec<u8>,
1763        allowed_extended_key_usages: Option<&HashSet<ExtendedKeyPurpose>>,
1764    ) -> Result<(), X509ChainError> {
1765        let info = X509DataInfo {
1766            certificate_chain: (0..certificates.len()).collect(),
1767            certificates,
1768            ..X509DataInfo::default()
1769        };
1770        let anchors = vec![trusted_anchor];
1771        verify_x509_certificate_chain(
1772            &info,
1773            &X509ChainOptions {
1774                trusted_certs: &anchors,
1775                verification_time: SystemTime::now(),
1776                max_chain_depth: info.certificate_chain.len(),
1777                check_crls: false,
1778                allowed_extended_key_usages,
1779                rsa_keys: RsaKeyPolicy::default(),
1780                dsa_keys: DsaKeyPolicy::default(),
1781            },
1782        )
1783    }
1784
1785    #[test]
1786    fn noncritical_ca_basic_constraints_remain_path_compatible() {
1787        // Criticality is an issuer conformance requirement, not an additional
1788        // relying-party path gate; historical xmlsec1 chains depend on this.
1789        let mut params = generated_certificate_params("non-critical authority", false);
1790        params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
1791        params
1792            .custom_extensions
1793            .push(rcgen::CustomExtension::from_oid_content(
1794                &[2, 5, 29, 19],
1795                vec![0x30, 0x03, 0x01, 0x01, 0xff],
1796            ));
1797        let certificate = params
1798            .self_signed(&rcgen::KeyPair::generate().expect("CA key generation should succeed"))
1799            .expect("test CA should be self-signable");
1800        let parsed = parse_certificate(certificate.der()).expect("test CA DER should parse");
1801
1802        assert_eq!(validate_ca_constraints(&parsed, 1), Ok(()));
1803    }
1804
1805    #[test]
1806    fn restricted_leaf_eku_requires_an_approved_purpose() {
1807        // A server-authentication certificate is not implicitly authorized for
1808        // XML signatures merely because its key permits digital signatures.
1809        let root = rcgen::CertifiedIssuer::self_signed(
1810            generated_certificate_params("EKU authority", true),
1811            rcgen::KeyPair::generate().expect("root key generation should succeed"),
1812        )
1813        .expect("root should be self-signable");
1814        let mut leaf_params = generated_certificate_params("TLS-only signer", false);
1815        leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1816        leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
1817        let leaf = leaf_params
1818            .signed_by(
1819                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1820                &root,
1821            )
1822            .expect("root should sign leaf certificate");
1823        let leaf_der = leaf.der().to_vec();
1824        let root_der = root.der().to_vec();
1825
1826        assert!(matches!(
1827            verify_generated_path(vec![leaf_der.clone(), root_der.clone()], root_der.clone(),),
1828            Err(X509ChainError::InvalidKeyUsage {
1829                position: 0,
1830                required: "an approved extended key usage",
1831            })
1832        ));
1833
1834        let allowed = HashSet::from([ExtendedKeyPurpose::ServerAuth]);
1835        verify_generated_path_with_eku(vec![leaf_der, root_der.clone()], root_der, Some(&allowed))
1836            .expect("an explicitly approved leaf purpose must be accepted");
1837    }
1838
1839    #[test]
1840    fn critical_leaf_eku_uses_the_same_purpose_policy() {
1841        // Criticality changes whether an unknown extension may be ignored, not
1842        // the authorization semantics of an EKU that this validator implements.
1843        let root = rcgen::CertifiedIssuer::self_signed(
1844            generated_certificate_params("critical EKU authority", true),
1845            rcgen::KeyPair::generate().expect("root key generation should succeed"),
1846        )
1847        .expect("root should be self-signable");
1848        let mut leaf_params = generated_certificate_params("critical TLS signer", false);
1849        leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1850        let mut extension = rcgen::CustomExtension::from_oid_content(
1851            &[2, 5, 29, 37],
1852            vec![
1853                0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
1854            ],
1855        );
1856        extension.set_criticality(true);
1857        leaf_params.custom_extensions.push(extension);
1858        let leaf = leaf_params
1859            .signed_by(
1860                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1861                &root,
1862            )
1863            .expect("root should sign leaf certificate");
1864        let allowed = HashSet::from([ExtendedKeyPurpose::ServerAuth]);
1865
1866        verify_generated_path_with_eku(
1867            vec![leaf.der().to_vec(), root.der().to_vec()],
1868            root.der().to_vec(),
1869            Some(&allowed),
1870        )
1871        .expect("approved critical EKU must be processed rather than rejected as unknown");
1872    }
1873
1874    #[test]
1875    fn issuer_eku_restricts_the_entire_certificate_path() {
1876        // RFC 5280 applies an issuer EKU as a path-wide purpose constraint. A
1877        // leaf approval cannot override an incompatible critical CA authorization.
1878        for (issuer_purpose_der, accepted) in [
1879            (
1880                vec![
1881                    0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
1882                ],
1883                false,
1884            ),
1885            (
1886                vec![
1887                    0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
1888                ],
1889                true,
1890            ),
1891        ] {
1892            let mut root_params =
1893                generated_certificate_params("purpose-constrained authority", true);
1894            let mut extension =
1895                rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 37], issuer_purpose_der);
1896            extension.set_criticality(true);
1897            root_params.custom_extensions.push(extension);
1898            let root = rcgen::CertifiedIssuer::self_signed(
1899                root_params,
1900                rcgen::KeyPair::generate().expect("root key generation should succeed"),
1901            )
1902            .expect("root should be self-signable");
1903            let mut leaf_params = generated_certificate_params("TLS server signer", false);
1904            leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1905            leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
1906            let leaf = leaf_params
1907                .signed_by(
1908                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1909                    &root,
1910                )
1911                .expect("root should sign leaf certificate");
1912            let allowed = HashSet::from([
1913                ExtendedKeyPurpose::ServerAuth,
1914                ExtendedKeyPurpose::ClientAuth,
1915            ]);
1916            let result = verify_generated_path_with_eku(
1917                vec![leaf.der().to_vec(), root.der().to_vec()],
1918                root.der().to_vec(),
1919                Some(&allowed),
1920            );
1921
1922            if accepted {
1923                result.expect("a shared allowed purpose must satisfy the complete path");
1924            } else {
1925                assert!(matches!(
1926                    result,
1927                    Err(X509ChainError::InvalidKeyUsage {
1928                        position: 1,
1929                        required: "an approved extended key usage",
1930                    })
1931                ));
1932            }
1933        }
1934    }
1935
1936    #[test]
1937    fn any_extended_key_usage_does_not_restrict_xml_signing() {
1938        // RFC 5280 anyExtendedKeyUsage explicitly leaves the key unrestricted,
1939        // so it does not require a deployment-specific purpose allowlist entry.
1940        let root = rcgen::CertifiedIssuer::self_signed(
1941            generated_certificate_params("any EKU authority", true),
1942            rcgen::KeyPair::generate().expect("root key generation should succeed"),
1943        )
1944        .expect("root should be self-signable");
1945        let mut leaf_params = generated_certificate_params("unrestricted signer", false);
1946        leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1947        leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::Any];
1948        let leaf = leaf_params
1949            .signed_by(
1950                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1951                &root,
1952            )
1953            .expect("root should sign leaf certificate");
1954
1955        verify_generated_path(
1956            vec![leaf.der().to_vec(), root.der().to_vec()],
1957            root.der().to_vec(),
1958        )
1959        .expect("anyExtendedKeyUsage must remain unrestricted");
1960    }
1961
1962    #[test]
1963    fn x509_ecdsa_hash_oid_does_not_select_the_issuer_curve() {
1964        // RFC 5758 signature OIDs select the digest while SubjectPublicKeyInfo
1965        // selects the curve. Both non-default pairings must therefore reach
1966        // the provider with the issuer's actual curve rather than a curve
1967        // inferred from the hash OID.
1968        let data = b"certificate tbs bytes";
1969
1970        let p384_key = p384::ecdsa::SigningKey::from_slice(&[0x42; 48])
1971            .expect("fixed P-384 test key must be valid");
1972        let p384_signature: p384::ecdsa::Signature = p384_key
1973            .sign_prehash(&Sha256::digest(data))
1974            .expect("P-384 must sign a SHA-256 prehash");
1975        let p384_spki = p384_key
1976            .verifying_key()
1977            .to_public_key_der()
1978            .expect("P-384 SPKI must encode");
1979        assert!(
1980            verify_x509_signature_with_provider(
1981                &AlgorithmIdentifier::new(OID_SIG_ECDSA_WITH_SHA256, None),
1982                p384_signature.to_der().as_bytes(),
1983                data,
1984                p384_spki.as_bytes(),
1985                crate::provider::default_provider(),
1986            )
1987            .expect("P-384 with SHA-256 must be a supported X.509 pairing")
1988        );
1989
1990        let p256_key = p256::ecdsa::SigningKey::from_slice(&[0x24; 32])
1991            .expect("fixed P-256 test key must be valid");
1992        let p256_signature: p256::ecdsa::Signature = p256_key
1993            .sign_prehash(&Sha384::digest(data))
1994            .expect("P-256 must sign a SHA-384 prehash");
1995        let p256_spki = p256_key
1996            .verifying_key()
1997            .to_public_key_der()
1998            .expect("P-256 SPKI must encode");
1999        assert!(
2000            verify_x509_signature_with_provider(
2001                &AlgorithmIdentifier::new(OID_SIG_ECDSA_WITH_SHA384, None),
2002                p256_signature.to_der().as_bytes(),
2003                data,
2004                p256_spki.as_bytes(),
2005                crate::provider::default_provider(),
2006            )
2007            .expect("P-256 with SHA-384 must be a supported X.509 pairing")
2008        );
2009    }
2010
2011    #[test]
2012    fn path_edge_signature_check_does_not_repeat_name_matching() {
2013        // Path construction performs RFC 5280 name matching before asking this
2014        // helper to disambiguate same-name candidates. Only proof of possession
2015        // of the issuer key belongs in this second gate.
2016        let issuer_key = rcgen::KeyPair::generate().expect("issuer key generation should succeed");
2017        let issuer_key_pem = issuer_key.serialize_pem();
2018        let mut signing_params = rcgen::CertificateParams::new(Vec::new())
2019            .expect("empty issuer SAN list should be valid");
2020        signing_params
2021            .distinguished_name
2022            .push(rcgen::DnType::CommonName, "signing name");
2023        signing_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2024        signing_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2025        let signing_issuer = rcgen::CertifiedIssuer::self_signed(signing_params, issuer_key)
2026            .expect("issuer certificate should be self-signable");
2027
2028        let mut alternate_params = rcgen::CertificateParams::new(Vec::new())
2029            .expect("empty alternate SAN list should be valid");
2030        alternate_params
2031            .distinguished_name
2032            .push(rcgen::DnType::CommonName, "name already matched by caller");
2033        alternate_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2034        alternate_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2035        let alternate_issuer = rcgen::CertifiedIssuer::self_signed(
2036            alternate_params,
2037            rcgen::KeyPair::from_pem(&issuer_key_pem)
2038                .expect("serialized issuer key should parse again"),
2039        )
2040        .expect("alternate issuer certificate should be self-signable");
2041
2042        let leaf = rcgen::CertificateParams::new(Vec::new())
2043            .expect("empty leaf SAN list should be valid")
2044            .signed_by(
2045                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2046                &signing_issuer,
2047            )
2048            .expect("issuer should sign leaf certificate");
2049
2050        assert!(certificate_signature_matches(
2051            leaf.der(),
2052            alternate_issuer.der()
2053        ));
2054    }
2055
2056    #[test]
2057    fn certificate_path_edge_preserves_ed25519_verification() {
2058        // Provider routing must preserve the certificate algorithms accepted by
2059        // the previous x509-parser verifier rather than narrowing them to the
2060        // XMLDSig SignatureMethod enum.
2061        let issuer_key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
2062            .expect("Ed25519 issuer key generation should succeed");
2063        let mut issuer_params = rcgen::CertificateParams::new(Vec::new())
2064            .expect("empty issuer SAN list should be valid");
2065        issuer_params
2066            .distinguished_name
2067            .push(rcgen::DnType::CommonName, "Ed25519 issuer");
2068        issuer_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2069        issuer_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2070        let issuer = rcgen::CertifiedIssuer::self_signed(issuer_params, issuer_key)
2071            .expect("Ed25519 issuer certificate should be self-signable");
2072        let leaf_key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
2073            .expect("Ed25519 leaf key generation should succeed");
2074        let leaf = rcgen::CertificateParams::new(Vec::new())
2075            .expect("empty leaf SAN list should be valid")
2076            .signed_by(&leaf_key, &issuer)
2077            .expect("Ed25519 issuer should sign leaf certificate");
2078
2079        assert!(certificate_signature_matches(leaf.der(), issuer.der()));
2080    }
2081
2082    #[test]
2083    fn every_modeled_non_parameterized_x509_algorithm_reaches_the_provider() {
2084        // Parsing and provider capability are separate contracts. Once an OID
2085        // has a typed representation, custom providers must get the chance to
2086        // implement it even when RustCrypto does not.
2087        for (oid, expected) in [
2088            (
2089                "2.16.840.1.101.3.4.3.2",
2090                X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha256),
2091            ),
2092            (
2093                "2.16.840.1.101.3.4.3.3",
2094                X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha384),
2095            ),
2096            (
2097                "2.16.840.1.101.3.4.3.4",
2098                X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha512),
2099            ),
2100            (
2101                "1.2.840.10045.4.1",
2102                X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha1),
2103            ),
2104            (
2105                "1.2.840.10045.4.3.4",
2106                X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha512),
2107            ),
2108        ] {
2109            let identifier = AlgorithmIdentifier::new(
2110                Oid::from_str(oid).expect("static signature OID must parse"),
2111                None,
2112            );
2113            assert_eq!(x509_signature_algorithm(&identifier), Ok(expected), "{oid}");
2114        }
2115    }
2116
2117    #[test]
2118    fn x509_signature_parameters_follow_each_algorithm_profile() {
2119        use x509_parser::asn1_rs::{Any, Tag};
2120
2121        // DSA, ECDSA, and Ed25519 signature identifiers require absent
2122        // parameters. A NULL is not equivalent for these algorithm profiles.
2123        for oid in [
2124            "1.2.840.10040.4.3",
2125            "2.16.840.1.101.3.4.3.2",
2126            "1.2.840.10045.4.1",
2127            "1.2.840.10045.4.3.2",
2128            "1.3.101.112",
2129        ] {
2130            let identifier = AlgorithmIdentifier::new(
2131                Oid::from_str(oid).expect("static signature OID must parse"),
2132                Some(Any::from_tag_and_data(Tag::Null, &[])),
2133            );
2134            assert!(matches!(
2135                x509_signature_algorithm(&identifier),
2136                Err(X509ChainError::InvalidDer {
2137                    kind: "X.509 signature AlgorithmIdentifier parameters",
2138                    ..
2139                })
2140            ));
2141        }
2142
2143        // RSA PKCS#1 signature identifiers accept absent and NULL parameters
2144        // for interoperability, but no other ASN.1 value.
2145        let rsa_oid =
2146            Oid::from_str("1.2.840.113549.1.1.11").expect("static RSA signature OID must parse");
2147        for parameters in [None, Some(Any::from_tag_and_data(Tag::Null, &[]))] {
2148            assert!(matches!(
2149                x509_signature_algorithm(&AlgorithmIdentifier::new(rsa_oid.clone(), parameters)),
2150                Ok(X509SignatureAlgorithm::RsaPkcs1v15(
2151                    super::super::DigestAlgorithm::Sha256
2152                ))
2153            ));
2154        }
2155        assert!(matches!(
2156            x509_signature_algorithm(&AlgorithmIdentifier::new(
2157                rsa_oid,
2158                Some(Any::from_tag_and_data(Tag::OctetString, &[])),
2159            )),
2160            Err(X509ChainError::InvalidDer {
2161                kind: "X.509 signature AlgorithmIdentifier parameters",
2162                ..
2163            })
2164        ));
2165    }
2166
2167    #[test]
2168    fn unknown_x509_signature_algorithm_remains_diagnosable() {
2169        let oid = "1.2.3.4.5";
2170        let identifier = AlgorithmIdentifier::new(
2171            Oid::from_str(oid).expect("static unknown OID must parse"),
2172            None,
2173        );
2174
2175        assert_eq!(
2176            x509_signature_algorithm(&identifier),
2177            Err(X509ChainError::UnsupportedSignatureAlgorithm { oid: oid.into() })
2178        );
2179    }
2180
2181    #[test]
2182    fn parses_rsa_pss_certificate_parameters_without_xml_dsig_loss() {
2183        // RFC 4055 carries the digest, MGF digest, and salt length inside the
2184        // AlgorithmIdentifier. Preserve all three values at the provider edge.
2185        let der = [
2186            0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
2187            0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
2188            0x02, 0x01, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
2189            0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
2190            0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x20,
2191        ];
2192        let (rest, identifier) = AlgorithmIdentifier::from_der(&der)
2193            .expect("standard SHA-256 RSA-PSS AlgorithmIdentifier must parse");
2194        assert!(rest.is_empty());
2195
2196        assert_eq!(
2197            x509_signature_algorithm(&identifier),
2198            Ok(X509SignatureAlgorithm::RsaPss {
2199                digest: super::super::DigestAlgorithm::Sha256,
2200                mgf_digest: super::super::DigestAlgorithm::Sha256,
2201                salt_len: 32,
2202            })
2203        );
2204    }
2205
2206    #[test]
2207    fn dsa_rollover_replaces_embedded_root_before_depth_validation() {
2208        let leaf = include_bytes!(
2209            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2210        )
2211        .to_vec();
2212        let embedded_root =
2213            include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2214                .to_vec();
2215
2216        // Trust-anchor self-signatures are not part of path validation. Changing
2217        // only that signature gives this test a distinct rollover certificate
2218        // with the same subject and DSA public key as the embedded stale root.
2219        let mut rollover_anchor = embedded_root.clone();
2220        *rollover_anchor
2221            .last_mut()
2222            .expect("certificate is non-empty") ^= 1;
2223        parse_certificate(&rollover_anchor).expect("modified trust anchor remains valid DER");
2224        let anchors = vec![rollover_anchor];
2225        let info = X509DataInfo {
2226            certificates: vec![leaf, embedded_root],
2227            certificate_chain: vec![0, 1],
2228            ..X509DataInfo::default()
2229        };
2230        let options = X509ChainOptions {
2231            trusted_certs: &anchors,
2232            verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2233            max_chain_depth: 2,
2234            check_crls: false,
2235            allowed_extended_key_usages: None,
2236            rsa_keys: RsaKeyPolicy::default(),
2237            dsa_keys: DsaKeyPolicy {
2238                minimum_modulus_bits: 1024,
2239            },
2240        };
2241
2242        verify_x509_certificate_chain(&info, &options)
2243            .expect("the stale DSA root must be replaced by the configured anchor");
2244    }
2245
2246    #[test]
2247    fn dsa_issuer_key_uses_the_configured_strength_policy() {
2248        let leaf = include_bytes!(
2249            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2250        )
2251        .to_vec();
2252        let anchor =
2253            include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2254                .to_vec();
2255        let anchors = vec![anchor.clone()];
2256        let info = X509DataInfo {
2257            certificates: vec![leaf, anchor],
2258            certificate_chain: vec![0, 1],
2259            ..X509DataInfo::default()
2260        };
2261        let options = X509ChainOptions {
2262            trusted_certs: &anchors,
2263            verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2264            max_chain_depth: 2,
2265            check_crls: false,
2266            allowed_extended_key_usages: None,
2267            rsa_keys: RsaKeyPolicy::default(),
2268            dsa_keys: DsaKeyPolicy::default(),
2269        };
2270
2271        assert!(matches!(
2272            verify_x509_certificate_chain(&info, &options),
2273            Err(X509ChainError::KeyPolicy {
2274                position: 1,
2275                source: crate::policy::PolicyViolation::KeySize {
2276                    key_type: "DSA",
2277                    minimum_bits: 2048,
2278                    actual_bits: 1024,
2279                    ..
2280                }
2281            })
2282        ));
2283    }
2284
2285    #[test]
2286    fn path_length_excludes_self_issued_rollover_certificates() {
2287        // RFC 5280 excludes self-issued rollover CAs from pathLenConstraint;
2288        // only non-self-issued intermediate CA certificates consume the limit.
2289        let mut root_params = generated_certificate_params("rollover path authority", true);
2290        root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Constrained(0));
2291        let root = rcgen::CertifiedIssuer::self_signed(
2292            root_params,
2293            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2294        )
2295        .expect("root should be self-signable");
2296        let rollover_params = generated_certificate_params("rollover path authority", true);
2297        let rollover_key =
2298            rcgen::KeyPair::generate().expect("rollover key generation should succeed");
2299        let rollover_certificate = rollover_params
2300            .signed_by(&rollover_key, &root)
2301            .expect("root should sign same-name rollover certificate");
2302        let rollover_issuer = rcgen::Issuer::from_params(&rollover_params, &rollover_key);
2303        let leaf = generated_certificate_params("rollover path leaf", false)
2304            .signed_by(
2305                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2306                &rollover_issuer,
2307            )
2308            .expect("rollover key should sign leaf certificate");
2309
2310        verify_generated_path(
2311            vec![
2312                leaf.der().to_vec(),
2313                rollover_certificate.der().to_vec(),
2314                root.der().to_vec(),
2315            ],
2316            root.der().to_vec(),
2317        )
2318        .expect("self-issued rollover must not consume a zero path-length allowance");
2319    }
2320
2321    #[test]
2322    fn ca_name_constraints_reject_disallowed_dns_names() {
2323        let mut root_params = generated_certificate_params("constrained authority", true);
2324        root_params.name_constraints = Some(rcgen::NameConstraints {
2325            permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2326            excluded_subtrees: vec![rcgen::GeneralSubtree::DnsName("blocked.example.com".into())],
2327        });
2328        let root = rcgen::CertifiedIssuer::self_signed(
2329            root_params,
2330            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2331        )
2332        .expect("constrained root should be self-signable");
2333
2334        for (dns_name, accepted) in [
2335            ("www.example.com", true),
2336            ("blocked.example.com", false),
2337            ("www.example.net", false),
2338        ] {
2339            let leaf = rcgen::CertificateParams::new(vec![dns_name.into()])
2340                .expect("DNS SAN should be valid")
2341                .signed_by(
2342                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2343                    &root,
2344                )
2345                .expect("root should sign leaf certificate");
2346            assert_eq!(
2347                verify_generated_path(
2348                    vec![leaf.der().to_vec(), root.der().to_vec()],
2349                    root.der().to_vec(),
2350                )
2351                .is_ok(),
2352                accepted,
2353                "unexpected name-constraint result for {dns_name}"
2354            );
2355        }
2356    }
2357
2358    #[test]
2359    fn rfc5280_dns_names_require_preferred_name_syntax() {
2360        let mut root_params = generated_certificate_params("DNS syntax authority", true);
2361        root_params.name_constraints = Some(rcgen::NameConstraints {
2362            permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2363            excluded_subtrees: Vec::new(),
2364        });
2365        let root = rcgen::CertifiedIssuer::self_signed(
2366            root_params,
2367            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2368        )
2369        .expect("constrained root should be self-signable");
2370
2371        let mut leaf_params = generated_certificate_params("malformed DNS leaf", false);
2372        let dns_name = b"bad..example.com";
2373        let mut san_der = vec![
2374            0x30,
2375            u8::try_from(dns_name.len() + 2).expect("test SAN must fit short-form DER"),
2376            0x82,
2377        ];
2378        san_der.push(u8::try_from(dns_name.len()).expect("test DNS name must fit short-form DER"));
2379        san_der.extend_from_slice(dns_name);
2380        leaf_params
2381            .custom_extensions
2382            .push(rcgen::CustomExtension::from_oid_content(
2383                &[2, 5, 29, 17],
2384                san_der,
2385            ));
2386        let leaf = leaf_params
2387            .signed_by(
2388                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2389                &root,
2390            )
2391            .expect("root should sign malformed-DNS leaf");
2392
2393        assert!(matches!(
2394            verify_generated_path(
2395                vec![leaf.der().to_vec(), root.der().to_vec()],
2396                root.der().to_vec(),
2397            ),
2398            Err(X509ChainError::InvalidDer {
2399                kind: "certificate DNS name",
2400                ..
2401            })
2402        ));
2403
2404        for dns_name in ["*.example.com", "_signing.example.com"] {
2405            assert!(validate_rfc5280_dns_name(dns_name).is_err(), "{dns_name}");
2406        }
2407    }
2408
2409    #[test]
2410    fn name_constraint_matchers_cover_email_uri_and_ip_forms() {
2411        // RFC 5280 gives each GeneralName form distinct subtree semantics;
2412        // exercise those rules directly so a DNS-only implementation cannot pass.
2413        assert!(email_within_subtree("ops@example.com", "example.com"));
2414        assert!(email_within_subtree("ops@example.com", "ops@example.com"));
2415        assert!(!email_within_subtree(
2416            "other@example.com",
2417            "ops@example.com"
2418        ));
2419        assert_eq!(
2420            uri_host("https://user@api.example.com:8443/path"),
2421            Some("api.example.com")
2422        );
2423        assert_eq!(
2424            uri_host("https://user@other@example.com/path"),
2425            None,
2426            "a second userinfo delimiter must not expose a constraint-matchable host"
2427        );
2428        assert!(dns_name_within_subtree(
2429            uri_host("https://api.example.com/path").expect("URI must expose a DNS host"),
2430            ".example.com",
2431            false,
2432        ));
2433        assert!(
2434            ip_address_within_subtree(&[192, 0, 2, 42], &[192, 0, 2, 0, 255, 255, 255, 0],)
2435                .expect("valid IPv4 constraint must evaluate")
2436        );
2437        assert!(
2438            !ip_address_within_subtree(&[192, 0, 3, 42], &[192, 0, 2, 0, 255, 255, 255, 0],)
2439                .expect("valid non-matching IPv4 constraint must evaluate")
2440        );
2441        assert!(matches!(
2442            ip_address_within_subtree(&[192, 0, 2, 42], &[192, 0, 2, 0, 255, 0, 255, 0],),
2443            Err(X509ChainError::InvalidDer {
2444                kind: "IP name constraint",
2445                ..
2446            })
2447        ));
2448    }
2449
2450    #[test]
2451    fn malformed_ip_name_constraints_fail_before_matching() {
2452        use x509_parser::extensions::GeneralSubtree;
2453
2454        let name = GeneralName::IPAddress(&[192, 0, 2, 42]);
2455        for malformed in [
2456            &[192, 0, 2, 0, 255, 255, 255][..],
2457            &[192, 0, 2, 0, 255, 0, 255, 0][..],
2458        ] {
2459            for permitted in [true, false] {
2460                let subtree = GeneralSubtree {
2461                    base: GeneralName::IPAddress(malformed),
2462                };
2463                let constraints = NameConstraints {
2464                    permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2465                    excluded_subtrees: (!permitted).then(|| vec![subtree]),
2466                };
2467                assert!(matches!(
2468                    validate_general_name(&name, &constraints, 0, 1),
2469                    Err(X509ChainError::InvalidDer {
2470                        kind: "IP name constraint",
2471                        ..
2472                    })
2473                ));
2474            }
2475        }
2476    }
2477
2478    #[test]
2479    fn malformed_string_name_constraints_fail_before_matching() {
2480        use x509_parser::extensions::GeneralSubtree;
2481
2482        // Matchers assume admitted string constraints have RFC 5280 syntax.
2483        // Invalid values must not degrade into ordinary non-matches.
2484        for malformed in [
2485            GeneralName::DNSName(""),
2486            GeneralName::DNSName("example..com"),
2487            GeneralName::RFC822Name("@example.com"),
2488            GeneralName::RFC822Name("bad..local@example.com"),
2489            GeneralName::URI("https://example.com"),
2490        ] {
2491            let constraints = NameConstraints {
2492                permitted_subtrees: None,
2493                excluded_subtrees: Some(vec![GeneralSubtree { base: malformed }]),
2494            };
2495            assert!(matches!(
2496                ensure_supported_name_constraints(&constraints, 1),
2497                Err(X509ChainError::InvalidDer {
2498                    kind: "string name constraint",
2499                    ..
2500                })
2501            ));
2502        }
2503
2504        for valid in [
2505            GeneralName::DNSName("example.com"),
2506            GeneralName::DNSName(".example.com"),
2507            GeneralName::RFC822Name("ops@example.com"),
2508            GeneralName::RFC822Name("example.com"),
2509            GeneralName::URI(".example.com"),
2510        ] {
2511            let constraints = NameConstraints {
2512                permitted_subtrees: Some(vec![GeneralSubtree { base: valid }]),
2513                excluded_subtrees: None,
2514            };
2515            ensure_supported_name_constraints(&constraints, 1)
2516                .expect("valid string constraints must remain supported");
2517        }
2518    }
2519
2520    #[test]
2521    fn empty_name_constraint_collections_are_rejected() {
2522        use der::Encode as _;
2523        use x509_cert::ext::pkix::NameConstraints as EncodedNameConstraints;
2524
2525        // RFC 5280 requires at least one subtree overall and at least one entry
2526        // in every explicitly present GeneralSubtrees collection.
2527        for constraints in [
2528            EncodedNameConstraints {
2529                permitted_subtrees: None,
2530                excluded_subtrees: None,
2531            },
2532            EncodedNameConstraints {
2533                permitted_subtrees: Some(Vec::new()),
2534                excluded_subtrees: None,
2535            },
2536            EncodedNameConstraints {
2537                permitted_subtrees: None,
2538                excluded_subtrees: Some(Vec::new()),
2539            },
2540        ] {
2541            let der = constraints
2542                .to_der()
2543                .expect("malformed NameConstraints test input must encode");
2544            assert!(matches!(
2545                validate_name_constraints_der(&der, 1),
2546                Err(X509ChainError::InvalidNameConstraints { position: 1 })
2547            ));
2548        }
2549    }
2550
2551    #[test]
2552    fn unsupported_name_constraint_distances_fail_path_validation() {
2553        use der::{Encode as _, asn1::Ia5String};
2554        use x509_cert::ext::pkix::{
2555            NameConstraints as EncodedNameConstraints,
2556            constraints::name::GeneralSubtree as EncodedGeneralSubtree,
2557            name::GeneralName as EncodedGeneralName,
2558        };
2559
2560        // x509-parser exposes only GeneralSubtree::base. Exercise the complete
2561        // extension DER so unsupported distance fields cannot disappear before
2562        // RFC 5280 path validation sees them.
2563        for (permitted, minimum, maximum) in [
2564            (true, 1, None),
2565            (false, 1, None),
2566            (true, 0, Some(1)),
2567            (false, 0, Some(1)),
2568        ] {
2569            let dns_name = if permitted {
2570                "example.com"
2571            } else {
2572                "blocked.example.com"
2573            };
2574            let subtree = EncodedGeneralSubtree {
2575                base: EncodedGeneralName::DnsName(
2576                    Ia5String::new(dns_name.as_bytes()).expect("valid DNS IA5String"),
2577                ),
2578                minimum,
2579                maximum,
2580            };
2581            let constraints = EncodedNameConstraints {
2582                permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2583                excluded_subtrees: (!permitted).then(|| vec![subtree]),
2584            };
2585            let mut extension = rcgen::CustomExtension::from_oid_content(
2586                &[2, 5, 29, 30],
2587                constraints
2588                    .to_der()
2589                    .expect("NameConstraints must encode as DER"),
2590            );
2591            extension.set_criticality(true);
2592
2593            let mut root_params = generated_certificate_params("distance authority", true);
2594            root_params.custom_extensions.push(extension);
2595            let root = rcgen::CertifiedIssuer::self_signed(
2596                root_params,
2597                rcgen::KeyPair::generate().expect("root key generation should succeed"),
2598            )
2599            .expect("constrained root should be self-signable");
2600            let leaf = rcgen::CertificateParams::new(vec!["www.example.com".into()])
2601                .expect("leaf DNS SAN should be valid")
2602                .signed_by(
2603                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2604                    &root,
2605                )
2606                .expect("root should sign leaf certificate");
2607
2608            assert!(matches!(
2609                verify_generated_path(
2610                    vec![leaf.der().to_vec(), root.der().to_vec()],
2611                    root.der().to_vec(),
2612                ),
2613                Err(X509ChainError::InvalidNameConstraints { position: 1 })
2614            ));
2615        }
2616    }
2617
2618    #[test]
2619    fn name_constraints_cover_subject_email_and_directory_name() {
2620        // RFC 5280 requires subject emailAddress attributes to be checked even
2621        // without a SAN, and directoryName constraints compare RDN subtrees.
2622        let mut permitted_directory = rcgen::DistinguishedName::new();
2623        permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
2624        let mut root_params = generated_certificate_params("name authority", true);
2625        root_params.name_constraints = Some(rcgen::NameConstraints {
2626            permitted_subtrees: vec![
2627                rcgen::GeneralSubtree::Rfc822Name("example.com".into()),
2628                rcgen::GeneralSubtree::DirectoryName(permitted_directory),
2629            ],
2630            excluded_subtrees: Vec::new(),
2631        });
2632        let root = rcgen::CertifiedIssuer::self_signed(
2633            root_params,
2634            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2635        )
2636        .expect("constrained root should be self-signable");
2637
2638        for (organization, email, accepted) in [
2639            ("Example Corp", "ops@example.com", true),
2640            ("Other Corp", "ops@example.com", false),
2641            ("Example Corp", "ops@example.net", false),
2642            ("Example Corp", "bad..local@example.com", false),
2643        ] {
2644            let mut leaf_params = generated_certificate_params("name-constrained leaf", false);
2645            leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2646            leaf_params
2647                .distinguished_name
2648                .push(rcgen::DnType::OrganizationName, organization);
2649            leaf_params
2650                .distinguished_name
2651                .push(rcgen::DnType::CommonName, "name-constrained leaf");
2652            leaf_params.distinguished_name.push(
2653                rcgen::DnType::CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]),
2654                rcgen::DnValue::Ia5String(
2655                    email
2656                        .try_into()
2657                        .expect("test email must be a valid IA5String"),
2658                ),
2659            );
2660            let leaf = leaf_params
2661                .signed_by(
2662                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2663                    &root,
2664                )
2665                .expect("root should sign leaf certificate");
2666            let result = verify_generated_path(
2667                vec![leaf.der().to_vec(), root.der().to_vec()],
2668                root.der().to_vec(),
2669            );
2670            assert_eq!(
2671                result.is_ok(),
2672                accepted,
2673                "unexpected subject constraint result for {organization} / {email}: {result:?}",
2674            );
2675        }
2676    }
2677
2678    #[test]
2679    fn empty_subject_requires_a_critical_nonempty_san() {
2680        let root = rcgen::CertifiedIssuer::self_signed(
2681            generated_certificate_params("subject identity authority", true),
2682            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2683        )
2684        .expect("root should be self-signable");
2685
2686        let mut missing_san = rcgen::CertificateParams::new(Vec::new())
2687            .expect("empty SAN list should produce certificate parameters");
2688        missing_san.distinguished_name = rcgen::DistinguishedName::new();
2689        let missing_san = missing_san
2690            .signed_by(
2691                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2692                &root,
2693            )
2694            .expect("test issuer should sign an empty-subject certificate");
2695
2696        let mut noncritical_san = rcgen::CertificateParams::new(Vec::new())
2697            .expect("empty SAN list should produce certificate parameters");
2698        noncritical_san.distinguished_name = rcgen::DistinguishedName::new();
2699        // GeneralNames ::= SEQUENCE { dNSName [2] "a" }. Using a custom
2700        // extension is intentional because rcgen correctly marks its normal
2701        // SAN extension critical whenever the subject is empty.
2702        noncritical_san
2703            .custom_extensions
2704            .push(rcgen::CustomExtension::from_oid_content(
2705                &[2, 5, 29, 17],
2706                vec![0x30, 0x03, 0x82, 0x01, b'a'],
2707            ));
2708        let noncritical_san = noncritical_san
2709            .signed_by(
2710                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2711                &root,
2712            )
2713            .expect("test issuer should sign a non-critical-SAN certificate");
2714
2715        for leaf in [missing_san, noncritical_san] {
2716            assert!(matches!(
2717                verify_generated_path(
2718                    vec![leaf.der().to_vec(), root.der().to_vec()],
2719                    root.der().to_vec(),
2720                ),
2721                Err(X509ChainError::InvalidDer {
2722                    kind: "certificate subject identity",
2723                    ..
2724                })
2725            ));
2726        }
2727    }
2728
2729    #[test]
2730    fn empty_subject_with_critical_san_skips_directory_name_constraints() {
2731        // RFC 5280 permits an empty subject when a critical SAN carries the
2732        // identity. An absent DirectoryName need not match a permitted subtree.
2733        let mut permitted_directory = rcgen::DistinguishedName::new();
2734        permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
2735        let mut root_params = generated_certificate_params("empty-subject authority", true);
2736        root_params.name_constraints = Some(rcgen::NameConstraints {
2737            permitted_subtrees: vec![rcgen::GeneralSubtree::DirectoryName(permitted_directory)],
2738            excluded_subtrees: Vec::new(),
2739        });
2740        let root = rcgen::CertifiedIssuer::self_signed(
2741            root_params,
2742            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2743        )
2744        .expect("constrained root should be self-signable");
2745        let mut leaf_params = rcgen::CertificateParams::new(vec!["allowed.example".into()])
2746            .expect("DNS SAN should be valid");
2747        leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2748        let leaf = leaf_params
2749            .signed_by(
2750                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2751                &root,
2752            )
2753            .expect("root should sign empty-subject leaf");
2754
2755        verify_generated_path(
2756            vec![leaf.der().to_vec(), root.der().to_vec()],
2757            root.der().to_vec(),
2758        )
2759        .expect("only present name forms should be constrained");
2760    }
2761
2762    #[test]
2763    fn malformed_general_names_in_san_fail_path_validation() {
2764        let root = rcgen::CertifiedIssuer::self_signed(
2765            generated_certificate_params("malformed-SAN root", true),
2766            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2767        )
2768        .expect("root should be self-signable");
2769        for empty_subject in [true, false] {
2770            let mut leaf_params = generated_certificate_params("malformed-SAN leaf", false);
2771            if empty_subject {
2772                leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2773            }
2774            // GeneralNames ::= SEQUENCE { dNSName [2] <invalid IA5 octet> }.
2775            let mut malformed_san = rcgen::CustomExtension::from_oid_content(
2776                &[2, 5, 29, 17],
2777                vec![0x30, 0x03, 0x82, 0x01, 0xff],
2778            );
2779            malformed_san.set_criticality(true);
2780            leaf_params.custom_extensions.push(malformed_san);
2781            let leaf = leaf_params
2782                .signed_by(
2783                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2784                    &root,
2785                )
2786                .expect("root should sign malformed-SAN leaf");
2787
2788            assert!(matches!(
2789                verify_generated_path(
2790                    vec![leaf.der().to_vec(), root.der().to_vec()],
2791                    root.der().to_vec(),
2792                ),
2793                Err(X509ChainError::InvalidDer {
2794                    kind: "certificate subject identity",
2795                    ..
2796                })
2797            ));
2798        }
2799    }
2800
2801    #[test]
2802    fn typed_subject_alternative_names_require_rfc5280_syntax() {
2803        let root = rcgen::CertifiedIssuer::self_signed(
2804            generated_certificate_params("typed-SAN root", true),
2805            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2806        )
2807        .expect("root should be self-signable");
2808
2809        for (tag, value) in [
2810            (0x81, b"operator@".as_slice()),
2811            (0x81, b"first..last@example.com".as_slice()),
2812            (0x86, b"relative/path".as_slice()),
2813            (0x86, b"https://example.com/%zz".as_slice()),
2814            (0x86, b"https://user@other@example.com/path".as_slice()),
2815            (0x86, b"file:///path".as_slice()),
2816            (0x87, &[192, 0, 2][..]),
2817        ] {
2818            for empty_subject in [false, true] {
2819                let mut leaf_params = generated_certificate_params("typed-SAN leaf", false);
2820                if empty_subject {
2821                    leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2822                }
2823                let mut san_der = vec![
2824                    0x30,
2825                    u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
2826                    tag,
2827                    u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
2828                ];
2829                san_der.extend_from_slice(value);
2830                let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
2831                san.set_criticality(true);
2832                leaf_params.custom_extensions.push(san);
2833                let leaf = leaf_params
2834                    .signed_by(
2835                        &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2836                        &root,
2837                    )
2838                    .expect("root should sign typed-SAN leaf");
2839
2840                assert!(matches!(
2841                    verify_generated_path(
2842                        vec![leaf.der().to_vec(), root.der().to_vec()],
2843                        root.der().to_vec(),
2844                    ),
2845                    Err(X509ChainError::InvalidDer {
2846                        kind: "certificate subject identity",
2847                        ..
2848                    })
2849                ));
2850            }
2851        }
2852
2853        for (tag, value) in [
2854            (0x81, b"operator@example.com".as_slice()),
2855            (0x81, b"operator@[192.0.2.1]".as_slice()),
2856            (0x81, b"operator@[IPv6:2001:db8::1]".as_slice()),
2857            (0x81, br#""operator desk"@example.com"#.as_slice()),
2858            (0x86, b"urn:example:operator".as_slice()),
2859            (
2860                0x86,
2861                b"https://operator@example.com:8443/path?q=1#id".as_slice(),
2862            ),
2863            (0x87, &[192, 0, 2, 1][..]),
2864            (
2865                0x87,
2866                &[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1][..],
2867            ),
2868        ] {
2869            let mut leaf_params = rcgen::CertificateParams::new(Vec::new())
2870                .expect("empty SAN list should produce certificate parameters");
2871            leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2872            let mut san_der = vec![
2873                0x30,
2874                u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
2875                tag,
2876                u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
2877            ];
2878            san_der.extend_from_slice(value);
2879            let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
2880            san.set_criticality(true);
2881            leaf_params.custom_extensions.push(san);
2882            let leaf = leaf_params
2883                .signed_by(
2884                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2885                    &root,
2886                )
2887                .expect("root should sign typed-SAN leaf");
2888
2889            verify_generated_path(
2890                vec![leaf.der().to_vec(), root.der().to_vec()],
2891                root.der().to_vec(),
2892            )
2893            .expect("valid typed SAN identity must satisfy an empty subject");
2894        }
2895    }
2896
2897    fn parsed_merlin_crl(der: &[u8]) -> CertificateRevocationList<'_> {
2898        CertificateRevocationList::from_der(der)
2899            .expect("modified Merlin CRL must remain parseable")
2900            .1
2901    }
2902
2903    fn merlin_crl_der() -> Vec<u8> {
2904        let xml = include_str!(
2905            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
2906        );
2907        let document = Document::parse(xml).expect("tracked Merlin document must parse");
2908        let key_info_node = document
2909            .descendants()
2910            .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
2911            .expect("tracked Merlin document contains KeyInfo");
2912        let key_info = parse_key_info(key_info_node).expect("tracked Merlin KeyInfo must parse");
2913        let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
2914            panic!("expected X509Data")
2915        };
2916        info.crls[0].clone()
2917    }
2918
2919    #[test]
2920    fn duplicate_crl_and_entry_extension_oids_fail_closed() {
2921        use der::{Decode as _, Encode as _};
2922        use x509_cert::crl::CertificateList;
2923
2924        let original = merlin_crl_der();
2925        let mut duplicate_crl: CertificateList =
2926            CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
2927        let extensions = duplicate_crl
2928            .tbs_cert_list
2929            .crl_extensions
2930            .as_mut()
2931            .expect("tracked Merlin CRL must contain extensions");
2932        extensions.push(extensions[0].clone());
2933        let duplicate_crl = duplicate_crl
2934            .to_der()
2935            .expect("duplicate CRL extension test vector must encode");
2936        assert_eq!(
2937            validate_crl_extensions(&parsed_merlin_crl(&duplicate_crl), 0),
2938            Err(X509ChainError::InvalidCrl(0))
2939        );
2940
2941        let mut duplicate_entry: CertificateList =
2942            CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
2943        let duplicate = duplicate_entry
2944            .tbs_cert_list
2945            .crl_extensions
2946            .as_ref()
2947            .and_then(|extensions| extensions.first())
2948            .expect("tracked Merlin CRL must contain an extension")
2949            .clone();
2950        let revoked = duplicate_entry
2951            .tbs_cert_list
2952            .revoked_certificates
2953            .as_mut()
2954            .and_then(|entries| entries.first_mut())
2955            .expect("tracked Merlin CRL must contain a revoked entry");
2956        revoked.crl_entry_extensions = Some(vec![duplicate.clone(), duplicate]);
2957        let duplicate_entry = duplicate_entry
2958            .to_der()
2959            .expect("duplicate entry extension test vector must encode");
2960        assert_eq!(
2961            validate_crl_extensions(&parsed_merlin_crl(&duplicate_entry), 0),
2962            Err(X509ChainError::InvalidCrl(0))
2963        );
2964    }
2965
2966    #[test]
2967    fn malformed_revoked_certificate_serials_fail_closed() {
2968        // Mutate the signed Merlin CRL fixture without changing DER lengths so
2969        // zero and negative serials exercise the actual CRL parser path.
2970        let original = merlin_crl_der();
2971        let serial = parsed_merlin_crl(&original)
2972            .iter_revoked_certificates()
2973            .next()
2974            .expect("tracked Merlin CRL must contain a revoked entry")
2975            .raw_serial()
2976            .to_vec();
2977        let offsets = original
2978            .windows(serial.len())
2979            .enumerate()
2980            .filter_map(|(offset, bytes)| (bytes == serial).then_some(offset))
2981            .collect::<Vec<_>>();
2982        assert_eq!(
2983            offsets.len(),
2984            1,
2985            "revoked serial fixture must be unambiguous"
2986        );
2987
2988        for replacement in [vec![0; serial.len()], {
2989            let mut negative = serial.clone();
2990            negative[0] = 0x80;
2991            negative
2992        }] {
2993            let mut malformed = original.clone();
2994            malformed[offsets[0]..offsets[0] + serial.len()].copy_from_slice(&replacement);
2995            assert_eq!(
2996                validate_crl_extensions(&parsed_merlin_crl(&malformed), 0),
2997                Err(X509ChainError::InvalidCrl(0))
2998            );
2999        }
3000    }
3001
3002    #[test]
3003    fn delta_crl_indicator_is_rejected_regardless_of_criticality() {
3004        use der::{Decode as _, Encode as _, asn1::OctetString};
3005        use x509_cert::{crl::CertificateList, ext::Extension};
3006
3007        let original = merlin_crl_der();
3008        for critical in [false, true] {
3009            let mut encoded: CertificateList =
3010                CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3011            encoded
3012                .tbs_cert_list
3013                .crl_extensions
3014                .get_or_insert_default()
3015                .push(Extension {
3016                    extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.27"),
3017                    critical,
3018                    extn_value: OctetString::new([0x02, 0x01, 0x01])
3019                        .expect("DER INTEGER extension payload must be valid"),
3020                });
3021            let encoded = encoded
3022                .to_der()
3023                .expect("delta CRL indicator test vector must encode");
3024            assert_eq!(
3025                validate_crl_extensions(&parsed_merlin_crl(&encoded), 0),
3026                Err(X509ChainError::InvalidCrl(0)),
3027                "delta CRL indicator criticality must not change unsupported semantics"
3028            );
3029        }
3030    }
3031
3032    #[test]
3033    fn remove_from_crl_is_rejected_in_a_complete_crl() {
3034        use der::{Decode as _, Encode as _, asn1::OctetString};
3035        use x509_cert::{crl::CertificateList, ext::Extension};
3036
3037        let original = merlin_crl_der();
3038        for (reason, accepted) in [(1_u8, true), (8_u8, false)] {
3039            let mut encoded: CertificateList =
3040                CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3041            let revoked = encoded
3042                .tbs_cert_list
3043                .revoked_certificates
3044                .as_mut()
3045                .and_then(|entries| entries.first_mut())
3046                .expect("tracked Merlin CRL must contain a revoked entry");
3047            revoked
3048                .crl_entry_extensions
3049                .get_or_insert_default()
3050                .push(Extension {
3051                    extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.21"),
3052                    critical: false,
3053                    extn_value: OctetString::new([0x0a, 0x01, reason])
3054                        .expect("DER ENUMERATED extension payload must be valid"),
3055                });
3056            let encoded = encoded
3057                .to_der()
3058                .expect("reason-code CRL test vector must encode");
3059            let result = validate_crl_extensions(&parsed_merlin_crl(&encoded), 0);
3060            if accepted {
3061                assert_eq!(result, Ok(()), "ordinary revocation reasons remain valid");
3062            } else {
3063                assert_eq!(result, Err(X509ChainError::InvalidCrl(0)));
3064            }
3065        }
3066    }
3067
3068    #[test]
3069    fn unevaluable_uri_names_fail_closed_for_both_constraint_forms() {
3070        use x509_parser::extensions::GeneralSubtree;
3071
3072        // A URI without a DNS host is not a non-match: treating it that way
3073        // would bypass excluded URI subtrees while rejecting permitted ones.
3074        let uri = GeneralName::URI("urn:example:opaque");
3075        for constraints in [
3076            NameConstraints {
3077                permitted_subtrees: Some(vec![GeneralSubtree {
3078                    base: GeneralName::URI(".example.com"),
3079                }]),
3080                excluded_subtrees: None,
3081            },
3082            NameConstraints {
3083                permitted_subtrees: None,
3084                excluded_subtrees: Some(vec![GeneralSubtree {
3085                    base: GeneralName::URI(".example.com"),
3086                }]),
3087            },
3088        ] {
3089            assert_eq!(
3090                validate_general_name(&uri, &constraints, 0, 1),
3091                Err(X509ChainError::NameConstraintViolation {
3092                    position: 0,
3093                    constraining_position: 1,
3094                })
3095            );
3096        }
3097    }
3098
3099    #[test]
3100    fn unknown_critical_certificate_extension_fails_closed() {
3101        let root = rcgen::CertifiedIssuer::self_signed(
3102            generated_certificate_params("critical-extension root", true),
3103            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3104        )
3105        .expect("root should be self-signable");
3106        let mut leaf_params = generated_certificate_params("critical-extension leaf", false);
3107        let mut extension =
3108            rcgen::CustomExtension::from_oid_content(&[1, 2, 3, 4], vec![0x05, 0x00]);
3109        extension.set_criticality(true);
3110        leaf_params.custom_extensions.push(extension);
3111        let leaf = leaf_params
3112            .signed_by(
3113                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3114                &root,
3115            )
3116            .expect("root should sign leaf certificate");
3117
3118        assert_eq!(
3119            verify_generated_path(
3120                vec![leaf.der().to_vec(), root.der().to_vec()],
3121                root.der().to_vec(),
3122            ),
3123            Err(X509ChainError::UnsupportedCriticalExtension {
3124                position: 0,
3125                oid: "1.2.3.4".into(),
3126            })
3127        );
3128    }
3129
3130    #[test]
3131    fn duplicate_certificate_extension_oids_fail_closed() {
3132        // RFC 5280 forbids repeated extension OIDs. Enforce that certificate-wide
3133        // invariant before individual extension consumers select a first match.
3134        let root = rcgen::CertifiedIssuer::self_signed(
3135            generated_certificate_params("duplicate-extension root", true),
3136            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3137        )
3138        .expect("root should be self-signable");
3139        let mut leaf_params = generated_certificate_params("duplicate-extension leaf", false);
3140        for _ in 0..2 {
3141            leaf_params
3142                .custom_extensions
3143                .push(rcgen::CustomExtension::from_oid_content(
3144                    &[1, 2, 3, 4],
3145                    vec![0x05, 0x00],
3146                ));
3147        }
3148        let leaf = leaf_params
3149            .signed_by(
3150                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3151                &root,
3152            )
3153            .expect("root should sign leaf certificate");
3154
3155        assert_eq!(
3156            verify_generated_path(
3157                vec![leaf.der().to_vec(), root.der().to_vec()],
3158                root.der().to_vec(),
3159            ),
3160            Err(X509ChainError::DuplicateExtension {
3161                position: 0,
3162                oid: "1.2.3.4".into(),
3163            })
3164        );
3165    }
3166
3167    #[test]
3168    fn invalid_certificate_serial_numbers_fail_path_validation() {
3169        for serial in [vec![0], vec![1; 21]] {
3170            let root = rcgen::CertifiedIssuer::self_signed(
3171                generated_certificate_params("serial root", true),
3172                rcgen::KeyPair::generate().expect("root key generation should succeed"),
3173            )
3174            .expect("root should be self-signable");
3175            let mut leaf_params = generated_certificate_params("invalid serial leaf", false);
3176            leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&serial));
3177            let leaf = leaf_params
3178                .signed_by(
3179                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3180                    &root,
3181                )
3182                .expect("root should sign leaf certificate");
3183
3184            assert!(matches!(
3185                verify_generated_path(
3186                    vec![leaf.der().to_vec(), root.der().to_vec()],
3187                    root.der().to_vec(),
3188                ),
3189                Err(X509ChainError::InvalidDer {
3190                    kind: "certificate serial number",
3191                    ..
3192                })
3193            ));
3194        }
3195
3196        assert!(validate_positive_serial_bytes(&[0x80], "certificate serial number").is_err());
3197        assert!(validate_positive_serial_bytes(&[1; 20], "certificate serial number").is_ok());
3198
3199        let root = rcgen::CertifiedIssuer::self_signed(
3200            generated_certificate_params("serial-padding root", true),
3201            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3202        )
3203        .expect("root should be self-signable");
3204        let mut leaf_params = generated_certificate_params("serial-padding leaf", false);
3205        leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&[0x80; 20]));
3206        let leaf = leaf_params
3207            .signed_by(
3208                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3209                &root,
3210            )
3211            .expect("root should sign a maximum-magnitude serial");
3212
3213        verify_generated_path(
3214            vec![leaf.der().to_vec(), root.der().to_vec()],
3215            root.der().to_vec(),
3216        )
3217        .expect("a 20-octet magnitude may require a DER sign-padding octet");
3218    }
3219
3220    #[test]
3221    fn name_constraints_are_rejected_on_end_entity_certificates() {
3222        // RFC 5280 limits NameConstraints to critical CA extensions; merely
3223        // parsing the extension on an end entity must not count as processing it.
3224        let root = rcgen::CertifiedIssuer::self_signed(
3225            generated_certificate_params("name-placement root", true),
3226            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3227        )
3228        .expect("root should be self-signable");
3229        let mut leaf_params = generated_certificate_params("name-placement leaf", false);
3230        leaf_params.name_constraints = Some(rcgen::NameConstraints {
3231            permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
3232            excluded_subtrees: Vec::new(),
3233        });
3234        let leaf = leaf_params
3235            .signed_by(
3236                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3237                &root,
3238            )
3239            .expect("root should sign leaf certificate");
3240
3241        assert!(matches!(
3242            verify_generated_path(
3243                vec![leaf.der().to_vec(), root.der().to_vec()],
3244                root.der().to_vec(),
3245            ),
3246            Err(X509ChainError::InvalidNameConstraints { position: 0 })
3247        ));
3248    }
3249
3250    #[test]
3251    fn dsa_certificate_rejects_mismatched_inner_signature_algorithm() {
3252        // The signed TBSCertificate algorithm is a separate RFC 5280 invariant;
3253        // a valid signature over the original bytes must not bypass a mismatch
3254        // in the parsed metadata through the legacy DSA fallback.
3255        let (_, mut certificate) = X509Certificate::from_der(include_bytes!(
3256            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
3257        ))
3258        .expect("the tracked Merlin certificate is valid DER");
3259        let (_, issuer) = X509Certificate::from_der(include_bytes!(
3260            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3261        ))
3262        .expect("the tracked Merlin issuer is a DER certificate");
3263        assert!(verify_certificate_signature(&certificate, &issuer));
3264
3265        certificate.tbs_certificate.signature = issuer.public_key().algorithm.clone();
3266
3267        assert_ne!(
3268            certificate.tbs_certificate.signature,
3269            certificate.signature_algorithm
3270        );
3271        assert!(!verify_certificate_signature(&certificate, &issuer));
3272    }
3273
3274    #[test]
3275    fn dsa_sha1_crl_signature_uses_the_same_fallback_as_certificates() {
3276        let xml = include_str!(
3277            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3278        );
3279        let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3280        let key_info_node = document
3281            .descendants()
3282            .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3283            .expect("the Merlin document contains KeyInfo");
3284        let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3285        let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3286            panic!("expected X509Data")
3287        };
3288        let (_, issuer) = X509Certificate::from_der(include_bytes!(
3289            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3290        ))
3291        .expect("the tracked Merlin issuer is a DER certificate");
3292        let (_, crl) = CertificateRevocationList::from_der(&info.crls[0])
3293            .expect("the tracked Merlin CRL is valid DER");
3294
3295        assert!(verify_crl_signature(&crl, &issuer));
3296    }
3297
3298    #[test]
3299    fn dsa_crl_rejects_mismatched_inner_signature_algorithm() {
3300        let xml = include_str!(
3301            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3302        );
3303        let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3304        let key_info_node = document
3305            .descendants()
3306            .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3307            .expect("the Merlin document contains KeyInfo");
3308        let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3309        let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3310            panic!("expected X509Data")
3311        };
3312        let (_, issuer) = X509Certificate::from_der(include_bytes!(
3313            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3314        ))
3315        .expect("the tracked Merlin issuer is a DER certificate");
3316        let (_, mut crl) = CertificateRevocationList::from_der(&info.crls[0])
3317            .expect("the tracked Merlin CRL is valid DER");
3318        assert!(verify_crl_signature(&crl, &issuer));
3319
3320        crl.tbs_cert_list.signature = issuer.public_key().algorithm.clone();
3321
3322        assert_ne!(crl.tbs_cert_list.signature, crl.signature_algorithm);
3323        assert!(!verify_crl_signature(&crl, &issuer));
3324    }
3325}