1use super::{Interval, Purpose, TemporalError, Verified};
4use crate::pki::{SignerReceipt, TimestampPath};
5use crate::source::{Query, ScopeError};
6use structured_pkix::temporal::LeapTable;
7
8#[derive(Clone, Copy)]
9enum Input {
10 SignatureValue,
11 Existence,
12}
13
14pub struct SignatureTime<'receipt, S, N, D, G, I> {
17 signer: &'receipt S,
18 timestamp: &'receipt Verified<N, D, G, I>,
19 generation: Interval,
20}
21
22impl<'receipt, S, N, D, G, I> SignatureTime<'receipt, S, N, D, G, I> {
23 #[must_use]
25 pub fn signer(&self) -> &'receipt S {
26 self.signer
27 }
28 #[must_use]
30 pub fn timestamp(&self) -> &'receipt Verified<N, D, G, I> {
31 self.timestamp
32 }
33 #[must_use]
35 pub fn generation(&self) -> Interval {
36 self.generation
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum SignatureTimeFailure {
43 Certificate,
45 Interval,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum SignatureTimeError<N> {
52 Scope(ScopeError<N>),
54 Foreign {
56 property: N,
58 },
59 Purpose {
62 property: N,
64 },
65 Time {
67 property: N,
69 cause: TemporalError,
71 },
72}
73
74pub enum SignatureTimeBinding<'receipt, S, N, D, G, I> {
76 Bound(SignatureTime<'receipt, S, N, D, G, I>),
78 Unproven(SignatureTimeFailure),
80}
81
82pub type SignatureTimeResult<'receipt, S, Q, I> = Result<
84 SignatureTimeBinding<
85 'receipt,
86 S,
87 <Q as Query>::Id,
88 <Q as Query>::Document,
89 <Q as Query>::Generation,
90 I,
91 >,
92 SignatureTimeError<<Q as Query>::Id>,
93>;
94
95pub fn signature_time<'receipt, 'path, 'document, S, Q, I>(
105 signer: &'receipt S,
106 timestamp: &'receipt Verified<Q::Id, Q::Document, Q::Generation, I>,
107 utc: &LeapTable<'_>,
108 query: &Q,
109) -> SignatureTimeResult<'receipt, S, Q, I>
110where
111 'document: 'path,
112 S: SignerReceipt<
113 'path,
114 'document,
115 Id = Q::Id,
116 Document = Q::Document,
117 Generation = Q::Generation,
118 >,
119 Q: Query,
120{
121 bind(signer, timestamp, query, Input::SignatureValue, || {
122 timestamp.interval(utc, query.execution())
123 })
124}
125
126pub fn signature_time_path<'receipt, 'path, 'document, S, Q, I>(
131 signer: &'receipt S,
132 path: &'receipt TimestampPath<'_, '_, '_, Q::Id, Q::Document, Q::Generation, I>,
133 query: &Q,
134) -> SignatureTimeResult<'receipt, S, Q, I>
135where
136 'document: 'path,
137 S: SignerReceipt<
138 'path,
139 'document,
140 Id = Q::Id,
141 Document = Q::Document,
142 Generation = Q::Generation,
143 >,
144 Q: Query,
145{
146 bind(
147 signer,
148 path.actor().verified(),
149 query,
150 Input::SignatureValue,
151 || Ok(path.generation()),
152 )
153}
154
155pub fn existence_time_path<'receipt, 'path, 'document, S, Q, I>(
162 signer: &'receipt S,
163 path: &'receipt TimestampPath<'_, '_, '_, Q::Id, Q::Document, Q::Generation, I>,
164 query: &Q,
165) -> SignatureTimeResult<'receipt, S, Q, I>
166where
167 'document: 'path,
168 S: SignerReceipt<
169 'path,
170 'document,
171 Id = Q::Id,
172 Document = Q::Document,
173 Generation = Q::Generation,
174 >,
175 Q: Query,
176{
177 bind(
178 signer,
179 path.actor().verified(),
180 query,
181 Input::Existence,
182 || Ok(path.generation()),
183 )
184}
185
186fn bind<'receipt, 'path, 'document, S, Q, I>(
187 signer: &'receipt S,
188 timestamp: &'receipt Verified<Q::Id, Q::Document, Q::Generation, I>,
189 query: &Q,
190 input: Input,
191 interval: impl FnOnce() -> Result<Interval, TemporalError>,
192) -> SignatureTimeResult<'receipt, S, Q, I>
193where
194 'document: 'path,
195 S: SignerReceipt<
196 'path,
197 'document,
198 Id = Q::Id,
199 Document = Q::Document,
200 Generation = Q::Generation,
201 >,
202 Q: Query,
203{
204 let selected = signer.signature();
205 let property = timestamp.property();
206 if selected.snapshot != query.snapshot() || timestamp.token().snapshot != selected.snapshot {
207 return Err(SignatureTimeError::Foreign { property });
208 }
209 if !query
210 .equal(timestamp.signature(), selected.node)
211 .map_err(|cause| {
212 SignatureTimeError::Scope(ScopeError::Resource {
213 node: property,
214 cause,
215 })
216 })?
217 {
218 return Err(SignatureTimeError::Foreign { property });
219 }
220 query
221 .admit(selected.node, 1)
222 .map_err(SignatureTimeError::Scope)?;
223 if timestamp.purpose() != Purpose::Signature
224 && !(matches!(input, Input::Existence) && timestamp.purpose() == Purpose::Archive)
225 {
226 return Err(SignatureTimeError::Purpose { property });
227 }
228 let Some(path) = signer.path() else {
229 return Ok(SignatureTimeBinding::Unproven(
230 SignatureTimeFailure::Certificate,
231 ));
232 };
233 let generation = interval().map_err(|cause| SignatureTimeError::Time { property, cause })?;
234 query
235 .admit(selected.node, 1)
236 .map_err(SignatureTimeError::Scope)?;
237 let checked = path.interval();
238 if checked.from_instant() > generation.earliest || checked.to_instant() < generation.latest {
239 return Ok(SignatureTimeBinding::Unproven(
240 SignatureTimeFailure::Interval,
241 ));
242 }
243 Ok(SignatureTimeBinding::Bound(SignatureTime {
244 signer,
245 timestamp,
246 generation,
247 }))
248}