1use super::{ArchiveChecked, Checked};
12use crate::{
13 Cades, Error, ISSUER,
14 prepare::{Preparation, encoding::SignerView},
15 preservation::Kind,
16 source::{DocumentId, Node},
17};
18use core::cell::Cell;
19use structured_asn1::{
20 Oid, Reader,
21 length::DerLength,
22 memory::{Memory, OwnedMap, OwnedVec, Work},
23};
24use structured_cms::{
25 Octets,
26 stream::Source,
27 timestamp::{
28 self as tsp, ComputedInput, ImprintAgreement, ImprintContext, ImprintProof, InputFailure,
29 },
30};
31use structured_ltv::{
32 material::Coverage,
33 source::{Scope, Source as _},
34 timestamp::{Purpose, Verified},
35};
36use structured_pkix::algorithm::digest::AsnDigest;
37
38const FIELD: &str = "cades.historical";
39
40mod selection;
41mod snapshot;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Edition {
47 Rfc3126,
50 Ts101733V151,
53 Rfc5126,
56}
57impl Edition {
58 fn kind(self) -> Kind {
59 match self {
60 Self::Rfc3126 | Self::Ts101733V151 => Kind::ArchiveV1,
61 Self::Rfc5126 => Kind::ArchiveV2,
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum Framing {
70 OriginalSnapshot,
72 CanonicalDefiniteProjection,
74}
75
76#[derive(Debug)]
79pub enum SelectionError {
80 Processing(Error),
82 PriorProcessing(Error),
84 Coverage(structured_ltv::material::CoverageError<Node>),
86 WrongEdition,
88 WrongOccurrence,
90 MissingDetached,
92 PriorNotPreserved,
94 SelfIncluded,
96 UnprovenLater {
98 property: Node,
100 },
101 Unavailable {
103 property: Node,
105 },
106 LengthOverflow,
108}
109impl From<Error> for SelectionError {
110 fn from(cause: Error) -> Self {
111 Self::Processing(cause)
112 }
113}
114
115pub struct Later<'view, 'model, 'document> {
119 evidence: &'view Verified<Node, DocumentId<'model, 'document>, (), Cades>,
120 coverage: &'view Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
121}
122impl<'v, 'm, 'd> Later<'v, 'm, 'd> {
123 pub fn modern(checked: &'v ArchiveChecked<'m, 'd, '_>) -> Option<Self> {
125 Some(Self {
126 evidence: checked.timestamp().actor()?.verified(),
127 coverage: checked.coverage(),
128 })
129 }
130 pub fn historical(checked: &'v HistoricalChecked<'m, 'd, '_>) -> Option<Self> {
132 Some(Self {
133 evidence: checked.timestamp.actor()?.verified(),
134 coverage: &checked.coverage,
135 })
136 }
137 #[must_use]
140 pub fn record(checked: &'v crate::evidence::RecordArchive<'_, '_, 'm, 'd, '_>) -> Option<Self> {
141 Some(Self {
142 evidence: checked.timestamp().actor()?.verified(),
143 coverage: checked.coverage(),
144 })
145 }
146 pub(crate) fn permits(
147 &self,
148 prepared: &Preparation<'m, 'd>,
149 later: &crate::prepare::Timestamp<'d>,
150 earlier: &crate::prepare::Timestamp<'d>,
151 operation: &Reader<'d>,
152 ) -> Result<bool, SelectionError> {
153 operation
154 .charge(1)
155 .map_err(|cause| Error::encoding(cause, FIELD))?;
156 let Some(value) = later.value() else {
157 return Ok(false);
158 };
159 if self.evidence.token().node != Node::of(value.span()) {
160 return Ok(false);
161 }
162 let source = prepared.document();
163 let work = operation.execution();
164 let query = Scope::new(source, source.snapshot(), &work);
165 let node = earlier
166 .value()
167 .map_or(earlier.property(), |value| Node::of(value.span()));
168 let selected = self
169 .coverage
170 .covers(self.evidence, node, &query)
171 .map_err(SelectionError::Coverage)?;
172 if selected {
173 return Ok(true);
174 }
175 self.coverage
176 .covers(self.evidence, earlier.property(), &query)
177 .map_err(SelectionError::Coverage)
178 }
179}
180
181pub struct OriginalSnapshot<'view, 'model, 'document> {
185 prepared: &'view Preparation<'model, 'document>,
186 owner: Node,
187 reader: &'view Reader<'document>,
188 signer: SignerView<'view, 'document>,
189}
190impl<'v, 'm, 'd> OriginalSnapshot<'v, 'm, 'd> {
191 pub fn capture(
193 prepared: &'v Preparation<'m, 'd>,
194 owner: Node,
195 reader: &'v Reader<'d>,
196 ) -> Result<Self, SelectionError> {
197 let signer = prepared.signer_encoding(owner, reader)?;
198 Ok(Self {
199 prepared,
200 owner,
201 reader,
202 signer,
203 })
204 }
205}
206
207#[derive(Clone, Copy)]
208struct Header {
209 bytes: [u8; 10],
210 length: u8,
211}
212impl Header {
213 fn new(tag: u8, length: u64) -> Self {
214 let length = DerLength::new(length);
215 let mut bytes = [0; 10];
216 bytes[0] = tag;
217 bytes[1..=length.as_bytes().len()].copy_from_slice(length.as_bytes());
218 Self {
219 bytes,
220 length: 1 + u8::try_from(length.as_bytes().len())
221 .expect("DER length uses at most nine octets"),
222 }
223 }
224 fn bytes(&self) -> &[u8] {
225 &self.bytes[..usize::from(self.length)]
226 }
227}
228enum Part<'a> {
229 Current(&'a [u8]),
230 Prior(&'a [u8]),
231 Framing(Header),
232}
233impl Part<'_> {
234 fn length(&self) -> usize {
235 match self {
236 Self::Current(bytes) | Self::Prior(bytes) => bytes.len(),
237 Self::Framing(header) => header.bytes().len(),
238 }
239 }
240}
241enum Payload<'s, 'd, S> {
242 Absent,
243 Carried(&'s Octets<'d>),
244 Detached(&'s mut S),
245}
246
247struct Carried<'s, 'd, E> {
248 source: &'s Octets<'d>,
249 error: core::marker::PhantomData<E>,
250}
251impl<E> Source for Carried<'_, '_, E> {
252 type Error = StreamError<E>;
253 fn stream<W: Work>(
254 &mut self,
255 receiver: &mut impl structured_cms::stream::Receiver,
256 work: &W,
257 ) -> Result<(), Self::Error> {
258 self.source
259 .stream(receiver, work)
260 .map_err(StreamError::Carried)
261 }
262}
263struct Lift<'a, S>(&'a mut S);
264impl<S: Source> Source for Lift<'_, S> {
265 type Error = StreamError<S::Error>;
266 fn stream<W: Work>(
267 &mut self,
268 receiver: &mut impl structured_cms::stream::Receiver,
269 work: &W,
270 ) -> Result<(), Self::Error> {
271 self.0.stream(receiver, work).map_err(StreamError::Source)
272 }
273}
274
275#[derive(PartialEq, Eq, Hash)]
276struct ArchiveValue<'a> {
277 generation: u8,
278 bytes: &'a [u8],
279}
280impl structured_asn1::memory::HashWork for ArchiveValue<'_> {
281 fn hash_work(&self) -> Result<u64, structured_asn1::memory::MemoryError> {
282 (self.bytes.len() as u64)
283 .checked_add(2)
284 .ok_or(structured_asn1::memory::MemoryError::WorkExceeded)
285 }
286}
287
288pub struct HistoricalInput<'input, 'model, 'document, S> {
292 prepared: &'input Preparation<'model, 'document>,
293 operation: &'input Reader<'document>,
294 prior: Option<&'input Reader<'input>>,
295 ordinal: usize,
296 edition: Edition,
297 framing: Framing,
298 parts: OwnedVec<Part<'input>>,
299 payload_at: usize,
300 unsigned_at: usize,
301 unsigned_header: Option<Header>,
302 original_unsigned: Option<&'input [u8]>,
303 payload: Payload<'input, 'document, S>,
304 covered: Option<OwnedMap<Node, bool>>,
305 matched: Cell<Option<Variant>>,
306}
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310pub enum Variant {
311 ListedContents,
313 UnsignedField,
315 UnsignedContents,
317}
318
319#[derive(Debug)]
322pub enum StreamError<E> {
323 Source(E),
325 Carried(structured_cms::octets::SourceError),
327 Processing(Error),
329}
330
331impl<'s, 'm, 'd, S> HistoricalInput<'s, 'm, 'd, S> {
332 pub fn from_snapshot<'old_model, 'old_document: 's>(
338 prepared: &'s Preparation<'m, 'd>,
339 ordinal: usize,
340 edition: Edition,
341 snapshot: &'s OriginalSnapshot<'_, 'old_model, 'old_document>,
342 later: &[Later<'_, 'm, 'd>],
343 operation: &'s Reader<'d>,
344 detached: Option<&'s mut S>,
345 ) -> Result<Self, SelectionError> {
346 prepared
347 .document()
348 .message()
349 .require_session(operation, FIELD)
350 .map_err(Error::Cms)?;
351 snapshot
352 .prepared
353 .document()
354 .message()
355 .require_session(snapshot.reader, FIELD)
356 .map_err(|cause| SelectionError::PriorProcessing(Error::Cms(cause)))?;
357 let target = prepared
358 .timestamps()
359 .get(ordinal)
360 .ok_or(SelectionError::WrongOccurrence)?;
361 let view = prepared.signer_encoding(target.owner(), operation)?;
362 snapshot.boundaries(prepared, view, operation)?;
363 snapshot.parent(prepared, target.owner(), operation)?;
364 let mut attributes = snapshot.attributes(view, operation)?;
365 let previous = snapshot.previous(prepared, target.owner(), ordinal, operation)?;
366 Self::project(
367 prepared, ordinal, edition, &previous, later, operation, detached,
368 )?
369 .restore(snapshot, view, &mut attributes)
370 }
371 pub fn project(
376 prepared: &'s Preparation<'m, 'd>,
377 ordinal: usize,
378 edition: Edition,
379 previous: &[usize],
380 later: &[Later<'_, 'm, 'd>],
381 operation: &'s Reader<'d>,
382 detached: Option<&'s mut S>,
383 ) -> Result<Self, SelectionError> {
384 prepared
385 .document()
386 .message()
387 .require_session(operation, FIELD)
388 .map_err(Error::Cms)?;
389 operation
390 .charge(1)
391 .map_err(|cause| Error::encoding(cause, FIELD))?;
392 let target = prepared
393 .timestamps()
394 .get(ordinal)
395 .ok_or(SelectionError::WrongOccurrence)?;
396 if target.preservation_kind() != Some(edition.kind()) {
397 return Err(SelectionError::WrongEdition);
398 }
399 if target.value().is_none() {
400 return Err(SelectionError::WrongOccurrence);
401 }
402 let view = prepared.signer_encoding(target.owner(), operation)?;
403 let work = operation.execution();
404 let resource = |cause| {
405 SelectionError::Processing(Error::resource(
406 cause,
407 FIELD,
408 target.property().span().start,
409 ))
410 };
411 let mut placement = false;
412 for index in 0..view.attributes() {
413 work.charge_work(1).map_err(resource)?;
414 if Node::of(
415 view.attribute(index)
416 .ok_or(SelectionError::WrongOccurrence)?
417 .span,
418 ) == target.property()
419 {
420 placement = true;
421 break;
422 }
423 }
424 if !placement {
425 return Err(SelectionError::WrongOccurrence);
426 }
427 let chosen = selection::choose(prepared, ordinal, edition, previous, later, operation)?;
428 let content = prepared.document().message().content().content();
429 let payload = match (edition, content, detached) {
430 (Edition::Rfc3126, Some(source), _) => Payload::Carried(source),
431 (Edition::Rfc5126, None, Some(source)) => Payload::Detached(source),
432 (Edition::Rfc5126, None, None) => return Err(SelectionError::MissingDetached),
433 _ => Payload::Absent,
434 };
435 let mut parts = work.vector();
436 let mut covered = work.map();
437 let payload_at = selection::Output::new(&mut parts, &mut covered, &work, target.property())
438 .prefix(prepared, view, target.owner(), edition, operation)?;
439 let unsigned_at = parts.len();
440 {
441 let mut output =
442 selection::Output::new(&mut parts, &mut covered, &work, target.property());
443 output.unsigned(prepared, view, edition, &chosen)?;
444 output.context(prepared, view, target.owner(), edition)?;
445 }
446 let mut unsigned_length = 0u64;
447 for part in &parts[unsigned_at..] {
448 work.charge_work(1).map_err(resource)?;
449 unsigned_length = unsigned_length
450 .checked_add(part.length() as u64)
451 .ok_or(SelectionError::LengthOverflow)?;
452 }
453 Ok(Self {
454 prepared,
455 operation,
456 prior: None,
457 ordinal,
458 edition,
459 framing: Framing::CanonicalDefiniteProjection,
460 parts: parts.into_owned(),
461 payload_at,
462 unsigned_at,
463 unsigned_header: (unsigned_length != 0).then(|| Header::new(0xa1, unsigned_length)),
464 original_unsigned: None,
465 payload,
466 covered: Some(covered.into_owned()),
467 matched: Cell::new(None),
468 })
469 }
470 pub fn framing(&self) -> Framing {
472 self.framing
473 }
474 pub fn matched_variant(&self) -> Option<Variant> {
476 self.matched.get()
477 }
478}
479
480fn archive_kind(kind: Oid<'_>) -> Option<Kind> {
481 match Kind::from_oid(kind) {
482 Some(value) if value.is_archive() => Some(value),
483 _ => None,
484 }
485}
486fn category(kind: Oid<'_>) -> Option<usize> {
487 for (index, last) in [14, 21, 22, 23, 24, 25, 26, 27].into_iter().enumerate() {
488 if kind == Oid::from_bytes(&[42, 134, 72, 134, 247, 13, 1, 9, 16, 2, last]) {
489 return Some(index);
490 }
491 }
492 None
493}
494
495fn covered_container<W: Work>(
496 view: SignerView<'_, '_>,
497 node: Node,
498 selected: &OwnedMap<Node, bool, W>,
499 work: &impl Work,
500) -> Result<bool, structured_asn1::memory::MemoryError> {
501 let span = node.span();
505 let mut from = 0;
506 let mut to = view.attributes();
507 while from < to {
508 work.charge_work(1)?;
509 let middle = from + (to - from) / 2;
510 let attribute = view.attribute(middle).expect("bounded original catalogue");
511 if attribute.span.start <= span.start {
512 from = middle + 1;
513 } else {
514 to = middle;
515 }
516 }
517 if from == 0 {
518 return Ok(false);
519 }
520 work.charge_work(1)?;
521 let attribute = view
522 .attribute(from - 1)
523 .expect("preceding original attribute");
524 if span.end() > attribute.span.end() {
525 return Ok(false);
526 }
527 Ok(selected.get(&Node::of(attribute.span), work)? == Some(&true))
528}
529
530impl<P, PE, S: Source> ComputedInput<P, PE> for HistoricalInput<'_, '_, '_, S> {
531 type Error = StreamError<S::Error>;
532 fn compute<'context, 'reader, 'document>(
533 &mut self,
534 context: &'context ImprintContext<'reader, 'document, P, Self::Error>,
535 ) -> Result<ImprintProof<'context>, InputFailure<Self::Error>>
536 where
537 P: AsnDigest<'document, Error = PE>,
538 {
539 self.matched.set(None);
540 let mut first = context.start()?;
541 let mut second = if self.edition == Edition::Rfc5126 {
542 Some(context.start()?)
543 } else {
544 None
545 };
546 for part in &self.parts[..self.payload_at] {
547 emit(part, &mut first, self.operation, self.prior)?;
548 if let Some(second) = &mut second {
549 emit(part, second, self.operation, self.prior)?;
550 }
551 }
552 match &mut self.payload {
553 Payload::Absent => {}
554 Payload::Carried(source) => {
555 let mut source = Carried::<S::Error> {
556 source,
557 error: core::marker::PhantomData,
558 };
559 if let Some(second) = &mut second {
560 context.stream_from(&mut source, self.operation, &mut [&mut first, second])?;
561 } else {
562 context.stream_from(&mut source, self.operation, &mut [&mut first])?;
563 }
564 }
565 Payload::Detached(source) => {
566 if let Some(second) = &mut second {
567 context.stream_from(
568 &mut Lift(*source),
569 self.operation,
570 &mut [&mut first, second],
571 )?;
572 } else {
573 context.stream_from(&mut Lift(*source), self.operation, &mut [&mut first])?;
574 }
575 }
576 }
577 for part in &self.parts[self.payload_at..self.unsigned_at] {
578 emit(part, &mut first, self.operation, self.prior)?;
579 if let Some(second) = &mut second {
580 emit(part, second, self.operation, self.prior)?;
581 }
582 }
583 if self.edition != Edition::Rfc3126 {
584 if let Some(bytes) = self.original_unsigned {
585 first.write_from(
586 bytes,
587 self.prior
588 .expect("original unsigned belongs to captured source"),
589 )?;
590 } else if let Some(header) = &self.unsigned_header {
591 first.write(header.bytes())?;
592 }
593 }
594 for part in &self.parts[self.unsigned_at..] {
595 if self.original_unsigned.is_some() && self.edition != Edition::Rfc3126 {
596 if let Some(second) = &mut second {
597 emit(part, second, self.operation, self.prior)?;
598 }
599 } else {
600 emit(part, &mut first, self.operation, self.prior)?;
601 if let Some(second) = &mut second {
602 emit(part, second, self.operation, self.prior)?;
603 }
604 }
605 }
606 let first = first.finish()?;
607 let second = match second {
608 Some(state) => Some(state.finish()?),
609 None => None,
610 };
611 let a = context.compare(&first)?;
612 let b = match &second {
613 Some(digest) => Some(context.compare(digest)?),
614 None => None,
615 };
616 if a.agreement() == ImprintAgreement::Matches {
617 self.matched.set(Some(if self.edition == Edition::Rfc3126 {
618 Variant::ListedContents
619 } else {
620 Variant::UnsignedField
621 }));
622 Ok(a)
623 } else if let Some(b) = b {
624 if b.agreement() == ImprintAgreement::Matches {
625 self.matched.set(Some(Variant::UnsignedContents));
626 }
627 Ok(b)
628 } else {
629 Ok(a)
630 }
631 }
632}
633fn emit<'c, 'r, 'd, P: AsnDigest<'d>, E>(
634 part: &Part<'_>,
635 state: &mut tsp::ImprintState<'c, 'r, 'd, P, E>,
636 operation: &Reader<'_>,
637 prior: Option<&Reader<'_>>,
638) -> Result<(), structured_cms::stream::Refused> {
639 match part {
640 Part::Current(bytes) => state.write_from(bytes, operation),
641 Part::Prior(bytes) => {
642 state.write_from(bytes, prior.expect("prior descriptor has its source"))
643 }
644 Part::Framing(header) => state.write(header.bytes()),
645 }
646}
647
648pub struct HistoricalChecked<'model, 'document, 'path> {
651 timestamp: Checked<'model, 'document, 'path>,
652 coverage: Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
653 framing: Framing,
654 variant: Option<Variant>,
655}
656impl<'m, 'd, 'p> HistoricalChecked<'m, 'd, 'p> {
657 pub fn timestamp(&self) -> &Checked<'m, 'd, 'p> {
659 &self.timestamp
660 }
661 pub fn coverage(&self) -> &Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades> {
663 &self.coverage
664 }
665 pub fn framing(&self) -> Framing {
667 self.framing
668 }
669 pub fn matched_variant(&self) -> Option<Variant> {
671 self.variant
672 }
673 pub fn requirements(
677 &self,
678 prepared: &Preparation<'m, 'd>,
679 operation: &Reader<'d>,
680 ) -> Result<
681 structured_ltv::timestamp::TimestampRequirements<Node, DocumentId<'m, 'd>, (), Cades>,
682 super::ArchiveScopeError,
683 > {
684 super::requirements::derive(&self.timestamp, &self.coverage, prepared, operation)
685 }
686}
687
688impl<'m, 'd> super::Context<'_, 'm, 'd> {
689 #[expect(
693 clippy::result_large_err,
694 reason = "original profile causes stay unboxed on refusal"
695 )]
696 pub fn check_historical<'a, 'p, P, S: Source, PE, RE>(
697 &'a self,
698 provider: &'a P,
699 path: &'p structured_ltv::pki::CheckedPath<'d>,
700 certificate_parent: &Reader<'d>,
701 time: &super::CheckTime<'_, 'd>,
702 input: &mut HistoricalInput<'_, 'm, 'd, S>,
703 recognizes: impl FnMut(
704 &tsp::TokenExtension<'_>,
705 &structured_asn1::memory::Execution<'_>,
706 ) -> Result<bool, RE>,
707 ) -> Result<HistoricalChecked<'m, 'd, 'p>, super::CheckError<StreamError<S::Error>, PE, RE>>
708 where
709 P: for<'scope> structured_cms::provider::SignatureProvider<'scope, Error = PE>,
710 {
711 if self.ordinal != input.ordinal
712 || !core::ptr::eq(self.prepared, input.prepared)
713 || input.covered.is_none()
714 {
715 return Err(super::CheckError::Selection(Error::Foreign {
716 at: self.entry.property().span().start,
717 }));
718 }
719 let value = self
720 .entry
721 .value()
722 .ok_or(super::CheckError::Selection(Error::Foreign {
723 at: self.entry.property().span().start,
724 }))?;
725 let timestamp = self.check_computed_profile(
726 provider,
727 path,
728 certificate_parent,
729 time,
730 input,
731 (recognizes, |_| {
732 Ok::<(), tsp::ProfileFailure<core::convert::Infallible>>(())
733 }),
734 )?;
735 let coverage = Coverage::for_token(
736 ISSUER,
737 self.prepared.document().snapshot(),
738 self.entry.owner(),
739 self.entry.property(),
740 Node::of(value.span()),
741 Purpose::Archive,
742 input
743 .covered
744 .take()
745 .ok_or(super::CheckError::Selection(Error::Foreign {
746 at: self.entry.property().span().start,
747 }))?,
748 );
749 Ok(HistoricalChecked {
750 timestamp,
751 coverage,
752 framing: input.framing,
753 variant: input.matched.get(),
754 })
755 }
756}