Skip to main content

xml_sec/xmldsig/
pkix.rs

1//! Adapter-owned execution domain for the published ASN.1 and PKIX mechanisms.
2
3use std::sync::Arc;
4
5use structured_asn1::memory::{Memory, MemoryAccount, MemoryError, OwnedVec, Work};
6use structured_asn1::{Encoding, Limits, Parser};
7use structured_pkix::Certificate;
8
9use super::x509::X509ChainError;
10use crate::policy::{PolicyViolation, ResourcePolicy, resource_name};
11
12/// A certificate path's source bytes; display projections are not authority.
13/// Both parsed public data and private borrowed pools use this same view.
14pub(crate) trait X509DataSource {
15    fn certificate_count(&self) -> usize;
16    fn certificate(&self, index: usize) -> Option<&[u8]>;
17    fn chain(&self) -> &[usize];
18    fn crls(&self) -> &[Vec<u8>];
19
20    fn encoded_certificates(&self) -> impl ExactSizeIterator<Item = &[u8]> {
21        (0..self.certificate_count())
22            .map(|index| self.certificate(index).expect("source index is in range"))
23    }
24}
25
26impl X509DataSource for super::X509DataInfo {
27    fn certificate_count(&self) -> usize {
28        self.certificates.len()
29    }
30    fn certificate(&self, index: usize) -> Option<&[u8]> {
31        self.certificates.get(index).map(Vec::as_slice)
32    }
33    fn chain(&self) -> &[usize] {
34        &self.certificate_chain
35    }
36    fn crls(&self) -> &[Vec<u8>] {
37        &self.crls
38    }
39}
40
41enum XmlNameText<'a> {
42    Borrowed(&'a str),
43    Rewritten(structured_asn1::memory::OwnedString),
44}
45
46impl XmlNameText<'_> {
47    fn text<'a>(&'a self, memory: &Memory) -> Result<&'a str, X509ChainError> {
48        match self {
49            Self::Borrowed(text) => Ok(text),
50            Self::Rewritten(text) => text.as_str(memory).map_err(|reason| {
51                X509ChainError::Processing(structured_pkix::Error::resource(
52                    reason,
53                    "name.selector",
54                    0,
55                ))
56            }),
57        }
58    }
59}
60
61fn xml_whitespace(byte: u8) -> bool {
62    matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
63}
64
65fn escaped_at(bytes: &[u8], at: usize) -> bool {
66    let mut start = at;
67    while start > 0 && bytes[start - 1] == b'\\' {
68        start -= 1;
69    }
70    (at - start) % 2 == 1
71}
72
73fn xml_name_pieces(
74    text: &str,
75    mut emit: impl FnMut(&str) -> Result<(), X509ChainError>,
76) -> Result<bool, X509ChainError> {
77    let bytes = text.as_bytes();
78    let mut start = 0;
79    while start < bytes.len() && xml_whitespace(bytes[start]) {
80        start += 1;
81    }
82    let mut end = bytes.len();
83    while end > start && xml_whitespace(bytes[end - 1]) && !escaped_at(bytes, end - 1) {
84        end -= 1;
85    }
86    let mut changed = start != 0 || end != bytes.len();
87    let mut piece = |segment: &str| -> Result<bool, X509ChainError> {
88        let input = segment.as_bytes();
89        let mut index = 0;
90        while index < input.len() {
91            if input[index] == b'\\' {
92                index += 2;
93                continue;
94            }
95            if input[index] == b'=' {
96                // XMLDSig's recommended textual form historically accepts
97                // this alias; the shared RFC 4514 codec uses emailAddress.
98                let alias = segment[..index].eq_ignore_ascii_case("EMAIL");
99                if alias {
100                    emit("emailAddress")?;
101                }
102                // Preserve a historically accepted leading value space by
103                // expressing it as RFC 4514's escaped space, not by deleting it.
104                if index + 1 < input.len() && input[index + 1] == b' ' {
105                    emit(if alias { "=" } else { &segment[..index + 1] })?;
106                    emit("\\")?;
107                    emit(&segment[index + 1..])?;
108                    return Ok(true);
109                }
110                if alias {
111                    emit(&segment[index..])?;
112                    return Ok(true);
113                }
114                break;
115            }
116            index += 1;
117        }
118        emit(segment)?;
119        Ok(false)
120    };
121    let mut at = start;
122    let mut segment = start;
123    while at < end {
124        if bytes[at] == b'\\' {
125            at += 2;
126            continue;
127        }
128        if matches!(bytes[at], b',' | b'+') {
129            let mut cut = at;
130            while cut > segment && xml_whitespace(bytes[cut - 1]) && !escaped_at(bytes, cut - 1) {
131                cut -= 1;
132            }
133            changed |= piece(&text[segment..cut])?;
134            changed |= piece(&text[at..at + 1])?;
135            let previous = at + 1;
136            at = previous;
137            while at < end && xml_whitespace(bytes[at]) {
138                at += 1;
139            }
140            // Track removals separately from inserted escapes; equal lengths
141            // do not prove that the source was already canonical.
142            if cut != previous - 1 || at != previous {
143                changed = true;
144            }
145            segment = at;
146        } else {
147            at += 1;
148        }
149    }
150    changed |= piece(&text[segment..end])?;
151    Ok(changed)
152}
153
154fn xml_name_text<'a>(text: &'a str, memory: &Memory) -> Result<XmlNameText<'a>, X509ChainError> {
155    // XMLDSig 1.1 4.5.4.1 recommends RFC 4514, rather than requiring strict
156    // lexical input. Preserve existing XML formatting tolerance here; the
157    // shared codec remains strict and owns all DN value/equality semantics.
158    // https://www.w3.org/TR/2013/REC-xmldsig-core1-20130411/#dname-encrules
159    let fail = |reason| {
160        X509ChainError::Processing(structured_pkix::Error::resource(reason, "name.selector", 0))
161    };
162    let work = (text.len() as u64)
163        .checked_mul(12)
164        .and_then(|work| work.checked_add(1))
165        .ok_or_else(|| fail(MemoryError::WorkExceeded))?;
166    memory.charge_work(work).map_err(fail)?;
167    if !xml_name_pieces(text, |_| Ok(()))? {
168        return Ok(XmlNameText::Borrowed(text));
169    }
170    let mut output = memory.string();
171    xml_name_pieces(text, |piece| output.try_push_str(piece).map_err(fail))?;
172    Ok(XmlNameText::Rewritten(output))
173}
174
175impl CertificateSession {
176    pub(crate) fn read_pool<'a>(
177        &'a self,
178        certificates: &'a [Vec<u8>],
179    ) -> Result<OwnedVec<Certificate<'a>>, X509ChainError> {
180        self.read_sources(certificates.iter().map(Vec::as_slice))
181    }
182
183    pub(crate) fn read_sources<'a>(
184        &'a self,
185        certificates: impl ExactSizeIterator<Item = &'a [u8]>,
186    ) -> Result<OwnedVec<Certificate<'a>>, X509ChainError> {
187        let mut pool = self.memory().vector();
188        pool.try_reserve_exact(certificates.len())
189            .map_err(|reason| {
190                self.error(structured_pkix::Error::resource(
191                    reason,
192                    "certificate.pool",
193                    0,
194                ))
195            })?;
196        for encoded in certificates {
197            pool.try_push(self.read(encoded)?).map_err(|reason| {
198                self.error(structured_pkix::Error::resource(
199                    reason,
200                    "certificate.pool",
201                    0,
202                ))
203            })?;
204        }
205        Ok(pool)
206    }
207
208    pub(crate) fn source_names_equal(
209        &self,
210        left: &structured_pkix::name::Name<'_>,
211        right: &structured_pkix::name::Name<'_>,
212    ) -> Result<bool, X509ChainError> {
213        left.matches(right, self.memory())
214            .map_err(|error| self.error(error))
215    }
216
217    pub(crate) fn selector(
218        &self,
219        text: &str,
220    ) -> Result<structured_pkix::name::Selector, X509ChainError> {
221        let normalize = |error| match error {
222            X509ChainError::Processing(error) => self.error(error),
223            error => error,
224        };
225        let text = xml_name_text(text, self.memory()).map_err(normalize)?;
226        structured_pkix::name::Selector::parse(
227            text.text(self.memory()).map_err(normalize)?,
228            self.memory(),
229        )
230        .map_err(|error| self.error(error))
231    }
232
233    #[cfg(test)]
234    pub(crate) fn names_equal(&self, left: &str, right: &str) -> Result<bool, X509ChainError> {
235        let left = self.selector(left)?;
236        let right = self.selector(right)?;
237        left.matches(&right, self.memory())
238            .map_err(|error| self.error(error))
239    }
240
241    #[cfg(test)]
242    fn read_name<'a>(
243        &'a self,
244        bytes: &'a [u8],
245    ) -> Result<structured_pkix::name::Name<'a>, X509ChainError> {
246        let mut reader = self.parser.read(bytes, 0).map_err(|error| {
247            self.error(structured_pkix::Error::new(
248                structured_pkix::ErrorCode::Encoding(error.code()),
249                "name",
250                error.offset(),
251            ))
252        })?;
253        let element = reader
254            .next_element()
255            .map_err(|error| {
256                self.error(structured_pkix::Error::new(
257                    structured_pkix::ErrorCode::Encoding(error.code()),
258                    "name",
259                    error.offset(),
260                ))
261            })?
262            .expect("test Name element");
263        structured_pkix::name::read(&reader, &element, "name").map_err(|error| self.error(error))
264    }
265}
266
267pub(crate) fn verification_instant(
268    time: std::time::SystemTime,
269) -> Result<structured_pkix::temporal::Instant, X509ChainError> {
270    use std::time::UNIX_EPOCH;
271    let nanos = match time.duration_since(UNIX_EPOCH) {
272        Ok(duration) => duration.as_nanos() as i128,
273        Err(error) => -(error.duration().as_nanos() as i128),
274    };
275    let seconds = i64::try_from(nanos.div_euclid(1_000_000_000))
276        .map_err(|_| X509ChainError::CertificateNotValid(0))?;
277    let fraction = nanos.rem_euclid(1_000_000_000) as u32;
278    structured_pkix::temporal::Instant::new(seconds, fraction)
279        .ok_or(X509ChainError::CertificateNotValid(0))
280}
281
282#[cfg(test)]
283pub(crate) fn test_utc_facts() -> structured_pkix::temporal::LeapTable<'static> {
284    // Synthetic caller evidence for test labels; never used by a runtime default.
285    structured_pkix::temporal::LeapTable::new(-2_208_988_800, 4_102_444_800, &[])
286        .expect("test UTC coverage")
287}
288
289/// Consume only original sources and the already authenticated leaf-first path.
290pub(crate) fn verify_crls<'a>(
291    path: &'a [Certificate<'a>],
292    embedded: &'a [Vec<u8>],
293    additional: &'a [Vec<u8>],
294    options: &super::x509::X509ChainOptions<'_>,
295    provider: &dyn crate::provider::CryptoProvider,
296    session: &'a CertificateSession,
297) -> Result<(), X509ChainError> {
298    use structured_pkix::crl::{
299        Authentication, AuthenticationFailure, CompleteEvidence, EntryLookup, Evaluation,
300        Freshness, ScopeOutcome, SourceOrigin,
301    };
302    use structured_pkix::error::CallbackError;
303    use structured_pkix::path::WorkingKey;
304    use structured_pkix::time::Relation;
305
306    if embedded.is_empty() && additional.is_empty() {
307        return Ok(());
308    }
309    let Some(anchor) = path.last() else {
310        return Ok(());
311    };
312    let execution = session.memory();
313    let mut crls = execution.vector();
314    let count = embedded
315        .len()
316        .checked_add(additional.len())
317        .ok_or_else(|| {
318            session.error(structured_pkix::Error::resource(
319                MemoryError::CapacityOverflow,
320                "certificateList.pool",
321                0,
322            ))
323        })?;
324    crls.try_reserve_exact(count).map_err(|reason| {
325        session.error(structured_pkix::Error::resource(
326            reason,
327            "certificateList.pool",
328            0,
329        ))
330    })?;
331    for bytes in embedded.iter().chain(additional) {
332        let crl = session.read_crl(bytes)?;
333        crls.try_push(crl).map_err(|reason| {
334            session.error(structured_pkix::Error::resource(
335                reason,
336                "certificateList.pool",
337                0,
338            ))
339        })?;
340    }
341    let verifier = ProviderVerifier {
342        provider,
343        allowed: options.certificate_signature_algorithms,
344    };
345    let now = verification_instant(options.verification_time)?.into();
346    let mut issuer_key = WorkingKey::standing(anchor.tbs().public_key());
347    // RFC 5280 §6.1.4: project effective parameters once from the validated
348    // anchor toward its subjects. Original SPKI/TBS bytes are never rewritten.
349    // https://www.rfc-editor.org/rfc/rfc5280#section-6.1.4
350    for position in (0..path.len() - 1).rev() {
351        let issuer = &path[position + 1];
352        let target = &path[position];
353        let mut reasons = structured_pkix::crl::Reasons::NONE;
354        for (index, crl) in crls.iter().enumerate() {
355            if !crl
356                .tbs()
357                .issuer()
358                .matches(issuer.tbs().subject(), execution)
359                .map_err(|error| session.error(error))?
360            {
361                continue;
362            }
363            let authority = crl
364                .tbs()
365                .extensions()
366                .authority_key_identifier(execution)
367                .map_err(|error| session.error(error))?;
368            let subject = issuer
369                .tbs()
370                .extensions()
371                .subject_key_identifier(execution)
372                .map_err(|error| session.error(error))?;
373            let identified = if let Some((authority, subject)) = authority
374                .as_ref()
375                .and_then(|authority| authority.key_id())
376                .zip(subject.map(|id| id.as_bytes()))
377            {
378                execution
379                    .charge_work(authority.len().max(subject.len()) as u64 + 1)
380                    .map_err(|reason| {
381                        session.error(structured_pkix::Error::resource(
382                            reason,
383                            "certificateList.authorityKeyIdentifier",
384                            crl.span().start,
385                        ))
386                    })?;
387                if authority != subject {
388                    continue;
389                }
390                true
391            } else {
392                false
393            };
394            let proof = crl
395                .authenticate(issuer, issuer_key, &verifier, execution)
396                .map_err(|error| match error {
397                    CallbackError::Processing(error) => session.error(error),
398                    CallbackError::Callback(error) => error,
399                })?;
400            let proof = match proof {
401                Authentication::Authenticated(proof) => proof,
402                Authentication::Rejected(AuthenticationFailure::KeyUsage) => {
403                    return Err(X509ChainError::InvalidKeyUsage {
404                        position: position + 1,
405                        required: "cRLSign",
406                    });
407                }
408                Authentication::Rejected(AuthenticationFailure::Signature) if !identified => {
409                    continue;
410                }
411                Authentication::Rejected(_) | Authentication::Unsupported => {
412                    return Err(X509ChainError::InvalidCrl(index));
413                }
414            };
415            // UTC history is a caller fact, not an implicit clock/table default.
416            validate_crl_serials(crl, session)?;
417            let utc = match options.utc_facts.as_ref() {
418                Some(utc) => utc,
419                None => {
420                    if matches!(
421                        proof
422                            .scope(target, execution)
423                            .map_err(|error| session.error(error))?,
424                        ScopeOutcome::NotApplicable(_)
425                    ) {
426                        continue;
427                    }
428                    return Err(X509ChainError::MissingUtcFacts);
429                }
430            };
431            let evidence = proof
432                .evaluate(
433                    target,
434                    &Evaluation {
435                        now,
436                        utc,
437                        execution,
438                        origin: SourceOrigin::Cached,
439                    },
440                )
441                .map_err(|error| session.error(error))?;
442            let facts = match evidence {
443                CompleteEvidence::Facts(facts) => facts,
444                CompleteEvidence::NotApplicable(_) => continue,
445                CompleteEvidence::DeltaNeedsBase => return Err(X509ChainError::InvalidCrl(index)),
446            };
447            match facts.freshness() {
448                Freshness::Current | Freshness::AtNextUpdate => (),
449                Freshness::Uncertain(reason) => {
450                    return Err(X509ChainError::CrlTimeUncertain { index, reason });
451                }
452                _ => return Err(X509ChainError::InvalidCrl(index)),
453            }
454            match facts.entry() {
455                EntryLookup::Absent => (),
456                EntryLookup::Found(entry) => {
457                    match entry
458                        .time_relation(now, utc, execution)
459                        .map_err(|error| session.error(error))?
460                    {
461                        Relation::Before | Relation::At => {
462                            return Err(X509ChainError::Revoked(position));
463                        }
464                        Relation::After => (),
465                        Relation::Uncertain(reason) => {
466                            return Err(X509ChainError::CrlTimeUncertain { index, reason });
467                        }
468                    }
469                }
470                EntryLookup::Conflict { .. } | EntryLookup::Unsupported { .. } => {
471                    return Err(X509ChainError::InvalidCrl(index));
472                }
473            }
474            execution.charge_work(1).map_err(|reason| {
475                session.error(structured_pkix::Error::resource(
476                    reason,
477                    "certificateList.reasons",
478                    crl.span().start,
479                ))
480            })?;
481            // RFC 5280 §6.3.3(d),(l): partial-list absence cannot establish
482            // non-revocation. All contributors share this authenticated path,
483            // target and operation time by construction; no new evidence pool.
484            // https://www.rfc-editor.org/rfc/rfc5280#section-6.3.3
485            reasons = reasons.union(facts.reasons());
486        }
487        // The existing policy validates supplied applicable lists, rather than
488        // requiring callers to provide a list for every path certificate.
489        if !reasons.is_empty() && !reasons.is_complete() {
490            return Err(X509ChainError::IncompleteCrlCoverage(position));
491        }
492        issuer_key = issuer_key
493            .subject(target, execution)
494            .map_err(|error| session.error(error))?;
495    }
496    Ok(())
497}
498
499fn validate_crl_serials(
500    crl: &structured_pkix::crl::CertificateList<'_>,
501    session: &CertificateSession,
502) -> Result<(), X509ChainError> {
503    // Preserve the consumer's conforming-positive serial profile while the
504    // shared mechanism exposes arbitrary-width signed INTEGER facts.
505    for entry in crl.tbs().revoked_certificates() {
506        let bytes = entry.serial().as_bytes();
507        session
508            .memory()
509            .charge_work(bytes.len() as u64 + 1)
510            .map_err(|reason| {
511                session.error(structured_pkix::Error::resource(
512                    reason,
513                    "certificateList.serial",
514                    entry.span().start,
515                ))
516            })?;
517        super::x509::validate_positive_serial_bytes(bytes, "CRL revoked certificate serial")?;
518    }
519    Ok(())
520}
521
522/// Shared path-processor bounds for a maximum path length that includes the
523/// anchor; RFC 5280 §6.1 counts only the certificates below it.
524pub(crate) fn path_bounds(max_chain_depth: usize) -> structured_pkix::path::ValidationBounds {
525    structured_pkix::path::ValidationBounds {
526        depth: max_chain_depth.saturating_sub(1),
527        // The operation account owns the work/storage ceilings. These
528        // mechanism limits must not divide or reset that shared budget.
529        work: u64::MAX,
530        policies: usize::MAX,
531    }
532}
533
534pub(crate) fn validate_supplied_path<'a>(
535    chain: &[&'a Certificate<'a>],
536    anchor: &'a Certificate<'a>,
537    options: &super::x509::X509ChainOptions<'_>,
538    provider: &dyn crate::provider::CryptoProvider,
539    session: &CertificateSession,
540) -> Result<structured_pkix::path::ValidationFacts<'a>, X509ChainError> {
541    use structured_pkix::path::{
542        Anchor, BuildError, ConstraintSource, Interval, Policy, RejectionReason, Validation,
543        ValidationContext,
544    };
545    let verifier = CertificatePathVerifier {
546        signatures: ProviderVerifier {
547            provider,
548            allowed: options.certificate_signature_algorithms,
549        },
550        chain,
551        anchor,
552        rsa_keys: options.rsa_keys,
553        dsa_keys: options.dsa_keys,
554    };
555    let context = ValidationContext {
556        execution: session.memory(),
557        interval: Interval::at_instant(verification_instant(options.verification_time)?),
558        policy: Policy::default(),
559        bounds: path_bounds(options.max_chain_depth),
560        verifier: &verifier,
561        watch: &(),
562        recognizer: &(),
563    };
564    let result = structured_pkix::path::validate(chain, Anchor::from_certificate(anchor), &context)
565        .map_err(|error| match error {
566            BuildError::Processing(error) => session.error(error),
567            BuildError::Verification(X509ChainError::Processing(error)) => session.error(error),
568            BuildError::Verification(error) => error,
569            BuildError::Recognition(never) => match never {},
570        })?;
571    let position = |source| match source {
572        ConstraintSource::Anchor => chain.len(),
573        ConstraintSource::Certificate(position) => position,
574    };
575    match result {
576        Validation::Accepted(facts) => Ok(facts),
577        Validation::Incomplete(reason) => Err(X509ChainError::PathIncomplete(reason)),
578        Validation::Rejected(rejection) => {
579            let Some(at) = rejection.at.map(position) else {
580                // A whole-input rejection has no certificate position.
581                return Err(X509ChainError::UntrustedRoot);
582            };
583            Err(match rejection.reason {
584                RejectionReason::EmptyPath | RejectionReason::AnchorIncluded => {
585                    X509ChainError::UntrustedRoot
586                }
587                RejectionReason::Validity => X509ChainError::CertificateNotValid(at),
588                RejectionReason::NotAuthority | RejectionReason::EmptyAuthorityName => {
589                    X509ChainError::IssuerNotCa(at)
590                }
591                RejectionReason::KeyCertSign => X509ChainError::InvalidKeyUsage {
592                    position: at,
593                    required: "keyCertSign",
594                },
595                RejectionReason::PathLength { source, limit } => {
596                    X509ChainError::PathLengthExceeded {
597                        position: position(source),
598                        limit,
599                    }
600                }
601                RejectionReason::NameConstraints { source, .. } => {
602                    X509ChainError::NameConstraintViolation {
603                        position: at,
604                        constraining_position: position(source),
605                    }
606                }
607                RejectionReason::NameConstraintsOnNonAuthority => {
608                    X509ChainError::InvalidNameConstraints { position: at }
609                }
610                RejectionReason::CriticalExtension(oid) => {
611                    let text = oid.text(session.memory()).map_err(|reason| {
612                        X509ChainError::Processing(structured_pkix::Error::resource(
613                            reason,
614                            "certificate.extensions",
615                            0,
616                        ))
617                    })?;
618                    X509ChainError::UnsupportedCriticalExtension {
619                        position: at,
620                        oid: text
621                            .as_str(session.memory())
622                            .map_err(|reason| {
623                                X509ChainError::Processing(structured_pkix::Error::resource(
624                                    reason,
625                                    "certificate.extensions",
626                                    0,
627                                ))
628                            })?
629                            .to_owned(),
630                    }
631                }
632                RejectionReason::MalformedExtension { extension, .. }
633                    if extension == structured_pkix::oid::NAME_CONSTRAINTS =>
634                {
635                    X509ChainError::InvalidNameConstraints { position: at }
636                }
637                RejectionReason::MalformedExtension {
638                    code,
639                    field,
640                    offset,
641                    ..
642                } => X509ChainError::Processing(structured_pkix::Error::new(code, field, offset)),
643                RejectionReason::Policies => X509ChainError::CertificatePolicies,
644                RejectionReason::Issuer | RejectionReason::Signature => {
645                    X509ChainError::InvalidSignature(at)
646                }
647                RejectionReason::UnsupportedSignature => X509ChainError::UnsupportedSignature(at),
648                RejectionReason::DuplicateCertificate { .. } => X509ChainError::InvalidDer {
649                    kind: "certificate path",
650                    message: "repeated certificate".into(),
651                },
652                _ => X509ChainError::InvalidDer {
653                    kind: "certificate path",
654                    message: "unhandled static path rejection".into(),
655                },
656            })
657        }
658    }
659}
660
661fn with_effective_spki<T, W: Work>(
662    encoded: &[u8],
663    parameters: Option<&[u8]>,
664    execution: &W,
665    verify: impl FnOnce(&[u8]) -> Result<T, X509ChainError>,
666) -> Result<T, X509ChainError> {
667    let Some(parameters) = parameters else {
668        return verify(encoded);
669    };
670    let effective = encode_effective_spki(encoded, parameters, execution)?;
671    verify(&effective)
672}
673
674/// An authenticated path may supply parameters absent from the target SPKI.
675/// Retaining the owned projection also retains its execution-account charge.
676#[derive(Debug)]
677pub(crate) struct ValidatedX509Key {
678    pub(crate) effective_spki: Option<OwnedVec<u8>>,
679}
680
681pub(crate) fn effective_target_spki(
682    key: structured_pkix::path::WorkingKey<'_>,
683    execution: &Memory,
684) -> Result<ValidatedX509Key, X509ChainError> {
685    let original = key.key();
686    if !original.algorithm().has_no_parameters() {
687        return Ok(ValidatedX509Key {
688            effective_spki: None,
689        });
690    }
691    match key.parameters() {
692        Some(parameters) => {
693            encode_effective_spki(original.as_bytes(), parameters, execution).map(|spki| {
694                ValidatedX509Key {
695                    effective_spki: Some(spki),
696                }
697            })
698        }
699        None => Ok(ValidatedX509Key {
700            effective_spki: None,
701        }),
702    }
703}
704
705fn encode_effective_spki<W: Work>(
706    encoded: &[u8],
707    parameters: &[u8],
708    execution: &W,
709) -> Result<OwnedVec<u8, W>, X509ChainError> {
710    use der::{Decode as _, Encode as _};
711    execution
712        .charge_work(encoded.len() as u64)
713        .map_err(|reason| {
714            X509ChainError::Processing(structured_pkix::Error::resource(
715                reason,
716                "issuer.public_key",
717                0,
718            ))
719        })?;
720    let key = pkcs8::SubjectPublicKeyInfoRef::from_der(encoded).map_err(|error| {
721        X509ChainError::InvalidDer {
722            kind: "issuer public key",
723            message: error.to_string(),
724        }
725    })?;
726    let parameters =
727        der::asn1::AnyRef::from_der(parameters).map_err(|error| X509ChainError::InvalidDer {
728            kind: "inherited issuer parameters",
729            message: error.to_string(),
730        })?;
731    let effective = pkcs8::SubjectPublicKeyInfoRef {
732        algorithm: pkcs8::AlgorithmIdentifierRef {
733            oid: key.algorithm.oid,
734            parameters: Some(parameters),
735        },
736        subject_public_key: key.subject_public_key,
737    };
738    let length = effective
739        .encoded_len()
740        .map_err(|error| X509ChainError::InvalidDer {
741            kind: "effective issuer public key",
742            message: error.to_string(),
743        })?;
744    let mut writer = AccountedDerWriter {
745        bytes: execution.vector(),
746        failure: None,
747    };
748    writer
749        .bytes
750        .try_reserve_exact(usize::try_from(length).map_err(|_| {
751            X509ChainError::Processing(structured_pkix::Error::resource(
752                MemoryError::CapacityOverflow,
753                "issuer.public_key",
754                0,
755            ))
756        })?)
757        .map_err(|error| {
758            X509ChainError::Processing(structured_pkix::Error::resource(
759                error,
760                "issuer.public_key",
761                0,
762            ))
763        })?;
764    if let Err(error) = effective.encode(&mut writer) {
765        return Err(match writer.failure {
766            Some(reason) => X509ChainError::Processing(structured_pkix::Error::resource(
767                reason,
768                "issuer.public_key",
769                0,
770            )),
771            None => X509ChainError::InvalidDer {
772                kind: "effective issuer public key",
773                message: error.to_string(),
774            },
775        });
776    }
777    Ok(writer.bytes)
778}
779
780struct AccountedDerWriter<W: Work> {
781    bytes: OwnedVec<u8, W>,
782    failure: Option<MemoryError>,
783}
784
785impl<W: Work> der::Writer for AccountedDerWriter<W> {
786    fn write(&mut self, bytes: &[u8]) -> der::Result<()> {
787        self.bytes.try_extend_from_slice(bytes).map_err(|error| {
788            self.failure = Some(error);
789            der::ErrorKind::Failed.into()
790        })
791    }
792}
793
794/// Certificate signatures use the same selected engine and permissions as CRLs.
795pub(crate) struct ProviderVerifier<'a> {
796    pub(crate) provider: &'a dyn crate::provider::CryptoProvider,
797    pub(crate) allowed: Option<&'a crate::policy::CertificateSignatureAlgorithms>,
798}
799
800impl<W: Work> structured_pkix::path::Verify<W> for ProviderVerifier<'_> {
801    type Error = X509ChainError;
802
803    fn verify(
804        &self,
805        algorithm: &structured_pkix::algorithm::AlgorithmIdentifier<'_>,
806        key: structured_pkix::path::WorkingKey<'_>,
807        signed: &[u8],
808        signature: structured_asn1::BitString<'_>,
809        execution: &W,
810    ) -> Result<structured_pkix::path::Verified, Self::Error> {
811        self.verify_with_key_check(algorithm, key, signed, signature, execution, |_| Ok(()))
812    }
813}
814
815impl ProviderVerifier<'_> {
816    fn verify_with_key_check<W: Work>(
817        &self,
818        algorithm: &structured_pkix::algorithm::AlgorithmIdentifier<'_>,
819        key: structured_pkix::path::WorkingKey<'_>,
820        signed: &[u8],
821        signature: structured_asn1::BitString<'_>,
822        execution: &W,
823        check: impl FnOnce(&[u8]) -> Result<(), X509ChainError>,
824    ) -> Result<structured_pkix::path::Verified, X509ChainError> {
825        use x509_parser::prelude::FromDer as _;
826        let key_algorithm = key.key().algorithm();
827        let parameters = key.parameters();
828        execution
829            .charge_work(
830                algorithm.as_bytes().len() as u64
831                    + key_algorithm.algorithm().as_bytes().len() as u64
832                    + parameters.map_or(0, |bytes| bytes.len() as u64),
833            )
834            .map_err(|reason| {
835                X509ChainError::Processing(structured_pkix::Error::resource(
836                    reason,
837                    "certificate.signature",
838                    0,
839                ))
840            })?;
841        let (rest, identifier) = x509_parser::x509::AlgorithmIdentifier::from_der(
842            algorithm.as_bytes(),
843        )
844        .map_err(|error| X509ChainError::InvalidDer {
845            kind: "signature algorithm",
846            message: error.to_string(),
847        })?;
848        if !rest.is_empty() {
849            return Err(X509ChainError::InvalidDer {
850                kind: "signature algorithm",
851                message: "trailing data".into(),
852            });
853        }
854        let signature = signature
855            .octets()
856            .ok_or_else(|| X509ChainError::InvalidDer {
857                kind: "certificate signature",
858                message: "signature is not octet-aligned".into(),
859            })?;
860        // Permission and capability checks precede inherited-key allocation.
861        let algorithm = super::x509::authorize_x509_signature_algorithm(
862            &identifier,
863            self.provider,
864            self.allowed,
865        )?;
866        if matches!(algorithm, crate::provider::X509SignatureAlgorithm::Dsa(_))
867            && key_algorithm.algorithm().as_bytes() == dsa::OID.as_bytes()
868            && parameters.is_none()
869        {
870            // RFC 5280 6.1.4(e) needs the prospective path's working parameters.
871            // An isolated discovery edge cannot prove rejection from their absence.
872            // https://www.rfc-editor.org/rfc/rfc5280.html#section-6.1.4
873            return Ok(structured_pkix::path::Verified::Unsupported);
874        }
875        let verify = |spki: &[u8]| {
876            check(spki)?;
877            self.provider
878                .verify_x509_signature(algorithm, signed, signature, spki)
879                .map_err(X509ChainError::from)
880        };
881        let valid = if parameters.is_some() && parameters != key_algorithm.parameters() {
882            // RFC 5280 6.1.4(e): inherit only the effective working-key parameters.
883            // The temporary provider SPKI is accounted; original TBS stays borrowed.
884            // https://www.rfc-editor.org/rfc/rfc5280.html#section-6.1.4
885            with_effective_spki(key.key().as_bytes(), parameters, execution, verify)?
886        } else {
887            verify(key.key().as_bytes())?
888        };
889        Ok(if valid {
890            structured_pkix::path::Verified::Yes
891        } else {
892            structured_pkix::path::Verified::No
893        })
894    }
895}
896
897/// Certificate-path signature checks through the selected provider, under the
898/// operation's certificate-signature permissions and issuer key-strength policy.
899/// It verifies only keys of the supplied chain or anchor; any other key is a
900/// processing error, so a discovery edge cannot borrow this permission.
901pub struct CertificatePathVerifier<'a, 'p> {
902    signatures: ProviderVerifier<'p>,
903    chain: &'p [&'a Certificate<'a>],
904    anchor: &'a Certificate<'a>,
905    rsa_keys: crate::policy::RsaKeyPolicy,
906    dsa_keys: crate::policy::DsaKeyPolicy,
907}
908
909impl<'a, 'p> CertificatePathVerifier<'a, 'p> {
910    pub(crate) fn new(
911        provider: &'p dyn crate::provider::CryptoProvider,
912        trust: &'p crate::policy::KeyTrustPolicy,
913        chain: &'p [&'a Certificate<'a>],
914        anchor: &'a Certificate<'a>,
915    ) -> Self {
916        Self {
917            signatures: ProviderVerifier {
918                provider,
919                allowed: trust.certificate_signature_algorithms.as_ref(),
920            },
921            chain,
922            anchor,
923            rsa_keys: trust.rsa_keys,
924            dsa_keys: trust.dsa_keys,
925        }
926    }
927}
928
929impl<W: Work> structured_pkix::path::Verify<W> for CertificatePathVerifier<'_, '_> {
930    type Error = X509ChainError;
931
932    fn verify(
933        &self,
934        algorithm: &structured_pkix::algorithm::AlgorithmIdentifier<'_>,
935        key: structured_pkix::path::WorkingKey<'_>,
936        signed: &[u8],
937        signature: structured_asn1::BitString<'_>,
938        execution: &W,
939    ) -> Result<structured_pkix::path::Verified, Self::Error> {
940        let raw = key.key().as_bytes();
941        execution
942            .charge_work(self.chain.len() as u64)
943            .map_err(|reason| {
944                X509ChainError::Processing(structured_pkix::Error::resource(
945                    reason,
946                    "issuer.public_key",
947                    0,
948                ))
949            })?;
950        let position = if std::ptr::eq(raw, self.anchor.tbs().public_key().as_bytes()) {
951            self.chain.len()
952        } else {
953            self.chain
954                .iter()
955                .position(|certificate| {
956                    std::ptr::eq(raw, certificate.tbs().public_key().as_bytes())
957                })
958                .ok_or_else(|| X509ChainError::InvalidDer {
959                    kind: "issuer public key",
960                    message: "key outside supplied path".into(),
961                })?
962        };
963        self.signatures.verify_with_key_check(
964            algorithm,
965            key,
966            signed,
967            signature,
968            execution,
969            |effective| {
970                super::x509::validate_issuer_spki_policy(
971                    effective,
972                    position,
973                    self.rsa_keys,
974                    self.dsa_keys,
975                )
976            },
977        )
978    }
979}
980
981/// Structure of an RSA or EC public key, borrowed from its SPKI encoding.
982pub(crate) enum PublicKey<'a> {
983    Rsa(structured_pkix::key::rsa::PublicKey<'a>),
984    Ec(structured_pkix::key::ec::PublicKey<'a>),
985}
986
987/// A single account covers all candidates, nested decodes and retries.
988#[derive(Debug)]
989pub(crate) struct CertificateSession {
990    parser: Parser,
991    work_limit: usize,
992    memory_limit: usize,
993}
994
995impl CertificateSession {
996    pub(crate) fn new(resources: &ResourcePolicy) -> Self {
997        let work_limit = resources
998            .max_x509_work
999            .min(crate::hard_limits::X509_WORK_CEILING);
1000        let memory_limit = resources
1001            .max_x509_workspace_bytes
1002            .min(crate::hard_limits::X509_WORKSPACE_BYTE_CEILING);
1003        let memory = Memory::new(Arc::new(MemoryAccount::new(memory_limit, work_limit)));
1004        Self::with_account(work_limit, memory_limit, memory)
1005    }
1006
1007    /// The bounded parser, keeping its fresh policy-limited account.
1008    pub(crate) fn into_parser(self) -> Parser {
1009        self.parser
1010    }
1011
1012    pub(crate) fn share(&self) -> Self {
1013        Self::with_account(self.work_limit, self.memory_limit, self.memory().clone())
1014    }
1015
1016    fn with_account(work_limit: usize, memory_limit: usize, memory: Memory) -> Self {
1017        Self {
1018            parser: Parser::new(
1019                Encoding::Der,
1020                Limits {
1021                    max_depth: crate::hard_limits::ASN1_DEPTH_CEILING,
1022                    max_elements: work_limit as u64,
1023                    // This reader borrows contents; admission limits belong
1024                    // to each source boundary. Work and allocated capacity
1025                    // remain bounded even for caller-owned certificate bytes.
1026                    max_element_length: u64::MAX,
1027                    max_work: work_limit as u64,
1028                },
1029                memory,
1030            ),
1031            work_limit,
1032            memory_limit,
1033        }
1034    }
1035
1036    pub(crate) fn require_resources(
1037        &self,
1038        resources: &ResourcePolicy,
1039    ) -> Result<(), PolicyViolation> {
1040        if self.work_limit != resources.max_x509_work
1041            || self.memory_limit != resources.max_x509_workspace_bytes
1042        {
1043            return Err(PolicyViolation::KeyTrust {
1044                reason: "certificate accounting must retain one immutable resource policy",
1045            });
1046        }
1047        Ok(())
1048    }
1049
1050    pub(crate) fn memory(&self) -> &Memory {
1051        self.parser.memory()
1052    }
1053
1054    pub(crate) fn read<'a>(&'a self, bytes: &'a [u8]) -> Result<Certificate<'a>, X509ChainError> {
1055        Certificate::read(&self.parser, bytes, 0).map_err(|error| self.error(error))
1056    }
1057
1058    /// Structural RSA or EC public key of a standalone SPKI, read with this
1059    /// session's work.
1060    pub(crate) fn read_public_key<'a>(
1061        &'a self,
1062        spki: &'a [u8],
1063    ) -> Result<PublicKey<'a>, structured_pkix::Error> {
1064        use structured_pkix::key::{ec, rsa};
1065        self.with_public_key_info(spki, |info, execution| {
1066            // Each reader repeats the algorithm check, paid; this only dispatches.
1067            if info.algorithm().algorithm() == structured_pkix::oid::EC_PUBLIC_KEY {
1068                ec::read(structured_pkix::path::WorkingKey::standing(info), execution)
1069                    .map(PublicKey::Ec)
1070            } else {
1071                rsa::read(info, execution).map(PublicKey::Rsa)
1072            }
1073        })
1074    }
1075
1076    pub(crate) fn read_dsa_public_key<'a>(
1077        &'a self,
1078        spki: &'a [u8],
1079    ) -> Result<structured_pkix::key::dsa::PublicKey<'a>, structured_pkix::Error> {
1080        self.with_public_key_info(spki, |info, execution| {
1081            structured_pkix::key::dsa::read(
1082                structured_pkix::path::WorkingKey::standing(info),
1083                execution,
1084            )
1085        })
1086    }
1087
1088    fn with_public_key_info<'a, T>(
1089        &'a self,
1090        spki: &'a [u8],
1091        inspect: impl FnOnce(
1092            structured_pkix::PublicKeyInfo<'a>,
1093            &structured_asn1::memory::Execution<'_>,
1094        ) -> Result<T, structured_pkix::Error>,
1095    ) -> Result<T, structured_pkix::Error> {
1096        use structured_pkix::key::PublicKeyInfo;
1097        use structured_pkix::{Error, ErrorCode};
1098        const FIELD: &str = "subjectPublicKeyInfo";
1099        let encoding = |error: structured_asn1::Error| {
1100            Error::new(ErrorCode::Encoding(error.code()), FIELD, error.offset())
1101        };
1102        let mut reader = self.parser.read(spki, 0).map_err(encoding)?;
1103        let element = reader
1104            .next_element()
1105            .map_err(encoding)?
1106            .ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, 0))?;
1107        let info = PublicKeyInfo::read_element(&reader, &element, FIELD)?;
1108        // RFC 5280 4.1.2.7: the encoding is exactly one SubjectPublicKeyInfo.
1109        if !reader.is_empty() {
1110            return Err(Error::new(ErrorCode::TrailingData, FIELD, reader.offset()));
1111        }
1112        let execution = reader.execution();
1113        inspect(info, &execution)
1114    }
1115
1116    pub(crate) fn read_crl<'a>(
1117        &'a self,
1118        bytes: &'a [u8],
1119    ) -> Result<structured_pkix::crl::CertificateList<'a>, X509ChainError> {
1120        structured_pkix::crl::CertificateList::read(&self.parser, bytes, 0)
1121            .map_err(|error| self.error(error))
1122    }
1123
1124    pub(crate) fn parse_error(&self, error: X509ChainError) -> super::ParseError {
1125        match error {
1126            X509ChainError::Policy(error) => super::ParseError::Policy(error),
1127            X509ChainError::Processing(error) => super::ParseError::Certificate(error),
1128            error => super::ParseError::InvalidStructure(error.to_string()),
1129        }
1130    }
1131
1132    #[cfg(test)]
1133    pub(crate) fn metadata(
1134        &self,
1135        bytes: &[u8],
1136    ) -> Result<super::parse::ParsedX509Certificate, X509ChainError> {
1137        let certificate = self.read(bytes)?;
1138        super::parse::certificate_metadata(&certificate, self).map_err(|error| match error {
1139            super::ParseError::Policy(error) => X509ChainError::Policy(error),
1140            super::ParseError::Certificate(error) => X509ChainError::Processing(error),
1141            error => X509ChainError::InvalidDer {
1142                kind: "certificate metadata",
1143                message: error.to_string(),
1144            },
1145        })
1146    }
1147
1148    pub(crate) fn error(&self, error: structured_pkix::Error) -> X509ChainError {
1149        if error.is_resource() {
1150            use structured_asn1::{ErrorCode, Limit};
1151            let memory = matches!(
1152                error.code(),
1153                structured_pkix::ErrorCode::Encoding(
1154                    ErrorCode::LimitExceeded(Limit::Memory)
1155                        | ErrorCode::AllocatorExhausted
1156                        | ErrorCode::CapacityOverflow
1157                )
1158            );
1159            return PolicyViolation::ResourceLimitExceeded {
1160                resource: if memory {
1161                    resource_name::X509_WORKSPACE_BYTES
1162                } else {
1163                    resource_name::X509_WORK
1164                },
1165                maximum: if memory {
1166                    self.memory_limit
1167                } else {
1168                    self.work_limit
1169                },
1170            }
1171            .into();
1172        }
1173        X509ChainError::Processing(error)
1174    }
1175}
1176
1177#[cfg(test)]
1178mod tests {
1179    use super::*;
1180    use structured_pkix::path::{Verified, Verify as _, WorkingKey};
1181
1182    #[test]
1183    fn dsa_public_key_keeps_borrowed_components_and_absent_parameters() {
1184        // Structural inspection must not allocate bigints or invent parameters
1185        // for an isolated DSA key; the resolver requires the latter separately.
1186        let bytes =
1187            b"\x30\x12\x30\x09\x06\x07\x2a\x86\x48\xce\x38\x04\x01\x03\x05\x00\x02\x02\x00\x80";
1188        let session = CertificateSession::new(&ResourcePolicy::default());
1189        let key = session.read_dsa_public_key(bytes).expect("DSA structure");
1190        assert!(key.parameters().is_none());
1191        assert_eq!(key.y().as_bytes(), [0x80]);
1192        assert_eq!(key.y().integer().as_bytes(), [0, 0x80]);
1193        assert_eq!(key.y().as_bytes().as_ptr(), bytes[19..].as_ptr());
1194    }
1195
1196    #[test]
1197    fn algorithm_inspection_faults_never_become_strict_crypto_identifiers() {
1198        // The released inspection API preserves diagnostic bytes and offsets;
1199        // our Certificate/CRL crypto path must still require strict identifiers.
1200        let session = CertificateSession::new(&ResourcePolicy::default());
1201        let bytes = [0x30, 0x05, 0x06, 0x01, 0x2a, 0x25, 0x00];
1202        let mut reader = session
1203            .parser
1204            .read(&bytes, 500)
1205            .expect("funded source reader");
1206        let element = reader
1207            .next_element()
1208            .expect("structural sequence")
1209            .expect("one algorithm element");
1210        let reading = structured_pkix::algorithm::read_inspection(&reader, &element, "algorithm")
1211            .expect("recoverable parameter fault");
1212        assert!(reading.fault().is_some());
1213        assert!(reading.identifier().is_none());
1214        assert_eq!(reading.parameters(), Some(&bytes[5..]));
1215        assert_eq!(
1216            reading
1217                .parameters_span()
1218                .expect("original parameter range")
1219                .start,
1220            505
1221        );
1222        assert!(std::ptr::eq(reading.as_bytes().as_ptr(), bytes.as_ptr()));
1223        assert!(structured_pkix::algorithm::read(&reader, &element, "algorithm").is_err());
1224    }
1225
1226    #[test]
1227    fn shared_general_name_queries_keep_semantics_and_work_limits() {
1228        // GeneralName equality is not raw text equality or mailbox local-part folding.
1229        use structured_pkix::general_name::{self, GeneralName};
1230        let session = CertificateSession::new(&ResourcePolicy::default());
1231        for (left, right, expected) in [
1232            (
1233                GeneralName::Dns("EXAMPLE.test"),
1234                GeneralName::Dns("example.TEST"),
1235                true,
1236            ),
1237            (
1238                GeneralName::Email("User@example.test"),
1239                GeneralName::Email("user@example.test"),
1240                false,
1241            ),
1242            (
1243                GeneralName::Uri("HTTP://Example.test:80/a/../%7e"),
1244                GeneralName::Uri("http://example.test/~"),
1245                true,
1246            ),
1247        ] {
1248            assert_eq!(
1249                left.matches(&right, session.memory())
1250                    .expect("funded GeneralName comparison"),
1251                expected
1252            );
1253        }
1254        let resources = ResourcePolicy {
1255            max_x509_work: 1,
1256            ..Default::default()
1257        };
1258        let limited = CertificateSession::new(&resources);
1259        let reader = limited
1260            .parser
1261            .read(&[], 500)
1262            .expect("empty producing reader");
1263        let error = general_name::matches(
1264            &reader,
1265            &GeneralName::Dns("example.test"),
1266            &GeneralName::Dns("EXAMPLE.test"),
1267            777,
1268            "certificate.generalName",
1269        )
1270        .expect_err("GeneralName work must be denied");
1271        assert!(error.is_resource());
1272        assert_eq!(error.offset(), 777);
1273        assert!(matches!(limited.error(error), X509ChainError::Policy(_)));
1274    }
1275
1276    #[test]
1277    fn xml_name_adapter_preserves_escaped_delimiters_and_spaces() {
1278        // XML formatting tolerance must not erase escaped DN value bytes.
1279        let session = CertificateSession::new(&ResourcePolicy::default());
1280        for (input, expected) in [
1281            ("CN=Alice,O=Example", "CN=Alice,O=Example"),
1282            (" \tCN=Alice ,\n O=Example\r ", "CN=Alice,O=Example"),
1283            (
1284                r"CN=Alice\,Bob + OU=Ops\+Dev , O=Example",
1285                r"CN=Alice\,Bob+OU=Ops\+Dev,O=Example",
1286            ),
1287            (r"CN= Alice\ , O=Example", r"CN=\ Alice\ ,O=Example"),
1288            ("CN=Ștefan, O=Exemplu", "CN=Ștefan,O=Exemplu"),
1289        ] {
1290            let adapted = xml_name_text(input, session.memory()).expect("lexical adaptation");
1291            assert_eq!(
1292                adapted.text(session.memory()).expect("funded text"),
1293                expected
1294            );
1295            let _ = session.selector(input).expect("strict shared selector");
1296        }
1297        let canonical =
1298            xml_name_text("CN=Alice,O=Example", session.memory()).expect("canonical text");
1299        assert!(matches!(canonical, XmlNameText::Borrowed(_)));
1300        assert_eq!(session.memory().used(), 0);
1301    }
1302
1303    #[test]
1304    fn xml_name_adapter_reserves_before_rewriting() {
1305        // Input scans and any rewritten storage belong to the operation account.
1306        let memory = Memory::new(Arc::new(MemoryAccount::new(0, 0)));
1307        assert!(xml_name_text("CN=Alice", &memory).is_err());
1308        assert_eq!(memory.work_used(), 0);
1309        assert_eq!(memory.used(), 0);
1310        let memory = Memory::new(Arc::new(MemoryAccount::new(0, 4096)));
1311        assert!(xml_name_text(" CN=Alice ", &memory).is_err());
1312        assert_eq!(memory.used(), 0);
1313    }
1314
1315    #[test]
1316    fn xml_name_selector_preserves_pseudonym_case_exact_semantics() {
1317        // RFC 2985 5.2.9 assigns caseExactMatch, not caseIgnoreMatch.
1318        // https://www.rfc-editor.org/rfc/rfc2985.html#section-5.2.9
1319        let session = CertificateSession::new(&ResourcePolicy::default());
1320        assert!(
1321            !session
1322                .names_equal("pseudonym=Alice", "pseudonym=alice")
1323                .expect("supported pseudonym")
1324        );
1325        assert!(
1326            !session
1327                .names_equal("pseudonym=Straße", "pseudonym=STRASSE")
1328                .expect("supported Unicode pseudonym")
1329        );
1330        assert!(
1331            session
1332                .names_equal(r" pseudonym=\ Alice\  ", "pseudonym=Alice")
1333                .expect("insignificant space preparation")
1334        );
1335        let bytes = [
1336            0x30, 16, 0x31, 14, 0x30, 12, 6, 3, 0x55, 4, 65, 12, 5, b'A', b'l', b'i', b'c', b'e',
1337        ];
1338        let name = session.read_name(&bytes).expect("borrowed pseudonym Name");
1339        assert!(
1340            session
1341                .selector("pseudonym=Alice")
1342                .expect("same selector")
1343                .matches_name(&name, session.memory())
1344                .expect("same comparison")
1345        );
1346        assert!(
1347            !session
1348                .selector("pseudonym=alice")
1349                .expect("different selector")
1350                .matches_name(&name, session.memory())
1351                .expect("different comparison")
1352        );
1353    }
1354
1355    #[test]
1356    fn name_wire_rendering_preserves_invalid_text_without_accepting_it_as_text() {
1357        // RFC 4514 2.4's #BER form preserves an AttributeValue encoding;
1358        // retaining bytes must not silently turn invalid UTF-8 into readable text.
1359        // https://www.rfc-editor.org/rfc/rfc4514.html#section-2.4
1360        let session = CertificateSession::new(&ResourcePolicy::default());
1361        let bytes = [0x30, 12, 0x31, 10, 0x30, 8, 6, 3, 0x55, 4, 3, 12, 1, 0xff];
1362        let name = session.read_name(&bytes).expect("wire Name");
1363        let text = name
1364            .rfc4514(session.memory())
1365            .expect("lossless wire rendering");
1366        assert_eq!(
1367            text.as_str(session.memory()).expect("rendered text"),
1368            "CN=#0C01FF"
1369        );
1370        let selector = session.selector("CN=#0C01FF").expect("opaque selector");
1371        assert!(
1372            selector
1373                .matches_name(&name, session.memory())
1374                .expect("opaque wire identity")
1375        );
1376    }
1377
1378    #[test]
1379    fn inherited_issuer_parameters_are_provided_without_changing_source() {
1380        // RFC 5280 6.1.4(e) supplies inherited parameters to the working key only.
1381        use der::{Decode as _, Encode as _};
1382        let y = [2, 1, 1];
1383        let parameters = [0x30, 9, 2, 1, 23, 2, 1, 11, 2, 1, 2];
1384        for encoded_parameters in [None, Some(der::asn1::AnyRef::NULL)] {
1385            let key = pkcs8::SubjectPublicKeyInfoRef {
1386                algorithm: pkcs8::AlgorithmIdentifierRef {
1387                    oid: dsa::OID,
1388                    parameters: encoded_parameters,
1389                },
1390                subject_public_key: der::asn1::BitStringRef::new(0, &y).expect("public bits"),
1391            }
1392            .to_der()
1393            .expect("SPKI encoding");
1394            let source = key.clone();
1395            let session = CertificateSession::new(&ResourcePolicy::default());
1396            with_effective_spki(&key, Some(&parameters), session.memory(), |effective| {
1397                let parsed =
1398                    pkcs8::SubjectPublicKeyInfoRef::from_der(effective).expect("effective SPKI");
1399                assert_eq!(
1400                    parsed
1401                        .algorithm
1402                        .parameters
1403                        .expect("inherited params")
1404                        .to_der()
1405                        .expect("DER"),
1406                    parameters
1407                );
1408                assert_eq!(parsed.subject_public_key.raw_bytes(), y);
1409                assert!(session.memory().used() >= effective.len());
1410                Ok(())
1411            })
1412            .expect("inherited key is supported");
1413            assert_eq!(key, source);
1414            assert_eq!(session.memory().used(), 0);
1415        }
1416    }
1417
1418    #[test]
1419    fn inherited_key_workspace_is_reserved_before_provider_execution() {
1420        // No cryptographic callback runs when the effective-key allocation is refused.
1421        use der::Encode as _;
1422        let key = pkcs8::SubjectPublicKeyInfoRef {
1423            algorithm: pkcs8::AlgorithmIdentifierRef {
1424                oid: dsa::OID,
1425                parameters: None,
1426            },
1427            subject_public_key: der::asn1::BitStringRef::new(0, &[2, 1, 1]).expect("public bits"),
1428        }
1429        .to_der()
1430        .expect("SPKI encoding");
1431        let session = CertificateSession::new(&ResourcePolicy {
1432            max_x509_workspace_bytes: 0,
1433            ..ResourcePolicy::default()
1434        });
1435        let called = std::cell::Cell::new(false);
1436        let result = with_effective_spki(&key, Some(&[0x30, 0]), session.memory(), |_| {
1437            called.set(true);
1438            Ok(())
1439        });
1440        assert!(matches!(result, Err(X509ChainError::Processing(error)) if error.is_resource()));
1441        assert!(!called.get());
1442        assert_eq!(session.memory().used(), 0);
1443    }
1444
1445    #[test]
1446    fn standalone_issuer_key_borrows_original_without_workspace() {
1447        // The ordinary path requires no normalization buffer or key copy.
1448        use der::Encode as _;
1449        let key = pkcs8::SubjectPublicKeyInfoRef {
1450            algorithm: pkcs8::AlgorithmIdentifierRef {
1451                oid: dsa::OID,
1452                parameters: None,
1453            },
1454            subject_public_key: der::asn1::BitStringRef::new(0, &[2, 1, 1]).expect("public bits"),
1455        }
1456        .to_der()
1457        .expect("SPKI encoding");
1458        let session = CertificateSession::new(&ResourcePolicy {
1459            max_x509_workspace_bytes: 0,
1460            max_x509_work: 0,
1461            ..ResourcePolicy::default()
1462        });
1463        with_effective_spki(&key, None, session.memory(), |effective| {
1464            assert_eq!(effective.as_ptr(), key.as_ptr());
1465            assert_eq!(effective.len(), key.len());
1466            Ok(())
1467        })
1468        .expect("borrowed key needs no adapter workspace");
1469        assert_eq!(session.memory().used(), 0);
1470        assert_eq!(session.memory().work_used(), 0);
1471    }
1472
1473    #[test]
1474    fn certificate_decoding_shares_work_across_failed_candidates() {
1475        // A malformed candidate still spends work; a retry cannot buy a fresh budget.
1476        let resources = ResourcePolicy {
1477            max_x509_work: 2,
1478            ..ResourcePolicy::default()
1479        };
1480        let session = CertificateSession::new(&resources);
1481        assert!(session.read(&[0x30, 0]).is_err());
1482        assert!(matches!(
1483            session.read(&[0x30, 0]),
1484            Err(X509ChainError::Policy(_))
1485        ));
1486    }
1487
1488    #[test]
1489    fn certificate_parser_keeps_exact_signed_and_key_ranges() {
1490        // Shared decoding borrows the original authenticated bytes, with no re-encoding.
1491        let pair = rcgen::KeyPair::generate().expect("test key generation");
1492        let cert = rcgen::CertificateParams::new(Vec::<String>::new())
1493            .expect("empty SAN list")
1494            .self_signed(&pair)
1495            .expect("test certificate signing");
1496        let resources = ResourcePolicy::default();
1497        let session = CertificateSession::new(&resources);
1498        let parsed = session.read(cert.der()).expect("generated certificate");
1499        let start = cert.der().as_ptr() as usize;
1500        let end = start + cert.der().len();
1501        for bytes in [parsed.signed_bytes(), parsed.tbs().public_key().as_bytes()] {
1502            assert!((start..end).contains(&(bytes.as_ptr() as usize)));
1503            assert!(bytes.as_ptr() as usize + bytes.len() <= end);
1504        }
1505    }
1506
1507    #[test]
1508    fn issuer_edge_cannot_reset_exhausted_certificate_work() {
1509        // Candidate signature checks must retain the caller's spent account.
1510        let pair = rcgen::KeyPair::generate().expect("test key generation");
1511        let cert = rcgen::CertificateParams::new(Vec::<String>::new())
1512            .expect("empty SAN list")
1513            .self_signed(&pair)
1514            .expect("test certificate signing");
1515        let resources = ResourcePolicy {
1516            max_x509_work: 0,
1517            ..ResourcePolicy::default()
1518        };
1519        let session = CertificateSession::new(&resources);
1520        let result = super::super::x509::certificate_signature_matches_with_provider(
1521            cert.der(),
1522            cert.der(),
1523            crate::provider::default_provider(),
1524            None,
1525            &session,
1526        );
1527        assert!(session.read(cert.der()).is_err());
1528        assert!(matches!(result, Err(X509ChainError::Policy(_))));
1529    }
1530
1531    #[test]
1532    fn completed_candidate_does_not_mask_path_resource_exhaustion() {
1533        // A selected prefix cannot turn exhausted continuation work into success.
1534        let pair = rcgen::KeyPair::generate().expect("test key generation");
1535        let cert = rcgen::CertificateParams::new(Vec::<String>::new())
1536            .expect("empty SAN list")
1537            .self_signed(&pair)
1538            .expect("test certificate signing");
1539        let info = super::super::parse::X509DataInfo {
1540            certificates: vec![cert.der().to_vec()],
1541            parsed_certificates: vec![
1542                super::super::parse::parse_x509_certificate(cert.der())
1543                    .expect("generated certificate metadata"),
1544            ],
1545            ..Default::default()
1546        };
1547        let resources = ResourcePolicy {
1548            max_x509_work: 0,
1549            ..ResourcePolicy::default()
1550        };
1551        let session = CertificateSession::new(&resources);
1552        let result = super::super::parse::build_x509_certificate_paths_to_selector_targets(
1553            &info,
1554            0,
1555            &[0],
1556            super::super::parse::X509PathBounds {
1557                max_depth: 2,
1558                max_candidate_paths: 2,
1559            },
1560            crate::provider::default_provider(),
1561            None,
1562            &session,
1563        );
1564        assert!(matches!(
1565            result,
1566            Err(super::super::parse::X509ChainBuildError::Policy(_))
1567        ));
1568    }
1569
1570    #[test]
1571    fn shared_certificate_verifier_uses_exact_source_ranges() {
1572        // The provider receives original TBS and SPKI bytes; corruption remains a No.
1573        let pair = rcgen::KeyPair::generate().expect("test key generation");
1574        let cert = rcgen::CertificateParams::new(Vec::<String>::new())
1575            .expect("empty SAN list")
1576            .self_signed(&pair)
1577            .expect("test certificate signing");
1578        let session = CertificateSession::new(&ResourcePolicy::default());
1579        let parsed = session.read(cert.der()).expect("generated certificate");
1580        let verifier = ProviderVerifier {
1581            provider: crate::provider::default_provider(),
1582            allowed: None,
1583        };
1584        assert_eq!(
1585            verifier
1586                .verify(
1587                    &parsed.tbs().signature(),
1588                    WorkingKey::standing(parsed.tbs().public_key()),
1589                    parsed.signed_bytes(),
1590                    parsed.signature(),
1591                    session.memory()
1592                )
1593                .expect("signature execution succeeds"),
1594            Verified::Yes
1595        );
1596        let mut altered = parsed.signed_bytes().to_vec();
1597        *altered.last_mut().expect("nonempty signed bytes") ^= 1;
1598        assert_eq!(
1599            verifier
1600                .verify(
1601                    &parsed.tbs().signature(),
1602                    WorkingKey::standing(parsed.tbs().public_key()),
1603                    &altered,
1604                    parsed.signature(),
1605                    session.memory()
1606                )
1607                .expect("corrupt signature execution succeeds"),
1608            Verified::No
1609        );
1610    }
1611
1612    #[test]
1613    fn published_scoped_verifier_preserves_both_current_work_ceilings() {
1614        // Actual registry Verify<Execution>, not Verify<Memory>: either the
1615        // caller's local ceiling or aggregate account can refuse before crypto.
1616        let pair = rcgen::KeyPair::generate().expect("generate scoped verifier test key");
1617        let certificate = rcgen::CertificateParams::new(Vec::<String>::new())
1618            .expect("valid scoped verifier certificate parameters")
1619            .self_signed(&pair)
1620            .expect("sign scoped verifier test certificate");
1621        let source = CertificateSession::new(&ResourcePolicy::default());
1622        let certificate = source
1623            .read(certificate.der())
1624            .expect("read original certificate");
1625        let verifier = ProviderVerifier {
1626            provider: crate::provider::default_provider(),
1627            allowed: None,
1628        };
1629        for (local, aggregate) in [(0, 100000), (100000, 0), (100000, 100000)] {
1630            let memory = Memory::new(Arc::new(MemoryAccount::new(1 << 20, aggregate)));
1631            let operation = Parser::new(
1632                Encoding::Der,
1633                Limits {
1634                    max_work: local,
1635                    ..Limits::default()
1636                },
1637                memory,
1638            );
1639            let reader = operation.read(&[], 0).expect("create current execution");
1640            let result = verifier.verify(
1641                &certificate.tbs().signature(),
1642                WorkingKey::standing(certificate.tbs().public_key()),
1643                certificate.signed_bytes(),
1644                certificate.signature(),
1645                &reader.execution(),
1646            );
1647            if local == 0 || aggregate == 0 {
1648                assert!(
1649                    matches!(result, Err(X509ChainError::Processing(error)) if error == structured_pkix::Error::resource(MemoryError::WorkExceeded, "certificate.signature", 0))
1650                );
1651            } else {
1652                assert_eq!(result.expect("funded verification succeeds"), Verified::Yes);
1653            }
1654        }
1655    }
1656
1657    #[test]
1658    fn discovery_defers_a_dsa_key_whose_parameters_need_inheritance() {
1659        // RFC 5280 6.1.4(e) cannot be evaluated from this isolated pool edge.
1660        // Missing parameters are not a proved bad signature; path validation
1661        // must get a chance to supply the effective key before the provider.
1662        // https://www.rfc-editor.org/rfc/rfc5280.html#section-6.1.4
1663        use der::{Decode as _, Encode as _};
1664        let pair = rcgen::KeyPair::generate().expect("test key");
1665        let cert = rcgen::CertificateParams::new(Vec::<String>::new())
1666            .expect("test parameters")
1667            .self_signed(&pair)
1668            .expect("test certificate");
1669        let source = x509_cert::Certificate::from_der(cert.der()).expect("test schema");
1670        let tbs = source.tbs_certificate();
1671        let algorithm = x509_cert::spki::AlgorithmIdentifierOwned {
1672            oid: der::asn1::ObjectIdentifier::new_unwrap("1.2.840.10040.4.3"),
1673            parameters: None,
1674        };
1675        let key = x509_cert::SubjectPublicKeyInfo {
1676            algorithm: x509_cert::spki::AlgorithmIdentifierOwned {
1677                oid: dsa::OID,
1678                parameters: None,
1679            },
1680            subject_public_key: der::asn1::BitString::from_bytes(&[2, 1, 1]).expect("DSA integer"),
1681        };
1682        let mut body = Vec::new();
1683        for field in [
1684            tbs.serial_number().to_der(),
1685            algorithm.to_der(),
1686            tbs.issuer().to_der(),
1687            tbs.validity().to_der(),
1688            tbs.subject().to_der(),
1689            key.to_der(),
1690        ] {
1691            body.extend(field.expect("test TBS field"));
1692        }
1693        let tbs = der::asn1::Any::new(der::Tag::Sequence, body).expect("TBS sequence");
1694        let mut body = tbs.to_der().expect("test TBS");
1695        body.extend(algorithm.to_der().expect("test outer algorithm"));
1696        body.extend(source.signature().to_der().expect("test signature"));
1697        let bytes = der::asn1::Any::new(der::Tag::Sequence, body)
1698            .expect("certificate sequence")
1699            .to_der()
1700            .expect("test encoding");
1701        let session = CertificateSession::new(&ResourcePolicy::default());
1702        let parsed = session.read(&bytes).expect("DSA certificate");
1703        let verifier = ProviderVerifier {
1704            provider: crate::provider::default_provider(),
1705            allowed: None,
1706        };
1707        let checked = core::cell::Cell::new(false);
1708        let result = verifier.verify_with_key_check(
1709            &parsed.tbs().signature(),
1710            WorkingKey::standing(parsed.tbs().public_key()),
1711            parsed.signed_bytes(),
1712            parsed.signature(),
1713            session.memory(),
1714            |_| {
1715                checked.set(true);
1716                Ok(())
1717            },
1718        );
1719        assert!(matches!(result, Ok(Verified::Unsupported)), "{result:?}");
1720        assert!(
1721            !checked.get(),
1722            "no strength/provider check of an incomplete key"
1723        );
1724        let mut other_bytes = bytes.clone();
1725        *other_bytes.last_mut().expect("signature bytes") ^= 1;
1726        let info = super::super::parse::X509DataInfo {
1727            parsed_certificates: vec![
1728                session.metadata(&bytes).expect("leaf metadata"),
1729                session.metadata(&other_bytes).expect("issuer metadata"),
1730            ],
1731            certificates: vec![bytes.clone(), other_bytes.clone()],
1732            ..Default::default()
1733        };
1734        let paths = super::super::parse::build_x509_certificate_paths_to_selector_targets(
1735            &info,
1736            0,
1737            &[1],
1738            super::super::parse::X509PathBounds {
1739                max_depth: 2,
1740                max_candidate_paths: 4,
1741            },
1742            crate::provider::default_provider(),
1743            None,
1744            &session,
1745        )
1746        .expect("deferred edge remains a prospective path");
1747        assert_eq!(paths.len(), 1);
1748        assert_eq!(paths[0], [0, 1]);
1749        let anchor = session.read(&other_bytes).expect("candidate anchor");
1750        let anchors = [other_bytes.clone()];
1751        let options = super::super::x509::X509ChainOptions {
1752            utc_facts: None,
1753            trusted_certs: &anchors,
1754            verification_time: std::time::SystemTime::now(),
1755            max_chain_depth: 2,
1756            check_crls: false,
1757            allowed_extended_key_usages: None,
1758            rsa_keys: crate::policy::RsaKeyPolicy::default(),
1759            dsa_keys: crate::policy::DsaKeyPolicy::default(),
1760            certificate_signature_algorithms: None,
1761        };
1762        let validation = validate_supplied_path(
1763            &[&parsed],
1764            &anchor,
1765            &options,
1766            crate::provider::default_provider(),
1767            &session,
1768        );
1769        assert!(
1770            matches!(validation, Err(X509ChainError::UnsupportedSignature(0))),
1771            "a deferred edge is never authenticated: {validation:?}"
1772        );
1773    }
1774
1775    #[test]
1776    fn public_key_info_parser_enforces_certificate_workspace_policy() {
1777        // XML ingestion must not turn resource exhaustion into malformed DER.
1778        use base64::Engine as _;
1779        let pair = rcgen::KeyPair::generate().expect("test key generation");
1780        let cert = rcgen::CertificateParams::new(Vec::<String>::new())
1781            .expect("empty SAN list")
1782            .self_signed(&pair)
1783            .expect("test certificate signing");
1784        let encoded = base64::engine::general_purpose::STANDARD.encode(cert.der());
1785        let xml = format!(
1786            "<KeyInfo xmlns='http://www.w3.org/2000/09/xmldsig#'><X509Data><X509Certificate>{encoded}</X509Certificate></X509Data></KeyInfo>"
1787        );
1788        let document = crate::xml::dom::Document::parse(&xml).expect("generated XML");
1789        let resources = ResourcePolicy {
1790            max_x509_workspace_bytes: 0,
1791            ..ResourcePolicy::default()
1792        };
1793        let mut session = super::super::parse::KeyInfoParsingSession::new(&resources)
1794            .expect("valid resource policy");
1795        assert!(matches!(
1796            session.parse(document.root_element()),
1797            Err(super::super::ParseError::Policy(
1798                PolicyViolation::ResourceLimitExceeded {
1799                    resource: resource_name::X509_WORKSPACE_BYTES,
1800                    maximum: 0
1801                }
1802            ))
1803        ));
1804    }
1805}