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, Work};
14
15mod processing;
16
17#[derive(Debug, Clone, Copy)]
21pub struct EffectiveAnchor<'a> {
22 anchor: Anchor<'a>,
23 key: WorkingKey<'a>,
24}
25
26impl<'a> EffectiveAnchor<'a> {
27 pub fn bind<E: Work>(
31 anchor: Anchor<'a>,
32 key: WorkingKey<'a>,
33 execution: &E,
34 ) -> crate::Result<Option<Self>> {
35 let left = anchor.key().as_bytes();
36 let right = key.key().as_bytes();
37 let work = u64::try_from(left.len().max(right.len()))
38 .ok()
39 .and_then(|n| n.checked_mul(2))
40 .and_then(|n| n.checked_add(1))
41 .ok_or_else(|| anchor.name().resource(MemoryError::WorkExceeded))?;
42 execution
43 .charge_work(work)
44 .map_err(|reason| anchor.name().resource(reason))?;
45 if left != right {
46 return Ok(None);
47 }
48 Ok(Some(Self { anchor, key }))
49 }
50
51 #[must_use]
53 pub fn anchor(self) -> Anchor<'a> {
54 self.anchor
55 }
56
57 #[must_use]
59 pub fn key(self) -> WorkingKey<'a> {
60 self.key
61 }
62
63 fn standing(anchor: Anchor<'a>) -> Self {
64 Self {
65 anchor,
66 key: WorkingKey::standing(anchor.key()),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum ConstraintSource {
74 Anchor,
76 Certificate(usize),
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum NameConstraintFailure {
83 Excluded,
85 NotPermitted,
87 Unenforceable,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[non_exhaustive]
94pub enum RejectionReason<'a> {
95 EmptyPath,
97 DuplicateCertificate {
99 earlier: usize,
101 },
102 AnchorIncluded,
104 Validity,
106 Issuer,
108 NotAuthority,
110 KeyCertSign,
112 EmptyAuthorityName,
114 PathLength {
116 source: ConstraintSource,
118 limit: u32,
120 },
121 Signature,
123 UnsupportedSignature,
125 CriticalExtension(Oid<'a>),
127 MalformedExtension {
129 extension: Oid<'a>,
131 code: ErrorCode,
133 field: &'static str,
135 offset: u64,
137 },
138 NameConstraintsOnNonAuthority,
140 NameConstraints {
142 source: ConstraintSource,
144 failure: NameConstraintFailure,
146 },
147 Policies,
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct Rejection<'a> {
154 pub at: Option<ConstraintSource>,
156 pub reason: RejectionReason<'a>,
158}
159
160#[derive(Debug)]
164pub struct ValidationFacts<'a> {
165 pub key: WorkingKey<'a>,
167 pub non_self_issued_intermediates: u32,
170 policies: OwnedVec<Oid<'a>>,
171}
172
173impl<'a> ValidationFacts<'a> {
174 #[must_use]
176 pub fn policies(&self) -> &[Oid<'a>] {
177 &self.policies
178 }
179
180 #[must_use]
182 pub fn into_policies(self) -> OwnedVec<Oid<'a>> {
183 self.policies
184 }
185}
186
187#[derive(Debug)]
189pub enum Validation<'a> {
190 Accepted(ValidationFacts<'a>),
192 Rejected(Rejection<'a>),
194 Incomplete(Truncation),
196}
197
198#[derive(Debug, Clone, Copy)]
200pub struct ValidationBounds {
201 pub depth: usize,
203 pub work: u64,
205 pub policies: usize,
207}
208
209impl Default for ValidationBounds {
210 fn default() -> Self {
211 let bounds = super::Bounds::default();
212 Self {
213 depth: bounds.depth,
214 work: bounds.work,
215 policies: bounds.policies,
216 }
217 }
218}
219
220pub struct ValidationContext<'a, 'p, V: Verify<E>, W: Watch, R: Recognizer<E>, E: Work = Memory> {
224 pub execution: &'p E,
226 pub interval: Interval,
228 pub policy: Policy<'a>,
230 pub bounds: ValidationBounds,
232 pub verifier: &'p V,
234 pub watch: &'p W,
236 pub recognizer: &'p R,
238}
239
240pub(super) struct Settings<'a, 'p, R: Recognizer<E>, E: Work = Memory> {
241 pub(super) execution: &'p E,
242 pub(super) interval: Interval,
243 pub(super) policy: Policy<'a>,
244 pub(super) policies: usize,
245 pub(super) recognizer: &'p R,
246}
247
248#[derive(Debug, Clone, Copy)]
250pub(super) struct CertificateFacts<'a> {
251 pub(super) self_issued: bool,
252 pub(super) basic: Option<BasicConstraints>,
253 pub(super) issuing: Option<RejectionReason<'a>>,
254 pub(super) rejected: Option<RejectionReason<'a>>,
255}
256
257pub(super) trait Driver<'a, R: Recognizer<E>, E: Work = Memory>: Meter {
258 type VerificationError;
259 fn verified(
260 &mut self,
261 certificate: &'a Certificate<'a>,
262 key: WorkingKey<'a>,
263 ) -> BuildResult<Option<Verified>, Self::VerificationError, R::Error>;
264 fn facts(
265 &mut self,
266 certificate: &'a Certificate<'a>,
267 settings: &Settings<'a, '_, R, E>,
268 ) -> BuildResult<Option<CertificateFacts<'a>>, Self::VerificationError, R::Error>;
269}
270
271#[derive(Clone, Copy)]
275pub(super) enum Chain<'a, 'p> {
276 Supplied(&'p [&'a Certificate<'a>]),
277 Constructed(&'p [&'a Certificate<'a>]),
278}
279
280enum Decoded<T> {
281 Value(Option<T>),
282 Rejected(RejectionReason<'static>),
283 Incomplete,
284}
285
286fn decode<T>(
287 meter: &mut impl Meter,
288 kind: Oid<'static>,
289 action: impl FnOnce() -> crate::Result<Option<T>>,
290) -> crate::Result<Decoded<T>> {
291 if !meter.spend(cost::EXTENSION)? {
292 return Ok(Decoded::Incomplete);
293 }
294 Ok(match decoded(kind, action())? {
295 Ok(value) => Decoded::Value(value),
296 Err(reason) => Decoded::Rejected(reason),
297 })
298}
299
300fn decoded<T>(
301 kind: Oid<'static>,
302 result: crate::Result<T>,
303) -> crate::Result<core::result::Result<T, RejectionReason<'static>>> {
304 match result {
305 Ok(value) => Ok(Ok(value)),
306 Err(error) if error.is_resource() => Err(error),
307 Err(error) => Ok(Err(RejectionReason::MalformedExtension {
308 extension: kind,
309 code: error.code(),
310 field: error.field(),
311 offset: error.offset(),
312 })),
313 }
314}
315
316pub(super) fn spend_names(
317 meter: &mut impl Meter,
318 left: &Name<'_>,
319 right: &Name<'_>,
320) -> crate::Result<bool> {
321 let parts = left.parts().len() as u64 + right.parts().len() as u64;
322 if !meter.spend(parts)? {
323 return Ok(false);
324 }
325 let bytes = left.as_bytes().len() as u64 + right.as_bytes().len() as u64;
326 let rounds = sorting_rounds(left).max(sorting_rounds(right));
327 let work = rounds
328 .checked_mul(bytes / cost::COMPARED)
329 .and_then(|work| work.checked_add(cost::NAME))
330 .ok_or_else(|| left.resource(MemoryError::WorkExceeded))?;
331 meter.spend(work)
332}
333
334fn critical_policy<E: Work>(
335 item: &Extension<'_>,
336 execution: &E,
337) -> Option<(Oid<'static>, crate::Result<bool>)> {
338 let kind = item.kind();
340 if kind == oid::CERTIFICATE_POLICIES {
341 Some((
342 oid::CERTIFICATE_POLICIES,
343 crate::policy::CertificatePolicies::read(item, execution)
347 .and_then(|policies| policies.understood(item.parser(), execution)),
348 ))
349 } else if kind == oid::POLICY_CONSTRAINTS {
350 Some((
351 oid::POLICY_CONSTRAINTS,
352 crate::constraints::PolicyConstraints::read(item, execution).map(|_| true),
353 ))
354 } else if kind == oid::POLICY_MAPPINGS {
355 Some((
356 oid::POLICY_MAPPINGS,
357 crate::policy::PolicyMappings::read(item, execution).map(|_| true),
358 ))
359 } else if kind == oid::INHIBIT_ANY_POLICY {
360 Some((
361 oid::INHIBIT_ANY_POLICY,
362 crate::constraints::InhibitAnyPolicy::read(item, execution).map(|_| true),
363 ))
364 } else {
365 None
366 }
367}
368
369pub(super) fn certificate_facts<'a, E, R: Recognizer<Q>, M: Meter, Q: Work>(
370 certificate: &'a Certificate<'a>,
371 settings: &Settings<'a, '_, R, Q>,
372 meter: &mut M,
373) -> BuildResult<Option<CertificateFacts<'a>>, E, R::Error> {
374 let execution = settings.execution;
375 let extensions = certificate.tbs().extensions();
376 if !meter.spend(cost::CHECK)? {
377 return Ok(None);
378 }
379 let mut facts = CertificateFacts {
380 self_issued: false,
381 basic: None,
382 issuing: Some(RejectionReason::NotAuthority),
383 rejected: None,
384 };
385 if !settings.interval.inside(certificate.tbs().validity()) {
386 facts.rejected = Some(RejectionReason::Validity);
387 return Ok(Some(facts));
388 }
389 if !spend_names(
390 meter,
391 certificate.tbs().subject(),
392 certificate.tbs().issuer(),
393 )? {
394 return Ok(None);
395 }
396 facts.self_issued = certificate
397 .tbs()
398 .subject()
399 .matches(certificate.tbs().issuer(), execution)?;
400 match decode(meter, oid::BASIC_CONSTRAINTS, || {
401 extensions.basic_constraints(execution)
402 })? {
403 Decoded::Value(basic) => facts.basic = basic,
404 Decoded::Rejected(reason) => {
405 facts.rejected = Some(reason);
406 return Ok(Some(facts));
407 }
408 Decoded::Incomplete => return Ok(None),
409 }
410 if facts.basic.is_some_and(BasicConstraints::is_authority) {
411 facts.issuing = None;
412 if certificate.tbs().subject().is_empty() {
413 facts.rejected = Some(RejectionReason::EmptyAuthorityName);
414 return Ok(Some(facts));
415 }
416 match decode(meter, oid::KEY_USAGE, || extensions.key_usage(execution))? {
417 Decoded::Value(Some(usage)) if !usage.key_cert_sign() => {
418 facts.issuing = Some(RejectionReason::KeyCertSign);
419 }
420 Decoded::Rejected(reason) => facts.issuing = Some(reason),
421 Decoded::Incomplete => return Ok(None),
422 Decoded::Value(_) => {}
423 }
424 }
425 for demand in extensions.criticals(execution) {
426 let demand = demand?;
427 if !meter.spend(cost::EXTENSION)? {
428 return Ok(None);
429 }
430 if demand.kind() == oid::NAME_CONSTRAINTS || demand.kind() == oid::BASIC_CONSTRAINTS {
434 continue;
435 }
436 if demand.kind() == oid::KEY_USAGE
437 && facts.basic.is_some_and(BasicConstraints::is_authority)
438 {
439 if matches!(
440 facts.issuing,
441 Some(RejectionReason::MalformedExtension { .. })
442 ) {
443 facts.rejected = facts.issuing;
444 return Ok(Some(facts));
445 }
446 continue;
447 }
448 if let Some((kind, result)) = critical_policy(demand, execution) {
454 let reason = match decoded(kind, result)? {
455 Ok(true) => continue,
456 Ok(false) => RejectionReason::CriticalExtension(kind),
457 Err(reason) => reason,
458 };
459 facts.rejected = Some(reason);
460 return Ok(Some(facts));
461 }
462 if extensions.understands(demand, settings.recognizer, execution)? {
463 continue;
464 }
465 facts.rejected = Some(RejectionReason::CriticalExtension(demand.kind()));
466 return Ok(Some(facts));
467 }
468 Ok(Some(facts))
469}
470
471fn rejection(at: ConstraintSource, reason: RejectionReason<'_>) -> Validation<'_> {
472 Validation::Rejected(Rejection {
473 at: Some(at),
474 reason,
475 })
476}
477
478pub(super) fn process<'a, R: Recognizer<E>, D: Driver<'a, R, E>, E: Work>(
479 input: Chain<'a, '_>,
480 anchor: Anchor<'a>,
481 settings: &Settings<'a, '_, R, E>,
482 driver: &mut D,
483) -> BuildResult<Validation<'a>, D::VerificationError, R::Error> {
484 processing::process(input, &EffectiveAnchor::standing(anchor), settings, driver)
485}
486
487struct Supplied<'a, 'p, V: Verify<E>, W: Watch, R: Recognizer<E>, E: Work> {
488 context: &'p ValidationContext<'a, 'p, V, W, R, E>,
489 origin: &'a Certificate<'a>,
490 spent: u64,
491 halted: Option<Truncation>,
492}
493
494impl<V: Verify<E>, W: Watch, R: Recognizer<E>, E: Work> Supplied<'_, '_, V, W, R, E> {
495 fn admit(&mut self, work: u64) -> crate::Result<Option<Truncation>> {
496 if work > self.context.bounds.work - self.spent {
497 return Ok(Some(Truncation::Work));
498 }
499 if self.context.watch.out_of_time() {
500 return Ok(Some(Truncation::Time));
501 }
502 self.context
503 .execution
504 .charge_work(work)
505 .map_err(|reason| self.origin.tbs().subject().resource(reason))?;
506 self.spent += work;
507 Ok(None)
508 }
509}
510
511impl<V: Verify<E>, W: Watch, R: Recognizer<E>, E: Work> Meter for Supplied<'_, '_, V, W, R, E> {
512 fn spend(&mut self, work: u64) -> crate::Result<bool> {
513 if let Some(halted) = self.admit(work)? {
514 self.halted = Some(halted);
515 return Ok(false);
516 }
517 Ok(true)
518 }
519}
520
521impl<'a, V: Verify<E>, W: Watch, R: Recognizer<E>, E: Work> Driver<'a, R, E>
522 for Supplied<'a, '_, V, W, R, E>
523{
524 type VerificationError = V::Error;
525 fn verified(
526 &mut self,
527 certificate: &'a Certificate<'a>,
528 key: WorkingKey<'a>,
529 ) -> BuildResult<Option<Verified>, V::Error, R::Error> {
530 if !self.spend(cost::SIGNATURE)? {
531 return Ok(None);
532 }
533 self.context
534 .verifier
535 .verify(
536 &certificate.signature_algorithm(),
537 key,
538 certificate.signed_bytes(),
539 certificate.signature(),
540 self.context.execution,
541 )
542 .map(Some)
543 .map_err(BuildError::Verification)
544 }
545 fn facts(
546 &mut self,
547 certificate: &'a Certificate<'a>,
548 settings: &Settings<'a, '_, R, E>,
549 ) -> BuildResult<Option<CertificateFacts<'a>>, V::Error, R::Error> {
550 certificate_facts(certificate, settings, self)
551 }
552}
553
554pub fn validate<'a, V: Verify<E>, W: Watch, R: Recognizer<E>, E: Work>(
587 chain: &[&'a Certificate<'a>],
588 anchor: Anchor<'a>,
589 context: &ValidationContext<'a, '_, V, W, R, E>,
590) -> BuildResult<Validation<'a>, V::Error, R::Error> {
591 validate_effective(chain, EffectiveAnchor::standing(anchor), context)
592}
593
594pub fn validate_effective<'a, V: Verify<E>, W: Watch, R: Recognizer<E>, E: Work>(
600 chain: &[&'a Certificate<'a>],
601 effective: EffectiveAnchor<'a>,
602 context: &ValidationContext<'a, '_, V, W, R, E>,
603) -> BuildResult<Validation<'a>, V::Error, R::Error> {
604 let anchor = effective.anchor;
605 let Some(target) = chain.first() else {
606 return Ok(Validation::Rejected(Rejection {
607 at: None,
608 reason: RejectionReason::EmptyPath,
609 }));
610 };
611 if chain.len() > context.bounds.depth {
612 return Ok(Validation::Incomplete(Truncation::Depth));
613 }
614 let length = u32::try_from(chain.len()).map_err(|_| {
615 target
616 .tbs()
617 .subject()
618 .resource(MemoryError::CapacityOverflow)
619 })?;
620 length.checked_add(1).ok_or_else(|| {
621 target
622 .tbs()
623 .subject()
624 .resource(MemoryError::CapacityOverflow)
625 })?;
626 let mut driver = Supplied {
627 context,
628 origin: target,
629 spent: 0,
630 halted: None,
631 };
632 let mut seen = context.execution.map();
633 for (index, certificate) in chain.iter().enumerate() {
634 if let Some(halted) =
635 driver.admit(cost::ENTRY + certificate.as_bytes().len() as u64 / cost::HASHED)?
636 {
637 return Ok(Validation::Incomplete(halted));
638 }
639 if let Some(earlier) = seen
640 .try_insert(certificate.as_bytes(), index)
641 .map_err(|reason| certificate.tbs().subject().resource(reason))?
642 {
643 return Ok(rejection(
644 ConstraintSource::Certificate(index),
645 RejectionReason::DuplicateCertificate { earlier },
646 ));
647 }
648 if let Some(published) = anchor.certificate() {
649 let bytes = certificate.as_bytes().len().min(published.as_bytes().len()) as u64;
650 if let Some(halted) = driver.admit(bytes / cost::COMPARED)? {
651 return Ok(Validation::Incomplete(halted));
652 }
653 context
654 .execution
655 .charge_work(bytes)
656 .map_err(|reason| certificate.tbs().subject().resource(reason))?;
657 if super::same(certificate, published) {
658 return Ok(rejection(
659 ConstraintSource::Certificate(index),
660 RejectionReason::AnchorIncluded,
661 ));
662 }
663 }
664 }
665 drop(seen);
666 let settings = Settings {
667 execution: context.execution,
668 interval: context.interval,
669 policy: context.policy,
670 policies: context.bounds.policies,
671 recognizer: context.recognizer,
672 };
673 let outcome = processing::process(Chain::Supplied(chain), &effective, &settings, &mut driver)?;
674 if let Some(halted) = driver.halted {
675 return Ok(Validation::Incomplete(halted));
676 }
677 Ok(outcome)
678}
679
680#[cfg(test)]
681mod tests {
682 use super::*;
683 use crate::fixtures::{CertificateParts, oid as encoded_oid, printable, seq, set};
684 use core::cell::Cell;
685 use structured_asn1::{BitString, Parser};
686
687 struct Answer {
688 result: Verified,
689 calls: Cell<usize>,
690 }
691 impl Verify for Answer {
692 type Error = core::convert::Infallible;
693 fn verify(
694 &self,
695 _algorithm: &crate::AlgorithmIdentifier<'_>,
696 _key: WorkingKey<'_>,
697 _signed: &[u8],
698 _signature: BitString<'_>,
699 _execution: &Memory,
700 ) -> core::result::Result<Verified, Self::Error> {
701 self.calls.set(self.calls.get() + 1);
702 Ok(self.result)
703 }
704 }
705
706 fn target() -> alloc::vec::Vec<u8> {
707 CertificateParts {
708 subject: seq(&[set(&[seq(&[
709 encoded_oid(oid::COMMON_NAME),
710 printable("target"),
711 ])])]),
712 ..CertificateParts::default()
713 }
714 .build()
715 }
716
717 fn root() -> alloc::vec::Vec<u8> {
718 let parts = CertificateParts::default();
719 CertificateParts {
720 subject: parts.issuer.clone(),
721 ..parts
722 }
723 .build()
724 }
725
726 #[test]
727 fn supplied_path_separates_success_no_and_unsupported() {
728 let root_der = root();
730 let leaf_der = target();
731 let parser = Parser::der(crate::fixtures::memory());
732 let root = Certificate::read(&parser, &root_der, 0).unwrap();
733 let leaf = Certificate::read(&parser, &leaf_der, 100).unwrap();
734 let anchor = Anchor::new(root.tbs().subject(), root.tbs().public_key());
735 for result in [Verified::Yes, Verified::No, Verified::Unsupported] {
736 let execution = crate::fixtures::memory();
737 let verifier = Answer {
738 result,
739 calls: Cell::new(0),
740 };
741 let context = ValidationContext {
742 execution: &execution,
743 interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
744 policy: Policy::default(),
745 bounds: ValidationBounds::default(),
746 verifier: &verifier,
747 watch: &(),
748 recognizer: &(),
749 };
750 let outcome = crate::path::validate(&[&leaf], anchor, &context).unwrap();
751 match (result, outcome) {
752 (Verified::Yes, Validation::Accepted(facts)) => {
753 assert_eq!(
754 facts.key.key().as_bytes(),
755 leaf.tbs().public_key().as_bytes()
756 );
757 assert_eq!(facts.policies(), []);
758 }
759 (Verified::No, Validation::Rejected(rejection)) => {
760 assert_eq!(rejection.at, Some(ConstraintSource::Certificate(0)));
761 assert_eq!(rejection.reason, RejectionReason::Signature);
762 }
763 (Verified::Unsupported, Validation::Rejected(rejection)) => {
764 assert_eq!(rejection.at, Some(ConstraintSource::Certificate(0)));
765 assert_eq!(rejection.reason, RejectionReason::UnsupportedSignature);
766 }
767 other => panic!("unexpected public path result: {other:?}"),
768 }
769 assert_eq!(verifier.calls.get(), 1);
770 assert_eq!(execution.used(), 0);
771 }
772 }
773
774 #[test]
775 fn supplied_local_work_refusal_never_calls_the_provider() {
776 let root_der = root();
778 let leaf_der = target();
779 let parser = Parser::der(crate::fixtures::memory());
780 let root = Certificate::read(&parser, &root_der, 0).unwrap();
781 let leaf = Certificate::read(&parser, &leaf_der, 100).unwrap();
782 let execution = crate::fixtures::memory();
783 let verifier = Answer {
784 result: Verified::Yes,
785 calls: Cell::new(0),
786 };
787 let context = ValidationContext {
788 execution: &execution,
789 interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
790 policy: Policy::default(),
791 bounds: ValidationBounds {
792 work: 0,
793 ..ValidationBounds::default()
794 },
795 verifier: &verifier,
796 watch: &(),
797 recognizer: &(),
798 };
799 assert!(matches!(
800 crate::path::validate(
801 &[&leaf],
802 Anchor::new(root.tbs().subject(), root.tbs().public_key()),
803 &context
804 )
805 .unwrap(),
806 Validation::Incomplete(Truncation::Work)
807 ));
808 assert_eq!(verifier.calls.get(), 0);
809 assert_eq!(execution.used(), 0);
810 assert_eq!(execution.work_used(), 0);
811 }
812}