1use super::{
4 AuthorityFacts, ImprintAgreement, RecognitionError, Request, RequestAgreement, RequestFields,
5 RequestWindow, RetainedFacts, SignatureError, Timeliness, Token, TokenExtension,
6 TokenSignature, TsaIdentity, TsaUsage,
7};
8use crate::{
9 Error, ErrorCode,
10 content_source::{ContentError, SourceMap},
11 directory, ess,
12 provider::{SignatureAnswer, SignatureProvider},
13 stream::Source,
14 verify,
15};
16use structured_asn1::{
17 Reader,
18 memory::{Execution, OwnedVec, Work},
19 value::OwnedOid,
20};
21use structured_pkix::{
22 Certificate,
23 path::WorkingKey,
24 temporal::{Instant, LeapTable},
25};
26
27pub struct Requested<'a, 'r> {
29 pub request: &'r Request<'a>,
31 pub parent: &'r Reader<'a>,
33}
34
35pub struct CheckContext<'a, 'r> {
37 pub certificate: &'r Certificate<'a>,
39 pub certificate_parent: &'r Reader<'a>,
41 pub key: WorkingKey<'a>,
43 pub requested: Option<Requested<'a, 'r>>,
45 pub utc: &'r LeapTable<'a>,
47 pub reference: Option<Instant>,
49 pub window: Option<RequestWindow>,
51}
52
53#[derive(Debug)]
55pub enum InfoCheckError<SE, PE, RE> {
56 Nonconforming {
58 span: structured_asn1::Span,
60 },
61 Signature(SignatureError<PE>),
63 Ess(ess::BindingError<PE>),
65 Processing(Error),
67 Provenance(Error),
70 Imprint(super::ImprintError<SE, PE>),
72 RequestImprint(super::RequestImprintError<PE>),
74 RequestField(super::RequestFieldError),
76 Authority(super::AuthorityError),
78 Recognition(RecognitionError<RE>),
80}
81
82#[derive(Debug)]
84pub enum CheckError<SE, PE, RE> {
85 Nonconforming {
87 span: structured_asn1::Span,
89 },
90 Processing(Error),
92 KeyMismatch {
94 certificate: structured_asn1::Span,
96 },
97 Identifier(directory::Error),
99 RequestField(super::RequestFieldError),
101 Information(ContentError<super::InfoError<InfoCheckError<SE, PE, RE>>>),
103}
104
105#[derive(Debug)]
108pub enum MappedCheckError<SE, PE, RE> {
109 Preflight(CheckError<SE, PE, RE>),
112 Provenance(Error),
114 Execution {
116 envelope: SourceMap,
118 source: CheckError<SE, PE, RE>,
120 },
121}
122
123#[derive(Debug)]
127pub struct Evidence {
128 facts: RetainedFacts,
129 source: SourceMap,
130}
131
132#[derive(Debug)]
134pub enum EvidenceCopyError {
135 Provenance(Error),
137 Facts {
139 source: Error,
141 context: SourceMap,
143 },
144}
145impl Evidence {
146 #[expect(
150 clippy::result_large_err,
151 reason = "retain admitted source metadata with the original copy refusal; an error box would itself require unavailable capacity"
152 )]
153 pub fn try_copy_in<W: Work>(&self, work: &W) -> Result<Self, EvidenceCopyError> {
154 let context = self
155 .source
156 .try_copy_in(work)
157 .map_err(EvidenceCopyError::Provenance)?;
158 match self.facts.try_copy_in(work) {
159 Ok(facts) => Ok(Self {
160 facts,
161 source: context,
162 }),
163 Err(source) => Err(EvidenceCopyError::Facts { source, context }),
164 }
165 }
166 #[must_use]
168 pub fn facts(&self) -> &RetainedFacts {
169 &self.facts
170 }
171 #[must_use]
173 pub fn source(&self) -> &SourceMap {
174 &self.source
175 }
176}
177
178pub struct CheckedTimestamp<'a> {
180 pub signature: TokenSignature<'a>,
182 pub identifier: directory::CertificateAgreement,
184 pub ess: ess::SigningAttributes<'a>,
186 pub bindings: ess::Bindings,
188 pub authority: AuthorityFacts,
190 pub imprint: ImprintAgreement,
192 pub requested_imprint: Option<RequestAgreement>,
194 pub requested_fields: Option<RequestFields>,
196 pub carried: Option<bool>,
198 pub timeliness: Timeliness,
200 pub accuracy_error: Option<super::DeviationError>,
202 pub unknown_critical: OwnedVec<OwnedOid>,
204 evidence: Option<Evidence>,
205}
206impl CheckedTimestamp<'_> {
207 pub fn into_evidence(self) -> Option<Evidence> {
209 self.evidence
210 }
211 #[must_use]
213 pub fn evidence(&self) -> Option<&Evidence> {
214 self.evidence.as_ref()
215 }
216}
217
218struct Information {
219 authority: AuthorityFacts,
220 imprint: ImprintAgreement,
221 requested_imprint: Option<RequestAgreement>,
222 requested_fields: Option<RequestFields>,
223 timeliness: Timeliness,
224 accuracy_error: Option<super::DeviationError>,
225 unknown: OwnedVec<OwnedOid>,
226 evidence: Option<Evidence>,
227}
228
229impl Information {
230 fn holds(&self) -> bool {
231 self.accuracy_error.is_none()
232 && matches!(self.authority.usage, TsaUsage::TimeStampingOnly)
233 && matches!(
234 self.authority.identity,
235 TsaIdentity::Absent | TsaIdentity::Matches
236 )
237 && self.imprint == ImprintAgreement::Matches
238 && self
239 .requested_imprint
240 .is_none_or(|answer| answer == RequestAgreement::Matches)
241 && self
242 .requested_fields
243 .is_none_or(|fields| fields.nonce != Some(false) && fields.policy != Some(false))
244 && self.unknown.is_empty()
245 && matches!(
246 self.timeliness,
247 Timeliness::NotRequested | Timeliness::LocalWindow | Timeliness::RequestNonce
248 )
249 }
250}
251
252struct Inspection<'a, 'r, P, S, R> {
253 provider: &'a P,
254 context: &'r CheckContext<'a, 'r>,
255 covered: &'r mut S,
256 recognizes: &'r mut R,
257 common_holds: bool,
258}
259
260impl<P, S: Source, R> Inspection<'_, '_, P, S, R> {
261 fn inspect<'scope, PE, RE>(
262 &mut self,
263 info: &super::TokenInfo<'scope>,
264 inside: &Reader<'scope>,
265 source: &SourceMap,
266 ) -> Result<Information, InfoCheckError<S::Error, PE, RE>>
267 where
268 P: for<'input> SignatureProvider<'input, Error = PE>,
269 R: FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
270 {
271 let authority = info
272 .authority(
273 self.context.certificate,
274 inside,
275 self.context.certificate_parent,
276 )
277 .map_err(InfoCheckError::Authority)?;
278 let requested_fields = self
279 .context
280 .requested
281 .as_ref()
282 .map(|request| info.matches_request_fields(request.request, inside, request.parent))
283 .transpose()
284 .map_err(InfoCheckError::RequestField)?;
285 let unknown = unrecognized(info, inside, self.recognizes)?;
286 let (imprint, requested_imprint) = match &self.context.requested {
287 Some(request) => {
288 let (imprint, agreement) = info
289 .imprint()
290 .check_requested_content(
291 request.request.imprint(),
292 self.provider,
293 (inside, request.parent),
294 self.covered,
295 )
296 .map_err(|error| match error {
297 super::RequestedContentError::Request(source) => {
298 InfoCheckError::RequestImprint(source)
299 }
300 super::RequestedContentError::Content(source) => {
301 InfoCheckError::Imprint(source)
302 }
303 })?;
304 (imprint, Some(agreement))
305 }
306 None => (
307 info.imprint()
308 .check_content(self.provider, inside, self.covered)
309 .map_err(InfoCheckError::Imprint)?,
310 None,
311 ),
312 };
313 let (accuracy, accuracy_error) = match info
314 .accuracy()
315 .map(super::AccuracyReading::deviation)
316 .transpose()
317 {
318 Ok(accuracy) => (accuracy, None),
319 Err(error) => (None, Some(error)),
320 };
321 let timeliness = super::timeliness(
322 info.gen_time(),
323 accuracy,
324 self.context.utc,
325 self.context.reference,
326 self.context.requested.is_some(),
327 requested_fields.and_then(|fields| fields.nonce),
328 self.context.window,
329 );
330 let mut information = Information {
331 authority,
332 imprint,
333 requested_imprint,
334 requested_fields,
335 timeliness,
336 accuracy_error,
337 unknown,
338 evidence: None,
339 };
340 if self.common_holds && !info.has_faults() && information.holds() {
341 information.evidence = Some(Evidence {
342 facts: info
343 .retain_facts(inside)
344 .map_err(InfoCheckError::Processing)?,
345 source: source
346 .try_copy_in(&inside.execution())
347 .map_err(InfoCheckError::Provenance)?,
348 });
349 }
350 Ok(information)
351 }
352}
353
354fn unrecognized<SE, PE, RE>(
355 info: &super::TokenInfo<'_>,
356 inside: &Reader<'_>,
357 recognizes: &mut impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
358) -> Result<OwnedVec<OwnedOid>, InfoCheckError<SE, PE, RE>> {
359 let scoped = inside.execution();
360 let mut unknown = scoped.vector();
361 if let Some(extensions) = info.extensions() {
362 for kind in extensions
363 .unrecognized_critical(inside, recognizes)
364 .map_err(InfoCheckError::Recognition)?
365 {
366 let owned = OwnedOid::from_oid_in(kind, &scoped).map_err(|reason| {
367 InfoCheckError::Processing(Error::resource(
368 reason,
369 "tstInfo.extensions",
370 extensions.span().start,
371 ))
372 })?;
373 unknown.try_push(owned).map_err(|reason| {
374 InfoCheckError::Processing(Error::resource(
375 reason,
376 "tstInfo.extensions",
377 extensions.span().start,
378 ))
379 })?;
380 }
381 }
382 Ok(unknown.into_owned())
383}
384
385fn common_holds(
386 signature: &TokenSignature<'_>,
387 bindings: ess::Bindings,
388 identifier: &directory::CertificateAgreement,
389 carried: Option<bool>,
390) -> bool {
391 matches!(signature, TokenSignature::Checked(checked)
392 if checked.signature() == Some(SignatureAnswer::Valid)
393 && matches!(checked.content(), verify::ContentAgreement::Matches | verify::ContentAgreement::Direct))
394 && (bindings.v1 == Some(ess::Binding::Matches)
395 || bindings.v2 == Some(ess::Binding::Matches))
396 && bindings
397 .v1
398 .is_none_or(|binding| binding == ess::Binding::Matches)
399 && bindings
400 .v2
401 .is_none_or(|binding| binding == ess::Binding::Matches)
402 && matches!(identifier, directory::CertificateAgreement::Matches)
403 && carried != Some(false)
404}
405
406impl<'doc> Token<'doc> {
407 #[expect(
417 clippy::result_large_err,
418 reason = "preserve original cause and accounted source context without allocating on refusal"
419 )]
420 pub fn check<'a, P, S: Source, PE, RE>(
421 &'a self,
422 provider: &'a P,
423 parent: &Reader<'a>,
424 context: &CheckContext<'a, '_>,
425 covered: &mut S,
426 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
427 ) -> Result<CheckedTimestamp<'a>, CheckError<S::Error, PE, RE>>
428 where
429 'doc: 'a,
430 P: for<'scope> SignatureProvider<'scope, Error = PE>,
431 {
432 self.check_in(provider, (parent, None), context, covered, recognizes)
433 }
434
435 #[expect(
440 clippy::result_large_err,
441 reason = "preserve both source frames and original opaque refusal without a fallible error box"
442 )]
443 pub fn check_mapped<'a, P, S: Source, PE, RE>(
444 &'a self,
445 provider: &'a P,
446 source: (&Reader<'a>, &SourceMap),
447 context: &CheckContext<'a, '_>,
448 covered: &mut S,
449 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
450 ) -> Result<CheckedTimestamp<'a>, MappedCheckError<S::Error, PE, RE>>
451 where
452 'doc: 'a,
453 P: for<'scope> SignatureProvider<'scope, Error = PE>,
454 {
455 let (parent, source) = source;
456 preflight_sessions(self, parent, context)
457 .map_err(|error| MappedCheckError::Preflight(error.into()))?;
458 let envelope = source
459 .try_copy_in(&parent.execution())
460 .map_err(MappedCheckError::Provenance)?;
461 self.check_in(
462 provider,
463 (parent, Some(source)),
464 context,
465 covered,
466 recognizes,
467 )
468 .map_err(|source| MappedCheckError::Execution { envelope, source })
469 }
470
471 #[expect(
472 clippy::result_large_err,
473 reason = "retain original cause and accounted source context on refusal"
474 )]
475 fn check_in<'a, P, S: Source, PE, RE>(
476 &'a self,
477 provider: &'a P,
478 origin: (&Reader<'a>, Option<&SourceMap>),
479 context: &CheckContext<'a, '_>,
480 covered: &mut S,
481 mut recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
482 ) -> Result<CheckedTimestamp<'a>, CheckError<S::Error, PE, RE>>
483 where
484 'doc: 'a,
485 P: for<'scope> SignatureProvider<'scope, Error = PE>,
486 {
487 let (parent, source) = origin;
488 preflight(self, parent, context)?;
489 let certificate = context.certificate;
490 let identifier =
491 directory::matches_certificate_in(self.signer().sid(), certificate, parent)
492 .map_err(CheckError::Identifier)?;
493 let ess =
494 ess::SigningAttributes::read(self.signer(), parent).map_err(CheckError::Processing)?;
495 let carried = context
496 .requested
497 .as_ref()
498 .map(|requested| {
499 self.matches_cert_req(
500 requested.request,
501 Some(certificate),
502 parent,
503 requested.parent,
504 )
505 })
506 .transpose()
507 .map_err(CheckError::RequestField)?;
508 if !self.faults().is_empty()
509 || !self.message().faults().is_empty()
510 || !self.signer().faults().is_empty()
511 || !ess.faults().is_empty()
512 || ess.v1().is_some_and(|reading| !reading.faults().is_empty())
513 || ess.v2().is_some_and(|reading| !reading.faults().is_empty())
514 {
515 return Err(CheckError::Nonconforming {
516 span: self.message().span(),
517 });
518 }
519 let inspect = |info: &super::TokenInfo<'_>, inside: &Reader<'_>, source: &SourceMap| {
520 if info.has_faults() {
521 return Err(InfoCheckError::Nonconforming { span: info.span() });
522 }
523 let bindings = ess
524 .bind_certificate(provider, certificate, parent)
525 .map_err(InfoCheckError::Ess)?;
526 let signature = self
527 .check_signature(provider, context.key, parent)
528 .map_err(InfoCheckError::Signature)?;
529 let common_holds = common_holds(&signature, bindings, &identifier, carried);
530 let mut inspection = Inspection {
531 provider,
532 context,
533 covered,
534 recognizes: &mut recognizes,
535 common_holds,
536 };
537 let information = inspection.inspect(info, inside, source)?;
538 Ok((signature, bindings, information))
539 };
540 let (signature, bindings, information) = match source {
541 Some(source) => self.with_info_mapped(parent, source, inspect),
542 None => self.with_info(parent, inspect),
543 }
544 .map_err(CheckError::Information)?;
545 Ok(CheckedTimestamp {
546 signature,
547 identifier,
548 ess,
549 bindings,
550 authority: information.authority,
551 imprint: information.imprint,
552 requested_imprint: information.requested_imprint,
553 requested_fields: information.requested_fields,
554 carried,
555 timeliness: information.timeliness,
556 accuracy_error: information.accuracy_error,
557 unknown_critical: information.unknown,
558 evidence: information.evidence,
559 })
560 }
561}
562
563enum PreflightError {
564 Processing(Error),
565 Requested(super::RequestFieldError),
566 KeyMismatch(structured_asn1::Span),
567}
568
569impl<SE, PE, RE> From<PreflightError> for CheckError<SE, PE, RE> {
570 fn from(error: PreflightError) -> Self {
571 match error {
572 PreflightError::Processing(error) => Self::Processing(error),
573 PreflightError::Requested(error) => Self::RequestField(error),
574 PreflightError::KeyMismatch(certificate) => Self::KeyMismatch { certificate },
575 }
576 }
577}
578
579fn preflight(
580 token: &Token<'_>,
581 parent: &Reader<'_>,
582 context: &CheckContext<'_, '_>,
583) -> Result<(), PreflightError> {
584 preflight_sessions(token, parent, context)?;
585 let certificate = context.certificate;
586 let written = certificate.tbs().public_key().as_bytes();
587 let key = context.key.key().as_bytes();
588 let length = u64::try_from(written.len().max(key.len()))
589 .ok()
590 .and_then(|length| length.checked_add(1))
591 .ok_or_else(|| {
592 PreflightError::Processing(Error::schema(
593 ErrorCode::LengthOverflow,
594 "certificate.subjectPublicKeyInfo",
595 certificate.span().start,
596 ))
597 })?;
598 parent.execution().charge_work(length).map_err(|reason| {
599 PreflightError::Processing(Error::resource(
600 reason,
601 "certificate.subjectPublicKeyInfo",
602 certificate.span().start,
603 ))
604 })?;
605 if written != key {
606 return Err(PreflightError::KeyMismatch(certificate.span()));
607 }
608 Ok(())
609}
610
611fn preflight_sessions(
612 token: &Token<'_>,
613 parent: &Reader<'_>,
614 context: &CheckContext<'_, '_>,
615) -> Result<(), PreflightError> {
616 token
617 .signer()
618 .require_session(parent, "signerInfo")
619 .map_err(PreflightError::Processing)?;
620 let certificate = context.certificate;
621 if !certificate
622 .tbs()
623 .public_key()
624 .algorithm()
625 .same_session(context.certificate_parent)
626 {
627 return Err(PreflightError::Processing(Error::pkix(
628 structured_pkix::Error::new(
629 structured_pkix::ErrorCode::ForeignReader,
630 "certificate",
631 certificate.span().start,
632 ),
633 )));
634 }
635 if let Some(requested) = &context.requested
636 && !requested.request.same_session(requested.parent)
637 {
638 return Err(PreflightError::Requested(super::RequestFieldError {
639 operand: super::ImprintOperand::Requested,
640 source: Error::pkix(structured_pkix::Error::new(
641 structured_pkix::ErrorCode::ForeignReader,
642 "timeStampReq",
643 requested.request.span().start,
644 )),
645 }));
646 }
647 Ok(())
648}