1mod asynchronous;
4#[cfg(test)]
5mod policy_tests;
6
7use super::profile::{
8 self, MappedProfileCheckError, ProfileCheckError, ProfileFailure, ProfileInput,
9};
10use super::{
11 AuthorityFacts, ComputedInput, ImprintAgreement, RawInput, RecognitionError, Request,
12 RequestAgreement, RequestFields, RequestWindow, RetainedFacts, SignatureError, Timeliness,
13 Token, TokenExtension, TokenSignature, TsaIdentity, TsaUsage,
14};
15use crate::{
16 Error, ErrorCode,
17 content_source::{ContentError, SourceMap},
18 directory, ess,
19 provider::{SignatureAnswer, SignatureProvider},
20 stream::Source,
21 verify,
22};
23use core::convert::Infallible;
24use structured_asn1::{
25 Reader,
26 memory::{Execution, OwnedVec, Work},
27 value::OwnedOid,
28};
29use structured_pkix::{
30 Certificate,
31 path::WorkingKey,
32 temporal::{Instant, LeapTable},
33};
34
35pub struct Requested<'a, 'r> {
37 pub request: &'r Request<'a>,
39 pub parent: &'r Reader<'a>,
41}
42
43pub struct CheckContext<'a, 'r> {
45 pub certificate: &'r Certificate<'a>,
47 pub certificate_parent: &'r Reader<'a>,
49 pub key: WorkingKey<'a>,
51 pub requested: Option<Requested<'a, 'r>>,
53 pub utc: &'r LeapTable<'a>,
55 pub reference: Option<Instant>,
57 pub window: Option<RequestWindow>,
59 pub policy_accuracy: Option<super::PolicyAccuracy<'r>>,
62}
63
64#[derive(Debug)]
66pub enum InfoCheckError<SE, PE, RE> {
67 PolicyAccuracy {
70 span: structured_asn1::Span,
72 },
73 Nonconforming {
75 span: structured_asn1::Span,
77 },
78 Signature(SignatureError<PE>),
80 Ess(ess::BindingError<PE>),
82 Processing(Error),
84 Provenance(Error),
87 Imprint(super::ImprintError<SE, PE>),
89 RequestImprint(super::RequestImprintError<PE>),
91 RequestField(super::RequestFieldError),
93 Authority(super::AuthorityError),
95 Recognition(RecognitionError<RE>),
97}
98
99#[derive(Debug)]
101pub enum CheckError<SE, PE, RE> {
102 Nonconforming {
104 span: structured_asn1::Span,
106 },
107 Processing(Error),
109 KeyMismatch {
111 certificate: structured_asn1::Span,
113 },
114 Identifier(directory::Error),
116 RequestField(super::RequestFieldError),
118 Information(ContentError<super::InfoError<InfoCheckError<SE, PE, RE>>>),
120}
121
122#[derive(Debug)]
125pub enum MappedCheckError<SE, PE, RE> {
126 Preflight(CheckError<SE, PE, RE>),
129 Provenance(Error),
131 Execution {
133 envelope: SourceMap,
135 source: CheckError<SE, PE, RE>,
137 },
138}
139
140#[derive(Debug)]
144pub struct Evidence {
145 facts: RetainedFacts,
146 source: SourceMap,
147}
148
149#[derive(Debug)]
151pub enum EvidenceCopyError {
152 Provenance(Error),
154 Facts {
156 source: Error,
158 context: SourceMap,
160 },
161}
162impl Evidence {
163 #[expect(
167 clippy::result_large_err,
168 reason = "retain admitted source metadata with the original copy refusal; an error box would itself require unavailable capacity"
169 )]
170 pub fn try_copy_in<W: Work>(&self, work: &W) -> Result<Self, EvidenceCopyError> {
171 let context = self
172 .source
173 .try_copy_in(work)
174 .map_err(EvidenceCopyError::Provenance)?;
175 match self.facts.try_copy_in(work) {
176 Ok(facts) => Ok(Self {
177 facts,
178 source: context,
179 }),
180 Err(source) => Err(EvidenceCopyError::Facts { source, context }),
181 }
182 }
183 #[must_use]
185 pub fn facts(&self) -> &RetainedFacts {
186 &self.facts
187 }
188 #[must_use]
190 pub fn source(&self) -> &SourceMap {
191 &self.source
192 }
193}
194
195pub struct CheckedTimestamp<'a> {
197 pub signature: TokenSignature<'a>,
199 pub identifier: directory::CertificateAgreement,
201 pub ess: ess::SigningAttributes<'a>,
203 pub bindings: ess::Bindings,
205 pub authority: AuthorityFacts,
207 pub imprint: ImprintAgreement,
209 pub requested_imprint: Option<RequestAgreement>,
211 pub requested_fields: Option<RequestFields>,
213 pub carried: Option<bool>,
215 pub timeliness: Timeliness,
217 pub accuracy_error: Option<super::DeviationError>,
219 pub unknown_critical: OwnedVec<OwnedOid>,
221 evidence: Option<Evidence>,
222}
223impl CheckedTimestamp<'_> {
224 pub fn take_evidence(&mut self) -> Option<Evidence> {
229 self.evidence.take()
230 }
231 pub fn into_evidence(self) -> Option<Evidence> {
233 self.evidence
234 }
235 #[must_use]
237 pub fn evidence(&self) -> Option<&Evidence> {
238 self.evidence.as_ref()
239 }
240}
241
242struct Information {
243 authority: AuthorityFacts,
244 imprint: ImprintAgreement,
245 requested_imprint: Option<RequestAgreement>,
246 requested_fields: Option<RequestFields>,
247 timeliness: Timeliness,
248 accuracy_error: Option<super::DeviationError>,
249 unknown: OwnedVec<OwnedOid>,
250 evidence: Option<Evidence>,
251}
252#[derive(Clone, Copy)]
253enum CheckPurpose {
254 Ordinary,
255 Historical,
256}
257
258impl Information {
259 fn holds(&self, purpose: CheckPurpose) -> bool {
260 self.accuracy_error.is_none()
261 && matches!(self.authority.usage, TsaUsage::TimeStampingOnly)
262 && matches!(
263 self.authority.identity,
264 TsaIdentity::Absent | TsaIdentity::Matches
265 )
266 && self.imprint == ImprintAgreement::Matches
267 && self
268 .requested_imprint
269 .is_none_or(|answer| answer == RequestAgreement::Matches)
270 && self
271 .requested_fields
272 .is_none_or(|fields| fields.nonce != Some(false) && fields.policy != Some(false))
273 && self.unknown.is_empty()
274 && match purpose {
275 CheckPurpose::Ordinary => matches!(
276 self.timeliness,
277 Timeliness::NotRequested | Timeliness::LocalWindow | Timeliness::RequestNonce
278 ),
279 CheckPurpose::Historical => self.timeliness == Timeliness::Unverified,
283 }
284 }
285}
286
287struct Inspection<'a, 'r, P, S, R> {
288 provider: &'a P,
289 context: &'r CheckContext<'a, 'r>,
290 covered: &'r mut S,
291 recognizes: &'r mut R,
292 common_holds: bool,
293 purpose: CheckPurpose,
294}
295
296impl<P, S, R> Inspection<'_, '_, P, S, R> {
297 fn inspect<'scope, PE, RE>(
298 &mut self,
299 info: &super::TokenInfo<'scope>,
300 inside: &Reader<'scope>,
301 source: &SourceMap,
302 accuracy: AccuracyFacts,
303 ) -> Result<Information, InfoCheckError<S::Error, PE, RE>>
304 where
305 P: for<'input> SignatureProvider<'input, Error = PE>,
306 S: ComputedInput<P, PE>,
307 R: FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
308 {
309 let authority = info
310 .authority(
311 self.context.certificate,
312 inside,
313 self.context.certificate_parent,
314 )
315 .map_err(InfoCheckError::Authority)?;
316 let requested_fields = self
317 .context
318 .requested
319 .as_ref()
320 .map(|request| info.matches_request_fields(request.request, inside, request.parent))
321 .transpose()
322 .map_err(InfoCheckError::RequestField)?;
323 let unknown = unrecognized(info, inside, self.recognizes)?;
324 let (imprint, requested_imprint) = match &self.context.requested {
325 Some(request) => {
326 let (imprint, agreement) = info
327 .imprint()
328 .check_requested_input(
329 request.request.imprint(),
330 self.provider,
331 (inside, request.parent),
332 self.covered,
333 )
334 .map_err(|error| match error {
335 super::RequestedContentError::Request(source) => {
336 InfoCheckError::RequestImprint(source)
337 }
338 super::RequestedContentError::Content(source) => {
339 InfoCheckError::Imprint(source)
340 }
341 })?;
342 (imprint, Some(agreement))
343 }
344 None => (
345 info.imprint()
346 .check_input(self.provider, inside, self.covered)
347 .map_err(InfoCheckError::Imprint)?,
348 None,
349 ),
350 };
351 finish_information(
352 info,
353 inside,
354 source,
355 self.context,
356 InformationFacts {
357 accuracy,
358 authority,
359 imprint,
360 requested_imprint,
361 requested_fields,
362 unknown,
363 },
364 self.common_holds,
365 self.purpose,
366 )
367 }
368}
369
370struct InformationFacts {
371 accuracy: AccuracyFacts,
372 authority: AuthorityFacts,
373 imprint: ImprintAgreement,
374 requested_imprint: Option<RequestAgreement>,
375 requested_fields: Option<RequestFields>,
376 unknown: OwnedVec<OwnedOid>,
377}
378
379struct AccuracyFacts {
380 effective: Option<super::EffectiveAccuracy>,
381 error: Option<super::DeviationError>,
382}
383
384fn resolve_accuracy<SE, PE, RE>(
385 info: &super::TokenInfo<'_>,
386 inside: &Reader<'_>,
387 policy: Option<super::PolicyAccuracy<'_>>,
388) -> Result<AccuracyFacts, InfoCheckError<SE, PE, RE>> {
389 if let Some(policy) = policy {
390 let written = info.policy().as_bytes();
391 let expected = policy.policy().as_bytes();
392 inside
393 .execution()
394 .charge_work(1 + written.len().min(expected.len()) as u64)
395 .map_err(|cause| {
396 InfoCheckError::Processing(Error::resource(
397 cause,
398 "tstInfo.policy",
399 info.span().start,
400 ))
401 })?;
402 if written != expected {
403 return Err(InfoCheckError::PolicyAccuracy { span: info.span() });
404 }
405 }
406 let (effective, error) = match info
410 .accuracy()
411 .map(super::AccuracyReading::deviation)
412 .transpose()
413 {
414 Ok(Some(value)) => (Some(super::EffectiveAccuracy::Token(value)), None),
415 Ok(None) => (
416 policy.map(|value| super::EffectiveAccuracy::CallerPolicy(value.accuracy())),
417 None,
418 ),
419 Err(error) => (None, Some(error)),
420 };
421 Ok(AccuracyFacts { effective, error })
422}
423
424fn finish_information<SE, PE, RE>(
425 info: &super::TokenInfo<'_>,
426 inside: &Reader<'_>,
427 source: &SourceMap,
428 context: &CheckContext<'_, '_>,
429 facts: InformationFacts,
430 common_holds: bool,
431 purpose: CheckPurpose,
432) -> Result<Information, InfoCheckError<SE, PE, RE>> {
433 let InformationFacts {
434 accuracy,
435 authority,
436 imprint,
437 requested_imprint,
438 requested_fields,
439 unknown,
440 } = facts;
441 let timeliness = super::timeliness(
442 info.gen_time(),
443 accuracy.effective.map(super::EffectiveAccuracy::accuracy),
444 context.utc,
445 context.reference,
446 context.requested.is_some(),
447 requested_fields.and_then(|fields| fields.nonce),
448 context.window,
449 );
450 let mut information = Information {
451 authority,
452 imprint,
453 requested_imprint,
454 requested_fields,
455 timeliness,
456 accuracy_error: accuracy.error,
457 unknown,
458 evidence: None,
459 };
460 if common_holds && !info.has_faults() && information.holds(purpose) {
461 information.evidence = Some(Evidence {
462 facts: info
463 .retain_facts(inside)
464 .map_err(InfoCheckError::Processing)?
465 .with_effective_accuracy(accuracy.effective),
466 source: source
467 .try_copy_in(&inside.execution())
468 .map_err(InfoCheckError::Provenance)?,
469 });
470 }
471 Ok(information)
472}
473
474fn unrecognized<SE, PE, RE>(
475 info: &super::TokenInfo<'_>,
476 inside: &Reader<'_>,
477 recognizes: &mut impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
478) -> Result<OwnedVec<OwnedOid>, InfoCheckError<SE, PE, RE>> {
479 let scoped = inside.execution();
480 let mut unknown = scoped.vector();
481 if let Some(extensions) = info.extensions() {
482 for kind in extensions
483 .unrecognized_critical(inside, recognizes)
484 .map_err(InfoCheckError::Recognition)?
485 {
486 let owned = OwnedOid::from_oid_in(kind, &scoped).map_err(|reason| {
487 InfoCheckError::Processing(Error::resource(
488 reason,
489 "tstInfo.extensions",
490 extensions.span().start,
491 ))
492 })?;
493 unknown.try_push(owned).map_err(|reason| {
494 InfoCheckError::Processing(Error::resource(
495 reason,
496 "tstInfo.extensions",
497 extensions.span().start,
498 ))
499 })?;
500 }
501 }
502 Ok(unknown.into_owned())
503}
504
505fn common_holds(
506 signature: &TokenSignature<'_>,
507 bindings: ess::Bindings,
508 identifier: &directory::CertificateAgreement,
509 carried: Option<bool>,
510) -> bool {
511 matches!(signature, TokenSignature::Checked(checked)
512 if checked.signature() == Some(SignatureAnswer::Valid)
513 && matches!(checked.content(), verify::ContentAgreement::Matches | verify::ContentAgreement::Direct))
514 && (bindings.v1 == Some(ess::Binding::Matches)
515 || bindings.v2 == Some(ess::Binding::Matches))
516 && bindings
517 .v1
518 .is_none_or(|binding| binding == ess::Binding::Matches)
519 && bindings
520 .v2
521 .is_none_or(|binding| binding == ess::Binding::Matches)
522 && matches!(identifier, directory::CertificateAgreement::Matches)
523 && carried != Some(false)
524}
525
526impl<'doc> Token<'doc> {
527 #[expect(
537 clippy::result_large_err,
538 reason = "preserve original cause and accounted source context without allocating on refusal"
539 )]
540 pub fn check<'a, P, S: Source, PE, RE>(
541 &'a self,
542 provider: &'a P,
543 parent: &Reader<'a>,
544 context: &CheckContext<'a, '_>,
545 covered: &mut S,
546 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
547 ) -> Result<CheckedTimestamp<'a>, CheckError<S::Error, PE, RE>>
548 where
549 'doc: 'a,
550 P: for<'scope> SignatureProvider<'scope, Error = PE>,
551 {
552 self.check_with_profile(provider, parent, context, covered, recognizes, |_| {
553 Ok::<(), ProfileFailure<Infallible>>(())
554 })
555 .map_err(ProfileCheckError::into_common)
556 }
557
558 #[expect(
566 clippy::result_large_err,
567 reason = "keep opaque common/profile refusal and its source context without allocating while returning an error"
568 )]
569 pub fn check_with_profile<'a, P, S: Source, PE, RE, FE>(
570 &'a self,
571 provider: &'a P,
572 parent: &Reader<'a>,
573 context: &CheckContext<'a, '_>,
574 covered: &mut S,
575 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
576 profile: impl for<'view, 'scope> FnOnce(
577 ProfileInput<'view, 'doc, 'a, 'scope>,
578 ) -> Result<(), ProfileFailure<FE>>,
579 ) -> Result<CheckedTimestamp<'a>, ProfileCheckError<S::Error, PE, RE, FE>>
580 where
581 'doc: 'a,
582 P: for<'scope> SignatureProvider<'scope, Error = PE>,
583 {
584 self.check_input_with_profile(
585 provider,
586 parent,
587 context,
588 &mut RawInput::new(covered),
589 recognizes,
590 profile,
591 )
592 }
593
594 #[expect(
599 clippy::result_large_err,
600 reason = "retain opaque input/provider/profile failures and original source maps"
601 )]
602 pub fn check_input_with_profile<'a, P, I, PE, RE, FE>(
603 &'a self,
604 provider: &'a P,
605 parent: &Reader<'a>,
606 context: &CheckContext<'a, '_>,
607 covered: &mut I,
608 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
609 profile: impl for<'view, 'scope> FnOnce(
610 ProfileInput<'view, 'doc, 'a, 'scope>,
611 ) -> Result<(), ProfileFailure<FE>>,
612 ) -> Result<CheckedTimestamp<'a>, ProfileCheckError<I::Error, PE, RE, FE>>
613 where
614 'doc: 'a,
615 P: for<'scope> SignatureProvider<'scope, Error = PE>,
616 I: ComputedInput<P, PE>,
617 {
618 self.check_in(
619 provider,
620 (parent, None),
621 (context, CheckPurpose::Ordinary),
622 covered,
623 recognizes,
624 profile,
625 )
626 }
627
628 #[expect(
635 clippy::result_large_err,
636 reason = "historical checking preserves the same original mapped input and profile causes as the complete token check, without allocating on refusal"
637 )]
638 pub fn check_historical_input_with_profile<'a, P, I, PE, RE, FE>(
639 &'a self,
640 provider: &'a P,
641 parent: &Reader<'a>,
642 context: &CheckContext<'a, '_>,
643 covered: &mut I,
644 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
645 profile: impl for<'view, 'scope> FnOnce(
646 ProfileInput<'view, 'doc, 'a, 'scope>,
647 ) -> Result<(), ProfileFailure<FE>>,
648 ) -> Result<CheckedTimestamp<'a>, ProfileCheckError<I::Error, PE, RE, FE>>
649 where
650 'doc: 'a,
651 P: for<'scope> SignatureProvider<'scope, Error = PE>,
652 I: ComputedInput<P, PE>,
653 {
654 if context.requested.is_some() || context.reference.is_some() || context.window.is_some() {
655 return Err(CheckError::Processing(Error::schema(
656 ErrorCode::InvalidLocation,
657 "tstInfo.historical",
658 self.message().span().start,
659 ))
660 .into());
661 }
662 self.check_in(
663 provider,
664 (parent, None),
665 (context, CheckPurpose::Historical),
666 covered,
667 recognizes,
668 profile,
669 )
670 }
671
672 #[expect(
677 clippy::result_large_err,
678 reason = "preserve both source frames and original opaque refusal without a fallible error box"
679 )]
680 pub fn check_mapped<'a, P, S: Source, PE, RE>(
681 &'a self,
682 provider: &'a P,
683 source: (&Reader<'a>, &SourceMap),
684 context: &CheckContext<'a, '_>,
685 covered: &mut S,
686 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
687 ) -> Result<CheckedTimestamp<'a>, MappedCheckError<S::Error, PE, RE>>
688 where
689 'doc: 'a,
690 P: for<'scope> SignatureProvider<'scope, Error = PE>,
691 {
692 self.check_mapped_with_profile(provider, source, context, covered, recognizes, |_| {
693 Ok::<(), ProfileFailure<Infallible>>(())
694 })
695 .map_err(MappedProfileCheckError::into_common)
696 }
697
698 #[expect(
703 clippy::result_large_err,
704 reason = "preserve both independently retained source frames and the original opaque refusal without a fallible error allocation"
705 )]
706 pub fn check_mapped_with_profile<'a, P, S: Source, PE, RE, FE>(
707 &'a self,
708 provider: &'a P,
709 source: (&Reader<'a>, &SourceMap),
710 context: &CheckContext<'a, '_>,
711 covered: &mut S,
712 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
713 profile: impl for<'view, 'scope> FnOnce(
714 ProfileInput<'view, 'doc, 'a, 'scope>,
715 ) -> Result<(), ProfileFailure<FE>>,
716 ) -> Result<CheckedTimestamp<'a>, MappedProfileCheckError<S::Error, PE, RE, FE>>
717 where
718 'doc: 'a,
719 P: for<'scope> SignatureProvider<'scope, Error = PE>,
720 {
721 self.check_mapped_input_with_profile(
722 provider,
723 source,
724 context,
725 &mut RawInput::new(covered),
726 recognizes,
727 profile,
728 )
729 }
730
731 #[expect(
734 clippy::result_large_err,
735 reason = "preserve both source frames and opaque refusal without allocating on failure"
736 )]
737 pub fn check_mapped_input_with_profile<'a, P, I, PE, RE, FE>(
738 &'a self,
739 provider: &'a P,
740 source: (&Reader<'a>, &SourceMap),
741 context: &CheckContext<'a, '_>,
742 covered: &mut I,
743 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
744 profile: impl for<'view, 'scope> FnOnce(
745 ProfileInput<'view, 'doc, 'a, 'scope>,
746 ) -> Result<(), ProfileFailure<FE>>,
747 ) -> Result<CheckedTimestamp<'a>, MappedProfileCheckError<I::Error, PE, RE, FE>>
748 where
749 'doc: 'a,
750 P: for<'scope> SignatureProvider<'scope, Error = PE>,
751 I: ComputedInput<P, PE>,
752 {
753 let (parent, source) = source;
754 preflight_sessions(self, parent, context)
755 .map_err(|error| MappedProfileCheckError::Preflight(error.into()))?;
756 let envelope = source
757 .try_copy_in(&parent.execution())
758 .map_err(MappedProfileCheckError::Provenance)?;
759 self.check_in(
760 provider,
761 (parent, Some(source)),
762 (context, CheckPurpose::Ordinary),
763 covered,
764 recognizes,
765 profile,
766 )
767 .map_err(|source| MappedProfileCheckError::Execution { envelope, source })
768 }
769
770 #[expect(
771 clippy::result_large_err,
772 reason = "retain original cause and accounted source context on refusal"
773 )]
774 fn check_in<'a, P, S, PE, RE, FE>(
775 &'a self,
776 provider: &'a P,
777 origin: (&Reader<'a>, Option<&SourceMap>),
778 context: (&CheckContext<'a, '_>, CheckPurpose),
779 covered: &mut S,
780 mut recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
781 profile: impl for<'view, 'scope> FnOnce(
782 ProfileInput<'view, 'doc, 'a, 'scope>,
783 ) -> Result<(), ProfileFailure<FE>>,
784 ) -> Result<CheckedTimestamp<'a>, ProfileCheckError<S::Error, PE, RE, FE>>
785 where
786 'doc: 'a,
787 P: for<'scope> SignatureProvider<'scope, Error = PE>,
788 S: ComputedInput<P, PE>,
789 {
790 let (parent, source) = origin;
791 let (context, purpose) = context;
792 preflight(self, parent, context).map_err(CheckError::from)?;
793 let certificate = context.certificate;
794 let identifier =
795 directory::matches_certificate_in(self.signer().sid(), certificate, parent)
796 .map_err(CheckError::Identifier)?;
797 let ess =
798 ess::SigningAttributes::read(self.signer(), parent).map_err(CheckError::Processing)?;
799 let carried = context
800 .requested
801 .as_ref()
802 .map(|requested| {
803 self.matches_cert_req(
804 requested.request,
805 Some(certificate),
806 parent,
807 requested.parent,
808 )
809 })
810 .transpose()
811 .map_err(CheckError::RequestField)?;
812 if !self.faults().is_empty()
813 || !self.message().faults().is_empty()
814 || !self.signer().faults().is_empty()
815 || !ess.faults().is_empty()
816 || ess.v1().is_some_and(|reading| !reading.faults().is_empty())
817 || ess.v2().is_some_and(|reading| !reading.faults().is_empty())
818 {
819 return Err(CheckError::Nonconforming {
820 span: self.message().span(),
821 }
822 .into());
823 }
824 let inspect = |info: &super::TokenInfo<'_>,
825 inside: &Reader<'_>,
826 information: &SourceMap|
827 -> Result<_, profile::InformationFailure<S::Error, PE, RE, FE>> {
828 if info.has_faults() {
829 return Err(InfoCheckError::Nonconforming { span: info.span() }.into());
830 }
831 let accuracy =
832 resolve_accuracy::<S::Error, PE, RE>(info, inside, context.policy_accuracy)?;
833 profile(ProfileInput {
834 token: self,
835 parent,
836 envelope: source,
837 ess: &ess,
838 info,
839 reader: inside,
840 source: information,
841 })
842 .map_err(profile::InformationFailure::Profile)?;
843 let bindings = ess
844 .bind_certificate(provider, certificate, parent)
845 .map_err(InfoCheckError::Ess)?;
846 let signature = self
847 .check_signature(provider, context.key, parent)
848 .map_err(InfoCheckError::Signature)?;
849 let common_holds = common_holds(&signature, bindings, &identifier, carried);
850 let mut inspection = Inspection {
851 provider,
852 context,
853 covered,
854 recognizes: &mut recognizes,
855 common_holds,
856 purpose,
857 };
858 let checked = inspection.inspect(info, inside, information, accuracy)?;
859 Ok((signature, bindings, checked))
860 };
861 let (signature, bindings, information) = match source {
862 Some(source) => self.with_info_mapped(parent, source, inspect),
863 None => self.with_info(parent, inspect),
864 }
865 .map_err(profile::information_error)?;
866 Ok(CheckedTimestamp {
867 signature,
868 identifier,
869 ess,
870 bindings,
871 authority: information.authority,
872 imprint: information.imprint,
873 requested_imprint: information.requested_imprint,
874 requested_fields: information.requested_fields,
875 carried,
876 timeliness: information.timeliness,
877 accuracy_error: information.accuracy_error,
878 unknown_critical: information.unknown,
879 evidence: information.evidence,
880 })
881 }
882}
883
884enum PreflightError {
885 Processing(Error),
886 Requested(super::RequestFieldError),
887 KeyMismatch(structured_asn1::Span),
888}
889
890impl<SE, PE, RE> From<PreflightError> for CheckError<SE, PE, RE> {
891 fn from(error: PreflightError) -> Self {
892 match error {
893 PreflightError::Processing(error) => Self::Processing(error),
894 PreflightError::Requested(error) => Self::RequestField(error),
895 PreflightError::KeyMismatch(certificate) => Self::KeyMismatch { certificate },
896 }
897 }
898}
899
900fn preflight(
901 token: &Token<'_>,
902 parent: &Reader<'_>,
903 context: &CheckContext<'_, '_>,
904) -> Result<(), PreflightError> {
905 preflight_sessions(token, parent, context)?;
906 let certificate = context.certificate;
907 let written = certificate.tbs().public_key().as_bytes();
908 let key = context.key.key().as_bytes();
909 let length = u64::try_from(written.len().max(key.len()))
910 .ok()
911 .and_then(|length| length.checked_add(1))
912 .ok_or_else(|| {
913 PreflightError::Processing(Error::schema(
914 ErrorCode::LengthOverflow,
915 "certificate.subjectPublicKeyInfo",
916 certificate.span().start,
917 ))
918 })?;
919 parent.execution().charge_work(length).map_err(|reason| {
920 PreflightError::Processing(Error::resource(
921 reason,
922 "certificate.subjectPublicKeyInfo",
923 certificate.span().start,
924 ))
925 })?;
926 if written != key {
927 return Err(PreflightError::KeyMismatch(certificate.span()));
928 }
929 Ok(())
930}
931
932fn preflight_sessions(
933 token: &Token<'_>,
934 parent: &Reader<'_>,
935 context: &CheckContext<'_, '_>,
936) -> Result<(), PreflightError> {
937 token
938 .signer()
939 .require_session(parent, "signerInfo")
940 .map_err(PreflightError::Processing)?;
941 let certificate = context.certificate;
942 if !certificate
943 .tbs()
944 .public_key()
945 .algorithm()
946 .same_session(context.certificate_parent)
947 {
948 return Err(PreflightError::Processing(Error::pkix(
949 structured_pkix::Error::new(
950 structured_pkix::ErrorCode::ForeignReader,
951 "certificate",
952 certificate.span().start,
953 ),
954 )));
955 }
956 if let Some(requested) = &context.requested
957 && !requested.request.same_session(requested.parent)
958 {
959 return Err(PreflightError::Requested(super::RequestFieldError {
960 operand: super::ImprintOperand::Requested,
961 source: Error::pkix(structured_pkix::Error::new(
962 structured_pkix::ErrorCode::ForeignReader,
963 "timeStampReq",
964 requested.request.span().start,
965 )),
966 }));
967 }
968 Ok(())
969}