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;