Skip to main content

xml_sec/xmldsig/
x509.rs

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