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