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