1use crate::{
4 container::Container,
5 error::{Error, ErrorKind},
6 model::admit,
7};
8use structured_asn1::memory::Work;
9use structured_cms::digest::{DigestBatch, DigestError, PassError};
10use structured_pkix::algorithm::digest::{Algorithm, AsnDigest};
11pub(super) mod sources;
12mod status;
13pub use sources::{Association, Associations, SourceError};
14pub use status::{StatusBinding, StatusError};
15
16pub struct Binding<'b, 'v, 'a, 's, D, G> {
20 parsed: &'b super::Parsed<'v, 's, D, G>,
21 root: &'b Container<'a, 's, D, G>,
22 vri: &'b super::Vri,
23}
24
25impl<'v, 's, D: Copy + Eq, G: Copy + Eq> super::Parsed<'v, 's, D, G> {
26 pub fn bind_signature_vri<'b, 'a, 'p, P: AsnDigest<'p>>(
29 &'b self,
30 root: &'b Container<'a, 's, D, G>,
31 provider: &'p P,
32 operation: &impl Work,
33 ) -> Result<Option<Binding<'b, 'v, 'a, 's, D, G>>, BindingError<P::Error>> {
34 let signature = root.signature();
35 let owner = signature.owner();
36 let location = signature.location();
37 let work = owner.value.session.query(operation);
38 admit(&work, location, 1).map_err(BindingError::Pdf)?;
39 if !core::ptr::eq(owner.value.session, self.owner.session)
40 || owner.value.stamp != self.owner.stamp
41 {
42 return Err(BindingError::Pdf(Error {
43 kind: ErrorKind::Foreign,
44 location,
45 }));
46 }
47 if self.owner.vri.is_empty() {
48 return Ok(None);
49 }
50 let name = root
51 .vri_name(provider, &work)
52 .map_err(BindingError::Name)?
53 .ok_or(BindingError::UnsupportedName)?;
54 let matched = self.owner.vri_names.get(&name, &work).map_err(|cause| {
55 BindingError::Pdf(Error {
56 kind: ErrorKind::Resource(cause),
57 location,
58 })
59 })?;
60 Ok(matched.map(|&ordinal| Binding {
61 parsed: self,
62 root,
63 vri: &self.owner.vri[ordinal],
64 }))
65 }
66}
67
68#[derive(Debug)]
70pub enum BindingError<E> {
71 Pdf(Error),
73 Name(NameError<E>),
75 UnsupportedName,
77}
78
79#[derive(Debug)]
81pub enum TimestampError<'a, P, R> {
82 Pdf(Error),
84 Material {
86 location: crate::source::Location,
88 cause: &'a super::MaterialError,
90 },
91 Encoding(structured_asn1::Error),
93 Check(
95 structured_cms::timestamp::ProfileCheckError<
96 structured_cms::octets::SourceError,
97 P,
98 R,
99 structured_cades::Error,
100 >,
101 ),
102}
103
104pub struct Time<'q, 'certificate, D, G> {
106 checks: structured_cms::timestamp::CheckedTimestamp<'q>,
107 certificate: &'q structured_pkix::Certificate<'certificate>,
108 key: structured_pkix::path::WorkingKey<'certificate>,
109 snapshot: structured_ltv::source::Snapshot<D, G>,
110 root: crate::timestamp::Node,
111 property: crate::timestamp::Node,
112 token: crate::timestamp::Node,
113 session: &'q crate::Session,
114}
115impl<'q, 'd, D: Copy, G: Copy> Time<'q, 'd, D, G> {
116 #[must_use]
118 pub fn checks(&self) -> &structured_cms::timestamp::CheckedTimestamp<'q> {
119 &self.checks
120 }
121 pub fn into_actor(
124 self,
125 ) -> Option<
126 structured_ltv::timestamp::CheckedActor<
127 'q,
128 'd,
129 crate::timestamp::Node,
130 D,
131 G,
132 crate::timestamp::Pades,
133 >,
134 > {
135 let root = self.root;
136 self.issue(root)
137 }
138 fn issue(
139 self,
140 root: crate::timestamp::Node,
141 ) -> Option<
142 structured_ltv::timestamp::CheckedActor<
143 'q,
144 'd,
145 crate::timestamp::Node,
146 D,
147 G,
148 crate::timestamp::Pades,
149 >,
150 > {
151 let evidence = self.checks.into_evidence()?;
152 let verified = structured_ltv::timestamp::Verified::new(
153 crate::timestamp::ISSUER,
154 structured_ltv::source::Location {
155 snapshot: self.snapshot,
156 node: self.token,
157 },
158 root,
159 self.property,
160 structured_ltv::timestamp::Purpose::ValidationData,
161 evidence,
162 );
163 Some(structured_ltv::timestamp::CheckedActor::new(
164 crate::timestamp::ISSUER,
165 verified,
166 self.certificate,
167 self.key,
168 ))
169 }
170}
171impl<'q, 'd, D: Copy + Eq, G: Copy + Eq> Time<'q, 'd, D, G> {
172 pub fn into_historical_actor(
176 self,
177 root: crate::signature::Signature<'_, '_, D, G>,
178 operation: &impl Work,
179 ) -> crate::Result<
180 Option<
181 structured_ltv::timestamp::CheckedActor<
182 'q,
183 'd,
184 crate::timestamp::Node,
185 D,
186 G,
187 crate::timestamp::Pades,
188 >,
189 >,
190 > {
191 let work = self.session.query(operation);
192 let location = root.location();
193 admit(&work, location, 1)?;
194 if !core::ptr::eq(self.session, root.owner().value.session)
195 || self.snapshot != root.owner().value.stamp.snapshot
196 {
197 return Err(Error {
198 kind: ErrorKind::Foreign,
199 location,
200 });
201 }
202 Ok(self.issue(crate::timestamp::Node::of(location)))
203 }
204}
205
206impl<'v, D: Copy + Eq, G: Copy + Eq> Binding<'_, 'v, '_, '_, D, G> {
207 #[must_use]
209 pub fn vri(&self) -> &super::Vri {
210 self.vri
211 }
212
213 #[expect(
219 clippy::result_large_err,
220 reason = "the common scoped timestamp error retains its original map, provider cause and enclosing material source without an error allocation"
221 )]
222 pub fn check_timestamp<'q, 'certificate, P, PE, RE>(
223 &'q self,
224 root: &'q structured_cms::SignedData<'_>,
225 parent: &'q structured_asn1::Reader<'q>,
226 provider: &'q P,
227 context: &'q structured_cms::timestamp::CheckContext<'certificate, 'q>,
228 recognizes: impl FnMut(
229 &structured_cms::timestamp::TokenExtension<'_>,
230 &structured_asn1::memory::Execution<'_>,
231 ) -> Result<bool, RE>,
232 ) -> Result<Option<Time<'q, 'certificate, D, G>>, TimestampError<'q, PE, RE>>
233 where
234 'v: 'q,
235 'certificate: 'q,
236 P: for<'scope> structured_cms::provider::SignatureProvider<'scope, Error = PE>,
237 {
238 let execution = parent.execution();
239 let work = self.parsed.owner.session.query(&execution);
240 let location = self.vri.location;
241 admit(&work, location, 1).map_err(TimestampError::Pdf)?;
242 self.root
243 .require(root, parent)
244 .map_err(TimestampError::Pdf)?;
245 if root.signers().len() != 1 {
246 return Err(TimestampError::Pdf(Error {
247 kind: ErrorKind::Syntax(crate::error::Syntax::Container),
248 location,
249 }));
250 }
251 let Some(ordinal) = self.vri.timestamp else {
252 return Ok(None);
253 };
254 let item = &self.parsed.owner.items[ordinal];
255 let token = match &self.parsed.values[ordinal] {
256 super::Value::Timestamp(value) => &value.token,
257 super::Value::Unreadable(cause) => {
258 return Err(TimestampError::Material {
259 location: item.location,
260 cause,
261 });
262 }
263 _ => {
264 return Err(TimestampError::Pdf(Error {
265 kind: ErrorKind::Foreign,
266 location,
267 }));
268 }
269 };
270 let reader = item
271 .session
272 .carried(item.as_bytes(), &parent.execution())
273 .map_err(TimestampError::Encoding)?;
274 let mut input = root.signers()[0].signature();
275 let checks = token
276 .check_historical_input_with_profile(
277 provider,
278 &reader,
279 context,
280 &mut structured_cms::timestamp::RawInput::new(&mut input),
281 recognizes,
282 |input| crate::timestamp::profile(&input),
283 )
284 .map_err(TimestampError::Check)?;
285 Ok(Some(Time {
286 checks,
287 certificate: context.certificate,
288 key: context.key,
289 snapshot: self.parsed.owner.stamp.snapshot,
290 root: crate::timestamp::Node::of(self.root.signature().location()),
291 property: crate::timestamp::Node::dss(self.vri.location, self.parsed.owner.revision),
292 token: crate::timestamp::Node::dss(item.location, self.parsed.owner.revision),
293 session: self.parsed.owner.session,
294 }))
295 }
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
300pub struct Name(pub(super) [u8; 40]);
301impl structured_asn1::memory::HashWork for Name {
302 fn hash_work(&self) -> Result<u64, structured_asn1::memory::MemoryError> {
303 Ok(41)
304 }
305}
306impl Name {
307 #[must_use]
309 pub fn as_bytes(&self) -> &[u8; 40] {
310 &self.0
311 }
312}
313
314#[derive(Debug)]
316pub enum NameError<E> {
317 PaddedSignature,
319 Pdf(Error),
321 Digest(DigestError<E>),
323 Pass(PassError<structured_cms::stream::RangesError, E>),
325 Width(usize),
327 LostRequest,
329}
330
331impl<D: Copy + Eq, G: Copy + Eq> Container<'_, '_, D, G> {
332 pub fn vri_name<'p, P: AsnDigest<'p>, W: Work>(
337 &self,
338 provider: &'p P,
339 operation: &W,
340 ) -> Result<Option<Name>, NameError<P::Error>> {
341 let location = self.signature().contents();
342 let work = self.signature().owner().value.session.query(operation);
343 name_for_ranges(provider, &mut [self.contents()], &work, location)
344 }
345}
346
347fn name_for_ranges<'p, P: AsnDigest<'p>, const N: usize>(
348 provider: &'p P,
349 ranges: &mut [&[u8]; N],
350 work: &impl Work,
351 location: crate::source::Location,
352) -> Result<Option<Name>, NameError<P::Error>> {
353 admit(work, location, 1).map_err(NameError::Pdf)?;
354 let Some(identity) = provider.identity(Algorithm::Sha1) else {
355 return Ok(None);
356 };
357 let mut identities = work.vector();
358 identities.try_push(identity).map_err(|cause| {
359 NameError::Pdf(Error {
360 kind: ErrorKind::Resource(cause),
361 location,
362 })
363 })?;
364 let batch = DigestBatch::prepare(
365 provider,
366 identities.into_owned(),
367 work,
368 "pades.vri",
369 location.span().start,
370 )
371 .map_err(NameError::Digest)?;
372 let memo = batch.run(ranges, work).map_err(NameError::Pass)?;
373 let output = memo
374 .for_request(0, work)
375 .map_err(NameError::Digest)?
376 .ok_or(NameError::LostRequest)?;
377 let bytes = output.as_bytes();
378 if bytes.len() != 20 {
379 return Err(NameError::Width(bytes.len()));
380 }
381 admit(work, location, 40).map_err(NameError::Pdf)?;
382 let mut name = [0; 40];
383 for (pair, byte) in name.as_chunks_mut::<2>().0.iter_mut().zip(bytes.iter()) {
384 pair[0] = b"0123456789ABCDEF"[usize::from(byte >> 4)];
385 pair[1] = b"0123456789ABCDEF"[usize::from(byte & 15)];
386 }
387 Ok(Some(Name(name)))
388}