Skip to main content

structured_ltv/
timestamp.rs

1//! Retained mathematical timestamp evidence with its original source identity.
2
3use crate::source::Location;
4use core::marker::PhantomData;
5use structured_asn1::memory::{MemoryError, Work};
6use structured_cms::timestamp::{BoundUncertainty, Evidence};
7use structured_pkix::temporal::{Instant, LeapTable};
8use structured_pkix::{Certificate, path::WorkingKey};
9
10mod obligation;
11pub use obligation::{
12    ObligationError, PathBindingResult, RequiredToken, TimestampPathBinding, TimestampPathFailure,
13    TimestampPaths, TimestampRequirements, TimestampScope,
14};
15mod signature;
16pub use signature::{
17    SignatureTime, SignatureTimeBinding, SignatureTimeError, SignatureTimeFailure,
18    SignatureTimeResult, signature_time, signature_time_path,
19};
20
21/// What a timestamp proves the existence of.
22///
23/// Every `AdES` format writes these four kinds under its own names (`XAdES`
24/// properties, `CAdES` attributes, `PAdES` document timestamps); the format layer
25/// maps its own input role onto one of them.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum Purpose {
28    /// Signed data objects, before the signature was produced.
29    Content,
30    /// The signature value.
31    Signature,
32    /// The signature and its validation-data references.
33    ValidationData,
34    /// The signature, its validation material and earlier timestamps.
35    Archive,
36}
37
38/// Successfully checked token covering the input its format layer selected.
39///
40/// Only the holder of issuer `I` constructs it, from its own complete token
41/// check over that input (see [`crate`] on issuers); this is not a TSA trust
42/// decision or a level.
43#[derive(Debug)]
44pub struct Verified<N, D, G, I> {
45    token: Location<N, D, G>,
46    signature: N,
47    property: N,
48    purpose: Purpose,
49    evidence: Evidence,
50    issuer: PhantomData<I>,
51}
52
53/// Successful mathematical timestamp check with its actual selected signer.
54///
55/// The certificate and effective key come from the same complete-check context,
56/// not a later caller-supplied association; only the holder of issuer `I`
57/// constructs it. This still establishes no TSA path, status or trust.
58/// Certificate/key borrows need not retain the provider or temporary context.
59pub struct CheckedActor<'certificate, 'document, N, D, G, I> {
60    verified: Verified<N, D, G, I>,
61    certificate: &'certificate Certificate<'document>,
62    key: WorkingKey<'document>,
63}
64
65impl<'certificate, 'document, N, D, G, I> CheckedActor<'certificate, 'document, N, D, G, I> {
66    /// Binds evidence to the certificate and effective key its check used.
67    /// Requiring the issuer value keeps a caller of the format from
68    /// associating the evidence with another certificate.
69    #[must_use]
70    pub fn new(
71        _issuer: I,
72        verified: Verified<N, D, G, I>,
73        certificate: &'certificate Certificate<'document>,
74        key: WorkingKey<'document>,
75    ) -> Self {
76        Self {
77            verified,
78            certificate,
79            key,
80        }
81    }
82
83    /// Actual candidate used for the successful token signature/ESS checks.
84    #[must_use]
85    pub fn certificate(&self) -> &'certificate Certificate<'document> {
86        self.certificate
87    }
88
89    /// Actual effective parameters supplied to that same selected provider.
90    #[must_use]
91    pub fn key(&self) -> WorkingKey<'document> {
92        self.key
93    }
94
95    /// Independently retained mathematical evidence with original identity.
96    #[must_use]
97    pub fn verified(&self) -> &Verified<N, D, G, I> {
98        &self.verified
99    }
100
101    /// Ends the borrowed signer association while moving independent evidence.
102    /// This performs no copy/allocation and makes no subsequent TSA claim.
103    #[must_use]
104    pub fn into_verified(self) -> Verified<N, D, G, I> {
105        self.verified
106    }
107}
108
109/// Factual generation-time interval, not the document's signing-time interval.
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct Interval {
112    /// Earliest generation consistent with the authority's stated accuracy.
113    pub earliest: Instant,
114    /// Latest generation, including any subnanosecond rounding.
115    pub latest: Instant,
116}
117
118/// Complete timestamp-generation interval relative to an explicit event label.
119/// This temporal fact does not authenticate that event or associate a
120/// certificate/status response with the timestamp's actual signing actor.
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum BoundaryRelation {
123    /// Every possible generation instant is strictly before the event.
124    Before,
125    /// The authority stated an exact generation instant equal to the event.
126    At,
127    /// Every possible generation instant is strictly after the event.
128    After,
129    /// The complete interval touches or straddles the event; strict ordering
130    /// cannot be established. Equality of just one endpoint is insufficient.
131    Overlaps,
132}
133
134/// Current execution refusal is distinct from incomplete authenticated UTC facts.
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum TemporalError {
137    /// Admission failed before UTC table lookup or arithmetic.
138    Resource(MemoryError),
139    /// Existing RFC3161/UTC kernel could not establish a complete interval.
140    Uncertain(BoundUncertainty),
141}
142
143impl<N, D, G, I> Verified<N, D, G, I> {
144    /// Retains a complete token check with the original identity of its input.
145    ///
146    /// `token` is the original token enclosure, `signature` the signature whose
147    /// input was covered, `property` the timestamp property or attribute and
148    /// `purpose` the kind of input selected for the check. Requiring the
149    /// issuer value keeps a caller of the format from relabeling evidence.
150    #[must_use]
151    pub fn new(
152        _issuer: I,
153        token: Location<N, D, G>,
154        signature: N,
155        property: N,
156        purpose: Purpose,
157        evidence: Evidence,
158    ) -> Self {
159        Self {
160            token,
161            signature,
162            property,
163            purpose,
164            evidence,
165            issuer: PhantomData,
166        }
167    }
168}
169
170impl<N: Copy, D: Copy, G: Copy, I> Verified<N, D, G, I> {
171    /// Original token enclosure, not an offset in its decoded ASN.1 source.
172    #[must_use]
173    pub fn token(&self) -> Location<N, D, G> {
174        self.token
175    }
176    /// Actual selected signature whose original input was covered.
177    #[must_use]
178    pub fn signature(&self) -> N {
179        self.signature
180    }
181    /// Original timestamp property, independently of its encapsulated token.
182    #[must_use]
183    pub fn property(&self) -> N {
184        self.property
185    }
186    /// Kind of input selected during checking.
187    #[must_use]
188    pub fn purpose(&self) -> Purpose {
189        self.purpose
190    }
191    /// Independently retained token facts and original ASN.1 source projection.
192    #[must_use]
193    pub fn evidence(&self) -> &Evidence {
194        &self.evidence
195    }
196    /// Moves retained evidence without a second allocation or deep copy.
197    #[must_use]
198    pub fn into_evidence(self) -> Evidence {
199        self.evidence
200    }
201
202    /// Derives the complete authority generation interval using the existing
203    /// RFC3161/UTC arithmetic, funded by the current operation. Missing accuracy
204    /// remains uncertainty, never an implicitly exact time. Caller policy is
205    /// responsible for authenticating the supplied leap table.
206    ///
207    /// EN319132-1 ยง6.3(o) requires the signature timestamp to precede signer
208    /// revocation/expiry; its bare genTime is insufficient when accuracy is
209    /// stated. This helper supplies endpoints, not path/status acceptance.
210    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
211    pub fn interval<W: Work>(
212        &self,
213        utc: &LeapTable<'_>,
214        execution: &W,
215    ) -> Result<Interval, TemporalError> {
216        interval(
217            self.evidence.facts().gen_time(),
218            self.evidence.facts().accuracy(),
219            utc,
220            execution,
221        )
222    }
223
224    /// Compares the complete generation interval to an explicit event label.
225    ///
226    /// This never substitutes the bare genTime or a claimed signing time for
227    /// the interval, nor decides certificate/status/trust policy. Missing
228    /// accuracy, invalid leap labels and absent UTC coverage remain
229    /// uncertainty; all table work is admitted through current execution.
230    pub fn relation<W: Work>(
231        &self,
232        boundary: Instant,
233        utc: &LeapTable<'_>,
234        execution: &W,
235    ) -> Result<BoundaryRelation, TemporalError> {
236        relation(self.interval(utc, execution)?, boundary, utc, execution)
237    }
238}
239
240fn relation<W: Work>(
241    interval: Interval,
242    boundary: Instant,
243    utc: &LeapTable<'_>,
244    execution: &W,
245) -> Result<BoundaryRelation, TemporalError> {
246    let bits = usize::BITS - utc.transitions().len().leading_zeros();
247    execution
248        .charge_work(16 + 3 * (u64::from(bits) + 1))
249        .map_err(TemporalError::Resource)?;
250    utc.shift(boundary, 0)
251        .map_err(|cause| TemporalError::Uncertain(BoundUncertainty::Utc(cause)))?;
252    Ok(if interval.latest < boundary {
253        BoundaryRelation::Before
254    } else if interval.earliest > boundary {
255        BoundaryRelation::After
256    } else if interval.earliest == boundary && interval.latest == boundary {
257        BoundaryRelation::At
258    } else {
259        BoundaryRelation::Overlaps
260    })
261}
262
263fn interval<W: Work>(
264    reading: structured_pkix::temporal::Reading,
265    accuracy: Option<structured_cms::timestamp::Accuracy>,
266    utc: &LeapTable<'_>,
267    execution: &W,
268) -> Result<Interval, TemporalError> {
269    let interval = structured_cms::timestamp::interval_in(reading, accuracy, utc, execution)
270        .map_err(|cause| match cause {
271            structured_cms::timestamp::IntervalError::Resource(cause) => {
272                TemporalError::Resource(cause)
273            }
274            structured_cms::timestamp::IntervalError::Uncertain(cause) => {
275                TemporalError::Uncertain(cause)
276            }
277        })?;
278    Ok(Interval {
279        earliest: interval.earliest,
280        latest: interval.latest,
281    })
282}
283
284#[cfg(test)]
285mod tests;