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 signature OID"))
2453                .expect("registered SHA3 ECDSA OID must parse");
2454            assert_eq!(
2455                x509_signature_algorithm(&AlgorithmIdentifier::new(oid.clone(), None)),
2456                Ok(expected)
2457            );
2458            assert!(matches!(
2459                x509_signature_algorithm(&AlgorithmIdentifier::new(
2460                    oid,
2461                    Some(Any::from_tag_and_data(Tag::Null, &[]))
2462                )),
2463                Err(X509ChainError::InvalidDer { .. })
2464            ));
2465        }
2466    }
2467
2468    #[test]
2469    fn x509_signature_parameters_follow_each_algorithm_profile() {
2470        use x509_parser::asn1_rs::{Any, Tag};
2471
2472        // DSA, ECDSA, and Ed25519 signature identifiers require absent
2473        // parameters. A NULL is not equivalent for these algorithm profiles.
2474        for oid in [
2475            "1.2.840.10040.4.3",
2476            "2.16.840.1.101.3.4.3.1",
2477            "2.16.840.1.101.3.4.3.2",
2478            "1.2.840.10045.4.1",
2479            "1.2.840.10045.4.3.1",
2480            "1.2.840.10045.4.3.2",
2481            "1.3.101.112",
2482        ] {
2483            let identifier = AlgorithmIdentifier::new(
2484                Oid::from_str(oid).expect("static signature OID must parse"),
2485                Some(Any::from_tag_and_data(Tag::Null, &[])),
2486            );
2487            assert!(matches!(
2488                x509_signature_algorithm(&identifier),
2489                Err(X509ChainError::InvalidDer {
2490                    kind: "X.509 signature AlgorithmIdentifier parameters",
2491                    ..
2492                })
2493            ));
2494        }
2495
2496        // RSA PKCS#1 signature identifiers accept absent and NULL parameters
2497        // for interoperability, but no other ASN.1 value.
2498        for (oid, digest) in [
2499            (
2500                "1.2.840.113549.1.1.14",
2501                super::super::DigestAlgorithm::Sha224,
2502            ),
2503            (
2504                "1.2.840.113549.1.1.11",
2505                super::super::DigestAlgorithm::Sha256,
2506            ),
2507        ] {
2508            let rsa_oid = Oid::from_str(oid).expect("static RSA signature OID must parse");
2509            for parameters in [None, Some(Any::from_tag_and_data(Tag::Null, &[]))] {
2510                assert_eq!(
2511                    x509_signature_algorithm(&AlgorithmIdentifier::new(
2512                        rsa_oid.clone(),
2513                        parameters,
2514                    )),
2515                    Ok(X509SignatureAlgorithm::RsaPkcs1v15(digest)),
2516                );
2517            }
2518            assert!(matches!(
2519                x509_signature_algorithm(&AlgorithmIdentifier::new(
2520                    rsa_oid,
2521                    Some(Any::from_tag_and_data(Tag::OctetString, &[])),
2522                )),
2523                Err(X509ChainError::InvalidDer {
2524                    kind: "X.509 signature AlgorithmIdentifier parameters",
2525                    ..
2526                })
2527            ));
2528        }
2529    }
2530
2531    #[test]
2532    fn unknown_x509_signature_algorithm_remains_diagnosable() {
2533        let oid = "1.2.3.4.5";
2534        let identifier = AlgorithmIdentifier::new(
2535            Oid::from_str(oid).expect("static unknown OID must parse"),
2536            None,
2537        );
2538
2539        assert_eq!(
2540            x509_signature_algorithm(&identifier),
2541            Err(X509ChainError::UnsupportedSignatureAlgorithm { oid: oid.into() })
2542        );
2543    }
2544
2545    #[test]
2546    fn parses_rsa_pss_certificate_parameters_without_xml_dsig_loss() {
2547        // RFC 4055 carries the digest, MGF digest, and salt length inside the
2548        // AlgorithmIdentifier. Preserve all three values at the provider edge.
2549        let der = [
2550            0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
2551            0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
2552            0x02, 0x01, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
2553            0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
2554            0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x20,
2555        ];
2556        let (rest, identifier) = AlgorithmIdentifier::from_der(&der)
2557            .expect("standard SHA-256 RSA-PSS AlgorithmIdentifier must parse");
2558        assert!(rest.is_empty());
2559
2560        assert_eq!(
2561            x509_signature_algorithm(&identifier),
2562            Ok(X509SignatureAlgorithm::RsaPss {
2563                digest: super::super::DigestAlgorithm::Sha256,
2564                mgf_digest: super::super::DigestAlgorithm::Sha256,
2565                salt_len: 32,
2566            })
2567        );
2568    }
2569
2570    #[test]
2571    fn parses_sha224_rsa_pss_certificate_parameters() {
2572        // RFC 4055 permits SHA-224 independently for the message digest and
2573        // MGF1. The provider-neutral parser must preserve that capability.
2574        let der = [
2575            0x30, 0x41, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x30,
2576            0x34, 0xa0, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
2577            0x02, 0x04, 0x05, 0x00, 0xa1, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
2578            0xf7, 0x0d, 0x01, 0x01, 0x08, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
2579            0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0xa2, 0x03, 0x02, 0x01, 0x1c,
2580        ];
2581        let (rest, identifier) = AlgorithmIdentifier::from_der(&der)
2582            .expect("standard SHA-224 RSA-PSS AlgorithmIdentifier must parse");
2583        assert!(rest.is_empty());
2584
2585        assert_eq!(
2586            x509_signature_algorithm(&identifier),
2587            Ok(X509SignatureAlgorithm::RsaPss {
2588                digest: super::super::DigestAlgorithm::Sha224,
2589                mgf_digest: super::super::DigestAlgorithm::Sha224,
2590                salt_len: 28,
2591            })
2592        );
2593    }
2594
2595    #[test]
2596    fn dsa_rollover_replaces_embedded_root_before_depth_validation() {
2597        let leaf = include_bytes!(
2598            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2599        )
2600        .to_vec();
2601        let embedded_root =
2602            include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2603                .to_vec();
2604
2605        // Trust-anchor self-signatures are not part of path validation. Changing
2606        // only that signature gives this test a distinct rollover certificate
2607        // with the same subject and DSA public key as the embedded stale root.
2608        let mut rollover_anchor = embedded_root.clone();
2609        *rollover_anchor
2610            .last_mut()
2611            .expect("certificate is non-empty") ^= 1;
2612        parse_certificate(&rollover_anchor).expect("modified trust anchor remains valid DER");
2613        let anchors = vec![rollover_anchor];
2614        let info = X509DataInfo {
2615            certificates: vec![leaf, embedded_root],
2616            certificate_chain: vec![0, 1],
2617            ..X509DataInfo::default()
2618        };
2619        let options = X509ChainOptions {
2620            trusted_certs: &anchors,
2621            verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2622            max_chain_depth: 2,
2623            check_crls: false,
2624            allowed_extended_key_usages: None,
2625            rsa_keys: RsaKeyPolicy::default(),
2626            dsa_keys: DsaKeyPolicy {
2627                minimum_modulus_bits: 1024,
2628            },
2629            certificate_signature_algorithms: None,
2630        };
2631
2632        verify_x509_certificate_chain(&info, &options)
2633            .expect("the stale DSA root must be replaced by the configured anchor");
2634    }
2635
2636    #[test]
2637    fn dsa_issuer_key_uses_the_configured_strength_policy() {
2638        let leaf = include_bytes!(
2639            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
2640        )
2641        .to_vec();
2642        let anchor =
2643            include_bytes!("../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der")
2644                .to_vec();
2645        let anchors = vec![anchor.clone()];
2646        let info = X509DataInfo {
2647            certificates: vec![leaf, anchor],
2648            certificate_chain: vec![0, 1],
2649            ..X509DataInfo::default()
2650        };
2651        let options = X509ChainOptions {
2652            trusted_certs: &anchors,
2653            verification_time: UNIX_EPOCH + Duration::from_secs(1_104_580_800),
2654            max_chain_depth: 2,
2655            check_crls: false,
2656            allowed_extended_key_usages: None,
2657            rsa_keys: RsaKeyPolicy::default(),
2658            dsa_keys: DsaKeyPolicy::default(),
2659            certificate_signature_algorithms: None,
2660        };
2661
2662        assert!(matches!(
2663            verify_x509_certificate_chain(&info, &options),
2664            Err(X509ChainError::KeyPolicy {
2665                position: 1,
2666                source: crate::policy::PolicyViolation::KeySize {
2667                    key_type: "DSA",
2668                    minimum_bits: 2048,
2669                    actual_bits: 1024,
2670                    ..
2671                }
2672            })
2673        ));
2674    }
2675
2676    #[test]
2677    fn path_length_excludes_self_issued_rollover_certificates() {
2678        // RFC 5280 excludes self-issued rollover CAs from pathLenConstraint;
2679        // only non-self-issued intermediate CA certificates consume the limit.
2680        let mut root_params = generated_certificate_params("rollover path authority", true);
2681        root_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Constrained(0));
2682        let root = rcgen::CertifiedIssuer::self_signed(
2683            root_params,
2684            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2685        )
2686        .expect("root should be self-signable");
2687        let rollover_params = generated_certificate_params("rollover path authority", true);
2688        let rollover_key =
2689            rcgen::KeyPair::generate().expect("rollover key generation should succeed");
2690        let rollover_certificate = rollover_params
2691            .signed_by(&rollover_key, &root)
2692            .expect("root should sign same-name rollover certificate");
2693        let rollover_issuer = rcgen::Issuer::from_params(&rollover_params, &rollover_key);
2694        let leaf = generated_certificate_params("rollover path leaf", false)
2695            .signed_by(
2696                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2697                &rollover_issuer,
2698            )
2699            .expect("rollover key should sign leaf certificate");
2700
2701        verify_generated_path(
2702            vec![
2703                leaf.der().to_vec(),
2704                rollover_certificate.der().to_vec(),
2705                root.der().to_vec(),
2706            ],
2707            root.der().to_vec(),
2708        )
2709        .expect("self-issued rollover must not consume a zero path-length allowance");
2710    }
2711
2712    #[test]
2713    fn ca_name_constraints_reject_disallowed_dns_names() {
2714        let mut root_params = generated_certificate_params("constrained authority", true);
2715        root_params.name_constraints = Some(rcgen::NameConstraints {
2716            permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2717            excluded_subtrees: vec![rcgen::GeneralSubtree::DnsName("blocked.example.com".into())],
2718        });
2719        let root = rcgen::CertifiedIssuer::self_signed(
2720            root_params,
2721            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2722        )
2723        .expect("constrained root should be self-signable");
2724
2725        for (dns_name, accepted) in [
2726            ("www.example.com", true),
2727            ("blocked.example.com", false),
2728            ("www.example.net", false),
2729        ] {
2730            let leaf = rcgen::CertificateParams::new(vec![dns_name.into()])
2731                .expect("DNS SAN should be valid")
2732                .signed_by(
2733                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2734                    &root,
2735                )
2736                .expect("root should sign leaf certificate");
2737            assert_eq!(
2738                verify_generated_path(
2739                    vec![leaf.der().to_vec(), root.der().to_vec()],
2740                    root.der().to_vec(),
2741                )
2742                .is_ok(),
2743                accepted,
2744                "unexpected name-constraint result for {dns_name}"
2745            );
2746        }
2747    }
2748
2749    #[test]
2750    fn rfc5280_dns_names_require_preferred_name_syntax() {
2751        let mut root_params = generated_certificate_params("DNS syntax authority", true);
2752        root_params.name_constraints = Some(rcgen::NameConstraints {
2753            permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
2754            excluded_subtrees: Vec::new(),
2755        });
2756        let root = rcgen::CertifiedIssuer::self_signed(
2757            root_params,
2758            rcgen::KeyPair::generate().expect("root key generation should succeed"),
2759        )
2760        .expect("constrained root should be self-signable");
2761
2762        let mut leaf_params = generated_certificate_params("malformed DNS leaf", false);
2763        let dns_name = b"bad..example.com";
2764        let mut san_der = vec![
2765            0x30,
2766            u8::try_from(dns_name.len() + 2).expect("test SAN must fit short-form DER"),
2767            0x82,
2768        ];
2769        san_der.push(u8::try_from(dns_name.len()).expect("test DNS name must fit short-form DER"));
2770        san_der.extend_from_slice(dns_name);
2771        leaf_params
2772            .custom_extensions
2773            .push(rcgen::CustomExtension::from_oid_content(
2774                &[2, 5, 29, 17],
2775                san_der,
2776            ));
2777        let leaf = leaf_params
2778            .signed_by(
2779                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2780                &root,
2781            )
2782            .expect("root should sign malformed-DNS leaf");
2783
2784        assert!(matches!(
2785            verify_generated_path(
2786                vec![leaf.der().to_vec(), root.der().to_vec()],
2787                root.der().to_vec(),
2788            ),
2789            Err(X509ChainError::InvalidDer {
2790                kind: "certificate DNS name",
2791                ..
2792            })
2793        ));
2794
2795        for dns_name in ["*.example.com", "_signing.example.com"] {
2796            assert!(validate_rfc5280_dns_name(dns_name).is_err(), "{dns_name}");
2797        }
2798    }
2799
2800    #[test]
2801    fn name_constraint_matchers_cover_email_uri_and_ip_forms() {
2802        // RFC 5280 gives each GeneralName form distinct subtree semantics;
2803        // exercise those rules directly so a DNS-only implementation cannot pass.
2804        assert!(email_within_subtree("ops@example.com", "example.com"));
2805        assert!(email_within_subtree("ops@example.com", "ops@example.com"));
2806        assert!(!email_within_subtree(
2807            "other@example.com",
2808            "ops@example.com"
2809        ));
2810        assert_eq!(
2811            uri_host("https://user@api.example.com:8443/path"),
2812            Some("api.example.com")
2813        );
2814        assert_eq!(
2815            uri_host("https://user@other@example.com/path"),
2816            None,
2817            "a second userinfo delimiter must not expose a constraint-matchable host"
2818        );
2819        assert!(dns_name_within_subtree(
2820            uri_host("https://api.example.com/path").expect("URI must expose a DNS host"),
2821            ".example.com",
2822            false,
2823        ));
2824        assert!(
2825            ip_address_within_subtree(&[192, 0, 2, 42], &[192, 0, 2, 0, 255, 255, 255, 0],)
2826                .expect("valid IPv4 constraint must evaluate")
2827        );
2828        assert!(
2829            !ip_address_within_subtree(&[192, 0, 3, 42], &[192, 0, 2, 0, 255, 255, 255, 0],)
2830                .expect("valid non-matching IPv4 constraint must evaluate")
2831        );
2832        assert!(matches!(
2833            ip_address_within_subtree(&[192, 0, 2, 42], &[192, 0, 2, 0, 255, 0, 255, 0],),
2834            Err(X509ChainError::InvalidDer {
2835                kind: "IP name constraint",
2836                ..
2837            })
2838        ));
2839    }
2840
2841    #[test]
2842    fn malformed_ip_name_constraints_fail_before_matching() {
2843        use x509_parser::extensions::GeneralSubtree;
2844
2845        let name = GeneralName::IPAddress(&[192, 0, 2, 42]);
2846        for malformed in [
2847            &[192, 0, 2, 0, 255, 255, 255][..],
2848            &[192, 0, 2, 0, 255, 0, 255, 0][..],
2849        ] {
2850            for permitted in [true, false] {
2851                let subtree = GeneralSubtree {
2852                    base: GeneralName::IPAddress(malformed),
2853                };
2854                let constraints = NameConstraints {
2855                    permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2856                    excluded_subtrees: (!permitted).then(|| vec![subtree]),
2857                };
2858                assert!(matches!(
2859                    validate_general_name(&name, &constraints, 0, 1),
2860                    Err(X509ChainError::InvalidDer {
2861                        kind: "IP name constraint",
2862                        ..
2863                    })
2864                ));
2865            }
2866        }
2867    }
2868
2869    #[test]
2870    fn malformed_string_name_constraints_fail_before_matching() {
2871        use x509_parser::extensions::GeneralSubtree;
2872
2873        // Matchers assume admitted string constraints have RFC 5280 syntax.
2874        // Invalid values must not degrade into ordinary non-matches.
2875        for malformed in [
2876            GeneralName::DNSName(""),
2877            GeneralName::DNSName("example..com"),
2878            GeneralName::RFC822Name("@example.com"),
2879            GeneralName::RFC822Name("bad..local@example.com"),
2880            GeneralName::URI("https://example.com"),
2881        ] {
2882            let constraints = NameConstraints {
2883                permitted_subtrees: None,
2884                excluded_subtrees: Some(vec![GeneralSubtree { base: malformed }]),
2885            };
2886            assert!(matches!(
2887                ensure_supported_name_constraints(&constraints, 1),
2888                Err(X509ChainError::InvalidDer {
2889                    kind: "string name constraint",
2890                    ..
2891                })
2892            ));
2893        }
2894
2895        for valid in [
2896            GeneralName::DNSName("example.com"),
2897            GeneralName::DNSName(".example.com"),
2898            GeneralName::RFC822Name("ops@example.com"),
2899            GeneralName::RFC822Name("example.com"),
2900            GeneralName::URI(".example.com"),
2901        ] {
2902            let constraints = NameConstraints {
2903                permitted_subtrees: Some(vec![GeneralSubtree { base: valid }]),
2904                excluded_subtrees: None,
2905            };
2906            ensure_supported_name_constraints(&constraints, 1)
2907                .expect("valid string constraints must remain supported");
2908        }
2909    }
2910
2911    #[test]
2912    fn empty_name_constraint_collections_are_rejected() {
2913        use der::Encode as _;
2914        use x509_cert::ext::pkix::NameConstraints as EncodedNameConstraints;
2915
2916        // RFC 5280 requires at least one subtree overall and at least one entry
2917        // in every explicitly present GeneralSubtrees collection.
2918        for constraints in [
2919            EncodedNameConstraints {
2920                permitted_subtrees: None,
2921                excluded_subtrees: None,
2922            },
2923            EncodedNameConstraints {
2924                permitted_subtrees: Some(Vec::new()),
2925                excluded_subtrees: None,
2926            },
2927            EncodedNameConstraints {
2928                permitted_subtrees: None,
2929                excluded_subtrees: Some(Vec::new()),
2930            },
2931        ] {
2932            let der = constraints
2933                .to_der()
2934                .expect("malformed NameConstraints test input must encode");
2935            assert!(matches!(
2936                validate_name_constraints_der(&der, 1),
2937                Err(X509ChainError::InvalidNameConstraints { position: 1 })
2938            ));
2939        }
2940    }
2941
2942    #[test]
2943    fn unsupported_name_constraint_distances_fail_path_validation() {
2944        use der::{Encode as _, asn1::Ia5String};
2945        use x509_cert::ext::pkix::{
2946            NameConstraints as EncodedNameConstraints,
2947            constraints::name::GeneralSubtree as EncodedGeneralSubtree,
2948            name::GeneralName as EncodedGeneralName,
2949        };
2950
2951        // x509-parser exposes only GeneralSubtree::base. Exercise the complete
2952        // extension DER so unsupported distance fields cannot disappear before
2953        // RFC 5280 path validation sees them.
2954        for (permitted, minimum, maximum) in [
2955            (true, 1, None),
2956            (false, 1, None),
2957            (true, 0, Some(1)),
2958            (false, 0, Some(1)),
2959        ] {
2960            let dns_name = if permitted {
2961                "example.com"
2962            } else {
2963                "blocked.example.com"
2964            };
2965            let subtree = EncodedGeneralSubtree {
2966                base: EncodedGeneralName::DnsName(
2967                    Ia5String::new(dns_name.as_bytes()).expect("valid DNS IA5String"),
2968                ),
2969                minimum,
2970                maximum,
2971            };
2972            let constraints = EncodedNameConstraints {
2973                permitted_subtrees: permitted.then(|| vec![subtree.clone()]),
2974                excluded_subtrees: (!permitted).then(|| vec![subtree]),
2975            };
2976            let mut extension = rcgen::CustomExtension::from_oid_content(
2977                &[2, 5, 29, 30],
2978                constraints
2979                    .to_der()
2980                    .expect("NameConstraints must encode as DER"),
2981            );
2982            extension.set_criticality(true);
2983
2984            let mut root_params = generated_certificate_params("distance authority", true);
2985            root_params.custom_extensions.push(extension);
2986            let root = rcgen::CertifiedIssuer::self_signed(
2987                root_params,
2988                rcgen::KeyPair::generate().expect("root key generation should succeed"),
2989            )
2990            .expect("constrained root should be self-signable");
2991            let leaf = rcgen::CertificateParams::new(vec!["www.example.com".into()])
2992                .expect("leaf DNS SAN should be valid")
2993                .signed_by(
2994                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
2995                    &root,
2996                )
2997                .expect("root should sign leaf certificate");
2998
2999            assert!(matches!(
3000                verify_generated_path(
3001                    vec![leaf.der().to_vec(), root.der().to_vec()],
3002                    root.der().to_vec(),
3003                ),
3004                Err(X509ChainError::InvalidNameConstraints { position: 1 })
3005            ));
3006        }
3007    }
3008
3009    #[test]
3010    fn name_constraints_cover_subject_email_and_directory_name() {
3011        // RFC 5280 requires subject emailAddress attributes to be checked even
3012        // without a SAN, and directoryName constraints compare RDN subtrees.
3013        let mut permitted_directory = rcgen::DistinguishedName::new();
3014        permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
3015        let mut root_params = generated_certificate_params("name authority", true);
3016        root_params.name_constraints = Some(rcgen::NameConstraints {
3017            permitted_subtrees: vec![
3018                rcgen::GeneralSubtree::Rfc822Name("example.com".into()),
3019                rcgen::GeneralSubtree::DirectoryName(permitted_directory),
3020            ],
3021            excluded_subtrees: Vec::new(),
3022        });
3023        let root = rcgen::CertifiedIssuer::self_signed(
3024            root_params,
3025            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3026        )
3027        .expect("constrained root should be self-signable");
3028
3029        for (organization, email, accepted) in [
3030            ("Example Corp", "ops@example.com", true),
3031            ("Other Corp", "ops@example.com", false),
3032            ("Example Corp", "ops@example.net", false),
3033            ("Example Corp", "bad..local@example.com", false),
3034        ] {
3035            let mut leaf_params = generated_certificate_params("name-constrained leaf", false);
3036            leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3037            leaf_params
3038                .distinguished_name
3039                .push(rcgen::DnType::OrganizationName, organization);
3040            leaf_params
3041                .distinguished_name
3042                .push(rcgen::DnType::CommonName, "name-constrained leaf");
3043            leaf_params.distinguished_name.push(
3044                rcgen::DnType::CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]),
3045                rcgen::DnValue::Ia5String(
3046                    email
3047                        .try_into()
3048                        .expect("test email must be a valid IA5String"),
3049                ),
3050            );
3051            let leaf = leaf_params
3052                .signed_by(
3053                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3054                    &root,
3055                )
3056                .expect("root should sign leaf certificate");
3057            let result = verify_generated_path(
3058                vec![leaf.der().to_vec(), root.der().to_vec()],
3059                root.der().to_vec(),
3060            );
3061            assert_eq!(
3062                result.is_ok(),
3063                accepted,
3064                "unexpected subject constraint result for {organization} / {email}: {result:?}",
3065            );
3066        }
3067    }
3068
3069    #[test]
3070    fn empty_subject_requires_a_critical_nonempty_san() {
3071        let root = rcgen::CertifiedIssuer::self_signed(
3072            generated_certificate_params("subject identity authority", true),
3073            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3074        )
3075        .expect("root should be self-signable");
3076
3077        let mut missing_san = rcgen::CertificateParams::new(Vec::new())
3078            .expect("empty SAN list should produce certificate parameters");
3079        missing_san.distinguished_name = rcgen::DistinguishedName::new();
3080        let missing_san = missing_san
3081            .signed_by(
3082                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3083                &root,
3084            )
3085            .expect("test issuer should sign an empty-subject certificate");
3086
3087        let mut noncritical_san = rcgen::CertificateParams::new(Vec::new())
3088            .expect("empty SAN list should produce certificate parameters");
3089        noncritical_san.distinguished_name = rcgen::DistinguishedName::new();
3090        // GeneralNames ::= SEQUENCE { dNSName [2] "a" }. Using a custom
3091        // extension is intentional because rcgen correctly marks its normal
3092        // SAN extension critical whenever the subject is empty.
3093        noncritical_san
3094            .custom_extensions
3095            .push(rcgen::CustomExtension::from_oid_content(
3096                &[2, 5, 29, 17],
3097                vec![0x30, 0x03, 0x82, 0x01, b'a'],
3098            ));
3099        let noncritical_san = noncritical_san
3100            .signed_by(
3101                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3102                &root,
3103            )
3104            .expect("test issuer should sign a non-critical-SAN certificate");
3105
3106        for leaf in [missing_san, noncritical_san] {
3107            assert!(matches!(
3108                verify_generated_path(
3109                    vec![leaf.der().to_vec(), root.der().to_vec()],
3110                    root.der().to_vec(),
3111                ),
3112                Err(X509ChainError::InvalidDer {
3113                    kind: "certificate subject identity",
3114                    ..
3115                })
3116            ));
3117        }
3118    }
3119
3120    #[test]
3121    fn empty_subject_with_critical_san_skips_directory_name_constraints() {
3122        // RFC 5280 permits an empty subject when a critical SAN carries the
3123        // identity. An absent DirectoryName need not match a permitted subtree.
3124        let mut permitted_directory = rcgen::DistinguishedName::new();
3125        permitted_directory.push(rcgen::DnType::OrganizationName, "Example Corp");
3126        let mut root_params = generated_certificate_params("empty-subject authority", true);
3127        root_params.name_constraints = Some(rcgen::NameConstraints {
3128            permitted_subtrees: vec![rcgen::GeneralSubtree::DirectoryName(permitted_directory)],
3129            excluded_subtrees: Vec::new(),
3130        });
3131        let root = rcgen::CertifiedIssuer::self_signed(
3132            root_params,
3133            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3134        )
3135        .expect("constrained root should be self-signable");
3136        let mut leaf_params = rcgen::CertificateParams::new(vec!["allowed.example".into()])
3137            .expect("DNS SAN should be valid");
3138        leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3139        let leaf = leaf_params
3140            .signed_by(
3141                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3142                &root,
3143            )
3144            .expect("root should sign empty-subject leaf");
3145
3146        verify_generated_path(
3147            vec![leaf.der().to_vec(), root.der().to_vec()],
3148            root.der().to_vec(),
3149        )
3150        .expect("only present name forms should be constrained");
3151    }
3152
3153    #[test]
3154    fn malformed_general_names_in_san_fail_path_validation() {
3155        let root = rcgen::CertifiedIssuer::self_signed(
3156            generated_certificate_params("malformed-SAN root", true),
3157            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3158        )
3159        .expect("root should be self-signable");
3160        for empty_subject in [true, false] {
3161            let mut leaf_params = generated_certificate_params("malformed-SAN leaf", false);
3162            if empty_subject {
3163                leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3164            }
3165            // GeneralNames ::= SEQUENCE { dNSName [2] <invalid IA5 octet> }.
3166            let mut malformed_san = rcgen::CustomExtension::from_oid_content(
3167                &[2, 5, 29, 17],
3168                vec![0x30, 0x03, 0x82, 0x01, 0xff],
3169            );
3170            malformed_san.set_criticality(true);
3171            leaf_params.custom_extensions.push(malformed_san);
3172            let leaf = leaf_params
3173                .signed_by(
3174                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3175                    &root,
3176                )
3177                .expect("root should sign malformed-SAN leaf");
3178
3179            assert!(matches!(
3180                verify_generated_path(
3181                    vec![leaf.der().to_vec(), root.der().to_vec()],
3182                    root.der().to_vec(),
3183                ),
3184                Err(X509ChainError::InvalidDer {
3185                    kind: "certificate subject identity",
3186                    ..
3187                })
3188            ));
3189        }
3190    }
3191
3192    #[test]
3193    fn typed_subject_alternative_names_require_rfc5280_syntax() {
3194        let root = rcgen::CertifiedIssuer::self_signed(
3195            generated_certificate_params("typed-SAN root", true),
3196            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3197        )
3198        .expect("root should be self-signable");
3199
3200        for (tag, value) in [
3201            (0x81, b"operator@".as_slice()),
3202            (0x81, b"first..last@example.com".as_slice()),
3203            (0x86, b"relative/path".as_slice()),
3204            (0x86, b"https://example.com/%zz".as_slice()),
3205            (0x86, b"https://user@other@example.com/path".as_slice()),
3206            (0x86, b"file:///path".as_slice()),
3207            (0x87, &[192, 0, 2][..]),
3208        ] {
3209            for empty_subject in [false, true] {
3210                let mut leaf_params = generated_certificate_params("typed-SAN leaf", false);
3211                if empty_subject {
3212                    leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3213                }
3214                let mut san_der = vec![
3215                    0x30,
3216                    u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
3217                    tag,
3218                    u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
3219                ];
3220                san_der.extend_from_slice(value);
3221                let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
3222                san.set_criticality(true);
3223                leaf_params.custom_extensions.push(san);
3224                let leaf = leaf_params
3225                    .signed_by(
3226                        &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3227                        &root,
3228                    )
3229                    .expect("root should sign typed-SAN leaf");
3230
3231                assert!(matches!(
3232                    verify_generated_path(
3233                        vec![leaf.der().to_vec(), root.der().to_vec()],
3234                        root.der().to_vec(),
3235                    ),
3236                    Err(X509ChainError::InvalidDer {
3237                        kind: "certificate subject identity",
3238                        ..
3239                    })
3240                ));
3241            }
3242        }
3243
3244        for (tag, value) in [
3245            (0x81, b"operator@example.com".as_slice()),
3246            (0x81, b"operator@[192.0.2.1]".as_slice()),
3247            (0x81, b"operator@[IPv6:2001:db8::1]".as_slice()),
3248            (0x81, br#""operator desk"@example.com"#.as_slice()),
3249            (0x86, b"urn:example:operator".as_slice()),
3250            (
3251                0x86,
3252                b"https://operator@example.com:8443/path?q=1#id".as_slice(),
3253            ),
3254            (0x87, &[192, 0, 2, 1][..]),
3255            (
3256                0x87,
3257                &[0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1][..],
3258            ),
3259        ] {
3260            let mut leaf_params = rcgen::CertificateParams::new(Vec::new())
3261                .expect("empty SAN list should produce certificate parameters");
3262            leaf_params.distinguished_name = rcgen::DistinguishedName::new();
3263            let mut san_der = vec![
3264                0x30,
3265                u8::try_from(value.len() + 2).expect("test SAN must fit short-form DER"),
3266                tag,
3267                u8::try_from(value.len()).expect("test GeneralName must fit short-form DER"),
3268            ];
3269            san_der.extend_from_slice(value);
3270            let mut san = rcgen::CustomExtension::from_oid_content(&[2, 5, 29, 17], san_der);
3271            san.set_criticality(true);
3272            leaf_params.custom_extensions.push(san);
3273            let leaf = leaf_params
3274                .signed_by(
3275                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3276                    &root,
3277                )
3278                .expect("root should sign typed-SAN leaf");
3279
3280            verify_generated_path(
3281                vec![leaf.der().to_vec(), root.der().to_vec()],
3282                root.der().to_vec(),
3283            )
3284            .expect("valid typed SAN identity must satisfy an empty subject");
3285        }
3286    }
3287
3288    fn parsed_merlin_crl(der: &[u8]) -> CertificateRevocationList<'_> {
3289        CertificateRevocationList::from_der(der)
3290            .expect("modified Merlin CRL must remain parseable")
3291            .1
3292    }
3293
3294    fn merlin_crl_der() -> Vec<u8> {
3295        let xml = include_str!(
3296            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3297        );
3298        let document = Document::parse(xml).expect("tracked Merlin document must parse");
3299        let key_info_node = document
3300            .descendants()
3301            .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3302            .expect("tracked Merlin document contains KeyInfo");
3303        let key_info = parse_key_info(key_info_node).expect("tracked Merlin KeyInfo must parse");
3304        let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3305            panic!("expected X509Data")
3306        };
3307        info.crls[0].clone()
3308    }
3309
3310    #[test]
3311    fn duplicate_crl_and_entry_extension_oids_fail_closed() {
3312        use der::{Decode as _, Encode as _};
3313        use x509_cert::crl::CertificateList;
3314
3315        let original = merlin_crl_der();
3316        let mut duplicate_crl: CertificateList =
3317            CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3318        let extensions = duplicate_crl
3319            .tbs_cert_list
3320            .crl_extensions
3321            .as_mut()
3322            .expect("tracked Merlin CRL must contain extensions");
3323        extensions.push(extensions[0].clone());
3324        let duplicate_crl = duplicate_crl
3325            .to_der()
3326            .expect("duplicate CRL extension test vector must encode");
3327        assert_eq!(
3328            validate_crl_extensions(&parsed_merlin_crl(&duplicate_crl), 0),
3329            Err(X509ChainError::InvalidCrl(0))
3330        );
3331
3332        let mut duplicate_entry: CertificateList =
3333            CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3334        let duplicate = duplicate_entry
3335            .tbs_cert_list
3336            .crl_extensions
3337            .as_ref()
3338            .and_then(|extensions| extensions.first())
3339            .expect("tracked Merlin CRL must contain an extension")
3340            .clone();
3341        let revoked = duplicate_entry
3342            .tbs_cert_list
3343            .revoked_certificates
3344            .as_mut()
3345            .and_then(|entries| entries.first_mut())
3346            .expect("tracked Merlin CRL must contain a revoked entry");
3347        revoked.crl_entry_extensions = Some(vec![duplicate.clone(), duplicate]);
3348        let duplicate_entry = duplicate_entry
3349            .to_der()
3350            .expect("duplicate entry extension test vector must encode");
3351        assert_eq!(
3352            validate_crl_extensions(&parsed_merlin_crl(&duplicate_entry), 0),
3353            Err(X509ChainError::InvalidCrl(0))
3354        );
3355    }
3356
3357    #[test]
3358    fn malformed_revoked_certificate_serials_fail_closed() {
3359        // Mutate the signed Merlin CRL fixture without changing DER lengths so
3360        // zero and negative serials exercise the actual CRL parser path.
3361        let original = merlin_crl_der();
3362        let serial = parsed_merlin_crl(&original)
3363            .iter_revoked_certificates()
3364            .next()
3365            .expect("tracked Merlin CRL must contain a revoked entry")
3366            .raw_serial()
3367            .to_vec();
3368        let offsets = original
3369            .windows(serial.len())
3370            .enumerate()
3371            .filter_map(|(offset, bytes)| (bytes == serial).then_some(offset))
3372            .collect::<Vec<_>>();
3373        assert_eq!(
3374            offsets.len(),
3375            1,
3376            "revoked serial fixture must be unambiguous"
3377        );
3378
3379        for replacement in [vec![0; serial.len()], {
3380            let mut negative = serial.clone();
3381            negative[0] = 0x80;
3382            negative
3383        }] {
3384            let mut malformed = original.clone();
3385            malformed[offsets[0]..offsets[0] + serial.len()].copy_from_slice(&replacement);
3386            assert_eq!(
3387                validate_crl_extensions(&parsed_merlin_crl(&malformed), 0),
3388                Err(X509ChainError::InvalidCrl(0))
3389            );
3390        }
3391    }
3392
3393    #[test]
3394    fn delta_crl_indicator_is_rejected_regardless_of_criticality() {
3395        use der::{Decode as _, Encode as _, asn1::OctetString};
3396        use x509_cert::{crl::CertificateList, ext::Extension};
3397
3398        let original = merlin_crl_der();
3399        for critical in [false, true] {
3400            let mut encoded: CertificateList =
3401                CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3402            encoded
3403                .tbs_cert_list
3404                .crl_extensions
3405                .get_or_insert_default()
3406                .push(Extension {
3407                    extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.27"),
3408                    critical,
3409                    extn_value: OctetString::new([0x02, 0x01, 0x01])
3410                        .expect("DER INTEGER extension payload must be valid"),
3411                });
3412            let encoded = encoded
3413                .to_der()
3414                .expect("delta CRL indicator test vector must encode");
3415            assert_eq!(
3416                validate_crl_extensions(&parsed_merlin_crl(&encoded), 0),
3417                Err(X509ChainError::InvalidCrl(0)),
3418                "delta CRL indicator criticality must not change unsupported semantics"
3419            );
3420        }
3421    }
3422
3423    #[test]
3424    fn remove_from_crl_is_rejected_in_a_complete_crl() {
3425        use der::{Decode as _, Encode as _, asn1::OctetString};
3426        use x509_cert::{crl::CertificateList, ext::Extension};
3427
3428        let original = merlin_crl_der();
3429        for (reason, accepted) in [(1_u8, true), (8_u8, false)] {
3430            let mut encoded: CertificateList =
3431                CertificateList::from_der(&original).expect("tracked Merlin CRL must decode");
3432            let revoked = encoded
3433                .tbs_cert_list
3434                .revoked_certificates
3435                .as_mut()
3436                .and_then(|entries| entries.first_mut())
3437                .expect("tracked Merlin CRL must contain a revoked entry");
3438            revoked
3439                .crl_entry_extensions
3440                .get_or_insert_default()
3441                .push(Extension {
3442                    extn_id: der::asn1::ObjectIdentifier::new_unwrap("2.5.29.21"),
3443                    critical: false,
3444                    extn_value: OctetString::new([0x0a, 0x01, reason])
3445                        .expect("DER ENUMERATED extension payload must be valid"),
3446                });
3447            let encoded = encoded
3448                .to_der()
3449                .expect("reason-code CRL test vector must encode");
3450            let result = validate_crl_extensions(&parsed_merlin_crl(&encoded), 0);
3451            if accepted {
3452                assert_eq!(result, Ok(()), "ordinary revocation reasons remain valid");
3453            } else {
3454                assert_eq!(result, Err(X509ChainError::InvalidCrl(0)));
3455            }
3456        }
3457    }
3458
3459    #[test]
3460    fn unevaluable_uri_names_fail_closed_for_both_constraint_forms() {
3461        use x509_parser::extensions::GeneralSubtree;
3462
3463        // A URI without a DNS host is not a non-match: treating it that way
3464        // would bypass excluded URI subtrees while rejecting permitted ones.
3465        let uri = GeneralName::URI("urn:example:opaque");
3466        for constraints in [
3467            NameConstraints {
3468                permitted_subtrees: Some(vec![GeneralSubtree {
3469                    base: GeneralName::URI(".example.com"),
3470                }]),
3471                excluded_subtrees: None,
3472            },
3473            NameConstraints {
3474                permitted_subtrees: None,
3475                excluded_subtrees: Some(vec![GeneralSubtree {
3476                    base: GeneralName::URI(".example.com"),
3477                }]),
3478            },
3479        ] {
3480            assert_eq!(
3481                validate_general_name(&uri, &constraints, 0, 1),
3482                Err(X509ChainError::NameConstraintViolation {
3483                    position: 0,
3484                    constraining_position: 1,
3485                })
3486            );
3487        }
3488    }
3489
3490    #[test]
3491    fn unknown_critical_certificate_extension_fails_closed() {
3492        let root = rcgen::CertifiedIssuer::self_signed(
3493            generated_certificate_params("critical-extension root", true),
3494            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3495        )
3496        .expect("root should be self-signable");
3497        let mut leaf_params = generated_certificate_params("critical-extension leaf", false);
3498        let mut extension =
3499            rcgen::CustomExtension::from_oid_content(&[1, 2, 3, 4], vec![0x05, 0x00]);
3500        extension.set_criticality(true);
3501        leaf_params.custom_extensions.push(extension);
3502        let leaf = leaf_params
3503            .signed_by(
3504                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3505                &root,
3506            )
3507            .expect("root should sign leaf certificate");
3508
3509        assert_eq!(
3510            verify_generated_path(
3511                vec![leaf.der().to_vec(), root.der().to_vec()],
3512                root.der().to_vec(),
3513            ),
3514            Err(X509ChainError::UnsupportedCriticalExtension {
3515                position: 0,
3516                oid: "1.2.3.4".into(),
3517            })
3518        );
3519    }
3520
3521    #[test]
3522    fn duplicate_certificate_extension_oids_fail_closed() {
3523        // RFC 5280 forbids repeated extension OIDs. Enforce that certificate-wide
3524        // invariant before individual extension consumers select a first match.
3525        let root = rcgen::CertifiedIssuer::self_signed(
3526            generated_certificate_params("duplicate-extension root", true),
3527            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3528        )
3529        .expect("root should be self-signable");
3530        let mut leaf_params = generated_certificate_params("duplicate-extension leaf", false);
3531        for _ in 0..2 {
3532            leaf_params
3533                .custom_extensions
3534                .push(rcgen::CustomExtension::from_oid_content(
3535                    &[1, 2, 3, 4],
3536                    vec![0x05, 0x00],
3537                ));
3538        }
3539        let leaf = leaf_params
3540            .signed_by(
3541                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3542                &root,
3543            )
3544            .expect("root should sign leaf certificate");
3545
3546        assert_eq!(
3547            verify_generated_path(
3548                vec![leaf.der().to_vec(), root.der().to_vec()],
3549                root.der().to_vec(),
3550            ),
3551            Err(X509ChainError::DuplicateExtension {
3552                position: 0,
3553                oid: "1.2.3.4".into(),
3554            })
3555        );
3556    }
3557
3558    #[test]
3559    fn invalid_certificate_serial_numbers_fail_path_validation() {
3560        for serial in [vec![0], vec![1; 21]] {
3561            let root = rcgen::CertifiedIssuer::self_signed(
3562                generated_certificate_params("serial root", true),
3563                rcgen::KeyPair::generate().expect("root key generation should succeed"),
3564            )
3565            .expect("root should be self-signable");
3566            let mut leaf_params = generated_certificate_params("invalid serial leaf", false);
3567            leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&serial));
3568            let leaf = leaf_params
3569                .signed_by(
3570                    &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3571                    &root,
3572                )
3573                .expect("root should sign leaf certificate");
3574
3575            assert!(matches!(
3576                verify_generated_path(
3577                    vec![leaf.der().to_vec(), root.der().to_vec()],
3578                    root.der().to_vec(),
3579                ),
3580                Err(X509ChainError::InvalidDer {
3581                    kind: "certificate serial number",
3582                    ..
3583                })
3584            ));
3585        }
3586
3587        assert!(validate_positive_serial_bytes(&[0x80], "certificate serial number").is_err());
3588        assert!(validate_positive_serial_bytes(&[1; 20], "certificate serial number").is_ok());
3589
3590        let root = rcgen::CertifiedIssuer::self_signed(
3591            generated_certificate_params("serial-padding root", true),
3592            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3593        )
3594        .expect("root should be self-signable");
3595        let mut leaf_params = generated_certificate_params("serial-padding leaf", false);
3596        leaf_params.serial_number = Some(rcgen::SerialNumber::from_slice(&[0x80; 20]));
3597        let leaf = leaf_params
3598            .signed_by(
3599                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3600                &root,
3601            )
3602            .expect("root should sign a maximum-magnitude serial");
3603
3604        verify_generated_path(
3605            vec![leaf.der().to_vec(), root.der().to_vec()],
3606            root.der().to_vec(),
3607        )
3608        .expect("a 20-octet magnitude may require a DER sign-padding octet");
3609    }
3610
3611    #[test]
3612    fn name_constraints_are_rejected_on_end_entity_certificates() {
3613        // RFC 5280 limits NameConstraints to critical CA extensions; merely
3614        // parsing the extension on an end entity must not count as processing it.
3615        let root = rcgen::CertifiedIssuer::self_signed(
3616            generated_certificate_params("name-placement root", true),
3617            rcgen::KeyPair::generate().expect("root key generation should succeed"),
3618        )
3619        .expect("root should be self-signable");
3620        let mut leaf_params = generated_certificate_params("name-placement leaf", false);
3621        leaf_params.name_constraints = Some(rcgen::NameConstraints {
3622            permitted_subtrees: vec![rcgen::GeneralSubtree::DnsName("example.com".into())],
3623            excluded_subtrees: Vec::new(),
3624        });
3625        let leaf = leaf_params
3626            .signed_by(
3627                &rcgen::KeyPair::generate().expect("leaf key generation should succeed"),
3628                &root,
3629            )
3630            .expect("root should sign leaf certificate");
3631
3632        assert!(matches!(
3633            verify_generated_path(
3634                vec![leaf.der().to_vec(), root.der().to_vec()],
3635                root.der().to_vec(),
3636            ),
3637            Err(X509ChainError::InvalidNameConstraints { position: 0 })
3638        ));
3639    }
3640
3641    #[test]
3642    fn dsa_certificate_rejects_mismatched_inner_signature_algorithm() {
3643        // The signed TBSCertificate algorithm is a separate RFC 5280 invariant;
3644        // a valid signature over the original bytes must not bypass a mismatch
3645        // in the parsed metadata through the legacy DSA fallback.
3646        let (_, mut certificate) = X509Certificate::from_der(include_bytes!(
3647            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/balor.der"
3648        ))
3649        .expect("the tracked Merlin certificate is valid DER");
3650        let (_, issuer) = X509Certificate::from_der(include_bytes!(
3651            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3652        ))
3653        .expect("the tracked Merlin issuer is a DER certificate");
3654        assert!(verify_certificate_signature(&certificate, &issuer));
3655
3656        certificate.tbs_certificate.signature = issuer.public_key().algorithm.clone();
3657
3658        assert_ne!(
3659            certificate.tbs_certificate.signature,
3660            certificate.signature_algorithm
3661        );
3662        assert!(!verify_certificate_signature(&certificate, &issuer));
3663    }
3664
3665    #[test]
3666    fn dsa_sha1_crl_signature_uses_the_same_fallback_as_certificates() {
3667        let xml = include_str!(
3668            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3669        );
3670        let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3671        let key_info_node = document
3672            .descendants()
3673            .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3674            .expect("the Merlin document contains KeyInfo");
3675        let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3676        let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3677            panic!("expected X509Data")
3678        };
3679        let (_, issuer) = X509Certificate::from_der(include_bytes!(
3680            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3681        ))
3682        .expect("the tracked Merlin issuer is a DER certificate");
3683        let (_, crl) = CertificateRevocationList::from_der(&info.crls[0])
3684            .expect("the tracked Merlin CRL is valid DER");
3685
3686        assert!(verify_crl_signature(&crl, &issuer));
3687    }
3688
3689    #[test]
3690    fn dsa_crl_rejects_mismatched_inner_signature_algorithm() {
3691        let xml = include_str!(
3692            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/signature-x509-crt-crl.xml"
3693        );
3694        let document = Document::parse(xml).expect("the tracked Merlin document is valid XML");
3695        let key_info_node = document
3696            .descendants()
3697            .find(|node| node.has_tag_name((XMLDSIG_NS, "KeyInfo")))
3698            .expect("the Merlin document contains KeyInfo");
3699        let key_info = parse_key_info(key_info_node).expect("the Merlin KeyInfo is valid");
3700        let KeyInfoSource::X509Data(info) = &key_info.sources[0] else {
3701            panic!("expected X509Data")
3702        };
3703        let (_, issuer) = X509Certificate::from_der(include_bytes!(
3704            "../../tests/fixtures/xmldsig/merlin-xmldsig-twenty-three/certs/ca.der"
3705        ))
3706        .expect("the tracked Merlin issuer is a DER certificate");
3707        let (_, mut crl) = CertificateRevocationList::from_der(&info.crls[0])
3708            .expect("the tracked Merlin CRL is valid DER");
3709        assert!(verify_crl_signature(&crl, &issuer));
3710
3711        crl.tbs_cert_list.signature = issuer.public_key().algorithm.clone();
3712
3713        assert_ne!(crl.tbs_cert_list.signature, crl.signature_algorithm);
3714        assert!(!verify_crl_signature(&crl, &issuer));
3715    }
3716}