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