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    verify_x509_signed_object_with_provider(
722        &certificate.signature_algorithm,
723        &certificate.tbs_certificate.signature,
724        &certificate.signature_value.data,
725        certificate.tbs_certificate.as_ref(),
726        issuer.public_key().raw,
727        provider,
728    )
729}
730
731/// Test a candidate certificate-path edge without assigning trust to either
732/// certificate. Path construction uses this only to distinguish certificates
733/// that share an issuer subject name; full policy validation still happens
734/// after the complete path has been assembled.
735#[cfg(test)]
736pub(crate) fn certificate_signature_matches(certificate_der: &[u8], issuer_der: &[u8]) -> bool {
737    certificate_signature_matches_with_provider(
738        certificate_der,
739        issuer_der,
740        crate::provider::default_provider(),
741    )
742    .unwrap_or(false)
743}
744
745pub(crate) fn certificate_signature_matches_with_provider(
746    certificate_der: &[u8],
747    issuer_der: &[u8],
748    provider: &dyn crate::provider::CryptoProvider,
749) -> Result<bool, X509ChainError> {
750    let (Ok(certificate), Ok(issuer)) = (
751        parse_certificate(certificate_der),
752        parse_certificate(issuer_der),
753    ) else {
754        return Ok(false);
755    };
756    verify_certificate_signature_with_provider(&certificate, &issuer, provider)
757}
758
759fn certificate_names_equal(
760    left: &x509_parser::x509::X509Name<'_>,
761    right: &x509_parser::x509::X509Name<'_>,
762) -> bool {
763    let (Ok(left), Ok(right)) = (x509_name_to_rfc4514(left), x509_name_to_rfc4514(right)) else {
764        return false;
765    };
766    distinguished_names_equal(&left, &right)
767}
768
769#[cfg(test)]
770fn verify_crl_signature(crl: &CertificateRevocationList<'_>, issuer: &X509Certificate<'_>) -> bool {
771    verify_crl_signature_with_provider(crl, issuer, crate::provider::default_provider())
772        .unwrap_or(false)
773}
774
775fn verify_crl_signature_with_provider(
776    crl: &CertificateRevocationList<'_>,
777    issuer: &X509Certificate<'_>,
778    provider: &dyn crate::provider::CryptoProvider,
779) -> Result<bool, X509ChainError> {
780    // RFC 5280 sections 5.1.1.2 and 5.1.2.2 impose the same equality rule on
781    // CRLs as certificates.
782    verify_x509_signed_object_with_provider(
783        &crl.signature_algorithm,
784        &crl.tbs_cert_list.signature,
785        &crl.signature_value.data,
786        crl.tbs_cert_list.as_ref(),
787        issuer.public_key().raw,
788        provider,
789    )
790}
791
792fn verify_x509_signed_object_with_provider(
793    outer_algorithm: &AlgorithmIdentifier<'_>,
794    signed_algorithm: &AlgorithmIdentifier<'_>,
795    signature_der: &[u8],
796    signed_data: &[u8],
797    issuer_spki_der: &[u8],
798    provider: &dyn crate::provider::CryptoProvider,
799) -> Result<bool, X509ChainError> {
800    if outer_algorithm != signed_algorithm {
801        return Ok(false);
802    }
803    verify_x509_signature_with_provider(
804        outer_algorithm,
805        signature_der,
806        signed_data,
807        issuer_spki_der,
808        provider,
809    )
810}
811
812fn verify_x509_signature_with_provider(
813    algorithm_identifier: &AlgorithmIdentifier<'_>,
814    signature_der: &[u8],
815    signed_data: &[u8],
816    issuer_spki_der: &[u8],
817    provider: &dyn crate::provider::CryptoProvider,
818) -> Result<bool, X509ChainError> {
819    let algorithm = x509_signature_algorithm(algorithm_identifier)?;
820    provider
821        .require_capability(crate::provider::ProviderCapability::VerifyCertificate(
822            algorithm,
823        ))
824        .map_err(X509ChainError::from)?;
825    provider
826        .verify_x509_signature(algorithm, signed_data, signature_der, issuer_spki_der)
827        .map_err(Into::into)
828}
829
830fn x509_signature_algorithm(
831    identifier: &AlgorithmIdentifier<'_>,
832) -> Result<X509SignatureAlgorithm, X509ChainError> {
833    let oid = identifier.algorithm.to_id_string();
834    let algorithm = match oid.as_str() {
835        "1.2.840.10040.4.3" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha1),
836        "2.16.840.1.101.3.4.3.1" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha224),
837        "2.16.840.1.101.3.4.3.2" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha256),
838        "2.16.840.1.101.3.4.3.3" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha384),
839        "2.16.840.1.101.3.4.3.4" => X509SignatureAlgorithm::Dsa(super::DigestAlgorithm::Sha512),
840        "1.2.840.113549.1.1.5" | "1.3.14.3.2.29" => {
841            X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha1)
842        }
843        "1.2.840.113549.1.1.14" => {
844            X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha224)
845        }
846        "1.2.840.113549.1.1.11" => {
847            X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha256)
848        }
849        "1.2.840.113549.1.1.12" => {
850            X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha384)
851        }
852        "1.2.840.113549.1.1.13" => {
853            X509SignatureAlgorithm::RsaPkcs1v15(super::DigestAlgorithm::Sha512)
854        }
855        "1.2.840.113549.1.1.10" => parse_rsa_pss_algorithm(identifier)?,
856        "1.2.840.10045.4.1" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha1),
857        "1.2.840.10045.4.3.1" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha224),
858        "1.2.840.10045.4.3.2" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha256),
859        "1.2.840.10045.4.3.3" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha384),
860        "1.2.840.10045.4.3.4" => X509SignatureAlgorithm::Ecdsa(super::DigestAlgorithm::Sha512),
861        "1.3.101.112" => X509SignatureAlgorithm::Ed25519,
862        _ => return Err(X509ChainError::UnsupportedSignatureAlgorithm { oid }),
863    };
864    match &algorithm {
865        X509SignatureAlgorithm::Dsa(_)
866        | X509SignatureAlgorithm::Ecdsa(_)
867        | X509SignatureAlgorithm::Ed25519 => require_absent_signature_parameters(identifier)?,
868        X509SignatureAlgorithm::RsaPkcs1v15(_) => {
869            require_null_or_absent_signature_parameters(identifier)?;
870        }
871        X509SignatureAlgorithm::RsaPss { .. } => {}
872    }
873    Ok(algorithm)
874}
875
876fn require_absent_signature_parameters(
877    identifier: &AlgorithmIdentifier<'_>,
878) -> Result<(), X509ChainError> {
879    if identifier.parameters.is_some() {
880        return Err(invalid_signature_parameters(
881            identifier,
882            "parameters must be absent",
883        ));
884    }
885    Ok(())
886}
887
888fn require_null_or_absent_signature_parameters(
889    identifier: &AlgorithmIdentifier<'_>,
890) -> Result<(), X509ChainError> {
891    if identifier
892        .parameters
893        .as_ref()
894        .is_some_and(|parameters| parameters.tag() != x509_parser::asn1_rs::Tag::Null)
895    {
896        return Err(invalid_signature_parameters(
897            identifier,
898            "parameters must be NULL or absent",
899        ));
900    }
901    Ok(())
902}
903
904fn invalid_signature_parameters(
905    identifier: &AlgorithmIdentifier<'_>,
906    requirement: &str,
907) -> X509ChainError {
908    X509ChainError::InvalidDer {
909        kind: "X.509 signature AlgorithmIdentifier parameters",
910        message: format!("{}: {requirement}", identifier.algorithm),
911    }
912}
913
914fn parse_rsa_pss_algorithm(
915    identifier: &AlgorithmIdentifier<'_>,
916) -> Result<X509SignatureAlgorithm, X509ChainError> {
917    let parameters = identifier
918        .parameters
919        .as_ref()
920        .ok_or_else(|| X509ChainError::InvalidDer {
921            kind: "RSASSA-PSS parameters",
922            message: "missing parameters".into(),
923        })?;
924    let parameters = x509_parser::signature_algorithm::RsaSsaPssParams::try_from(parameters)
925        .map_err(|error| X509ChainError::InvalidDer {
926            kind: "RSASSA-PSS parameters",
927            message: error.to_string(),
928        })?;
929    if parameters.trailer_field() != 1 {
930        return Err(X509ChainError::InvalidDer {
931            kind: "RSASSA-PSS parameters",
932            message: "trailerField must be 1".into(),
933        });
934    }
935    let digest = x509_digest_algorithm(&parameters.hash_algorithm_oid().to_id_string())?;
936    let mask = parameters
937        .mask_gen_algorithm()
938        .map_err(|error| X509ChainError::InvalidDer {
939            kind: "RSASSA-PSS parameters",
940            message: error.to_string(),
941        })?;
942    if mask.mgf.to_id_string() != "1.2.840.113549.1.1.8" {
943        return Err(X509ChainError::UnsupportedSignatureAlgorithm {
944            oid: mask.mgf.to_id_string(),
945        });
946    }
947    let mgf_digest = x509_digest_algorithm(&mask.hash.to_id_string())?;
948    let salt_len =
949        usize::try_from(parameters.salt_length()).map_err(|_| X509ChainError::InvalidDer {
950            kind: "RSASSA-PSS parameters",
951            message: "saltLength does not fit this platform".into(),
952        })?;
953    Ok(X509SignatureAlgorithm::RsaPss {
954        digest,
955        mgf_digest,
956        salt_len,
957    })
958}
959
960fn x509_digest_algorithm(oid: &str) -> Result<super::DigestAlgorithm, X509ChainError> {
961    match oid {
962        "1.3.14.3.2.26" => Ok(super::DigestAlgorithm::Sha1),
963        "2.16.840.1.101.3.4.2.4" => Ok(super::DigestAlgorithm::Sha224),
964        "2.16.840.1.101.3.4.2.1" => Ok(super::DigestAlgorithm::Sha256),
965        "2.16.840.1.101.3.4.2.2" => Ok(super::DigestAlgorithm::Sha384),
966        "2.16.840.1.101.3.4.2.3" => Ok(super::DigestAlgorithm::Sha512),
967        _ => Err(X509ChainError::UnsupportedSignatureAlgorithm {
968            oid: oid.to_owned(),
969        }),
970    }
971}
972
973fn validate_leaf_key_usage(cert: &X509Certificate<'_>) -> Result<(), X509ChainError> {
974    // RFC 5280 section 4.2.1.3 restricts key purpose only when KeyUsage is present.
975    if cert
976        .key_usage()
977        .map_err(|error| X509ChainError::InvalidDer {
978            kind: "certificate KeyUsage",
979            message: error.to_string(),
980        })?
981        .is_some_and(|usage| !usage.value.digital_signature() && !usage.value.non_repudiation())
982    {
983        return Err(X509ChainError::InvalidKeyUsage {
984            position: 0,
985            required: "digitalSignature or nonRepudiation",
986        });
987    }
988    Ok(())
989}
990
991fn validate_extended_key_usage(
992    cert: &X509Certificate<'_>,
993    position: usize,
994    effective_extended_key_usages: &mut Option<HashSet<ExtendedKeyPurpose>>,
995) -> Result<(), X509ChainError> {
996    let Some(usage) = cert
997        .extended_key_usage()
998        .map_err(|error| X509ChainError::InvalidDer {
999            kind: "certificate ExtendedKeyUsage",
1000            message: error.to_string(),
1001        })?
1002    else {
1003        return Ok(());
1004    };
1005    if usage.value.any {
1006        return Ok(());
1007    }
1008    if let Some(effective) = effective_extended_key_usages {
1009        effective.retain(|purpose| extended_key_usage_contains(usage.value, purpose));
1010        if !effective.is_empty() {
1011            return Ok(());
1012        }
1013    }
1014    Err(X509ChainError::InvalidKeyUsage {
1015        position,
1016        required: "an approved extended key usage",
1017    })
1018}
1019
1020fn extended_key_usage_contains(
1021    usage: &x509_parser::extensions::ExtendedKeyUsage<'_>,
1022    purpose: &ExtendedKeyPurpose,
1023) -> bool {
1024    match purpose {
1025        ExtendedKeyPurpose::ServerAuth => usage.server_auth,
1026        ExtendedKeyPurpose::ClientAuth => usage.client_auth,
1027        ExtendedKeyPurpose::CodeSigning => usage.code_signing,
1028        ExtendedKeyPurpose::EmailProtection => usage.email_protection,
1029        ExtendedKeyPurpose::TimeStamping => usage.time_stamping,
1030        ExtendedKeyPurpose::OcspSigning => usage.ocsp_signing,
1031        ExtendedKeyPurpose::Other(arcs) => usage.other.iter().any(|oid| {
1032            let oid = oid.to_id_string();
1033            arcs.iter()
1034                .map(u64::to_string)
1035                .collect::<Vec<_>>()
1036                .join(".")
1037                == oid
1038        }),
1039    }
1040}
1041
1042fn parse_certificate(der: &[u8]) -> Result<X509Certificate<'_>, X509ChainError> {
1043    let (rest, cert) =
1044        X509Certificate::from_der(der).map_err(|error| X509ChainError::InvalidDer {
1045            kind: "certificate",
1046            message: error.to_string(),
1047        })?;
1048    if !rest.is_empty() {
1049        return Err(X509ChainError::InvalidDer {
1050            kind: "certificate",
1051            message: "trailing data".into(),
1052        });
1053    }
1054    Ok(cert)
1055}
1056
1057fn system_time_to_asn1(time: SystemTime) -> Result<ASN1Time, X509ChainError> {
1058    let seconds = time
1059        .duration_since(UNIX_EPOCH)
1060        .map_err(|_| X509ChainError::CertificateNotValid(0))?
1061        .as_secs();
1062    let timestamp = i64::try_from(seconds).map_err(|_| X509ChainError::CertificateNotValid(0))?;
1063    ASN1Time::from_timestamp(timestamp).map_err(|error| X509ChainError::InvalidDer {
1064        kind: "verification time",
1065        message: error.to_string(),
1066    })
1067}
1068
1069fn validate_ca_constraints(
1070    cert: &X509Certificate<'_>,
1071    position: usize,
1072) -> Result<(), X509ChainError> {
1073    let extension = cert
1074        .extensions()
1075        .iter()
1076        .find(|extension| {
1077            matches!(
1078                extension.parsed_extension(),
1079                ParsedExtension::BasicConstraints(_)
1080            )
1081        })
1082        .ok_or(X509ChainError::IssuerNotCa(position))?;
1083    let ParsedExtension::BasicConstraints(constraints) = extension.parsed_extension() else {
1084        unreachable!("extension was selected by parsed type")
1085    };
1086    if !constraints.ca {
1087        return Err(X509ChainError::IssuerNotCa(position));
1088    }
1089    // RFC 5280 section 4.2.1.9 requires conforming issuers to mark CA
1090    // BasicConstraints critical, but the path-validation algorithm requires
1091    // the cA assertion and does not turn issuer non-conformance into a path
1092    // failure. OpenSSL/xmlsec1 accepts historical non-critical CA extensions.
1093
1094    if cert
1095        .key_usage()
1096        .map_err(|error| X509ChainError::InvalidDer {
1097            kind: "certificate KeyUsage",
1098            message: error.to_string(),
1099        })?
1100        .is_some_and(|usage| !usage.value.key_cert_sign())
1101    {
1102        return Err(X509ChainError::InvalidKeyUsage {
1103            position,
1104            required: "keyCertSign",
1105        });
1106    }
1107
1108    Ok(())
1109}
1110
1111fn basic_constraints(
1112    cert: &X509Certificate<'_>,
1113) -> Option<x509_parser::extensions::BasicConstraints> {
1114    cert.extensions()
1115        .iter()
1116        .find_map(|extension| match extension.parsed_extension() {
1117            ParsedExtension::BasicConstraints(value) => Some(value.clone()),
1118            _ => None,
1119        })
1120}
1121
1122fn validate_path_length_constraints(path: &[X509Certificate<'_>]) -> Result<(), X509ChainError> {
1123    for (position, cert) in path.iter().enumerate().skip(1) {
1124        let Some(limit) = basic_constraints(cert).and_then(|value| value.path_len_constraint)
1125        else {
1126            continue;
1127        };
1128        let subordinate_ca_count = path[1..position]
1129            .iter()
1130            .filter(|subordinate| {
1131                basic_constraints(subordinate).is_some_and(|value| value.ca)
1132                    && !certificate_names_equal(subordinate.subject(), subordinate.issuer())
1133            })
1134            .count();
1135        if subordinate_ca_count > limit as usize {
1136            return Err(X509ChainError::PathLengthExceeded { position, limit });
1137        }
1138    }
1139    Ok(())
1140}
1141
1142fn validate_critical_extensions(
1143    cert: &X509Certificate<'_>,
1144    position: usize,
1145) -> Result<(), X509ChainError> {
1146    for extension in cert
1147        .extensions()
1148        .iter()
1149        .filter(|extension| extension.critical)
1150    {
1151        let oid = extension.oid.to_id_string();
1152        if !matches!(
1153            oid.as_str(),
1154            "2.5.29.15" | "2.5.29.17" | "2.5.29.19" | "2.5.29.30" | "2.5.29.37"
1155        ) {
1156            return Err(X509ChainError::UnsupportedCriticalExtension { position, oid });
1157        }
1158        if matches!(
1159            extension.parsed_extension(),
1160            ParsedExtension::UnsupportedExtension { .. }
1161                | ParsedExtension::ParseError { .. }
1162                | ParsedExtension::Unparsed
1163        ) {
1164            return Err(X509ChainError::UnsupportedCriticalExtension { position, oid });
1165        }
1166    }
1167    Ok(())
1168}
1169
1170fn validate_name_constraints(path: &[X509Certificate<'_>]) -> Result<(), X509ChainError> {
1171    for (position, certificate) in path.iter().enumerate() {
1172        if let Some(extension) = certificate
1173            .extensions()
1174            .iter()
1175            .find(|extension| extension.oid.to_id_string() == "2.5.29.30")
1176            && (position == 0 || !extension.critical)
1177        {
1178            return Err(X509ChainError::InvalidNameConstraints { position });
1179        }
1180    }
1181    for (constraining_position, issuer) in path.iter().enumerate().skip(1) {
1182        let Some(extension) = issuer
1183            .extensions()
1184            .iter()
1185            .find(|extension| extension.oid.to_id_string() == "2.5.29.30")
1186        else {
1187            continue;
1188        };
1189        let ParsedExtension::NameConstraints(constraints) = extension.parsed_extension() else {
1190            continue;
1191        };
1192        validate_name_constraints_der(extension.value, constraining_position)?;
1193        ensure_supported_name_constraints(constraints, constraining_position)?;
1194        for (position, subordinate) in path[..constraining_position].iter().enumerate() {
1195            // The target certificate is always checked. Self-issued CA rollover
1196            // certificates between it and the constraint issuer are exempt.
1197            if position != 0 && certificate_names_equal(subordinate.subject(), subordinate.issuer())
1198            {
1199                continue;
1200            }
1201            validate_certificate_names(subordinate, constraints, position, constraining_position)?;
1202        }
1203    }
1204    Ok(())
1205}
1206
1207fn validate_name_constraints_der(
1208    extension_der: &[u8],
1209    position: usize,
1210) -> Result<(), X509ChainError> {
1211    use der::Decode as _;
1212
1213    // x509-parser intentionally omits GeneralSubtree distance fields from its
1214    // public model. Decode the raw extension as well so they cannot silently
1215    // acquire the zero-minimum, unbounded semantics implemented below.
1216    let constraints =
1217        x509_cert::ext::pkix::NameConstraints::from_der(extension_der).map_err(|error| {
1218            X509ChainError::InvalidDer {
1219                kind: "NameConstraints",
1220                message: error.to_string(),
1221            }
1222        })?;
1223    if constraints.permitted_subtrees.is_none() && constraints.excluded_subtrees.is_none()
1224        || constraints
1225            .permitted_subtrees
1226            .as_ref()
1227            .is_some_and(Vec::is_empty)
1228        || constraints
1229            .excluded_subtrees
1230            .as_ref()
1231            .is_some_and(Vec::is_empty)
1232    {
1233        return Err(X509ChainError::InvalidNameConstraints { position });
1234    }
1235    let unsupported = constraints
1236        .permitted_subtrees
1237        .iter()
1238        .flatten()
1239        .chain(constraints.excluded_subtrees.iter().flatten())
1240        .any(|subtree| subtree.minimum != 0 || subtree.maximum.is_some());
1241    if unsupported {
1242        return Err(X509ChainError::InvalidNameConstraints { position });
1243    }
1244    Ok(())
1245}
1246
1247fn ensure_supported_name_constraints(
1248    constraints: &NameConstraints<'_>,
1249    position: usize,
1250) -> Result<(), X509ChainError> {
1251    for subtree in constraints
1252        .permitted_subtrees
1253        .iter()
1254        .flatten()
1255        .chain(constraints.excluded_subtrees.iter().flatten())
1256    {
1257        match &subtree.base {
1258            GeneralName::DNSName(value) | GeneralName::URI(value) => {
1259                validate_dns_name_constraint(value)?;
1260            }
1261            GeneralName::RFC822Name(value) => validate_email_name_constraint(value)?,
1262            GeneralName::IPAddress(bytes) => {
1263                validate_ip_name_constraint(bytes)?;
1264            }
1265            _ => {}
1266        }
1267        if matches!(
1268            subtree.base,
1269            GeneralName::OtherName(..)
1270                | GeneralName::X400Address(..)
1271                | GeneralName::EDIPartyName(..)
1272                | GeneralName::RegisteredID(..)
1273                | GeneralName::Invalid(..)
1274        ) {
1275            return Err(X509ChainError::UnsupportedCriticalExtension {
1276                position,
1277                oid: "2.5.29.30".into(),
1278            });
1279        }
1280    }
1281    Ok(())
1282}
1283
1284fn validate_email_name_constraint(value: &str) -> Result<(), X509ChainError> {
1285    if value.contains('@') {
1286        if !mailbox_has_valid_syntax(value) {
1287            return Err(invalid_string_name_constraint(value));
1288        }
1289        Ok(())
1290    } else {
1291        validate_dns_name_constraint(value)
1292    }
1293}
1294
1295fn validate_dns_name_constraint(value: &str) -> Result<(), X509ChainError> {
1296    if !dns_name_has_valid_syntax(value, true) {
1297        return Err(invalid_string_name_constraint(value));
1298    }
1299    Ok(())
1300}
1301
1302fn validate_rfc5280_dns_name(value: &str) -> Result<(), X509ChainError> {
1303    if !dns_name_has_valid_syntax(value, false) {
1304        return Err(X509ChainError::InvalidDer {
1305            kind: "certificate DNS name",
1306            message: format!("invalid RFC 5280 dNSName: {value:?}"),
1307        });
1308    }
1309    Ok(())
1310}
1311
1312fn dns_name_has_valid_syntax(value: &str, allow_leading_dot: bool) -> bool {
1313    let domain = if allow_leading_dot {
1314        value.strip_prefix('.').unwrap_or(value)
1315    } else {
1316        value
1317    };
1318    if domain.is_empty()
1319        || domain.len() > 253
1320        || domain.split('.').any(|label| {
1321            label.is_empty()
1322                || label.len() > 63
1323                || !label
1324                    .bytes()
1325                    .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
1326                || !label
1327                    .as_bytes()
1328                    .first()
1329                    .is_some_and(u8::is_ascii_alphanumeric)
1330                || !label
1331                    .as_bytes()
1332                    .last()
1333                    .is_some_and(u8::is_ascii_alphanumeric)
1334        })
1335    {
1336        return false;
1337    }
1338    true
1339}
1340
1341fn invalid_string_name_constraint(value: &str) -> X509ChainError {
1342    X509ChainError::InvalidDer {
1343        kind: "string name constraint",
1344        message: format!("invalid RFC 5280 string name constraint: {value:?}"),
1345    }
1346}
1347
1348fn validate_certificate_names(
1349    certificate: &X509Certificate<'_>,
1350    constraints: &NameConstraints<'_>,
1351    position: usize,
1352    constraining_position: usize,
1353) -> Result<(), X509ChainError> {
1354    if certificate.subject().iter().next().is_some() {
1355        let subject = GeneralName::DirectoryName(certificate.subject().clone());
1356        validate_general_name(&subject, constraints, position, constraining_position)?;
1357    }
1358    for attribute in certificate.subject().iter_email() {
1359        let email = attribute
1360            .as_str()
1361            .map_err(|error| X509ChainError::InvalidDer {
1362                kind: "certificate subject emailAddress",
1363                message: error.to_string(),
1364            })?;
1365        validate_general_name(
1366            &GeneralName::RFC822Name(email),
1367            constraints,
1368            position,
1369            constraining_position,
1370        )?;
1371    }
1372    if let Some(names) =
1373        certificate
1374            .extensions()
1375            .iter()
1376            .find_map(|extension| match extension.parsed_extension() {
1377                ParsedExtension::SubjectAlternativeName(value) => Some(&value.general_names),
1378                _ => None,
1379            })
1380    {
1381        for name in names {
1382            validate_general_name(name, constraints, position, constraining_position)?;
1383        }
1384    }
1385    Ok(())
1386}
1387
1388fn validate_general_name(
1389    name: &GeneralName<'_>,
1390    constraints: &NameConstraints<'_>,
1391    position: usize,
1392    constraining_position: usize,
1393) -> Result<(), X509ChainError> {
1394    let permitted = constraints
1395        .permitted_subtrees
1396        .iter()
1397        .flatten()
1398        .filter(|subtree| general_names_have_same_form(name, &subtree.base));
1399    let mut has_permitted_form = false;
1400    let mut matches_permitted = false;
1401    for subtree in permitted {
1402        has_permitted_form = true;
1403        matches_permitted |=
1404            general_name_within_subtree(name, &subtree.base)? == NameConstraintMatch::Match;
1405    }
1406    let excluded = constraints
1407        .excluded_subtrees
1408        .iter()
1409        .flatten()
1410        .filter(|subtree| general_names_have_same_form(name, &subtree.base))
1411        .try_fold(false, |rejected, subtree| {
1412            general_name_within_subtree(name, &subtree.base)
1413                .map(|current| rejected || current != NameConstraintMatch::NoMatch)
1414        })?;
1415    if excluded || (has_permitted_form && !matches_permitted) {
1416        return Err(X509ChainError::NameConstraintViolation {
1417            position,
1418            constraining_position,
1419        });
1420    }
1421    Ok(())
1422}
1423
1424fn general_names_have_same_form(left: &GeneralName<'_>, right: &GeneralName<'_>) -> bool {
1425    matches!(
1426        (left, right),
1427        (GeneralName::RFC822Name(_), GeneralName::RFC822Name(_))
1428            | (GeneralName::DNSName(_), GeneralName::DNSName(_))
1429            | (GeneralName::DirectoryName(_), GeneralName::DirectoryName(_))
1430            | (GeneralName::URI(_), GeneralName::URI(_))
1431            | (GeneralName::IPAddress(_), GeneralName::IPAddress(_))
1432    )
1433}
1434
1435#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1436enum NameConstraintMatch {
1437    Match,
1438    NoMatch,
1439    Unevaluable,
1440}
1441
1442impl From<bool> for NameConstraintMatch {
1443    fn from(matched: bool) -> Self {
1444        if matched { Self::Match } else { Self::NoMatch }
1445    }
1446}
1447
1448fn general_name_within_subtree(
1449    name: &GeneralName<'_>,
1450    subtree: &GeneralName<'_>,
1451) -> Result<NameConstraintMatch, X509ChainError> {
1452    Ok(match (name, subtree) {
1453        (GeneralName::DNSName(name), GeneralName::DNSName(subtree)) => {
1454            dns_name_within_subtree(name, subtree, true).into()
1455        }
1456        (GeneralName::RFC822Name(name), GeneralName::RFC822Name(subtree)) => {
1457            email_within_subtree(name, subtree).into()
1458        }
1459        (GeneralName::DirectoryName(name), GeneralName::DirectoryName(subtree)) => {
1460            let name = x509_name_to_rfc4514(name).map_err(|error| X509ChainError::InvalidDer {
1461                kind: "certificate name constraint",
1462                message: error.to_string(),
1463            })?;
1464            let subtree =
1465                x509_name_to_rfc4514(subtree).map_err(|error| X509ChainError::InvalidDer {
1466                    kind: "certificate name constraint",
1467                    message: error.to_string(),
1468                })?;
1469            distinguished_name_within_subtree(&name, &subtree).into()
1470        }
1471        (GeneralName::URI(name), GeneralName::URI(subtree)) => uri_host(name)
1472            .map_or(NameConstraintMatch::Unevaluable, |host| {
1473                dns_name_within_subtree(host, subtree, false).into()
1474            }),
1475        (GeneralName::IPAddress(name), GeneralName::IPAddress(subtree)) => {
1476            ip_address_within_subtree(name, subtree)?.into()
1477        }
1478        _ => NameConstraintMatch::NoMatch,
1479    })
1480}
1481
1482fn dns_name_within_subtree(name: &str, subtree: &str, include_subdomains: bool) -> bool {
1483    let name = name.trim_end_matches('.');
1484    let subtree = subtree.trim_end_matches('.');
1485    if let Some(domain) = subtree.strip_prefix('.') {
1486        return name.len() > domain.len()
1487            && name.as_bytes()[name.len() - domain.len() - 1] == b'.'
1488            && name[name.len() - domain.len()..].eq_ignore_ascii_case(domain);
1489    }
1490    name.eq_ignore_ascii_case(subtree)
1491        || (include_subdomains
1492            && name.len() > subtree.len()
1493            && name.as_bytes()[name.len() - subtree.len() - 1] == b'.'
1494            && name[name.len() - subtree.len()..].eq_ignore_ascii_case(subtree))
1495}
1496
1497fn email_within_subtree(name: &str, subtree: &str) -> bool {
1498    let Some((local, domain)) = name.rsplit_once('@') else {
1499        return false;
1500    };
1501    if let Some((expected_local, expected_domain)) = subtree.rsplit_once('@') {
1502        return local == expected_local && domain.eq_ignore_ascii_case(expected_domain);
1503    }
1504    dns_name_within_subtree(domain, subtree, false)
1505}
1506
1507fn uri_host(uri: &str) -> Option<&str> {
1508    let authority = uri.split_once("://")?.1;
1509    let authority = authority.split(['/', '?', '#']).next()?;
1510    match parse_uri_authority_host(authority)? {
1511        UriAuthorityHost::Dns(host) => Some(host),
1512        UriAuthorityHost::Ip => None,
1513    }
1514}
1515
1516fn ip_address_within_subtree(address: &[u8], subtree: &[u8]) -> Result<bool, X509ChainError> {
1517    if !matches!(address.len(), 4 | 16) {
1518        return Err(X509ChainError::InvalidDer {
1519            kind: "IP subject alternative name",
1520            message: format!("expected 4 or 16 octets, got {}", address.len()),
1521        });
1522    }
1523    let (network, mask) = validate_ip_name_constraint(subtree)?;
1524    if network.len() != address.len() {
1525        return Ok(false);
1526    }
1527    Ok(address
1528        .iter()
1529        .zip(network)
1530        .zip(mask)
1531        .all(|((address, network), mask)| address & mask == network & mask))
1532}
1533
1534fn validate_ip_name_constraint(subtree: &[u8]) -> Result<(&[u8], &[u8]), X509ChainError> {
1535    if !matches!(subtree.len(), 8 | 32) {
1536        return Err(X509ChainError::InvalidDer {
1537            kind: "IP name constraint",
1538            message: format!("expected 8 or 32 octets, got {}", subtree.len()),
1539        });
1540    }
1541    let (network, mask) = subtree.split_at(subtree.len() / 2);
1542    if !ip_mask_is_contiguous(mask) {
1543        return Err(X509ChainError::InvalidDer {
1544            kind: "IP name constraint",
1545            message: "network mask is not contiguous".into(),
1546        });
1547    }
1548    Ok((network, mask))
1549}
1550
1551fn ip_mask_is_contiguous(mask: &[u8]) -> bool {
1552    let mut zero_seen = false;
1553    for byte in mask {
1554        for bit in (0..8).rev() {
1555            let set = byte & (1 << bit) != 0;
1556            if zero_seen && set {
1557                return false;
1558            }
1559            zero_seen |= !set;
1560        }
1561    }
1562    true
1563}
1564
1565fn certificate_subject_key_identifier<'a>(
1566    certificate: &'a X509Certificate<'a>,
1567) -> Option<&'a [u8]> {
1568    certificate
1569        .extensions()
1570        .iter()
1571        .find_map(|extension| match extension.parsed_extension() {
1572            ParsedExtension::SubjectKeyIdentifier(identifier) => Some(identifier.0),
1573            _ => None,
1574        })
1575}
1576
1577fn crl_authority_key_matches(
1578    crl: &CertificateRevocationList<'_>,
1579    issuer: &X509Certificate<'_>,
1580) -> Result<Option<bool>, X509ChainError> {
1581    let authority_key = crl
1582        .extensions()
1583        .iter()
1584        .find(|extension| extension.oid.to_id_string() == "2.5.29.35")
1585        .map(|extension| match extension.parsed_extension() {
1586            ParsedExtension::AuthorityKeyIdentifier(identifier) => {
1587                Ok(identifier.key_identifier.as_ref().map(|key| key.0))
1588            }
1589            _ => Err(X509ChainError::InvalidDer {
1590                kind: "CRL AuthorityKeyIdentifier",
1591                message: "extension could not be decoded".into(),
1592            }),
1593        })
1594        .transpose()?
1595        .flatten();
1596    Ok(authority_key
1597        .zip(certificate_subject_key_identifier(issuer))
1598        .map(|(authority, subject)| authority == subject))
1599}
1600
1601fn validate_crl_extensions(
1602    crl: &CertificateRevocationList<'_>,
1603    crl_index: usize,
1604) -> Result<(), X509ChainError> {
1605    validate_crl_extension_uniqueness(crl, crl_index)?;
1606    validate_crl_extension_semantics(crl, crl_index)
1607}
1608
1609fn validate_crl_extension_uniqueness(
1610    crl: &CertificateRevocationList<'_>,
1611    crl_index: usize,
1612) -> Result<(), X509ChainError> {
1613    crl.tbs_cert_list
1614        .extensions_map()
1615        .map_err(|_| X509ChainError::InvalidCrl(crl_index))?;
1616    for revoked in crl.iter_revoked_certificates() {
1617        validate_positive_serial_bytes(revoked.raw_serial(), "CRL revoked certificate serial")
1618            .map_err(|_| X509ChainError::InvalidCrl(crl_index))?;
1619        revoked
1620            .extensions_map()
1621            .map_err(|_| X509ChainError::InvalidCrl(crl_index))?;
1622    }
1623    Ok(())
1624}
1625
1626fn validate_crl_extension_semantics(
1627    crl: &CertificateRevocationList<'_>,
1628    crl_index: usize,
1629) -> Result<(), X509ChainError> {
1630    for extension in crl.extensions() {
1631        let oid = extension.oid.to_id_string();
1632        // IssuingDistributionPoint changes which certificates and issuers a CRL
1633        // covers. Delta CRLs also cannot be treated as complete CRLs: in particular,
1634        // removeFromCRL has the opposite meaning from a complete-list revocation.
1635        if matches!(oid.as_str(), "2.5.29.27" | "2.5.29.28")
1636            || (extension.critical && oid != "2.5.29.35")
1637        {
1638            return Err(X509ChainError::InvalidCrl(crl_index));
1639        }
1640        if oid == "2.5.29.35"
1641            && !matches!(
1642                extension.parsed_extension(),
1643                ParsedExtension::AuthorityKeyIdentifier(_)
1644            )
1645        {
1646            return Err(X509ChainError::InvalidCrl(crl_index));
1647        }
1648    }
1649    for revoked in crl.iter_revoked_certificates() {
1650        for extension in revoked.extensions() {
1651            let oid = extension.oid.to_id_string();
1652            // certificateIssuer carries the issuer identity for indirect CRLs.
1653            // removeFromCRL is meaningful only in a delta CRL, which this
1654            // complete-CRL validator rejects above.
1655            let invalid_reason = oid == "2.5.29.21"
1656                && !matches!(
1657                    extension.parsed_extension(),
1658                    ParsedExtension::ReasonCode(code)
1659                        if *code != x509_parser::x509::ReasonCode::RemoveFromCRL
1660                );
1661            if oid == "2.5.29.29" || extension.critical || invalid_reason {
1662                return Err(X509ChainError::InvalidCrl(crl_index));
1663            }
1664        }
1665    }
1666    Ok(())
1667}
1668
1669fn verify_crls(
1670    path: &[X509Certificate<'_>],
1671    crl_der: &[Vec<u8>],
1672    verification_time: ASN1Time,
1673    provider: &dyn crate::provider::CryptoProvider,
1674) -> Result<(), X509ChainError> {
1675    let crls = crl_der
1676        .iter()
1677        .enumerate()
1678        .map(|(idx, der)| {
1679            let (rest, crl) = CertificateRevocationList::from_der(der).map_err(|error| {
1680                X509ChainError::InvalidDer {
1681                    kind: "CRL",
1682                    message: error.to_string(),
1683                }
1684            })?;
1685            if !rest.is_empty() {
1686                return Err(X509ChainError::InvalidDer {
1687                    kind: "CRL",
1688                    message: "trailing data".into(),
1689                });
1690            }
1691            Ok((idx, crl))
1692        })
1693        .collect::<Result<Vec<_>, _>>()?;
1694
1695    for (position, cert) in path.iter().enumerate().take(path.len().saturating_sub(1)) {
1696        let issuer = &path[position + 1];
1697        for (crl_index, crl) in crls
1698            .iter()
1699            .filter(|(_, crl)| certificate_names_equal(crl.issuer(), cert.issuer()))
1700        {
1701            // Duplicate OIDs make first-match AKI filtering ambiguous, so this
1702            // structural invariant must hold before key applicability is tested.
1703            validate_crl_extension_uniqueness(crl, *crl_index)?;
1704            let authority_key_match = crl_authority_key_matches(crl, issuer)?;
1705            if authority_key_match == Some(false) {
1706                continue;
1707            }
1708            if !verify_crl_signature_with_provider(crl, issuer, provider)? {
1709                if authority_key_match == Some(true) {
1710                    return Err(X509ChainError::InvalidCrl(*crl_index));
1711                }
1712                continue;
1713            }
1714            // Extension semantics can reject an applicable CRL, but unrelated
1715            // untrusted CRL material must not influence the selected path.
1716            validate_crl_extensions(crl, *crl_index)?;
1717            validate_crl_issuer_key_usage(issuer, position + 1)?;
1718            // RFC 5280 requires conforming CRL issuers to provide nextUpdate;
1719            // without it this verifier cannot establish a bounded freshness window.
1720            let time_valid = crl.next_update().is_some_and(|next| {
1721                crl.last_update() <= verification_time && verification_time <= next
1722            });
1723            if !time_valid {
1724                return Err(X509ChainError::InvalidCrl(*crl_index));
1725            }
1726            if crl.iter_revoked_certificates().any(|revoked| {
1727                revoked.raw_serial() == cert.raw_serial()
1728                    && revoked.revocation_date <= verification_time
1729            }) {
1730                return Err(X509ChainError::Revoked(position));
1731            }
1732        }
1733    }
1734    Ok(())
1735}
1736
1737/// Enforce the certificate-version-specific authorization for an authenticated CRL.
1738fn validate_crl_issuer_key_usage(
1739    issuer: &X509Certificate<'_>,
1740    position: usize,
1741) -> Result<(), X509ChainError> {
1742    // RFC 10007 section 4 updates RFC 5280 6.3.3(f): v3 requires both
1743    // KeyUsage and cRLSign; v1/v2 have no extensions field and are exempt.
1744    // https://www.rfc-editor.org/rfc/rfc10007.html#section-4
1745    let usage = issuer
1746        .key_usage()
1747        .map_err(|error| X509ChainError::InvalidDer {
1748            kind: "certificate KeyUsage",
1749            message: error.to_string(),
1750        })?;
1751    let authorized = match issuer.version() {
1752        x509_parser::x509::X509Version::V1 | x509_parser::x509::X509Version::V2 => {
1753            usage.is_none_or(|usage| usage.value.crl_sign())
1754        }
1755        x509_parser::x509::X509Version::V3 => usage.is_some_and(|usage| usage.value.crl_sign()),
1756        version => {
1757            // RFC 5280 section 4.1.2.1 defines only v1, v2, and v3. An unknown value cannot
1758            // inherit RFC 10007 section 4's narrowly defined v1/v2 KeyUsage exemption.
1759            // https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.1
1760            return Err(X509ChainError::InvalidDer {
1761                kind: "certificate version",
1762                message: format!("unsupported X.509 version value {}", version.0),
1763            });
1764        }
1765    };
1766    if !authorized {
1767        return Err(X509ChainError::InvalidKeyUsage {
1768            position,
1769            required: "cRLSign",
1770        });
1771    }
1772    Ok(())
1773}
1774
1775#[cfg(test)]
1776mod tests {
1777    use std::str::FromStr as _;
1778
1779    use super::*;
1780    use crate::xml::dom::Document;
1781    use crate::xmldsig::{KeyInfoSource, parse::XMLDSIG_NS, parse_key_info};
1782    use p256::pkcs8::EncodePublicKey;
1783    use sha2::{Digest, Sha256, Sha384};
1784    use signature::hazmat::PrehashSigner;
1785    use std::time::Duration;
1786    use x509_parser::oid_registry::{OID_SIG_ECDSA_WITH_SHA256, OID_SIG_ECDSA_WITH_SHA384, Oid};
1787
1788    #[test]
1789    fn crl_key_usage_absence_depends_on_certificate_version() {
1790        // Isolate RFC 10007's v1/v2 exemption from the chain's separate CA constraints.
1791        let params = rcgen::CertificateParams::new(Vec::new()).expect("empty SANs are valid");
1792        let certificate = params
1793            .self_signed(&rcgen::KeyPair::generate().expect("test key generation"))
1794            .expect("test certificate signing");
1795        let (_, mut parsed) =
1796            X509Certificate::from_der(certificate.der()).expect("generated certificate DER");
1797        assert!(parsed.key_usage().expect("generated extensions").is_none());
1798        for version in [
1799            x509_parser::x509::X509Version::V1,
1800            x509_parser::x509::X509Version::V2,
1801        ] {
1802            parsed.tbs_certificate.version = version;
1803            assert_eq!(validate_crl_issuer_key_usage(&parsed, 1), Ok(()));
1804        }
1805        parsed.tbs_certificate.version = x509_parser::x509::X509Version::V3;
1806        assert_eq!(
1807            validate_crl_issuer_key_usage(&parsed, 1),
1808            Err(X509ChainError::InvalidKeyUsage {
1809                position: 1,
1810                required: "cRLSign"
1811            })
1812        );
1813        parsed.tbs_certificate.version = x509_parser::x509::X509Version(3);
1814        assert!(matches!(
1815            validate_crl_issuer_key_usage(&parsed, 1),
1816            Err(X509ChainError::InvalidDer {
1817                kind: "certificate version",
1818                ..
1819            })
1820        ));
1821    }
1822
1823    fn generated_certificate_params(common_name: &str, is_ca: bool) -> rcgen::CertificateParams {
1824        let mut params = rcgen::CertificateParams::new(Vec::new())
1825            .expect("empty SAN list should produce valid certificate parameters");
1826        params
1827            .distinguished_name
1828            .push(rcgen::DnType::CommonName, common_name);
1829        if is_ca {
1830            params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1831            params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
1832        }
1833        params
1834    }
1835
1836    fn verify_generated_path(
1837        certificates: Vec<Vec<u8>>,
1838        trusted_anchor: Vec<u8>,
1839    ) -> Result<(), X509ChainError> {
1840        verify_generated_path_with_eku(certificates, trusted_anchor, None)
1841    }
1842
1843    fn verify_generated_path_with_eku(
1844        certificates: Vec<Vec<u8>>,
1845        trusted_anchor: Vec<u8>,
1846        allowed_extended_key_usages: Option<&HashSet<ExtendedKeyPurpose>>,
1847    ) -> Result<(), X509ChainError> {
1848        let info = X509DataInfo {
1849            certificate_chain: (0..certificates.len()).collect(),
1850            certificates,
1851            ..X509DataInfo::default()
1852        };
1853        let anchors = vec![trusted_anchor];
1854        verify_x509_certificate_chain(
1855            &info,
1856            &X509ChainOptions {
1857                trusted_certs: &anchors,
1858                verification_time: SystemTime::now(),
1859                max_chain_depth: info.certificate_chain.len(),
1860                check_crls: false,
1861                allowed_extended_key_usages,
1862                rsa_keys: RsaKeyPolicy::default(),
1863                dsa_keys: DsaKeyPolicy::default(),
1864            },
1865        )
1866    }
1867
1868    #[test]
1869    fn noncritical_ca_basic_constraints_remain_path_compatible() {
1870        // Criticality is an issuer conformance requirement, not an additional
1871        // relying-party path gate; historical xmlsec1 chains depend on this.
1872        let mut params = generated_certificate_params("non-critical authority", false);
1873        params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
1874        params
1875            .custom_extensions
1876            .push(rcgen::CustomExtension::from_oid_content(
1877                &[2, 5, 29, 19],
1878                vec![0x30, 0x03, 0x01, 0x01, 0xff],
1879            ));
1880        let certificate = params
1881            .self_signed(&rcgen::KeyPair::generate().expect("CA key generation should succeed"))
1882            .expect("test CA should be self-signable");
1883        let parsed = parse_certificate(certificate.der()).expect("test CA DER should parse");
1884
1885        assert_eq!(validate_ca_constraints(&parsed, 1), Ok(()));
1886    }
1887
1888    #[test]
1889    fn restricted_leaf_eku_requires_an_approved_purpose() {
1890        // A server-authentication certificate is not implicitly authorized for
1891        // XML signatures merely because its key permits digital signatures.
1892        let root = rcgen::CertifiedIssuer::self_signed(
1893            generated_certificate_params("EKU authority", true),
1894            rcgen::KeyPair::generate().expect("root key generation should succeed"),
1895        )
1896        .expect("root should be self-signable");
1897        let mut leaf_params = generated_certificate_params("TLS-only signer", false);
1898        leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1899        leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
1900        let leaf = leaf_params
1901            .signed_by(
1902                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1903                &root,
1904            )
1905            .expect("root should sign leaf certificate");
1906        let leaf_der = leaf.der().to_vec();
1907        let root_der = root.der().to_vec();
1908
1909        assert!(matches!(
1910            verify_generated_path(vec![leaf_der.clone(), root_der.clone()], root_der.clone(),),
1911            Err(X509ChainError::InvalidKeyUsage {
1912                position: 0,
1913                required: "an approved extended key usage",
1914            })
1915        ));
1916
1917        let allowed = HashSet::from([ExtendedKeyPurpose::ServerAuth]);
1918        verify_generated_path_with_eku(vec![leaf_der, root_der.clone()], root_der, Some(&allowed))
1919            .expect("an explicitly approved leaf purpose must be accepted");
1920    }
1921
1922    #[test]
1923    fn critical_leaf_eku_uses_the_same_purpose_policy() {
1924        // Criticality changes whether an unknown extension may be ignored, not
1925        // the authorization semantics of an EKU that this validator implements.
1926        let root = rcgen::CertifiedIssuer::self_signed(
1927            generated_certificate_params("critical EKU authority", true),
1928            rcgen::KeyPair::generate().expect("root key generation should succeed"),
1929        )
1930        .expect("root should be self-signable");
1931        let mut leaf_params = generated_certificate_params("critical TLS signer", false);
1932        leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1933        let mut extension = rcgen::CustomExtension::from_oid_content(
1934            &[2, 5, 29, 37],
1935            vec![
1936                0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
1937            ],
1938        );
1939        extension.set_criticality(true);
1940        leaf_params.custom_extensions.push(extension);
1941        let leaf = leaf_params
1942            .signed_by(
1943                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1944                &root,
1945            )
1946            .expect("root should sign leaf certificate");
1947        let allowed = HashSet::from([ExtendedKeyPurpose::ServerAuth]);
1948
1949        verify_generated_path_with_eku(
1950            vec![leaf.der().to_vec(), root.der().to_vec()],
1951            root.der().to_vec(),
1952            Some(&allowed),
1953        )
1954        .expect("approved critical EKU must be processed rather than rejected as unknown");
1955    }
1956
1957    #[test]
1958    fn issuer_eku_restricts_the_entire_certificate_path() {
1959        // RFC 5280 applies an issuer EKU as a path-wide purpose constraint. A
1960        // leaf approval cannot override an incompatible critical CA authorization.
1961        for (issuer_purpose_der, accepted) in [
1962            (
1963                vec![
1964                    0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
1965                ],
1966                false,
1967            ),
1968            (
1969                vec![
1970                    0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
1971                ],
1972                true,
1973            ),
1974        ] {
1975            let mut root_params =
1976                generated_certificate_params("purpose-constrained authority", true);
1977            let mut extension =
1978                rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 37], issuer_purpose_der);
1979            extension.set_criticality(true);
1980            root_params.custom_extensions.push(extension);
1981            let root = rcgen::CertifiedIssuer::self_signed(
1982                root_params,
1983                rcgen::KeyPair::generate().expect("root key generation should succeed"),
1984            )
1985            .expect("root should be self-signable");
1986            let mut leaf_params = generated_certificate_params("TLS server signer", false);
1987            leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
1988            leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
1989            let leaf = leaf_params
1990                .signed_by(
1991                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
1992                    &root,
1993                )
1994                .expect("root should sign leaf certificate");
1995            let allowed = HashSet::from([
1996                ExtendedKeyPurpose::ServerAuth,
1997                ExtendedKeyPurpose::ClientAuth,
1998            ]);
1999            let result = verify_generated_path_with_eku(
2000                vec![leaf.der().to_vec(), root.der().to_vec()],
2001                root.der().to_vec(),
2002                Some(&allowed),
2003            );
2004
2005            if accepted {
2006                result.expect("a shared allowed purpose must satisfy the complete path");
2007            } else {
2008                assert!(matches!(
2009                    result,
2010                    Err(X509ChainError::InvalidKeyUsage {
2011                        position: 1,
2012                        required: "an approved extended key usage",
2013                    })
2014                ));
2015            }
2016        }
2017    }
2018
2019    #[test]
2020    fn any_extended_key_usage_does_not_restrict_xml_signing() {
2021        // RFC 5280 anyExtendedKeyUsage explicitly leaves the key unrestricted,
2022        // so it does not require a deployment-specific purpose allowlist entry.
2023        let root = rcgen::CertifiedIssuer::self_signed(
2024            generated_certificate_params("any EKU authority", true),
2025            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2026        )
2027        .expect("root should be self-signable");
2028        let mut leaf_params = generated_certificate_params("unrestricted signer", false);
2029        leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
2030        leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::Any];
2031        let leaf = leaf_params
2032            .signed_by(
2033                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2034                &root,
2035            )
2036            .expect("root should sign leaf certificate");
2037
2038        verify_generated_path(
2039            vec![leaf.der().to_vec(), root.der().to_vec()],
2040            root.der().to_vec(),
2041        )
2042        .expect("anyExtendedKeyUsage must remain unrestricted");
2043    }
2044
2045    #[test]
2046    fn x509_ecdsa_hash_oid_does_not_select_the_issuer_curve() {
2047        // RFC 5758 signature OIDs select the digest while SubjectPublicKeyInfo
2048        // selects the curve. Both non-default pairings must therefore reach
2049        // the provider with the issuer's actual curve rather than a curve
2050        // inferred from the hash OID.
2051        let data = b"certificate tbs bytes";
2052
2053        let p384_key = p384::ecdsa::SigningKey::from_slice(&[0x42; 48])
2054            .expect("fixed P-384 test key must be valid");
2055        let p384_signature: p384::ecdsa::Signature = p384_key
2056            .sign_prehash(&Sha256::digest(data))
2057            .expect("P-384 must sign a SHA-256 prehash");
2058        let p384_spki = p384_key
2059            .verifying_key()
2060            .to_public_key_der()
2061            .expect("P-384 SPKI must encode");
2062        assert!(
2063            verify_x509_signature_with_provider(
2064                &AlgorithmIdentifier::new(OID_SIG_ECDSA_WITH_SHA256, None),
2065                p384_signature.to_der().as_bytes(),
2066                data,
2067                p384_spki.as_bytes(),
2068                crate::provider::default_provider(),
2069            )
2070            .expect("P-384 with SHA-256 must be a supported X.509 pairing")
2071        );
2072
2073        let p256_key = p256::ecdsa::SigningKey::from_slice(&[0x24; 32])
2074            .expect("fixed P-256 test key must be valid");
2075        let p256_signature: p256::ecdsa::Signature = p256_key
2076            .sign_prehash(&Sha384::digest(data))
2077            .expect("P-256 must sign a SHA-384 prehash");
2078        let p256_spki = p256_key
2079            .verifying_key()
2080            .to_public_key_der()
2081            .expect("P-256 SPKI must encode");
2082        assert!(
2083            verify_x509_signature_with_provider(
2084                &AlgorithmIdentifier::new(OID_SIG_ECDSA_WITH_SHA384, None),
2085                p256_signature.to_der().as_bytes(),
2086                data,
2087                p256_spki.as_bytes(),
2088                crate::provider::default_provider(),
2089            )
2090            .expect("P-256 with SHA-384 must be a supported X.509 pairing")
2091        );
2092    }
2093
2094    #[test]
2095    fn path_edge_signature_check_does_not_repeat_name_matching() {
2096        // Path construction performs RFC 5280 name matching before asking this
2097        // helper to disambiguate same-name candidates. Only proof of possession
2098        // of the issuer key belongs in this second gate.
2099        let issuer_key = rcgen::KeyPair::generate().expect("issuer key generation should succeed");
2100        let issuer_key_pem = issuer_key.serialize_pem();
2101        let mut signing_params = rcgen::CertificateParams::new(Vec::new())
2102            .expect("empty issuer SAN list should be valid");
2103        signing_params
2104            .distinguished_name
2105            .push(rcgen::DnType::CommonName, "signing name");
2106        signing_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2107        signing_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2108        let signing_issuer = rcgen::CertifiedIssuer::self_signed(signing_params, issuer_key)
2109            .expect("issuer certificate should be self-signable");
2110
2111        let mut alternate_params = rcgen::CertificateParams::new(Vec::new())
2112            .expect("empty alternate SAN list should be valid");
2113        alternate_params
2114            .distinguished_name
2115            .push(rcgen::DnType::CommonName, "name already matched by caller");
2116        alternate_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2117        alternate_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2118        let alternate_issuer = rcgen::CertifiedIssuer::self_signed(
2119            alternate_params,
2120            rcgen::KeyPair::from_pem(&issuer_key_pem)
2121                .expect("serialized issuer key should parse again"),
2122        )
2123        .expect("alternate issuer certificate should be self-signable");
2124
2125        let leaf = rcgen::CertificateParams::new(Vec::new())
2126            .expect("empty leaf SAN list should be valid")
2127            .signed_by(
2128                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2129                &signing_issuer,
2130            )
2131            .expect("issuer should sign leaf certificate");
2132
2133        assert!(certificate_signature_matches(
2134            leaf.der(),
2135            alternate_issuer.der()
2136        ));
2137    }
2138
2139    #[test]
2140    fn certificate_path_edge_preserves_ed25519_verification() {
2141        // Provider routing must preserve the certificate algorithms accepted by
2142        // the previous x509-parser verifier rather than narrowing them to the
2143        // XMLDSig SignatureMethod enum.
2144        let issuer_key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
2145            .expect("Ed25519 issuer key generation should succeed");
2146        let mut issuer_params = rcgen::CertificateParams::new(Vec::new())
2147            .expect("empty issuer SAN list should be valid");
2148        issuer_params
2149            .distinguished_name
2150            .push(rcgen::DnType::CommonName, "Ed25519 issuer");
2151        issuer_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
2152        issuer_params.key_usages = vec![rcgen::KeyUsagePurpose::KeyCertSign];
2153        let issuer = rcgen::CertifiedIssuer::self_signed(issuer_params, issuer_key)
2154            .expect("Ed25519 issuer certificate should be self-signable");
2155        let leaf_key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
2156            .expect("Ed25519 leaf key generation should succeed");
2157        let leaf = rcgen::CertificateParams::new(Vec::new())
2158            .expect("empty leaf SAN list should be valid")
2159            .signed_by(&leaf_key, &issuer)
2160            .expect("Ed25519 issuer should sign leaf certificate");
2161
2162        assert!(certificate_signature_matches(leaf.der(), issuer.der()));
2163    }
2164
2165    #[test]
2166    fn every_modeled_non_parameterized_x509_algorithm_reaches_the_provider() {
2167        // Parsing and provider capability are separate contracts. Once an OID
2168        // has a typed representation, custom providers must get the chance to
2169        // implement it even when RustCrypto does not.
2170        for (oid, expected) in [
2171            (
2172                "2.16.840.1.101.3.4.3.1",
2173                X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha224),
2174            ),
2175            (
2176                "2.16.840.1.101.3.4.3.2",
2177                X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha256),
2178            ),
2179            (
2180                "2.16.840.1.101.3.4.3.3",
2181                X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha384),
2182            ),
2183            (
2184                "2.16.840.1.101.3.4.3.4",
2185                X509SignatureAlgorithm::Dsa(super::super::DigestAlgorithm::Sha512),
2186            ),
2187            (
2188                "1.2.840.10045.4.1",
2189                X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha1),
2190            ),
2191            (
2192                "1.2.840.113549.1.1.14",
2193                X509SignatureAlgorithm::RsaPkcs1v15(super::super::DigestAlgorithm::Sha224),
2194            ),
2195            (
2196                "1.2.840.10045.4.3.1",
2197                X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha224),
2198            ),
2199            (
2200                "1.2.840.10045.4.3.4",
2201                X509SignatureAlgorithm::Ecdsa(super::super::DigestAlgorithm::Sha512),
2202            ),
2203        ] {
2204            let identifier = AlgorithmIdentifier::new(
2205                Oid::from_str(oid).expect("static signature OID must parse"),
2206                None,
2207            );
2208            assert_eq!(x509_signature_algorithm(&identifier), Ok(expected), "{oid}");
2209        }
2210    }
2211
2212    #[test]
2213    fn x509_signature_parameters_follow_each_algorithm_profile() {
2214        use x509_parser::asn1_rs::{Any, Tag};
2215
2216        // DSA, ECDSA, and Ed25519 signature identifiers require absent
2217        // parameters. A NULL is not equivalent for these algorithm profiles.
2218        for oid in [
2219            "1.2.840.10040.4.3",
2220            "2.16.840.1.101.3.4.3.1",
2221            "2.16.840.1.101.3.4.3.2",
2222            "1.2.840.10045.4.1",
2223            "1.2.840.10045.4.3.1",
2224            "1.2.840.10045.4.3.2",
2225            "1.3.101.112",
2226        ] {
2227            let identifier = AlgorithmIdentifier::new(
2228                Oid::from_str(oid).expect("static signature OID must parse"),
2229                Some(Any::from_tag_and_data(Tag::Null, &[])),
2230            );
2231            assert!(matches!(
2232                x509_signature_algorithm(&identifier),
2233                Err(X509ChainError::InvalidDer {
2234                    kind: "X.509 signature AlgorithmIdentifier parameters",
2235                    ..
2236                })
2237            ));
2238        }
2239
2240        // RSA PKCS#1 signature identifiers accept absent and NULL parameters
2241        // for interoperability, but no other ASN.1 value.
2242        for (oid, digest) in [
2243            (
2244                "1.2.840.113549.1.1.14",
2245                super::super::DigestAlgorithm::Sha224,
2246            ),
2247            (
2248                "1.2.840.113549.1.1.11",
2249                super::super::DigestAlgorithm::Sha256,
2250            ),
2251        ] {
2252            let rsa_oid = Oid::from_str(oid).expect("static RSA signature OID must parse");
2253            for parameters in [None, Some(Any::from_tag_and_data(Tag::Null, &[]))] {
2254                assert_eq!(
2255                    x509_signature_algorithm(&AlgorithmIdentifier::new(
2256                        rsa_oid.clone(),
2257                        parameters,
2258                    )),
2259                    Ok(X509SignatureAlgorithm::RsaPkcs1v15(digest)),
2260                );
2261            }
2262            assert!(matches!(
2263                x509_signature_algorithm(&AlgorithmIdentifier::new(
2264                    rsa_oid,
2265                    Some(Any::from_tag_and_data(Tag::OctetString, &[])),
2266                )),
2267                Err(X509ChainError::InvalidDer {
2268                    kind: "X.509 signature AlgorithmIdentifier parameters",
2269                    ..
2270                })
2271            ));
2272        }
2273    }
2274
2275    #[test]
2276    fn unknown_x509_signature_algorithm_remains_diagnosable() {
2277        let oid = "1.2.3.4.5";
2278        let identifier = AlgorithmIdentifier::new(
2279            Oid::from_str(oid).expect("static unknown OID must parse"),
2280            None,
2281        );
2282
2283        assert_eq!(
2284            x509_signature_algorithm(&identifier),
2285            Err(X509ChainError::UnsupportedSignatureAlgorithm { oid: oid.into() })
2286        );
2287    }
2288
2289    #[test]
2290    fn parses_rsa_pss_certificate_parameters_without_xml_dsig_loss() {
2291        // RFC 4055 carries the digest, MGF digest, and salt length inside the
2292        // AlgorithmIdentifier. Preserve all three values at the provider edge.
2293        let der = [
2294            0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
2295            0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
2296            0x02, 0x01, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
2297            0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
2298            0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x20,
2299        ];
2300        let (rest, identifier) = AlgorithmIdentifier::from_der(&der)
2301            .expect("standard SHA-256 RSA-PSS AlgorithmIdentifier must parse");
2302        assert!(rest.is_empty());
2303
2304        assert_eq!(
2305            x509_signature_algorithm(&identifier),
2306            Ok(X509SignatureAlgorithm::RsaPss {
2307                digest: super::super::DigestAlgorithm::Sha256,
2308                mgf_digest: super::super::DigestAlgorithm::Sha256,
2309                salt_len: 32,
2310            })
2311        );
2312    }
2313
2314    #[test]
2315    fn parses_sha224_rsa_pss_certificate_parameters() {
2316        // RFC 4055 permits SHA-224 independently for the message digest and
2317        // MGF1. The provider-neutral parser must preserve that capability.
2318        let der = [
2319            0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
2320            0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
2321            0x02, 0x04, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
2322            0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
2323            0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x1c,
2324        ];
2325        let (rest, identifier) = AlgorithmIdentifier::from_der(&der)
2326            .expect("standard SHA-224 RSA-PSS AlgorithmIdentifier must parse");
2327        assert!(rest.is_empty());
2328
2329        assert_eq!(
2330            x509_signature_algorithm(&identifier),
2331            Ok(X509SignatureAlgorithm::RsaPss {
2332                digest: super::super::DigestAlgorithm::Sha224,
2333                mgf_digest: super::super::DigestAlgorithm::Sha224,
2334                salt_len: 28,
2335            })
2336        );
2337    }
2338
2339    #[test]
2340    fn dsa_rollover_replaces_embedded_root_before_depth_validation() {
2341        let leaf = include_bytes!(
2342            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2343        )
2344        .to_vec();
2345        let embedded_root =
2346            include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2347                .to_vec();
2348
2349        // Trust-anchor self-signatures are not part of path validation. Changing
2350        // only that signature gives this test a distinct rollover certificate
2351        // with the same subject and DSA public key as the embedded stale root.
2352        let mut rollover_anchor = embedded_root.clone();
2353        *rollover_anchor
2354            .last_mut()
2355            .expect("certificate is non-empty") ^= 1;
2356        parse_certificate(&rollover_anchor).expect("modified trust anchor remains valid DER");
2357        let anchors = vec![rollover_anchor];
2358        let info = X509DataInfo {
2359            certificates: vec![leaf, embedded_root],
2360            certificate_chain: vec![0, 1],
2361            ..X509DataInfo::default()
2362        };
2363        let options = X509ChainOptions {
2364            trusted_certs: &anchors,
2365            verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2366            max_chain_depth: 2,
2367            check_crls: false,
2368            allowed_extended_key_usages: None,
2369            rsa_keys: RsaKeyPolicy::default(),
2370            dsa_keys: DsaKeyPolicy {
2371                minimum_modulus_bits: 1024,
2372            },
2373        };
2374
2375        verify_x509_certificate_chain(&info, &options)
2376            .expect("the stale DSA root must be replaced by the configured anchor");
2377    }
2378
2379    #[test]
2380    fn dsa_issuer_key_uses_the_configured_strength_policy() {
2381        let leaf = include_bytes!(
2382            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2383        )
2384        .to_vec();
2385        let anchor =
2386            include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2387                .to_vec();
2388        let anchors = vec![anchor.clone()];
2389        let info = X509DataInfo {
2390            certificates: vec![leaf, anchor],
2391            certificate_chain: vec![0, 1],
2392            ..X509DataInfo::default()
2393        };
2394        let options = X509ChainOptions {
2395            trusted_certs: &anchors,
2396            verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2397            max_chain_depth: 2,
2398            check_crls: false,
2399            allowed_extended_key_usages: None,
2400            rsa_keys: RsaKeyPolicy::default(),
2401            dsa_keys: DsaKeyPolicy::default(),
2402        };
2403
2404        assert!(matches!(
2405            verify_x509_certificate_chain(&info, &options),
2406            Err(X509ChainError::KeyPolicy {
2407                position: 1,
2408                source: crate::policy::PolicyViolation::KeySize {
2409                    key_type: "DSA",
2410                    minimum_bits: 2048,
2411                    actual_bits: 1024,
2412                    ..
2413                }
2414            })
2415        ));
2416    }
2417
2418    #[test]
2419    fn path_length_excludes_self_issued_rollover_certificates() {
2420        // RFC 5280 excludes self-issued rollover CAs from pathLenConstraint;
2421        // only non-self-issued intermediate CA certificates consume the limit.
2422        let mut root_params = generated_certificate_params("rollover path authority", true);
2423        root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Constrained(0));
2424        let root = rcgen::CertifiedIssuer::self_signed(
2425            root_params,
2426            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2427        )
2428        .expect("root should be self-signable");
2429        let rollover_params = generated_certificate_params("rollover path authority", true);
2430        let rollover_key =
2431            rcgen::KeyPair::generate().expect("rollover key generation should succeed");
2432        let rollover_certificate = rollover_params
2433            .signed_by(&rollover_key, &root)
2434            .expect("root should sign same-name rollover certificate");
2435        let rollover_issuer = rcgen::Issuer::from_params(&rollover_params, &rollover_key);
2436        let leaf = generated_certificate_params("rollover path leaf", false)
2437            .signed_by(
2438                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2439                &rollover_issuer,
2440            )
2441            .expect("rollover key should sign leaf certificate");
2442
2443        verify_generated_path(
2444            vec![
2445                leaf.der().to_vec(),
2446                rollover_certificate.der().to_vec(),
2447                root.der().to_vec(),
2448            ],
2449            root.der().to_vec(),
2450        )
2451        .expect("self-issued rollover must not consume a zero path-length allowance");
2452    }
2453
2454    #[test]
2455    fn ca_name_constraints_reject_disallowed_dns_names() {
2456        let mut root_params = generated_certificate_params("constrained authority", true);
2457        root_params.name_constraints = Some(rcgen::NameConstraints {
2458            permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2459            excluded_subtrees: vec![rcgen::GeneralSubtree::DnsName("blocked.example.com".into())],
2460        });
2461        let root = rcgen::CertifiedIssuer::self_signed(
2462            root_params,
2463            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2464        )
2465        .expect("constrained root should be self-signable");
2466
2467        for (dns_name, accepted) in [
2468            ("www.example.com", true),
2469            ("blocked.example.com", false),
2470            ("www.example.net", false),
2471        ] {
2472            let leaf = rcgen::CertificateParams::new(vec![dns_name.into()])
2473                .expect("DNS SAN should be valid")
2474                .signed_by(
2475                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2476                    &root,
2477                )
2478                .expect("root should sign leaf certificate");
2479            assert_eq!(
2480                verify_generated_path(
2481                    vec![leaf.der().to_vec(), root.der().to_vec()],
2482                    root.der().to_vec(),
2483                )
2484                .is_ok(),
2485                accepted,
2486                "unexpected name-constraint result for {dns_name}"
2487            );
2488        }
2489    }
2490
2491    #[test]
2492    fn rfc5280_dns_names_require_preferred_name_syntax() {
2493        let mut root_params = generated_certificate_params("DNS syntax authority", true);
2494        root_params.name_constraints = Some(rcgen::NameConstraints {
2495            permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2496            excluded_subtrees: Vec::new(),
2497        });
2498        let root = rcgen::CertifiedIssuer::self_signed(
2499            root_params,
2500            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2501        )
2502        .expect("constrained root should be self-signable");
2503
2504        let mut leaf_params = generated_certificate_params("malformed DNS leaf", false);
2505        let dns_name = b"bad..example.com";
2506        let mut san_der = vec![
2507            0x30,
2508            u8::try_from(dns_name.len() + 2).expect("test SAN must fit short-form DER"),
2509            0x82,
2510        ];
2511        san_der.push(u8::try_from(dns_name.len()).expect("test DNS name must fit short-form DER"));
2512        san_der.extend_from_slice(dns_name);
2513        leaf_params
2514            .custom_extensions
2515            .push(rcgen::CustomExtension::from_oid_content(
2516                &[2, 5, 29, 17],
2517                san_der,
2518            ));
2519        let leaf = leaf_params
2520            .signed_by(
2521                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2522                &root,
2523            )
2524            .expect("root should sign malformed-DNS leaf");
2525
2526        assert!(matches!(
2527            verify_generated_path(
2528                vec![leaf.der().to_vec(), root.der().to_vec()],
2529                root.der().to_vec(),
2530            ),
2531            Err(X509ChainError::InvalidDer {
2532                kind: "certificate DNS name",
2533                ..
2534            })
2535        ));
2536
2537        for dns_name in ["*.example.com", "_signing.example.com"] {
2538            assert!(validate_rfc5280_dns_name(dns_name).is_err(), "{dns_name}");
2539        }
2540    }
2541
2542    #[test]
2543    fn name_constraint_matchers_cover_email_uri_and_ip_forms() {
2544        // RFC 5280 gives each GeneralName form distinct subtree semantics;
2545        // exercise those rules directly so a DNS-only implementation cannot pass.
2546        assert!(email_within_subtree("ops@example.com", "example.com"));
2547        assert!(email_within_subtree("ops@example.com", "ops@example.com"));
2548        assert!(!email_within_subtree(
2549            "other@example.com",
2550            "ops@example.com"
2551        ));
2552        assert_eq!(
2553            uri_host("https://user@api.example.com:8443/path"),
2554            Some("api.example.com")
2555        );
2556        assert_eq!(
2557            uri_host("https://user@other@example.com/path"),
2558            None,
2559            "a second userinfo delimiter must not expose a constraint-matchable host"
2560        );
2561        assert!(dns_name_within_subtree(
2562            uri_host("https://api.example.com/path").expect("URI must expose a DNS host"),
2563            ".example.com",
2564            false,
2565        ));
2566        assert!(
2567            ip_address_within_subtree(&[192, 0, 2, 42], &[192, 0, 2, 0, 255, 255, 255, 0],)
2568                .expect("valid IPv4 constraint must evaluate")
2569        );
2570        assert!(
2571            !ip_address_within_subtree(&[192, 0, 3, 42], &[192, 0, 2, 0, 255, 255, 255, 0],)
2572                .expect("valid non-matching IPv4 constraint must evaluate")
2573        );
2574        assert!(matches!(
2575            ip_address_within_subtree(&[192, 0, 2, 42], &[192, 0, 2, 0, 255, 0, 255, 0],),
2576            Err(X509ChainError::InvalidDer {
2577                kind: "IP name constraint",
2578                ..
2579            })
2580        ));
2581    }
2582
2583    #[test]
2584    fn malformed_ip_name_constraints_fail_before_matching() {
2585        use x509_parser::extensions::GeneralSubtree;
2586
2587        let name = GeneralName::IPAddress(&[192, 0, 2, 42]);
2588        for malformed in [
2589            &[192, 0, 2, 0, 255, 255, 255][..],
2590            &[192, 0, 2, 0, 255, 0, 255, 0][..],
2591        ] {
2592            for permitted in [true, false] {
2593                let subtree = GeneralSubtree {
2594                    base: GeneralName::IPAddress(malformed),
2595                };
2596                let constraints = NameConstraints {
2597                    permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2598                    excluded_subtrees: (!permitted).then(|| vec![subtree]),
2599                };
2600                assert!(matches!(
2601                    validate_general_name(&name, &constraints, 0, 1),
2602                    Err(X509ChainError::InvalidDer {
2603                        kind: "IP name constraint",
2604                        ..
2605                    })
2606                ));
2607            }
2608        }
2609    }
2610
2611    #[test]
2612    fn malformed_string_name_constraints_fail_before_matching() {
2613        use x509_parser::extensions::GeneralSubtree;
2614
2615        // Matchers assume admitted string constraints have RFC 5280 syntax.
2616        // Invalid values must not degrade into ordinary non-matches.
2617        for malformed in [
2618            GeneralName::DNSName(""),
2619            GeneralName::DNSName("example..com"),
2620            GeneralName::RFC822Name("@example.com"),
2621            GeneralName::RFC822Name("bad..local@example.com"),
2622            GeneralName::URI("https://example.com"),
2623        ] {
2624            let constraints = NameConstraints {
2625                permitted_subtrees: None,
2626                excluded_subtrees: Some(vec![GeneralSubtree { base: malformed }]),
2627            };
2628            assert!(matches!(
2629                ensure_supported_name_constraints(&constraints, 1),
2630                Err(X509ChainError::InvalidDer {
2631                    kind: "string name constraint",
2632                    ..
2633                })
2634            ));
2635        }
2636
2637        for valid in [
2638            GeneralName::DNSName("example.com"),
2639            GeneralName::DNSName(".example.com"),
2640            GeneralName::RFC822Name("ops@example.com"),
2641            GeneralName::RFC822Name("example.com"),
2642            GeneralName::URI(".example.com"),
2643        ] {
2644            let constraints = NameConstraints {
2645                permitted_subtrees: Some(vec![GeneralSubtree { base: valid }]),
2646                excluded_subtrees: None,
2647            };
2648            ensure_supported_name_constraints(&constraints, 1)
2649                .expect("valid string constraints must remain supported");
2650        }
2651    }
2652
2653    #[test]
2654    fn empty_name_constraint_collections_are_rejected() {
2655        use der::Encode as _;
2656        use x509_cert::ext::pkix::NameConstraints as EncodedNameConstraints;
2657
2658        // RFC 5280 requires at least one subtree overall and at least one entry
2659        // in every explicitly present GeneralSubtrees collection.
2660        for constraints in [
2661            EncodedNameConstraints {
2662                permitted_subtrees: None,
2663                excluded_subtrees: None,
2664            },
2665            EncodedNameConstraints {
2666                permitted_subtrees: Some(Vec::new()),
2667                excluded_subtrees: None,
2668            },
2669            EncodedNameConstraints {
2670                permitted_subtrees: None,
2671                excluded_subtrees: Some(Vec::new()),
2672            },
2673        ] {
2674            let der = constraints
2675                .to_der()
2676                .expect("malformed NameConstraints test input must encode");
2677            assert!(matches!(
2678                validate_name_constraints_der(&der, 1),
2679                Err(X509ChainError::InvalidNameConstraints { position: 1 })
2680            ));
2681        }
2682    }
2683
2684    #[test]
2685    fn unsupported_name_constraint_distances_fail_path_validation() {
2686        use der::{Encode as _, asn1::Ia5String};
2687        use x509_cert::ext::pkix::{
2688            NameConstraints as EncodedNameConstraints,
2689            constraints::name::GeneralSubtree as EncodedGeneralSubtree,
2690            name::GeneralName as EncodedGeneralName,
2691        };
2692
2693        // x509-parser exposes only GeneralSubtree::base. Exercise the complete
2694        // extension DER so unsupported distance fields cannot disappear before
2695        // RFC 5280 path validation sees them.
2696        for (permitted, minimum, maximum) in [
2697            (true, 1, None),
2698            (false, 1, None),
2699            (true, 0, Some(1)),
2700            (false, 0, Some(1)),
2701        ] {
2702            let dns_name = if permitted {
2703                "example.com"
2704            } else {
2705                "blocked.example.com"
2706            };
2707            let subtree = EncodedGeneralSubtree {
2708                base: EncodedGeneralName::DnsName(
2709                    Ia5String::new(dns_name.as_bytes()).expect("valid DNS IA5String"),
2710                ),
2711                minimum,
2712                maximum,
2713            };
2714            let constraints = EncodedNameConstraints {
2715                permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2716                excluded_subtrees: (!permitted).then(|| vec![subtree]),
2717            };
2718            let mut extension = rcgen::CustomExtension::from_oid_content(
2719                &[2, 5, 29, 30],
2720                constraints
2721                    .to_der()
2722                    .expect("NameConstraints must encode as DER"),
2723            );
2724            extension.set_criticality(true);
2725
2726            let mut root_params = generated_certificate_params("distance authority", true);
2727            root_params.custom_extensions.push(extension);
2728            let root = rcgen::CertifiedIssuer::self_signed(
2729                root_params,
2730                rcgen::KeyPair::generate().expect("root key generation should succeed"),
2731            )
2732            .expect("constrained root should be self-signable");
2733            let leaf = rcgen::CertificateParams::new(vec!["www.example.com".into()])
2734                .expect("leaf DNS SAN should be valid")
2735                .signed_by(
2736                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2737                    &root,
2738                )
2739                .expect("root should sign leaf certificate");
2740
2741            assert!(matches!(
2742                verify_generated_path(
2743                    vec![leaf.der().to_vec(), root.der().to_vec()],
2744                    root.der().to_vec(),
2745                ),
2746                Err(X509ChainError::InvalidNameConstraints { position: 1 })
2747            ));
2748        }
2749    }
2750
2751    #[test]
2752    fn name_constraints_cover_subject_email_and_directory_name() {
2753        // RFC 5280 requires subject emailAddress attributes to be checked even
2754        // without a SAN, and directoryName constraints compare RDN subtrees.
2755        let mut permitted_directory = rcgen::DistinguishedName::new();
2756        permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
2757        let mut root_params = generated_certificate_params("name authority", true);
2758        root_params.name_constraints = Some(rcgen::NameConstraints {
2759            permitted_subtrees: vec![
2760                rcgen::GeneralSubtree::Rfc822Name("example.com".into()),
2761                rcgen::GeneralSubtree::DirectoryName(permitted_directory),
2762            ],
2763            excluded_subtrees: Vec::new(),
2764        });
2765        let root = rcgen::CertifiedIssuer::self_signed(
2766            root_params,
2767            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2768        )
2769        .expect("constrained root should be self-signable");
2770
2771        for (organization, email, accepted) in [
2772            ("Example Corp", "ops@example.com", true),
2773            ("Other Corp", "ops@example.com", false),
2774            ("Example Corp", "ops@example.net", false),
2775            ("Example Corp", "bad..local@example.com", false),
2776        ] {
2777            let mut leaf_params = generated_certificate_params("name-constrained leaf", false);
2778            leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2779            leaf_params
2780                .distinguished_name
2781                .push(rcgen::DnType::OrganizationName, organization);
2782            leaf_params
2783                .distinguished_name
2784                .push(rcgen::DnType::CommonName, "name-constrained leaf");
2785            leaf_params.distinguished_name.push(
2786                rcgen::DnType::CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]),
2787                rcgen::DnValue::Ia5String(
2788                    email
2789                        .try_into()
2790                        .expect("test email must be a valid IA5String"),
2791                ),
2792            );
2793            let leaf = leaf_params
2794                .signed_by(
2795                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2796                    &root,
2797                )
2798                .expect("root should sign leaf certificate");
2799            let result = verify_generated_path(
2800                vec![leaf.der().to_vec(), root.der().to_vec()],
2801                root.der().to_vec(),
2802            );
2803            assert_eq!(
2804                result.is_ok(),
2805                accepted,
2806                "unexpected subject constraint result for {organization} / {email}: {result:?}",
2807            );
2808        }
2809    }
2810
2811    #[test]
2812    fn empty_subject_requires_a_critical_nonempty_san() {
2813        let root = rcgen::CertifiedIssuer::self_signed(
2814            generated_certificate_params("subject identity authority", true),
2815            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2816        )
2817        .expect("root should be self-signable");
2818
2819        let mut missing_san = rcgen::CertificateParams::new(Vec::new())
2820            .expect("empty SAN list should produce certificate parameters");
2821        missing_san.distinguished_name = rcgen::DistinguishedName::new();
2822        let missing_san = missing_san
2823            .signed_by(
2824                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2825                &root,
2826            )
2827            .expect("test issuer should sign an empty-subject certificate");
2828
2829        let mut noncritical_san = rcgen::CertificateParams::new(Vec::new())
2830            .expect("empty SAN list should produce certificate parameters");
2831        noncritical_san.distinguished_name = rcgen::DistinguishedName::new();
2832        // GeneralNames ::= SEQUENCE { dNSName [2] "a" }. Using a custom
2833        // extension is intentional because rcgen correctly marks its normal
2834        // SAN extension critical whenever the subject is empty.
2835        noncritical_san
2836            .custom_extensions
2837            .push(rcgen::CustomExtension::from_oid_content(
2838                &[2, 5, 29, 17],
2839                vec![0x30, 0x03, 0x82, 0x01, b'a'],
2840            ));
2841        let noncritical_san = noncritical_san
2842            .signed_by(
2843                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2844                &root,
2845            )
2846            .expect("test issuer should sign a non-critical-SAN certificate");
2847
2848        for leaf in [missing_san, noncritical_san] {
2849            assert!(matches!(
2850                verify_generated_path(
2851                    vec![leaf.der().to_vec(), root.der().to_vec()],
2852                    root.der().to_vec(),
2853                ),
2854                Err(X509ChainError::InvalidDer {
2855                    kind: "certificate subject identity",
2856                    ..
2857                })
2858            ));
2859        }
2860    }
2861
2862    #[test]
2863    fn empty_subject_with_critical_san_skips_directory_name_constraints() {
2864        // RFC 5280 permits an empty subject when a critical SAN carries the
2865        // identity. An absent DirectoryName need not match a permitted subtree.
2866        let mut permitted_directory = rcgen::DistinguishedName::new();
2867        permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
2868        let mut root_params = generated_certificate_params("empty-subject authority", true);
2869        root_params.name_constraints = Some(rcgen::NameConstraints {
2870            permitted_subtrees: vec![rcgen::GeneralSubtree::DirectoryName(permitted_directory)],
2871            excluded_subtrees: Vec::new(),
2872        });
2873        let root = rcgen::CertifiedIssuer::self_signed(
2874            root_params,
2875            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2876        )
2877        .expect("constrained root should be self-signable");
2878        let mut leaf_params = rcgen::CertificateParams::new(vec!["allowed.example".into()])
2879            .expect("DNS SAN should be valid");
2880        leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2881        let leaf = leaf_params
2882            .signed_by(
2883                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2884                &root,
2885            )
2886            .expect("root should sign empty-subject leaf");
2887
2888        verify_generated_path(
2889            vec![leaf.der().to_vec(), root.der().to_vec()],
2890            root.der().to_vec(),
2891        )
2892        .expect("only present name forms should be constrained");
2893    }
2894
2895    #[test]
2896    fn malformed_general_names_in_san_fail_path_validation() {
2897        let root = rcgen::CertifiedIssuer::self_signed(
2898            generated_certificate_params("malformed-SAN root", true),
2899            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2900        )
2901        .expect("root should be self-signable");
2902        for empty_subject in [true, false] {
2903            let mut leaf_params = generated_certificate_params("malformed-SAN leaf", false);
2904            if empty_subject {
2905                leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2906            }
2907            // GeneralNames ::= SEQUENCE { dNSName [2] <invalid IA5 octet> }.
2908            let mut malformed_san = rcgen::CustomExtension::from_oid_content(
2909                &[2, 5, 29, 17],
2910                vec![0x30, 0x03, 0x82, 0x01, 0xff],
2911            );
2912            malformed_san.set_criticality(true);
2913            leaf_params.custom_extensions.push(malformed_san);
2914            let leaf = leaf_params
2915                .signed_by(
2916                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2917                    &root,
2918                )
2919                .expect("root should sign malformed-SAN leaf");
2920
2921            assert!(matches!(
2922                verify_generated_path(
2923                    vec![leaf.der().to_vec(), root.der().to_vec()],
2924                    root.der().to_vec(),
2925                ),
2926                Err(X509ChainError::InvalidDer {
2927                    kind: "certificate subject identity",
2928                    ..
2929                })
2930            ));
2931        }
2932    }
2933
2934    #[test]
2935    fn typed_subject_alternative_names_require_rfc5280_syntax() {
2936        let root = rcgen::CertifiedIssuer::self_signed(
2937            generated_certificate_params("typed-SAN root", true),
2938            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2939        )
2940        .expect("root should be self-signable");
2941
2942        for (tag, value) in [
2943            (0x81, b"operator@".as_slice()),
2944            (0x81, b"first..last@example.com".as_slice()),
2945            (0x86, b"relative/path".as_slice()),
2946            (0x86, b"https://example.com/%zz".as_slice()),
2947            (0x86, b"https://user@other@example.com/path".as_slice()),
2948            (0x86, b"file:///path".as_slice()),
2949            (0x87, &[192, 0, 2][..]),
2950        ] {
2951            for empty_subject in [false, true] {
2952                let mut leaf_params = generated_certificate_params("typed-SAN leaf", false);
2953                if empty_subject {
2954                    leaf_params.distinguished_name = rcgen::DistinguishedName::new();
2955                }
2956                let mut san_der = vec![
2957                    0x30,
2958                    u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
2959                    tag,
2960                    u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
2961                ];
2962                san_der.extend_from_slice(value);
2963                let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
2964                san.set_criticality(true);
2965                leaf_params.custom_extensions.push(san);
2966                let leaf = leaf_params
2967                    .signed_by(
2968                        &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2969                        &root,
2970                    )
2971                    .expect("root should sign typed-SAN leaf");
2972
2973                assert!(matches!(
2974                    verify_generated_path(
2975                        vec![leaf.der().to_vec(), root.der().to_vec()],
2976                        root.der().to_vec(),
2977                    ),
2978                    Err(X509ChainError::InvalidDer {
2979                        kind: "certificate subject identity",
2980                        ..
2981                    })
2982                ));
2983            }
2984        }
2985
2986        for (tag, value) in [
2987            (0x81, b"operator@example.com".as_slice()),
2988            (0x81, b"operator@[192.0.2.1]".as_slice()),
2989            (0x81, b"operator@[IPv6:2001:db8::1]".as_slice()),
2990            (0x81, br#""operator desk"@example.com"#.as_slice()),
2991            (0x86, b"urn:example:operator".as_slice()),
2992            (
2993                0x86,
2994                b"https://operator@example.com:8443/path?q=1#id".as_slice(),
2995            ),
2996            (0x87, &[192, 0, 2, 1][..]),
2997            (
2998                0x87,
2999                &[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1][..],
3000            ),
3001        ] {
3002            let mut leaf_params = rcgen::CertificateParams::new(Vec::new())
3003                .expect("empty SAN list should produce certificate parameters");
3004            leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3005            let mut san_der = vec![
3006                0x30,
3007                u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
3008                tag,
3009                u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
3010            ];
3011            san_der.extend_from_slice(value);
3012            let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
3013            san.set_criticality(true);
3014            leaf_params.custom_extensions.push(san);
3015            let leaf = leaf_params
3016                .signed_by(
3017                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3018                    &root,
3019                )
3020                .expect("root should sign typed-SAN leaf");
3021
3022            verify_generated_path(
3023                vec![leaf.der().to_vec(), root.der().to_vec()],
3024                root.der().to_vec(),
3025            )
3026            .expect("valid typed SAN identity must satisfy an empty subject");
3027        }
3028    }
3029
3030    fn parsed_merlin_crl(der: &[u8]) -> CertificateRevocationList<'_> {
3031        CertificateRevocationList::from_der(der)
3032            .expect("modified Merlin CRL must remain parseable")
3033            .1
3034    }
3035
3036    fn merlin_crl_der() -> Vec<u8> {
3037        let xml = include_str!(
3038            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3039        );
3040        let document = Document::parse(xml).expect("tracked Merlin document must parse");
3041        let key_info_node = document
3042            .descendants()
3043            .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3044            .expect("tracked Merlin document contains KeyInfo");
3045        let key_info = parse_key_info(key_info_node).expect("tracked Merlin KeyInfo must parse");
3046        let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3047            panic!("expected X509Data")
3048        };
3049        info.crls[0].clone()
3050    }
3051
3052    #[test]
3053    fn duplicate_crl_and_entry_extension_oids_fail_closed() {
3054        use der::{Decode as _, Encode as _};
3055        use x509_cert::crl::CertificateList;
3056
3057        let original = merlin_crl_der();
3058        let mut duplicate_crl: CertificateList =
3059            CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3060        let extensions = duplicate_crl
3061            .tbs_cert_list
3062            .crl_extensions
3063            .as_mut()
3064            .expect("tracked Merlin CRL must contain extensions");
3065        extensions.push(extensions[0].clone());
3066        let duplicate_crl = duplicate_crl
3067            .to_der()
3068            .expect("duplicate CRL extension test vector must encode");
3069        assert_eq!(
3070            validate_crl_extensions(&parsed_merlin_crl(&duplicate_crl), 0),
3071            Err(X509ChainError::InvalidCrl(0))
3072        );
3073
3074        let mut duplicate_entry: CertificateList =
3075            CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3076        let duplicate = duplicate_entry
3077            .tbs_cert_list
3078            .crl_extensions
3079            .as_ref()
3080            .and_then(|extensions| extensions.first())
3081            .expect("tracked Merlin CRL must contain an extension")
3082            .clone();
3083        let revoked = duplicate_entry
3084            .tbs_cert_list
3085            .revoked_certificates
3086            .as_mut()
3087            .and_then(|entries| entries.first_mut())
3088            .expect("tracked Merlin CRL must contain a revoked entry");
3089        revoked.crl_entry_extensions = Some(vec![duplicate.clone(), duplicate]);
3090        let duplicate_entry = duplicate_entry
3091            .to_der()
3092            .expect("duplicate entry extension test vector must encode");
3093        assert_eq!(
3094            validate_crl_extensions(&parsed_merlin_crl(&duplicate_entry), 0),
3095            Err(X509ChainError::InvalidCrl(0))
3096        );
3097    }
3098
3099    #[test]
3100    fn malformed_revoked_certificate_serials_fail_closed() {
3101        // Mutate the signed Merlin CRL fixture without changing DER lengths so
3102        // zero and negative serials exercise the actual CRL parser path.
3103        let original = merlin_crl_der();
3104        let serial = parsed_merlin_crl(&original)
3105            .iter_revoked_certificates()
3106            .next()
3107            .expect("tracked Merlin CRL must contain a revoked entry")
3108            .raw_serial()
3109            .to_vec();
3110        let offsets = original
3111            .windows(serial.len())
3112            .enumerate()
3113            .filter_map(|(offset, bytes)| (bytes == serial).then_some(offset))
3114            .collect::<Vec<_>>();
3115        assert_eq!(
3116            offsets.len(),
3117            1,
3118            "revoked serial fixture must be unambiguous"
3119        );
3120
3121        for replacement in [vec![0; serial.len()], {
3122            let mut negative = serial.clone();
3123            negative[0] = 0x80;
3124            negative
3125        }] {
3126            let mut malformed = original.clone();
3127            malformed[offsets[0]..offsets[0] + serial.len()].copy_from_slice(&replacement);
3128            assert_eq!(
3129                validate_crl_extensions(&parsed_merlin_crl(&malformed), 0),
3130                Err(X509ChainError::InvalidCrl(0))
3131            );
3132        }
3133    }
3134
3135    #[test]
3136    fn delta_crl_indicator_is_rejected_regardless_of_criticality() {
3137        use der::{Decode as _, Encode as _, asn1::OctetString};
3138        use x509_cert::{crl::CertificateList, ext::Extension};
3139
3140        let original = merlin_crl_der();
3141        for critical in [false, true] {
3142            let mut encoded: CertificateList =
3143                CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3144            encoded
3145                .tbs_cert_list
3146                .crl_extensions
3147                .get_or_insert_default()
3148                .push(Extension {
3149                    extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.27"),
3150                    critical,
3151                    extn_value: OctetString::new([0x02, 0x01, 0x01])
3152                        .expect("DER INTEGER extension payload must be valid"),
3153                });
3154            let encoded = encoded
3155                .to_der()
3156                .expect("delta CRL indicator test vector must encode");
3157            assert_eq!(
3158                validate_crl_extensions(&parsed_merlin_crl(&encoded), 0),
3159                Err(X509ChainError::InvalidCrl(0)),
3160                "delta CRL indicator criticality must not change unsupported semantics"
3161            );
3162        }
3163    }
3164
3165    #[test]
3166    fn remove_from_crl_is_rejected_in_a_complete_crl() {
3167        use der::{Decode as _, Encode as _, asn1::OctetString};
3168        use x509_cert::{crl::CertificateList, ext::Extension};
3169
3170        let original = merlin_crl_der();
3171        for (reason, accepted) in [(1_u8, true), (8_u8, false)] {
3172            let mut encoded: CertificateList =
3173                CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3174            let revoked = encoded
3175                .tbs_cert_list
3176                .revoked_certificates
3177                .as_mut()
3178                .and_then(|entries| entries.first_mut())
3179                .expect("tracked Merlin CRL must contain a revoked entry");
3180            revoked
3181                .crl_entry_extensions
3182                .get_or_insert_default()
3183                .push(Extension {
3184                    extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.21"),
3185                    critical: false,
3186                    extn_value: OctetString::new([0x0a, 0x01, reason])
3187                        .expect("DER ENUMERATED extension payload must be valid"),
3188                });
3189            let encoded = encoded
3190                .to_der()
3191                .expect("reason-code CRL test vector must encode");
3192            let result = validate_crl_extensions(&parsed_merlin_crl(&encoded), 0);
3193            if accepted {
3194                assert_eq!(result, Ok(()), "ordinary revocation reasons remain valid");
3195            } else {
3196                assert_eq!(result, Err(X509ChainError::InvalidCrl(0)));
3197            }
3198        }
3199    }
3200
3201    #[test]
3202    fn unevaluable_uri_names_fail_closed_for_both_constraint_forms() {
3203        use x509_parser::extensions::GeneralSubtree;
3204
3205        // A URI without a DNS host is not a non-match: treating it that way
3206        // would bypass excluded URI subtrees while rejecting permitted ones.
3207        let uri = GeneralName::URI("urn:example:opaque");
3208        for constraints in [
3209            NameConstraints {
3210                permitted_subtrees: Some(vec![GeneralSubtree {
3211                    base: GeneralName::URI(".example.com"),
3212                }]),
3213                excluded_subtrees: None,
3214            },
3215            NameConstraints {
3216                permitted_subtrees: None,
3217                excluded_subtrees: Some(vec![GeneralSubtree {
3218                    base: GeneralName::URI(".example.com"),
3219                }]),
3220            },
3221        ] {
3222            assert_eq!(
3223                validate_general_name(&uri, &constraints, 0, 1),
3224                Err(X509ChainError::NameConstraintViolation {
3225                    position: 0,
3226                    constraining_position: 1,
3227                })
3228            );
3229        }
3230    }
3231
3232    #[test]
3233    fn unknown_critical_certificate_extension_fails_closed() {
3234        let root = rcgen::CertifiedIssuer::self_signed(
3235            generated_certificate_params("critical-extension root", true),
3236            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3237        )
3238        .expect("root should be self-signable");
3239        let mut leaf_params = generated_certificate_params("critical-extension leaf", false);
3240        let mut extension =
3241            rcgen::CustomExtension::from_oid_content(&[1, 2, 3, 4], vec![0x05, 0x00]);
3242        extension.set_criticality(true);
3243        leaf_params.custom_extensions.push(extension);
3244        let leaf = leaf_params
3245            .signed_by(
3246                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3247                &root,
3248            )
3249            .expect("root should sign leaf certificate");
3250
3251        assert_eq!(
3252            verify_generated_path(
3253                vec![leaf.der().to_vec(), root.der().to_vec()],
3254                root.der().to_vec(),
3255            ),
3256            Err(X509ChainError::UnsupportedCriticalExtension {
3257                position: 0,
3258                oid: "1.2.3.4".into(),
3259            })
3260        );
3261    }
3262
3263    #[test]
3264    fn duplicate_certificate_extension_oids_fail_closed() {
3265        // RFC 5280 forbids repeated extension OIDs. Enforce that certificate-wide
3266        // invariant before individual extension consumers select a first match.
3267        let root = rcgen::CertifiedIssuer::self_signed(
3268            generated_certificate_params("duplicate-extension root", true),
3269            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3270        )
3271        .expect("root should be self-signable");
3272        let mut leaf_params = generated_certificate_params("duplicate-extension leaf", false);
3273        for _ in 0..2 {
3274            leaf_params
3275                .custom_extensions
3276                .push(rcgen::CustomExtension::from_oid_content(
3277                    &[1, 2, 3, 4],
3278                    vec![0x05, 0x00],
3279                ));
3280        }
3281        let leaf = leaf_params
3282            .signed_by(
3283                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3284                &root,
3285            )
3286            .expect("root should sign leaf certificate");
3287
3288        assert_eq!(
3289            verify_generated_path(
3290                vec![leaf.der().to_vec(), root.der().to_vec()],
3291                root.der().to_vec(),
3292            ),
3293            Err(X509ChainError::DuplicateExtension {
3294                position: 0,
3295                oid: "1.2.3.4".into(),
3296            })
3297        );
3298    }
3299
3300    #[test]
3301    fn invalid_certificate_serial_numbers_fail_path_validation() {
3302        for serial in [vec![0], vec![1; 21]] {
3303            let root = rcgen::CertifiedIssuer::self_signed(
3304                generated_certificate_params("serial root", true),
3305                rcgen::KeyPair::generate().expect("root key generation should succeed"),
3306            )
3307            .expect("root should be self-signable");
3308            let mut leaf_params = generated_certificate_params("invalid serial leaf", false);
3309            leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&serial));
3310            let leaf = leaf_params
3311                .signed_by(
3312                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3313                    &root,
3314                )
3315                .expect("root should sign leaf certificate");
3316
3317            assert!(matches!(
3318                verify_generated_path(
3319                    vec![leaf.der().to_vec(), root.der().to_vec()],
3320                    root.der().to_vec(),
3321                ),
3322                Err(X509ChainError::InvalidDer {
3323                    kind: "certificate serial number",
3324                    ..
3325                })
3326            ));
3327        }
3328
3329        assert!(validate_positive_serial_bytes(&[0x80], "certificate serial number").is_err());
3330        assert!(validate_positive_serial_bytes(&[1; 20], "certificate serial number").is_ok());
3331
3332        let root = rcgen::CertifiedIssuer::self_signed(
3333            generated_certificate_params("serial-padding root", true),
3334            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3335        )
3336        .expect("root should be self-signable");
3337        let mut leaf_params = generated_certificate_params("serial-padding leaf", false);
3338        leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&[0x80; 20]));
3339        let leaf = leaf_params
3340            .signed_by(
3341                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3342                &root,
3343            )
3344            .expect("root should sign a maximum-magnitude serial");
3345
3346        verify_generated_path(
3347            vec![leaf.der().to_vec(), root.der().to_vec()],
3348            root.der().to_vec(),
3349        )
3350        .expect("a 20-octet magnitude may require a DER sign-padding octet");
3351    }
3352
3353    #[test]
3354    fn name_constraints_are_rejected_on_end_entity_certificates() {
3355        // RFC 5280 limits NameConstraints to critical CA extensions; merely
3356        // parsing the extension on an end entity must not count as processing it.
3357        let root = rcgen::CertifiedIssuer::self_signed(
3358            generated_certificate_params("name-placement root", true),
3359            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3360        )
3361        .expect("root should be self-signable");
3362        let mut leaf_params = generated_certificate_params("name-placement leaf", false);
3363        leaf_params.name_constraints = Some(rcgen::NameConstraints {
3364            permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
3365            excluded_subtrees: Vec::new(),
3366        });
3367        let leaf = leaf_params
3368            .signed_by(
3369                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3370                &root,
3371            )
3372            .expect("root should sign leaf certificate");
3373
3374        assert!(matches!(
3375            verify_generated_path(
3376                vec![leaf.der().to_vec(), root.der().to_vec()],
3377                root.der().to_vec(),
3378            ),
3379            Err(X509ChainError::InvalidNameConstraints { position: 0 })
3380        ));
3381    }
3382
3383    #[test]
3384    fn dsa_certificate_rejects_mismatched_inner_signature_algorithm() {
3385        // The signed TBSCertificate algorithm is a separate RFC 5280 invariant;
3386        // a valid signature over the original bytes must not bypass a mismatch
3387        // in the parsed metadata through the legacy DSA fallback.
3388        let (_, mut certificate) = X509Certificate::from_der(include_bytes!(
3389            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
3390        ))
3391        .expect("the tracked Merlin certificate is valid DER");
3392        let (_, issuer) = X509Certificate::from_der(include_bytes!(
3393            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3394        ))
3395        .expect("the tracked Merlin issuer is a DER certificate");
3396        assert!(verify_certificate_signature(&certificate, &issuer));
3397
3398        certificate.tbs_certificate.signature = issuer.public_key().algorithm.clone();
3399
3400        assert_ne!(
3401            certificate.tbs_certificate.signature,
3402            certificate.signature_algorithm
3403        );
3404        assert!(!verify_certificate_signature(&certificate, &issuer));
3405    }
3406
3407    #[test]
3408    fn dsa_sha1_crl_signature_uses_the_same_fallback_as_certificates() {
3409        let xml = include_str!(
3410            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3411        );
3412        let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3413        let key_info_node = document
3414            .descendants()
3415            .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3416            .expect("the Merlin document contains KeyInfo");
3417        let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3418        let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3419            panic!("expected X509Data")
3420        };
3421        let (_, issuer) = X509Certificate::from_der(include_bytes!(
3422            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3423        ))
3424        .expect("the tracked Merlin issuer is a DER certificate");
3425        let (_, crl) = CertificateRevocationList::from_der(&info.crls[0])
3426            .expect("the tracked Merlin CRL is valid DER");
3427
3428        assert!(verify_crl_signature(&crl, &issuer));
3429    }
3430
3431    #[test]
3432    fn dsa_crl_rejects_mismatched_inner_signature_algorithm() {
3433        let xml = include_str!(
3434            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3435        );
3436        let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3437        let key_info_node = document
3438            .descendants()
3439            .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3440            .expect("the Merlin document contains KeyInfo");
3441        let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3442        let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3443            panic!("expected X509Data")
3444        };
3445        let (_, issuer) = X509Certificate::from_der(include_bytes!(
3446            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3447        ))
3448        .expect("the tracked Merlin issuer is a DER certificate");
3449        let (_, mut crl) = CertificateRevocationList::from_der(&info.crls[0])
3450            .expect("the tracked Merlin CRL is valid DER");
3451        assert!(verify_crl_signature(&crl, &issuer));
3452
3453        crl.tbs_cert_list.signature = issuer.public_key().algorithm.clone();
3454
3455        assert_ne!(crl.tbs_cert_list.signature, crl.signature_algorithm);
3456        assert!(!verify_crl_signature(&crl, &issuer));
3457    }
3458}