Skip to main content

structured_ltv/timestamp/
signature.rs

1//! A signature timestamp's association with the signer's checked path.
2
3use super::{Interval, Purpose, TemporalError, Verified};
4use crate::pki::{SignerReceipt, TimestampPath};
5use crate::source::{Query, ScopeError};
6use structured_pkix::temporal::LeapTable;
7
8/// A completed mathematical/static association, still requiring authenticated
9/// TSA path/status and signer revocation/algorithm/policy processing.
10pub struct SignatureTime<'receipt, S, N, D, G, I> {
11    signer: &'receipt S,
12    timestamp: &'receipt Verified<N, D, G, I>,
13    generation: Interval,
14}
15
16impl<'receipt, S, N, D, G, I> SignatureTime<'receipt, S, N, D, G, I> {
17    /// Same original selected signature/certificate and checked static path.
18    #[must_use]
19    pub fn signer(&self) -> &'receipt S {
20        self.signer
21    }
22    /// Same complete token check over the selected original signature value.
23    #[must_use]
24    pub fn timestamp(&self) -> &'receipt Verified<N, D, G, I> {
25        self.timestamp
26    }
27    /// Entire stated generation interval, never a bare claimed signing time.
28    #[must_use]
29    pub fn generation(&self) -> Interval {
30        self.generation
31    }
32}
33
34/// Missing association facts do not assert that a certificate has expired.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum SignatureTimeFailure {
37    /// Selected key/certificate or an original path digest is not established.
38    Certificate,
39    /// Static path was not checked over the complete generation interval.
40    Interval,
41}
42
43/// Original execution/context error, not a completed negative time claim.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum SignatureTimeError<N> {
46    /// Original current-source/generation/resource refusal.
47    Scope(ScopeError<N>),
48    /// The receipt belongs to another selected signature or source generation.
49    Foreign {
50        /// Original timestamp property.
51        property: N,
52    },
53    /// The timestamp covers another kind of input; it cannot replace a
54    /// signature timestamp simply because it also covers some signature bytes.
55    Purpose {
56        /// Original timestamp property.
57        property: N,
58    },
59    /// Full stated accuracy/UTC interval cannot be established.
60    Time {
61        /// Original timestamp property.
62        property: N,
63        /// Original UTC uncertainty or execution refusal.
64        cause: TemporalError,
65    },
66}
67
68/// Mathematical/static binding or explicit insufficient facts, not a B-T grade.
69pub enum SignatureTimeBinding<'receipt, S, N, D, G, I> {
70    /// Same selected signature, with full generation-time static path coverage.
71    Bound(SignatureTime<'receipt, S, N, D, G, I>),
72    /// Required certificate/interval facts have not been established.
73    Unproven(SignatureTimeFailure),
74}
75
76/// Association result in the query's original identity domain.
77pub type SignatureTimeResult<'receipt, S, Q, I> = Result<
78    SignatureTimeBinding<
79        'receipt,
80        S,
81        <Q as Query>::Id,
82        <Q as Query>::Document,
83        <Q as Query>::Generation,
84        I,
85    >,
86    SignatureTimeError<<Q as Query>::Id>,
87>;
88
89/// Associate a complete original signature timestamp with the actual signing
90/// certificate/path, preserving source generation and its full accuracy.
91///
92/// EN319132-1 V1.3.1 ยงยง5.3/6.3(o) timestamp the original signature value and
93/// require creation before signing-certificate expiry/revocation. A checked
94/// path interval containing the entire generation interval supplies static
95/// validity; revocation and TSA authority remain separate necessary facts.
96/// Neither an archive timestamp nor a claimed signing time can substitute here.
97/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
98pub fn signature_time<'receipt, 'path, 'document, S, Q, I>(
99    signer: &'receipt S,
100    timestamp: &'receipt Verified<Q::Id, Q::Document, Q::Generation, I>,
101    utc: &LeapTable<'_>,
102    query: &Q,
103) -> SignatureTimeResult<'receipt, S, Q, I>
104where
105    'document: 'path,
106    S: SignerReceipt<
107            'path,
108            'document,
109            Id = Q::Id,
110            Document = Q::Document,
111            Generation = Q::Generation,
112        >,
113    Q: Query,
114{
115    bind(signer, timestamp, query, || {
116        timestamp.interval(utc, query.execution())
117    })
118}
119
120/// Reuse the complete generation interval already established by the actual
121/// checked TSA path for this SAME token receipt. This has the same
122/// source/purpose/certificate/static interval semantics as [`signature_time`],
123/// without repeating UTC lookup or arithmetic.
124pub fn signature_time_path<'receipt, 'path, 'document, S, Q, I>(
125    signer: &'receipt S,
126    path: &'receipt TimestampPath<'_, '_, '_, Q::Id, Q::Document, Q::Generation, I>,
127    query: &Q,
128) -> SignatureTimeResult<'receipt, S, Q, I>
129where
130    'document: 'path,
131    S: SignerReceipt<
132            'path,
133            'document,
134            Id = Q::Id,
135            Document = Q::Document,
136            Generation = Q::Generation,
137        >,
138    Q: Query,
139{
140    bind(signer, path.actor().verified(), query, || {
141        Ok(path.generation())
142    })
143}
144
145fn bind<'receipt, 'path, 'document, S, Q, I>(
146    signer: &'receipt S,
147    timestamp: &'receipt Verified<Q::Id, Q::Document, Q::Generation, I>,
148    query: &Q,
149    interval: impl FnOnce() -> Result<Interval, TemporalError>,
150) -> SignatureTimeResult<'receipt, S, Q, I>
151where
152    'document: 'path,
153    S: SignerReceipt<
154            'path,
155            'document,
156            Id = Q::Id,
157            Document = Q::Document,
158            Generation = Q::Generation,
159        >,
160    Q: Query,
161{
162    let selected = signer.signature();
163    let property = timestamp.property();
164    if selected.snapshot != query.snapshot() || timestamp.token().snapshot != selected.snapshot {
165        return Err(SignatureTimeError::Foreign { property });
166    }
167    if !query
168        .equal(timestamp.signature(), selected.node)
169        .map_err(|cause| {
170            SignatureTimeError::Scope(ScopeError::Resource {
171                node: property,
172                cause,
173            })
174        })?
175    {
176        return Err(SignatureTimeError::Foreign { property });
177    }
178    query
179        .admit(selected.node, 1)
180        .map_err(SignatureTimeError::Scope)?;
181    if timestamp.purpose() != Purpose::Signature {
182        return Err(SignatureTimeError::Purpose { property });
183    }
184    let Some(path) = signer.path() else {
185        return Ok(SignatureTimeBinding::Unproven(
186            SignatureTimeFailure::Certificate,
187        ));
188    };
189    let generation = interval().map_err(|cause| SignatureTimeError::Time { property, cause })?;
190    query
191        .admit(selected.node, 1)
192        .map_err(SignatureTimeError::Scope)?;
193    let checked = path.interval();
194    if checked.from_instant() > generation.earliest || checked.to_instant() < generation.latest {
195        return Ok(SignatureTimeBinding::Unproven(
196            SignatureTimeFailure::Interval,
197        ));
198    }
199    Ok(SignatureTimeBinding::Bound(SignatureTime {
200        signer,
201        timestamp,
202        generation,
203    }))
204}