Skip to main content

xml_sec/xmldsig/
x509.rs

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