Skip to main content

structured_cades/
preservation.rs

1//! Original legacy preservation obligations, separate from modern `ATSv3`.
2//!
3//! EN319122-1 §5.5.3/A.2.4–5 requires preserving older archive/PoE mechanisms
4//! even in another parallel root signer. Their historical encoding variants
5//! and RFC4998 evidence records are not interchangeable with `ATSv3` inputs.
6//! Unknown historical proof is an unavailable obligation, never a malformed
7//! RFC3161 token or an implicit successful empty participant set.
8//! <https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf>
9
10use crate::{
11    Error,
12    source::{Node, Value},
13};
14use structured_asn1::{
15    Oid, Reader,
16    memory::{OwnedVec, Work},
17};
18use structured_cms::{attribute::Placement, signer::SignerInfo};
19
20/// Original historical preservation mechanism, without guessing its variant.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Kind {
23    /// Earlier archiveTimestamp, whose encodings differ between TS editions.
24    ArchiveV1,
25    /// RFC5126 §6.4.1 archiveTimestampV2.
26    ArchiveV2,
27    /// TS101733 §6.5.1 `PoE` wrapper, potentially carrying an evidence record.
28    LongTermValidation,
29    /// RFC4998 Appendix A internal CMS evidence record (id-aa .49).
30    EvidenceRecordInternal,
31    /// RFC4998 Appendix A record covering CMS and separate content (.50).
32    EvidenceRecordExternal,
33    /// AnnexA.1.5.1 timestamp of certificate/revocation references.
34    References,
35    /// AnnexA.1.5.2 timestamp of the signature and its validation references.
36    SignatureReferences,
37}
38impl Kind {
39    pub(crate) fn from_oid(value: Oid<'_>) -> Option<Self> {
40        let bytes = value.as_bytes();
41        if bytes.len() == 11 {
42            if bytes[..10] != [42, 134, 72, 134, 247, 13, 1, 9, 16, 2] {
43                return None;
44            }
45            match bytes[10] {
46                27 => Some(Self::ArchiveV1),
47                48 => Some(Self::ArchiveV2),
48                26 => Some(Self::References),
49                25 => Some(Self::SignatureReferences),
50                49 => Some(Self::EvidenceRecordInternal),
51                50 => Some(Self::EvidenceRecordExternal),
52                _ => None,
53            }
54        } else if bytes == [4, 0, 0x8d, 0x45, 2, 2] {
55            Some(Self::LongTermValidation)
56        } else {
57            None
58        }
59    }
60    pub(crate) fn is_reference_timestamp(self) -> bool {
61        matches!(self, Self::References | Self::SignatureReferences)
62    }
63    pub(crate) fn is_archive(self) -> bool {
64        matches!(self, Self::ArchiveV1 | Self::ArchiveV2)
65    }
66    pub(crate) fn is_evidence(self) -> bool {
67        matches!(
68            self,
69            Self::EvidenceRecordInternal | Self::EvidenceRecordExternal | Self::LongTermValidation
70        )
71    }
72}
73
74/// One original obligation, retained even when absent/unreadable/unprocessed.
75#[derive(Debug, Clone, Copy)]
76pub struct Required<'document> {
77    /// Original root or counter which contains the historical attribute.
78    pub signature: Node,
79    /// Whole original attribute, never a fabricated input coordinate.
80    pub property: Node,
81    /// Original historical format, not an inferred `ATSv3` instance.
82    pub kind: Kind,
83    /// Actual signed/unsigned placement from its original `SignerInfo` field.
84    pub placement: Placement,
85    /// Original independent field context; None is an actual empty value set.
86    pub value: Option<Value<'document>>,
87}
88
89pub(crate) fn roots<'d, W: Work>(
90    message: &structured_cms::SignedData<'d>,
91    parent: &Reader<'d>,
92    work: &W,
93) -> crate::Result<OwnedVec<Required<'d>, W>> {
94    let mut out = work.vector();
95    for signer in message.signers() {
96        parent
97            .charge(1)
98            .map_err(|cause| Error::encoding(cause, "cades.preservation"))?;
99        collect(signer, parent, &mut out)?;
100    }
101    Ok(out)
102}
103
104pub(crate) fn collect<'d, W: Work>(
105    signer: &SignerInfo<'d>,
106    parent: &Reader<'d>,
107    out: &mut OwnedVec<Required<'d>, W>,
108) -> crate::Result<()> {
109    signer.require_session(parent, "cades.preservation")?;
110    for (placement, set) in [
111        (Placement::Signed, signer.signed_attributes()),
112        (Placement::Unsigned, signer.unsigned_attributes()),
113    ] {
114        let Some(set) = set else {
115            continue;
116        };
117        for attribute in set.items() {
118            parent
119                .charge(5 * (1 + attribute.kind().as_bytes().len().min(11) as u64))
120                .map_err(|cause| Error::encoding(cause, "cades.preservation"))?;
121            let kind = Kind::from_oid(attribute.kind());
122            let Some(kind) = kind else {
123                continue;
124            };
125            let resource =
126                |cause| Error::resource(cause, "cades.preservation", attribute.span().start);
127            if attribute.values().is_empty() {
128                out.try_push(Required {
129                    signature: Node::of(signer.span()),
130                    property: Node::of(attribute.span()),
131                    kind,
132                    placement,
133                    value: None,
134                })
135                .map_err(resource)?;
136            }
137            for (ordinal, value) in attribute.values().iter().enumerate() {
138                let reader = attribute
139                    .value_reader_in(ordinal, parent, "cades.preservation")?
140                    .ok_or(Error::Foreign {
141                        at: value.span().start,
142                    })?;
143                out.try_push(Required {
144                    signature: Node::of(signer.span()),
145                    property: Node::of(attribute.span()),
146                    kind,
147                    placement,
148                    value: Some(Value::of(&reader, *value, "cades.preservation")),
149                })
150                .map_err(resource)?;
151            }
152        }
153    }
154    Ok(())
155}