1use super::{
6 Anchor, BuildError, BuildResult, Interval, Meter, Policy, Truncation, Verified, Verify, Watch,
7 WorkingKey, cost, sorting_rounds,
8};
9use crate::constraints::BasicConstraints;
10use crate::extension::{Extension, Recognizer};
11use crate::{Certificate, ErrorCode, Name, oid};
12use structured_asn1::Oid;
13use structured_asn1::memory::{Memory, MemoryError, OwnedVec};
14
15mod processing;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ConstraintSource {
20 Anchor,
22 Certificate(usize),
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum NameConstraintFailure {
29 Excluded,
31 NotPermitted,
33 Unenforceable,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[non_exhaustive]
40pub enum RejectionReason<'a> {
41 EmptyPath,
43 DuplicateCertificate {
45 earlier: usize,
47 },
48 AnchorIncluded,
50 Validity,
52 Issuer,
54 NotAuthority,
56 KeyCertSign,
58 EmptyAuthorityName,
60 PathLength {
62 source: ConstraintSource,
64 limit: u32,
66 },
67 Signature,
69 UnsupportedSignature,
71 CriticalExtension(Oid<'a>),
73 MalformedExtension {
75 extension: Oid<'a>,
77 code: ErrorCode,
79 field: &'static str,
81 offset: u64,
83 },
84 NameConstraintsOnNonAuthority,
86 NameConstraints {
88 source: ConstraintSource,
90 failure: NameConstraintFailure,
92 },
93 Policies,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub struct Rejection<'a> {
100 pub at: Option<ConstraintSource>,
102 pub reason: RejectionReason<'a>,
104}
105
106#[derive(Debug)]
110pub struct ValidationFacts<'a> {
111 pub key: WorkingKey<'a>,
113 pub non_self_issued_intermediates: u32,
116 policies: OwnedVec<Oid<'a>>,
117}
118
119impl<'a> ValidationFacts<'a> {
120 #[must_use]
122 pub fn policies(&self) -> &[Oid<'a>] {
123 &self.policies
124 }
125
126 #[must_use]
128 pub fn into_policies(self) -> OwnedVec<Oid<'a>> {
129 self.policies
130 }
131}
132
133#[derive(Debug)]
135pub enum Validation<'a> {
136 Accepted(ValidationFacts<'a>),
138 Rejected(Rejection<'a>),
140 Incomplete(Truncation),
142}
143
144#[derive(Debug, Clone, Copy)]
146pub struct ValidationBounds {
147 pub depth: usize,
149 pub work: u64,
151 pub policies: usize,
153}
154
155impl Default for ValidationBounds {
156 fn default() -> Self {
157 let bounds = super::Bounds::default();
158 Self {
159 depth: bounds.depth,
160 work: bounds.work,
161 policies: bounds.policies,
162 }
163 }
164}
165
166pub struct ValidationContext<'a, 'p, V: Verify, W: Watch, R: Recognizer> {
170 pub execution: &'p Memory,
172 pub interval: Interval,
174 pub policy: Policy<'a>,
176 pub bounds: ValidationBounds,
178 pub verifier: &'p V,
180 pub watch: &'p W,
182 pub recognizer: &'p R,
184}
185
186pub(super) struct Settings<'a, 'p, R: Recognizer> {
187 pub(super) execution: &'p Memory,
188 pub(super) interval: Interval,
189 pub(super) policy: Policy<'a>,
190 pub(super) policies: usize,
191 pub(super) recognizer: &'p R,
192}
193
194#[derive(Debug, Clone, Copy)]
196pub(super) struct CertificateFacts<'a> {
197 pub(super) self_issued: bool,
198 pub(super) basic: Option<BasicConstraints>,
199 pub(super) issuing: Option<RejectionReason<'a>>,
200 pub(super) rejected: Option<RejectionReason<'a>>,
201}
202
203pub(super) trait Driver<'a, R: Recognizer>: Meter {
204 type VerificationError;
205 fn verified(
206 &mut self,
207 certificate: &'a Certificate<'a>,
208 key: WorkingKey<'a>,
209 ) -> BuildResult<Option<Verified>, Self::VerificationError, R::Error>;
210 fn facts(
211 &mut self,
212 certificate: &'a Certificate<'a>,
213 settings: &Settings<'a, '_, R>,
214 ) -> BuildResult<Option<CertificateFacts<'a>>, Self::VerificationError, R::Error>;
215}
216
217#[derive(Clone, Copy)]
221pub(super) enum Chain<'a, 'p> {
222 Supplied(&'p [&'a Certificate<'a>]),
223 Constructed(&'p [&'a Certificate<'a>]),
224}
225
226enum Decoded<T> {
227 Value(Option<T>),
228 Rejected(RejectionReason<'static>),
229 Incomplete,
230}
231
232fn decode<T>(
233 meter: &mut impl Meter,
234 kind: Oid<'static>,
235 action: impl FnOnce() -> crate::Result<Option<T>>,
236) -> crate::Result<Decoded<T>> {
237 if !meter.spend(cost::EXTENSION)? {
238 return Ok(Decoded::Incomplete);
239 }
240 Ok(match decoded(kind, action())? {
241 Ok(value) => Decoded::Value(value),
242 Err(reason) => Decoded::Rejected(reason),
243 })
244}
245
246fn decoded<T>(
247 kind: Oid<'static>,
248 result: crate::Result<T>,
249) -> crate::Result<core::result::Result<T, RejectionReason<'static>>> {
250 match result {
251 Ok(value) => Ok(Ok(value)),
252 Err(error) if error.is_resource() => Err(error),
253 Err(error) => Ok(Err(RejectionReason::MalformedExtension {
254 extension: kind,
255 code: error.code(),
256 field: error.field(),
257 offset: error.offset(),
258 })),
259 }
260}
261
262pub(super) fn spend_names(
263 meter: &mut impl Meter,
264 left: &Name<'_>,
265 right: &Name<'_>,
266) -> crate::Result<bool> {
267 let parts = left.parts().len() as u64 + right.parts().len() as u64;
268 if !meter.spend(parts)? {
269 return Ok(false);
270 }
271 let bytes = left.as_bytes().len() as u64 + right.as_bytes().len() as u64;
272 let rounds = sorting_rounds(left).max(sorting_rounds(right));
273 let work = rounds
274 .checked_mul(bytes / cost::COMPARED)
275 .and_then(|work| work.checked_add(cost::NAME))
276 .ok_or_else(|| left.resource(MemoryError::WorkExceeded))?;
277 meter.spend(work)
278}
279
280fn critical_policy(
281 item: &Extension<'_>,
282 execution: &Memory,
283) -> Option<(Oid<'static>, crate::Result<bool>)> {
284 let kind = item.kind();
286 if kind == oid::CERTIFICATE_POLICIES {
287 Some((
288 oid::CERTIFICATE_POLICIES,
289 crate::policy::CertificatePolicies::read(item, execution)
293 .and_then(|policies| policies.understood(item.parser(), execution)),
294 ))
295 } else if kind == oid::POLICY_CONSTRAINTS {
296 Some((
297 oid::POLICY_CONSTRAINTS,
298 crate::constraints::PolicyConstraints::read(item, execution).map(|_| true),
299 ))
300 } else if kind == oid::POLICY_MAPPINGS {
301 Some((
302 oid::POLICY_MAPPINGS,
303 crate::policy::PolicyMappings::read(item, execution).map(|_| true),
304 ))
305 } else if kind == oid::INHIBIT_ANY_POLICY {
306 Some((
307 oid::INHIBIT_ANY_POLICY,
308 crate::constraints::InhibitAnyPolicy::read(item, execution).map(|_| true),
309 ))
310 } else {
311 None
312 }
313}
314
315pub(super) fn certificate_facts<'a, E, R: Recognizer, M: Meter>(
316 certificate: &'a Certificate<'a>,
317 settings: &Settings<'a, '_, R>,
318 meter: &mut M,
319) -> BuildResult<Option<CertificateFacts<'a>>, E, R::Error> {
320 let execution = settings.execution;
321 let extensions = certificate.tbs().extensions();
322 if !meter.spend(cost::CHECK)? {
323 return Ok(None);
324 }
325 let mut facts = CertificateFacts {
326 self_issued: false,
327 basic: None,
328 issuing: Some(RejectionReason::NotAuthority),
329 rejected: None,
330 };
331 if !settings.interval.inside(certificate.tbs().validity()) {
332 facts.rejected = Some(RejectionReason::Validity);
333 return Ok(Some(facts));
334 }
335 if !spend_names(
336 meter,
337 certificate.tbs().subject(),
338 certificate.tbs().issuer(),
339 )? {
340 return Ok(None);
341 }
342 facts.self_issued = certificate
343 .tbs()
344 .subject()
345 .matches(certificate.tbs().issuer(), execution)?;
346 match decode(meter, oid::BASIC_CONSTRAINTS, || {
347 extensions.basic_constraints(execution)
348 })? {
349 Decoded::Value(basic) => facts.basic = basic,
350 Decoded::Rejected(reason) => {
351 facts.rejected = Some(reason);
352 return Ok(Some(facts));
353 }
354 Decoded::Incomplete => return Ok(None),
355 }
356 if facts.basic.is_some_and(BasicConstraints::is_authority) {
357 facts.issuing = None;
358 if certificate.tbs().subject().is_empty() {
359 facts.rejected = Some(RejectionReason::EmptyAuthorityName);
360 return Ok(Some(facts));
361 }
362 match decode(meter, oid::KEY_USAGE, || extensions.key_usage(execution))? {
363 Decoded::Value(Some(usage)) if !usage.key_cert_sign() => {
364 facts.issuing = Some(RejectionReason::KeyCertSign);
365 }
366 Decoded::Rejected(reason) => facts.issuing = Some(reason),
367 Decoded::Incomplete => return Ok(None),
368 Decoded::Value(_) => {}
369 }
370 }
371 for demand in extensions.criticals(execution) {
372 let demand = demand?;
373 if !meter.spend(cost::EXTENSION)? {
374 return Ok(None);
375 }
376 if demand.kind() == oid::NAME_CONSTRAINTS || demand.kind() == oid::BASIC_CONSTRAINTS {
380 continue;
381 }
382 if demand.kind() == oid::KEY_USAGE
383 && facts.basic.is_some_and(BasicConstraints::is_authority)
384 {
385 if matches!(
386 facts.issuing,
387 Some(RejectionReason::MalformedExtension { .. })
388 ) {
389 facts.rejected = facts.issuing;
390 return Ok(Some(facts));
391 }
392 continue;
393 }
394 if let Some((kind, result)) = critical_policy(demand, execution) {
400 let reason = match decoded(kind, result)? {
401 Ok(true) => continue,
402 Ok(false) => RejectionReason::CriticalExtension(kind),
403 Err(reason) => reason,
404 };
405 facts.rejected = Some(reason);
406 return Ok(Some(facts));
407 }
408 if extensions.understands(demand, settings.recognizer, execution)? {
409 continue;
410 }
411 facts.rejected = Some(RejectionReason::CriticalExtension(demand.kind()));
412 return Ok(Some(facts));
413 }
414 Ok(Some(facts))
415}
416
417fn rejection(at: ConstraintSource, reason: RejectionReason<'_>) -> Validation<'_> {
418 Validation::Rejected(Rejection {
419 at: Some(at),
420 reason,
421 })
422}
423
424pub(super) fn process<'a, R: Recognizer, D: Driver<'a, R>>(
425 input: Chain<'a, '_>,
426 anchor: Anchor<'a>,
427 settings: &Settings<'a, '_, R>,
428 driver: &mut D,
429) -> BuildResult<Validation<'a>, D::VerificationError, R::Error> {
430 processing::process(input, anchor, settings, driver)
431}
432
433struct Supplied<'a, 'p, V: Verify, W: Watch, R: Recognizer> {
434 context: &'p ValidationContext<'a, 'p, V, W, R>,
435 origin: &'a Certificate<'a>,
436 spent: u64,
437 halted: Option<Truncation>,
438}
439
440impl<V: Verify, W: Watch, R: Recognizer> Supplied<'_, '_, V, W, R> {
441 fn admit(&mut self, work: u64) -> crate::Result<Option<Truncation>> {
442 if work > self.context.bounds.work - self.spent {
443 return Ok(Some(Truncation::Work));
444 }
445 if self.context.watch.out_of_time() {
446 return Ok(Some(Truncation::Time));
447 }
448 self.context
449 .execution
450 .charge_work(work)
451 .map_err(|reason| self.origin.tbs().subject().resource(reason))?;
452 self.spent += work;
453 Ok(None)
454 }
455}
456
457impl<V: Verify, W: Watch, R: Recognizer> Meter for Supplied<'_, '_, V, W, R> {
458 fn spend(&mut self, work: u64) -> crate::Result<bool> {
459 if let Some(halted) = self.admit(work)? {
460 self.halted = Some(halted);
461 return Ok(false);
462 }
463 Ok(true)
464 }
465}
466
467impl<'a, V: Verify, W: Watch, R: Recognizer> Driver<'a, R> for Supplied<'a, '_, V, W, R> {
468 type VerificationError = V::Error;
469 fn verified(
470 &mut self,
471 certificate: &'a Certificate<'a>,
472 key: WorkingKey<'a>,
473 ) -> BuildResult<Option<Verified>, V::Error, R::Error> {
474 if !self.spend(cost::SIGNATURE)? {
475 return Ok(None);
476 }
477 self.context
478 .verifier
479 .verify(
480 &certificate.signature_algorithm(),
481 key,
482 certificate.signed_bytes(),
483 certificate.signature(),
484 self.context.execution,
485 )
486 .map(Some)
487 .map_err(BuildError::Verification)
488 }
489 fn facts(
490 &mut self,
491 certificate: &'a Certificate<'a>,
492 settings: &Settings<'a, '_, R>,
493 ) -> BuildResult<Option<CertificateFacts<'a>>, V::Error, R::Error> {
494 certificate_facts(certificate, settings, self)
495 }
496}
497
498pub fn validate<'a, V: Verify, W: Watch, R: Recognizer>(
531 chain: &[&'a Certificate<'a>],
532 anchor: Anchor<'a>,
533 context: &ValidationContext<'a, '_, V, W, R>,
534) -> BuildResult<Validation<'a>, V::Error, R::Error> {
535 let Some(target) = chain.first() else {
536 return Ok(Validation::Rejected(Rejection {
537 at: None,
538 reason: RejectionReason::EmptyPath,
539 }));
540 };
541 if chain.len() > context.bounds.depth {
542 return Ok(Validation::Incomplete(Truncation::Depth));
543 }
544 let length = u32::try_from(chain.len()).map_err(|_| {
545 target
546 .tbs()
547 .subject()
548 .resource(MemoryError::CapacityOverflow)
549 })?;
550 length.checked_add(1).ok_or_else(|| {
551 target
552 .tbs()
553 .subject()
554 .resource(MemoryError::CapacityOverflow)
555 })?;
556 let mut driver = Supplied {
557 context,
558 origin: target,
559 spent: 0,
560 halted: None,
561 };
562 let mut seen = context.execution.map();
563 for (index, certificate) in chain.iter().enumerate() {
564 if let Some(halted) =
565 driver.admit(cost::ENTRY + certificate.as_bytes().len() as u64 / cost::HASHED)?
566 {
567 return Ok(Validation::Incomplete(halted));
568 }
569 if let Some(earlier) = seen
570 .try_insert(certificate.as_bytes(), index)
571 .map_err(|reason| certificate.tbs().subject().resource(reason))?
572 {
573 return Ok(rejection(
574 ConstraintSource::Certificate(index),
575 RejectionReason::DuplicateCertificate { earlier },
576 ));
577 }
578 if let Some(published) = anchor.certificate() {
579 let bytes = certificate.as_bytes().len().min(published.as_bytes().len()) as u64;
580 if let Some(halted) = driver.admit(bytes / cost::COMPARED)? {
581 return Ok(Validation::Incomplete(halted));
582 }
583 context
584 .execution
585 .charge_work(bytes)
586 .map_err(|reason| certificate.tbs().subject().resource(reason))?;
587 if super::same(certificate, published) {
588 return Ok(rejection(
589 ConstraintSource::Certificate(index),
590 RejectionReason::AnchorIncluded,
591 ));
592 }
593 }
594 }
595 drop(seen);
596 let settings = Settings {
597 execution: context.execution,
598 interval: context.interval,
599 policy: context.policy,
600 policies: context.bounds.policies,
601 recognizer: context.recognizer,
602 };
603 let outcome = process(Chain::Supplied(chain), anchor, &settings, &mut driver)?;
604 if let Some(halted) = driver.halted {
605 return Ok(Validation::Incomplete(halted));
606 }
607 Ok(outcome)
608}
609
610#[cfg(test)]
611mod tests {
612 use super::*;
613 use crate::fixtures::{CertificateParts, oid as encoded_oid, printable, seq, set};
614 use core::cell::Cell;
615 use structured_asn1::{BitString, Parser};
616
617 struct Answer {
618 result: Verified,
619 calls: Cell<usize>,
620 }
621 impl Verify for Answer {
622 type Error = core::convert::Infallible;
623 fn verify(
624 &self,
625 _algorithm: &crate::AlgorithmIdentifier<'_>,
626 _key: WorkingKey<'_>,
627 _signed: &[u8],
628 _signature: BitString<'_>,
629 _execution: &Memory,
630 ) -> core::result::Result<Verified, Self::Error> {
631 self.calls.set(self.calls.get() + 1);
632 Ok(self.result)
633 }
634 }
635
636 fn target() -> alloc::vec::Vec<u8> {
637 CertificateParts {
638 subject: seq(&[set(&[seq(&[
639 encoded_oid(oid::COMMON_NAME),
640 printable("target"),
641 ])])]),
642 ..CertificateParts::default()
643 }
644 .build()
645 }
646
647 fn root() -> alloc::vec::Vec<u8> {
648 let parts = CertificateParts::default();
649 CertificateParts {
650 subject: parts.issuer.clone(),
651 ..parts
652 }
653 .build()
654 }
655
656 #[test]
657 fn supplied_path_separates_success_no_and_unsupported() {
658 let root_der = root();
660 let leaf_der = target();
661 let parser = Parser::der(crate::fixtures::memory());
662 let root = Certificate::read(&parser, &root_der, 0).unwrap();
663 let leaf = Certificate::read(&parser, &leaf_der, 100).unwrap();
664 let anchor = Anchor::new(root.tbs().subject(), root.tbs().public_key());
665 for result in [Verified::Yes, Verified::No, Verified::Unsupported] {
666 let execution = crate::fixtures::memory();
667 let verifier = Answer {
668 result,
669 calls: Cell::new(0),
670 };
671 let context = ValidationContext {
672 execution: &execution,
673 interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
674 policy: Policy::default(),
675 bounds: ValidationBounds::default(),
676 verifier: &verifier,
677 watch: &(),
678 recognizer: &(),
679 };
680 let outcome = crate::path::validate(&[&leaf], anchor, &context).unwrap();
681 match (result, outcome) {
682 (Verified::Yes, Validation::Accepted(facts)) => {
683 assert_eq!(
684 facts.key.key().as_bytes(),
685 leaf.tbs().public_key().as_bytes()
686 );
687 assert_eq!(facts.policies(), []);
688 }
689 (Verified::No, Validation::Rejected(rejection)) => {
690 assert_eq!(rejection.at, Some(ConstraintSource::Certificate(0)));
691 assert_eq!(rejection.reason, RejectionReason::Signature);
692 }
693 (Verified::Unsupported, Validation::Rejected(rejection)) => {
694 assert_eq!(rejection.at, Some(ConstraintSource::Certificate(0)));
695 assert_eq!(rejection.reason, RejectionReason::UnsupportedSignature);
696 }
697 other => panic!("unexpected public path result: {other:?}"),
698 }
699 assert_eq!(verifier.calls.get(), 1);
700 assert_eq!(execution.used(), 0);
701 }
702 }
703
704 #[test]
705 fn supplied_local_work_refusal_never_calls_the_provider() {
706 let root_der = root();
708 let leaf_der = target();
709 let parser = Parser::der(crate::fixtures::memory());
710 let root = Certificate::read(&parser, &root_der, 0).unwrap();
711 let leaf = Certificate::read(&parser, &leaf_der, 100).unwrap();
712 let execution = crate::fixtures::memory();
713 let verifier = Answer {
714 result: Verified::Yes,
715 calls: Cell::new(0),
716 };
717 let context = ValidationContext {
718 execution: &execution,
719 interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
720 policy: Policy::default(),
721 bounds: ValidationBounds {
722 work: 0,
723 ..ValidationBounds::default()
724 },
725 verifier: &verifier,
726 watch: &(),
727 recognizer: &(),
728 };
729 assert!(matches!(
730 crate::path::validate(
731 &[&leaf],
732 Anchor::new(root.tbs().subject(), root.tbs().public_key()),
733 &context
734 )
735 .unwrap(),
736 Validation::Incomplete(Truncation::Work)
737 ));
738 assert_eq!(verifier.calls.get(), 0);
739 assert_eq!(execution.used(), 0);
740 assert_eq!(execution.work_used(), 0);
741 }
742}