Skip to main content

structured_ltv/baseline/
timed.rs

1//! Whole original TSA-path census and root signature-time static association.
2
3use super::BasicStage;
4use crate::pki::SignerReceipt;
5use crate::source::{Query, ScopeError};
6use crate::timestamp::{
7    Purpose, SignatureTime, SignatureTimeBinding, SignatureTimeError, SignatureTimeFailure,
8    TimestampPaths, TimestampScope, existence_time_path, signature_time_path,
9};
10use structured_asn1::memory::{MemoryError, OwnedVec, Work};
11
12/// One actual root signature timestamp's complete static certificate binding.
13/// Negative results do not disappear because a different timestamp succeeded.
14pub enum SignatureTimeFact<'a, S, N, D, G, I> {
15    /// Exact original signature input and full generation/static path interval.
16    Bound(SignatureTime<'a, S, N, D, G, I>),
17    /// Completed insufficient root certificate/interval fact, not source refusal.
18    Unproven {
19        /// Original root signature timestamp property.
20        property: N,
21        /// Exact completed negative cause.
22        cause: SignatureTimeFailure,
23    },
24}
25
26/// Whole original token/TSA path set plus every root signature-time association.
27/// This does not assert pre-revocation chronology, current/historical status,
28/// format material completeness, algorithm-obsolescence policy or caller trust.
29/// In particular it is NOT an unconditional B-T acceptance verdict.
30pub struct Timed<'a, 'certificate, 'document, B, S, N, D, G, I> {
31    basic: &'a B,
32    paths: &'a TimestampPaths<'a, 'certificate, 'document, N, D, G, I>,
33    signatures: OwnedVec<SignatureTimeFact<'a, S, N, D, G, I>>,
34    static_complete: bool,
35}
36
37impl<'a, 'c, 'd, B, S, N, D, G, I> Timed<'a, 'c, 'd, B, S, N, D, G, I> {
38    /// Same original basic stage.
39    #[must_use]
40    pub fn basic(&self) -> &'a B {
41        self.basic
42    }
43    /// Complete source-derived root AND nested-counter timestamp/TSA paths.
44    #[must_use]
45    pub fn paths(&self) -> &'a TimestampPaths<'a, 'c, 'd, N, D, G, I> {
46        self.paths
47    }
48    /// Every original root signature timestamp, including negative associations.
49    #[must_use]
50    pub fn signatures(&self) -> &[SignatureTimeFact<'a, S, N, D, G, I>] {
51        &self.signatures
52    }
53    /// At least one authorized root existence timestamp exists and every required timestamp's
54    /// complete accuracy interval is covered by the actual static signer path.
55    /// Counter/data/archive timestamps cannot substitute for this condition.
56    #[must_use]
57    pub fn all_static_time_requirements_met(&self) -> bool {
58        self.static_complete
59    }
60}
61
62/// Terminal original source/selection/query refusal; no partial timed report.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum TimeError<N> {
65    /// Wrong scope, source or selected root/counter census.
66    Foreign {
67        /// Original selected signature.
68        signature: N,
69    },
70    /// Original current-operation/source failure, including the complete
71    /// counter-census membership query.
72    Scope(ScopeError<N>),
73    /// Original signature-time association processing failed.
74    Signature(SignatureTimeError<N>),
75    /// Work or retained report capacity was refused before use.
76    Resource {
77        /// Original selecting signature/property.
78        node: N,
79        /// Exact resource refusal.
80        cause: MemoryError,
81    },
82}
83
84/// Complete original static time facts in the query's identity domain.
85pub type TimeResult<'a, 'c, 'd, B, S, Q, I> = Result<
86    Timed<'a, 'c, 'd, B, S, <Q as Query>::Id, <Q as Query>::Document, <Q as Query>::Generation, I>,
87    TimeError<<Q as Query>::Id>,
88>;
89
90/// Bind all-token TSA-path facts to the SAME root/counter source and every actual
91/// root signature timestamp to the checked signer. Reuses previously established
92/// complete generation intervals, not another UTC parse or certificate hash.
93/// EN319132-1 §§5.3/6.3(n,o) distinguish the signature timestamp from other
94/// kinds and require EACH such timestamp before expiration/revocation. This
95/// establishes the static interval part; actual revocation facts stay explicit.
96/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
97pub fn timestamped<'a, 'c, 'd, 'path, 'document, B, Q, I>(
98    basic: &'a B,
99    paths: &'a TimestampPaths<'a, 'c, 'd, Q::Id, Q::Document, Q::Generation, I>,
100    query: &Q,
101) -> TimeResult<'a, 'c, 'd, B, B::Signer, Q, I>
102where
103    'document: 'path,
104    B: BasicStage<'path, 'document, Id = Q::Id, Document = Q::Document, Generation = Q::Generation>,
105    Q: Query,
106{
107    timed(basic, paths, None, query)
108}
109
110/// Same complete TSA/static core with one format-authorized archive input as
111/// the fallback existence time when no root signature timestamp is written.
112/// The format issuer supplies the original archive property, not the caller.
113/// EN319142-1 §6.3 allows `PAdES` document timestamps for this purpose; default
114/// XAdES/CAdES composition continues to require its signature timestamp kind.
115/// <https://www.etsi.org/deliver/etsi_EN/319100_319199/31914201/01.02.01_60/en_31914201v010201p.pdf>
116pub fn timestamped_existence<'a, 'c, 'd, 'path, 'document, B, Q, I>(
117    _issuer: I,
118    basic: &'a B,
119    paths: &'a TimestampPaths<'a, 'c, 'd, Q::Id, Q::Document, Q::Generation, I>,
120    archive: Option<Q::Id>,
121    query: &Q,
122) -> TimeResult<'a, 'c, 'd, B, B::Signer, Q, I>
123where
124    'document: 'path,
125    B: BasicStage<'path, 'document, Id = Q::Id, Document = Q::Document, Generation = Q::Generation>,
126    Q: Query,
127{
128    timed(basic, paths, archive, query)
129}
130
131fn timed<'a, 'c, 'd, 'path, 'document, B, Q, I>(
132    basic: &'a B,
133    paths: &'a TimestampPaths<'a, 'c, 'd, Q::Id, Q::Document, Q::Generation, I>,
134    archive: Option<Q::Id>,
135    query: &Q,
136) -> TimeResult<'a, 'c, 'd, B, B::Signer, Q, I>
137where
138    'document: 'path,
139    B: BasicStage<'path, 'document, Id = Q::Id, Document = Q::Document, Generation = Q::Generation>,
140    Q: Query,
141{
142    let selected = basic.signer().signature();
143    let signature = selected.node;
144    let required = paths.required();
145    let resource = |cause| TimeError::Resource {
146        node: signature,
147        cause,
148    };
149    if !matches!(required.scope(), TimestampScope::All(_))
150        || required.scope().location().snapshot != query.snapshot()
151        || selected.snapshot != query.snapshot()
152        || !query
153            .equal(required.signature(), signature)
154            .map_err(resource)?
155        || !required
156            .includes_counters(basic.counters(), query)
157            .map_err(TimeError::Scope)?
158    {
159        return Err(TimeError::Foreign { signature });
160    }
161    query.admit(signature, 1).map_err(TimeError::Scope)?;
162    let mut signature_written = false;
163    if let Some(archive) = archive {
164        let mut archive_written = false;
165        for entry in required.entries() {
166            query.admit(entry.property, 1).map_err(TimeError::Scope)?;
167            if !query.equal(entry.signature, signature).map_err(resource)? {
168                continue;
169            }
170            if entry.purpose == Purpose::Signature {
171                signature_written = true;
172            }
173            if entry.purpose == Purpose::Archive
174                && query.equal(entry.property, archive).map_err(resource)?
175            {
176                archive_written = true;
177            }
178        }
179        if !archive_written {
180            return Err(TimeError::Foreign { signature });
181        }
182    }
183    let mut signatures = query.execution().vector();
184    let mut static_complete = basic.all_requirements_met();
185    for path in paths.paths() {
186        query.execution().charge_work(1).map_err(resource)?;
187        let verified = path.actor().verified();
188        if verified.purpose() != Purpose::Signature && archive.is_none() {
189            continue;
190        }
191        if !query
192            .equal(verified.signature(), signature)
193            .map_err(resource)?
194        {
195            continue;
196        }
197        let fallback = !signature_written
198            && verified.purpose() == Purpose::Archive
199            && match archive {
200                Some(archive) => query
201                    .equal(verified.property(), archive)
202                    .map_err(resource)?,
203                None => false,
204            };
205        if verified.purpose() != Purpose::Signature && !fallback {
206            continue;
207        }
208        signatures.try_reserve(1).map_err(resource)?;
209        let association = if fallback {
210            existence_time_path(basic.signer(), path, query)
211        } else {
212            signature_time_path(basic.signer(), path, query)
213        };
214        let fact = match association.map_err(TimeError::Signature)? {
215            SignatureTimeBinding::Bound(value) => SignatureTimeFact::Bound(value),
216            SignatureTimeBinding::Unproven(cause) => {
217                static_complete = false;
218                SignatureTimeFact::Unproven {
219                    property: verified.property(),
220                    cause,
221                }
222            }
223        };
224        signatures.try_push(fact).map_err(resource)?;
225    }
226    if signatures.is_empty() {
227        static_complete = false;
228    }
229    query.admit(signature, 1).map_err(TimeError::Scope)?;
230    Ok(Timed {
231        basic,
232        paths,
233        signatures: signatures.into_owned(),
234        static_complete,
235    })
236}