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)]
21pub struct EffectiveAnchor<'a> {
22 anchor: Anchor<'a>,
23 key: WorkingKey<'a>,
24}
25
26impl<'a> EffectiveAnchor<'a> {
27 pub fn bind(
31 anchor: Anchor<'a>,
32 key: WorkingKey<'a>,
33 execution: &Memory,
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, W: Watch, R: Recognizer> {
224 pub execution: &'p Memory,
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> {
241 pub(super) execution: &'p Memory,
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>: 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>,
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(
335 item: &Extension<'_>,
336 execution: &Memory,
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, M: Meter>(
370 certificate: &'a Certificate<'a>,
371 settings: &Settings<'a, '_, R>,
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, D: Driver<'a, R>>(
479 input: Chain<'a, '_>,
480 anchor: Anchor<'a>,
481 settings: &Settings<'a, '_, R>,
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, W: Watch, R: Recognizer> {
488 context: &'p ValidationContext<'a, 'p, V, W, R>,
489 origin: &'a Certificate<'a>,
490 spent: u64,
491 halted: Option<Truncation>,
492}
493
494impl<V: Verify, W: Watch, R: Recognizer> Supplied<'_, '_, V, W, R> {
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, W: Watch, R: Recognizer> Meter for Supplied<'_, '_, V, W, R> {
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, W: Watch, R: Recognizer> Driver<'a, R> for Supplied<'a, '_, V, W, R> {
522 type VerificationError = V::Error;
523 fn verified(
524 &mut self,
525 certificate: &'a Certificate<'a>,
526 key: WorkingKey<'a>,
527 ) -> BuildResult<Option<Verified>, V::Error, R::Error> {
528 if !self.spend(cost::SIGNATURE)? {
529 return Ok(None);
530 }
531 self.context
532 .verifier
533 .verify(
534 &certificate.signature_algorithm(),
535 key,
536 certificate.signed_bytes(),
537 certificate.signature(),
538 self.context.execution,
539 )
540 .map(Some)
541 .map_err(BuildError::Verification)
542 }
543 fn facts(
544 &mut self,
545 certificate: &'a Certificate<'a>,
546 settings: &Settings<'a, '_, R>,
547 ) -> BuildResult<Option<CertificateFacts<'a>>, V::Error, R::Error> {
548 certificate_facts(certificate, settings, self)
549 }
550}
551
552pub fn validate<'a, V: Verify, W: Watch, R: Recognizer>(
585 chain: &[&'a Certificate<'a>],
586 anchor: Anchor<'a>,
587 context: &ValidationContext<'a, '_, V, W, R>,
588) -> BuildResult<Validation<'a>, V::Error, R::Error> {
589 validate_effective(chain, EffectiveAnchor::standing(anchor), context)
590}
591
592pub fn validate_effective<'a, V: Verify, W: Watch, R: Recognizer>(
598 chain: &[&'a Certificate<'a>],
599 effective: EffectiveAnchor<'a>,
600 context: &ValidationContext<'a, '_, V, W, R>,
601) -> BuildResult<Validation<'a>, V::Error, R::Error> {
602 let anchor = effective.anchor;
603 let Some(target) = chain.first() else {
604 return Ok(Validation::Rejected(Rejection {
605 at: None,
606 reason: RejectionReason::EmptyPath,
607 }));
608 };
609 if chain.len() > context.bounds.depth {
610 return Ok(Validation::Incomplete(Truncation::Depth));
611 }
612 let length = u32::try_from(chain.len()).map_err(|_| {
613 target
614 .tbs()
615 .subject()
616 .resource(MemoryError::CapacityOverflow)
617 })?;
618 length.checked_add(1).ok_or_else(|| {
619 target
620 .tbs()
621 .subject()
622 .resource(MemoryError::CapacityOverflow)
623 })?;
624 let mut driver = Supplied {
625 context,
626 origin: target,
627 spent: 0,
628 halted: None,
629 };
630 let mut seen = context.execution.map();
631 for (index, certificate) in chain.iter().enumerate() {
632 if let Some(halted) =
633 driver.admit(cost::ENTRY + certificate.as_bytes().len() as u64 / cost::HASHED)?
634 {
635 return Ok(Validation::Incomplete(halted));
636 }
637 if let Some(earlier) = seen
638 .try_insert(certificate.as_bytes(), index)
639 .map_err(|reason| certificate.tbs().subject().resource(reason))?
640 {
641 return Ok(rejection(
642 ConstraintSource::Certificate(index),
643 RejectionReason::DuplicateCertificate { earlier },
644 ));
645 }
646 if let Some(published) = anchor.certificate() {
647 let bytes = certificate.as_bytes().len().min(published.as_bytes().len()) as u64;
648 if let Some(halted) = driver.admit(bytes / cost::COMPARED)? {
649 return Ok(Validation::Incomplete(halted));
650 }
651 context
652 .execution
653 .charge_work(bytes)
654 .map_err(|reason| certificate.tbs().subject().resource(reason))?;
655 if super::same(certificate, published) {
656 return Ok(rejection(
657 ConstraintSource::Certificate(index),
658 RejectionReason::AnchorIncluded,
659 ));
660 }
661 }
662 }
663 drop(seen);
664 let settings = Settings {
665 execution: context.execution,
666 interval: context.interval,
667 policy: context.policy,
668 policies: context.bounds.policies,
669 recognizer: context.recognizer,
670 };
671 let outcome = processing::process(Chain::Supplied(chain), &effective, &settings, &mut driver)?;
672 if let Some(halted) = driver.halted {
673 return Ok(Validation::Incomplete(halted));
674 }
675 Ok(outcome)
676}
677
678#[cfg(test)]
679mod tests {
680 use super::*;
681 use crate::fixtures::{CertificateParts, oid as encoded_oid, printable, seq, set};
682 use core::cell::Cell;
683 use structured_asn1::{BitString, Parser};
684
685 struct Answer {
686 result: Verified,
687 calls: Cell<usize>,
688 }
689 impl Verify for Answer {
690 type Error = core::convert::Infallible;
691 fn verify(
692 &self,
693 _algorithm: &crate::AlgorithmIdentifier<'_>,
694 _key: WorkingKey<'_>,
695 _signed: &[u8],
696 _signature: BitString<'_>,
697 _execution: &Memory,
698 ) -> core::result::Result<Verified, Self::Error> {
699 self.calls.set(self.calls.get() + 1);
700 Ok(self.result)
701 }
702 }
703
704 fn target() -> alloc::vec::Vec<u8> {
705 CertificateParts {
706 subject: seq(&[set(&[seq(&[
707 encoded_oid(oid::COMMON_NAME),
708 printable("target"),
709 ])])]),
710 ..CertificateParts::default()
711 }
712 .build()
713 }
714
715 fn root() -> alloc::vec::Vec<u8> {
716 let parts = CertificateParts::default();
717 CertificateParts {
718 subject: parts.issuer.clone(),
719 ..parts
720 }
721 .build()
722 }
723
724 #[test]
725 fn supplied_path_separates_success_no_and_unsupported() {
726 let root_der = root();
728 let leaf_der = target();
729 let parser = Parser::der(crate::fixtures::memory());
730 let root = Certificate::read(&parser, &root_der, 0).unwrap();
731 let leaf = Certificate::read(&parser, &leaf_der, 100).unwrap();
732 let anchor = Anchor::new(root.tbs().subject(), root.tbs().public_key());
733 for result in [Verified::Yes, Verified::No, Verified::Unsupported] {
734 let execution = crate::fixtures::memory();
735 let verifier = Answer {
736 result,
737 calls: Cell::new(0),
738 };
739 let context = ValidationContext {
740 execution: &execution,
741 interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
742 policy: Policy::default(),
743 bounds: ValidationBounds::default(),
744 verifier: &verifier,
745 watch: &(),
746 recognizer: &(),
747 };
748 let outcome = crate::path::validate(&[&leaf], anchor, &context).unwrap();
749 match (result, outcome) {
750 (Verified::Yes, Validation::Accepted(facts)) => {
751 assert_eq!(
752 facts.key.key().as_bytes(),
753 leaf.tbs().public_key().as_bytes()
754 );
755 assert_eq!(facts.policies(), []);
756 }
757 (Verified::No, Validation::Rejected(rejection)) => {
758 assert_eq!(rejection.at, Some(ConstraintSource::Certificate(0)));
759 assert_eq!(rejection.reason, RejectionReason::Signature);
760 }
761 (Verified::Unsupported, Validation::Rejected(rejection)) => {
762 assert_eq!(rejection.at, Some(ConstraintSource::Certificate(0)));
763 assert_eq!(rejection.reason, RejectionReason::UnsupportedSignature);
764 }
765 other => panic!("unexpected public path result: {other:?}"),
766 }
767 assert_eq!(verifier.calls.get(), 1);
768 assert_eq!(execution.used(), 0);
769 }
770 }
771
772 #[test]
773 fn supplied_local_work_refusal_never_calls_the_provider() {
774 let root_der = root();
776 let leaf_der = target();
777 let parser = Parser::der(crate::fixtures::memory());
778 let root = Certificate::read(&parser, &root_der, 0).unwrap();
779 let leaf = Certificate::read(&parser, &leaf_der, 100).unwrap();
780 let execution = crate::fixtures::memory();
781 let verifier = Answer {
782 result: Verified::Yes,
783 calls: Cell::new(0),
784 };
785 let context = ValidationContext {
786 execution: &execution,
787 interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
788 policy: Policy::default(),
789 bounds: ValidationBounds {
790 work: 0,
791 ..ValidationBounds::default()
792 },
793 verifier: &verifier,
794 watch: &(),
795 recognizer: &(),
796 };
797 assert!(matches!(
798 crate::path::validate(
799 &[&leaf],
800 Anchor::new(root.tbs().subject(), root.tbs().public_key()),
801 &context
802 )
803 .unwrap(),
804 Validation::Incomplete(Truncation::Work)
805 ));
806 assert_eq!(verifier.calls.get(), 0);
807 assert_eq!(execution.used(), 0);
808 assert_eq!(execution.work_used(), 0);
809 }
810}