Skip to main content

structured_cades/timestamp/
references.rs

1//! Original reference timestamp inputs, EN319122-1 V1.3.1 A.1.5.1–A.1.5.2.
2//!
3//! Attribute CONTENTS contain attrType and attrValues with their own headers;
4//! the outer SEQUENCE header/EOC is excluded. BER is preserved, not replaced
5//! by a DER re-encoding. Signature-and-references requires signature timestamps
6//! under this edition: an external historical time-mark has no invented wire
7//! representation here. These proofs authenticate the original REFERENCES,
8//! not the certificates or revocation assertions to which they refer.
9//! <https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf>
10
11use super::{CheckError, CheckTime, Checked, Context};
12use crate::{
13    Cades, Error, ISSUER,
14    prepare::{
15        Preparation,
16        encoding::{AttributeEncoding, SignerView},
17    },
18    preservation::Kind,
19    source::{DocumentId, Node},
20};
21use core::convert::Infallible;
22use structured_asn1::{
23    Oid, Reader,
24    memory::{Memory, OwnedMap, OwnedVec, Work},
25};
26use structured_cms::{
27    provider::SignatureProvider,
28    timestamp::{self as tsp, ComputedInput, ImprintContext, ImprintProof, InputFailure},
29};
30use structured_ltv::{
31    material::Coverage, pki::CheckedPath, source::Source as _, timestamp::Purpose,
32};
33use structured_pkix::algorithm::digest::AsnDigest;
34
35const FIELD: &str = "cades.references";
36
37/// Actual required input object; absence is not an empty-byte fallback.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum Component {
40    /// Every original signature-time-stamp Attribute, in its original order.
41    SignatureTimestamp,
42    /// The original complete-certificate-references Attribute.
43    CertificateReferences,
44    /// The original complete-revocation-references Attribute.
45    RevocationReferences,
46}
47impl Component {
48    fn oid(self) -> Oid<'static> {
49        let bytes: &'static [u8] = match self {
50            Self::SignatureTimestamp => &[42, 134, 72, 134, 247, 13, 1, 9, 16, 2, 14],
51            Self::CertificateReferences => &[42, 134, 72, 134, 247, 13, 1, 9, 16, 2, 21],
52            Self::RevocationReferences => &[42, 134, 72, 134, 247, 13, 1, 9, 16, 2, 22],
53        };
54        Oid::from_bytes(bytes)
55    }
56}
57
58/// Original selection refusal, before content/provider side effects.
59#[derive(Debug)]
60pub enum SelectionError {
61    /// Original reader, capacity, work or source-processing cause.
62    Processing(Error),
63    /// Wrong source, owner, placement, purpose or token occurrence.
64    WrongOccurrence,
65    /// The selected edition requires this original component.
66    Missing(Component),
67    /// A unique references Attribute cannot be chosen from duplicates.
68    Duplicate {
69        /// Actual second attribute, without a fabricated input offset.
70        property: Node,
71    },
72    /// A registered required/target Attribute does not hold exactly one value.
73    Cardinality {
74        /// Actual malformed attribute.
75        property: Node,
76    },
77}
78impl From<Error> for SelectionError {
79    fn from(cause: Error) -> Self {
80        Self::Processing(cause)
81    }
82}
83
84/// Closed original recipe: only range descriptors are retained, never payload
85/// copies or a caller-selected replacement digest. No detached input is used.
86pub struct ReferenceInput<'input, 'model, 'document> {
87    prepared: &'input Preparation<'model, 'document>,
88    operation: &'input Reader<'document>,
89    ordinal: usize,
90    parts: OwnedVec<&'document [u8]>,
91    covered: Option<OwnedMap<Node, bool>>,
92}
93impl<'s, 'm, 'd> ReferenceInput<'s, 'm, 'd> {
94    /// Selects the actual unsigned owner and the exact registered token value.
95    /// Current EN rules require both references Attributes, and additionally
96    /// signature timestamps for .25. No genTime/order chronology is inferred.
97    pub fn new(
98        prepared: &'s Preparation<'m, 'd>,
99        ordinal: usize,
100        operation: &'s Reader<'d>,
101    ) -> Result<Self, SelectionError> {
102        prepared
103            .document()
104            .message()
105            .require_session(operation, FIELD)
106            .map_err(Error::Cms)?;
107        let work = operation.execution();
108        let refusal = |cause| {
109            SelectionError::Processing(Error::resource(
110                cause,
111                FIELD,
112                prepared.signer().span().start,
113            ))
114        };
115        work.charge_work(1).map_err(refusal)?;
116        let target = prepared
117            .timestamps()
118            .get(ordinal)
119            .ok_or(SelectionError::WrongOccurrence)?;
120        let kind = target
121            .preservation_kind()
122            .ok_or(SelectionError::WrongOccurrence)?;
123        if !kind.is_reference_timestamp() || target.purpose() != Purpose::ValidationData {
124            return Err(SelectionError::WrongOccurrence);
125        }
126        let value = target.value().ok_or(SelectionError::WrongOccurrence)?;
127        let view = prepared.signer_encoding(target.owner(), operation)?;
128        let mut placed = false;
129        for index in 0..view.attributes() {
130            work.charge_work(1).map_err(refusal)?;
131            let attribute = view
132                .attribute(index)
133                .ok_or(SelectionError::WrongOccurrence)?;
134            if Node::of(attribute.span) == target.property() {
135                if attribute.single != Some(value.span()) {
136                    return Err(SelectionError::Cardinality {
137                        property: target.property(),
138                    });
139                }
140                placed = true;
141                break;
142            }
143        }
144        if !placed {
145            return Err(SelectionError::WrongOccurrence);
146        }
147        let plan = Plan::read(view, kind, target.property(), &work)?;
148        let mut parts = work.vector();
149        let mut covered = work.map();
150        if kind == Kind::SignatureReferences {
151            for piece in prepared.signature_of(target.owner(), operation)? {
152                parts.try_push(piece.as_bytes()).map_err(refusal)?;
153            }
154        }
155        plan.emit(&work, &mut parts, &mut covered)?;
156        Ok(Self {
157            prepared,
158            operation,
159            ordinal,
160            parts: parts.into_owned(),
161            covered: Some(covered.into_owned()),
162        })
163    }
164}
165
166// Discovery/validation owns only scalar metadata. No descriptor allocation
167// begins until every mandatory component and cardinality is known. Retaining
168// the two unique references also avoids three full catalogue scans on emission.
169struct Plan<'v, 'd> {
170    view: SignerView<'v, 'd>,
171    signatures: bool,
172    certificate: AttributeEncoding<'d>,
173    revocation: AttributeEncoding<'d>,
174    at: Node,
175}
176impl<'v, 'd> Plan<'v, 'd> {
177    fn read(
178        view: SignerView<'v, 'd>,
179        kind: Kind,
180        at: Node,
181        work: &impl Work,
182    ) -> Result<Self, SelectionError> {
183        let mut certificate = None;
184        let mut revocation = None;
185        let signatures = kind == Kind::SignatureReferences;
186        let mut signature_found = false;
187        for index in 0..view.attributes() {
188            work.charge_work(12)
189                .map_err(|cause| Error::resource(cause, FIELD, at.span().start))?;
190            let attribute = view
191                .attribute(index)
192                .ok_or(SelectionError::WrongOccurrence)?;
193            let oid = attribute.kind.as_bytes();
194            if oid.len() != 11 {
195                continue;
196            }
197            if oid[..10] != [42, 134, 72, 134, 247, 13, 1, 9, 16, 2] {
198                continue;
199            }
200            match oid[10] {
201                14 if signatures => {
202                    value(attribute)?;
203                    signature_found = true;
204                }
205                21 => remember(&mut certificate, attribute)?,
206                22 => remember(&mut revocation, attribute)?,
207                _ => {}
208            }
209        }
210        if signatures && !signature_found {
211            return Err(SelectionError::Missing(Component::SignatureTimestamp));
212        }
213        Ok(Self {
214            view,
215            signatures,
216            at,
217            certificate: certificate
218                .ok_or(SelectionError::Missing(Component::CertificateReferences))?,
219            revocation: revocation
220                .ok_or(SelectionError::Missing(Component::RevocationReferences))?,
221        })
222    }
223    fn emit<W: Work>(
224        &self,
225        work: &W,
226        parts: &mut OwnedVec<&'d [u8], W>,
227        covered: &mut OwnedMap<Node, bool, W>,
228    ) -> Result<(), SelectionError> {
229        if self.signatures {
230            // EN319122-1 table1 note6 permits several signature timestamps.
231            // Source order (not genTime sorting) also matches DSS X1:
232            // https://github.com/esig/dss/blob/9f3af8f2ca672b8948e0a3ee8687e53adf9f5c33/dss-cades/src/main/java/eu/europa/esig/dss/cades/validation/timestamp/CAdESTimestampMessageDigestBuilder.java
233            for index in 0..self.view.attributes() {
234                work.charge_work(12)
235                    .map_err(|cause| Error::resource(cause, FIELD, self.at.span().start))?;
236                let attribute = self
237                    .view
238                    .attribute(index)
239                    .ok_or(SelectionError::WrongOccurrence)?;
240                if attribute.kind == Component::SignatureTimestamp.oid() {
241                    include(attribute, parts, covered)?;
242                }
243            }
244        }
245        include(self.certificate, parts, covered)?;
246        include(self.revocation, parts, covered)
247    }
248}
249fn value(attribute: AttributeEncoding<'_>) -> Result<structured_asn1::Span, SelectionError> {
250    attribute.single.ok_or(SelectionError::Cardinality {
251        property: Node::of(attribute.span),
252    })
253}
254fn remember<'d>(
255    slot: &mut Option<AttributeEncoding<'d>>,
256    attribute: AttributeEncoding<'d>,
257) -> Result<(), SelectionError> {
258    if slot.is_some() {
259        return Err(SelectionError::Duplicate {
260            property: Node::of(attribute.span),
261        });
262    }
263    value(attribute)?;
264    *slot = Some(attribute);
265    Ok(())
266}
267fn include<'d, W: Work>(
268    attribute: AttributeEncoding<'d>,
269    parts: &mut OwnedVec<&'d [u8], W>,
270    covered: &mut OwnedMap<Node, bool, W>,
271) -> Result<(), SelectionError> {
272    let value = value(attribute)?;
273    let refusal = |cause| Error::resource(cause, FIELD, attribute.span.start);
274    parts.try_push(attribute.contents).map_err(refusal)?;
275    // A.1.5.1/2 imprint attrType/attrValues TLVs, not the outer SEQUENCE.
276    covered
277        .try_insert(Node::of(attribute.span), false)
278        .map_err(refusal)?;
279    covered.try_insert(Node::of(value), true).map_err(refusal)?;
280    Ok(())
281}
282
283impl<P, PE> ComputedInput<P, PE> for ReferenceInput<'_, '_, '_> {
284    type Error = Infallible;
285    fn compute<'context, 'reader, 'document>(
286        &mut self,
287        context: &'context ImprintContext<'reader, 'document, P, Self::Error>,
288    ) -> Result<ImprintProof<'context>, InputFailure<Self::Error>>
289    where
290        P: AsnDigest<'document, Error = PE>,
291    {
292        let mut state = context.start()?;
293        for bytes in &self.parts {
294            // Source and current-operation admission occurs before EVERY
295            // descriptor/payload, including an empty signature fragment.
296            state.write_from(bytes, self.operation)?;
297        }
298        let digest = state.finish()?;
299        Ok(context.compare(&digest)?)
300    }
301}
302
303/// Complete TSA facts plus exact reference-byte selection. This does not
304/// assert that referenced artifacts match, are authenticated or are complete.
305pub struct ReferenceChecked<'model, 'document, 'path> {
306    timestamp: Checked<'model, 'document, 'path>,
307    coverage: Coverage<Node, DocumentId<'model, 'document>, (), Memory, Cades>,
308}
309impl<'m, 'd> ReferenceChecked<'m, 'd, '_> {
310    /// Original common CMS/ESS/key/path/time facts and optional sealed actor.
311    pub fn timestamp(&self) -> &Checked<'m, 'd, '_> {
312        &self.timestamp
313    }
314    /// Exact value coverage, usable only with this successful token actor.
315    pub fn coverage(&self) -> &Coverage<Node, DocumentId<'m, 'd>, (), Memory, Cades> {
316        &self.coverage
317    }
318}
319
320impl<'m, 'd> Context<'_, 'm, 'd> {
321    /// Runs the existing complete TSA pipeline with the sealed .25/.26 input.
322    /// This is `ValidationData` purpose, never Archive or Signature purpose;
323    /// successful legacy math cannot waive modern baseline conformance.
324    #[expect(
325        clippy::result_large_err,
326        reason = "original profile causes remain unboxed"
327    )]
328    pub fn check_references<'a, 'p, P, PE, RE>(
329        &'a self,
330        provider: &'a P,
331        path: &'p CheckedPath<'d>,
332        certificate_parent: &Reader<'d>,
333        time: &CheckTime<'_, 'd>,
334        input: &mut ReferenceInput<'_, 'm, 'd>,
335        recognizes: impl FnMut(
336            &tsp::TokenExtension<'_>,
337            &structured_asn1::memory::Execution<'_>,
338        ) -> Result<bool, RE>,
339    ) -> Result<ReferenceChecked<'m, 'd, 'p>, CheckError<Infallible, PE, RE>>
340    where
341        P: for<'scope> SignatureProvider<'scope, Error = PE>,
342    {
343        if self.ordinal != input.ordinal
344            || !core::ptr::eq(self.prepared, input.prepared)
345            || input.covered.is_none()
346        {
347            return Err(CheckError::Selection(Error::Foreign {
348                at: self.entry.property().span().start,
349            }));
350        }
351        let value = self
352            .entry
353            .value()
354            .ok_or(CheckError::Selection(Error::Foreign {
355                at: self.entry.property().span().start,
356            }))?;
357        let timestamp = self.check_computed_profile(
358            provider,
359            path,
360            certificate_parent,
361            time,
362            input,
363            (
364                recognizes,
365                |_| Ok::<(), tsp::ProfileFailure<Infallible>>(()),
366            ),
367        )?;
368        let coverage = Coverage::for_token(
369            ISSUER,
370            self.prepared.document().snapshot(),
371            self.entry.owner(),
372            self.entry.property(),
373            Node::of(value.span()),
374            Purpose::ValidationData,
375            input
376                .covered
377                .take()
378                .ok_or(CheckError::Selection(Error::Foreign {
379                    at: self.entry.property().span().start,
380                }))?,
381        );
382        Ok(ReferenceChecked {
383            timestamp,
384            coverage,
385        })
386    }
387}