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