1use crate::Error;
4use crate::basic::{PathRestriction, ess_matches, exact};
5use crate::prepare::Preparation;
6use crate::source::{DocumentId, Node, Original};
7use structured_asn1::memory::{OwnedVec, Work};
8use structured_asn1::{Reader, Span};
9use structured_cms::binding::Scope;
10use structured_cms::cades::Role;
11use structured_cms::counter::{Visit, WalkError};
12use structured_cms::directory::{self, CertificateAgreement};
13use structured_cms::prepare::{self, PrepareError};
14use structured_cms::provider::{SignatureAnswer, SignatureProvider};
15use structured_cms::verify::{Batch, ContentAgreement, PassError};
16use structured_cms::{Octets, signer::SignerInfo};
17use structured_ltv::pki::{CheckedPath, Counter, CounterReceipts};
18use structured_ltv::source::Snapshot;
19use structured_pkix::Certificate;
20use structured_pkix::algorithm::digest::AsnDigest;
21
22#[derive(Debug)]
24pub enum CheckError<E> {
25 Processing(Error),
27 Prepare(PrepareError<E>),
29 Verify(PassError<structured_cms::octets::SourceError, E>),
31 Identifier(directory::Error),
33 Ess(structured_cms::ess::BindingError<E>),
35 Restriction(crate::basic::BindError<E>),
37}
38
39#[derive(Debug)]
41pub enum Outcome {
42 Bound,
44 Differs,
46 Unsupported(prepare::Part),
48 Malformed(structured_cms::Error),
50}
51
52pub struct Binding<'model, 'document, 'path> {
55 source: Original<'model, 'document>,
56 parent: Node,
57 path: &'path CheckedPath<'document>,
58 outcome: Outcome,
59 profile: structured_cms::cades::Profile<'document>,
60 identifier: Option<CertificateAgreement>,
61 signing: Option<structured_cms::ess::Bindings>,
62 content: Option<ContentAgreement>,
63 signature: Option<SignatureAnswer>,
64 restrictions: Option<PathRestriction>,
65}
66
67impl<'m, 'd, 'p> Binding<'m, 'd, 'p> {
68 fn unproven(
69 source: Original<'m, 'd>,
70 parent: Span,
71 path: &'p CheckedPath<'d>,
72 profile: structured_cms::cades::Profile<'d>,
73 ) -> Self {
74 Self {
75 source,
76 parent: Node::of(parent),
77 path,
78 outcome: Outcome::Differs,
79 profile,
80 identifier: None,
81 signing: None,
82 content: None,
83 signature: None,
84 restrictions: None,
85 }
86 }
87 #[must_use]
89 pub fn source(&self) -> Original<'m, 'd> {
90 self.source
91 }
92 #[must_use]
94 pub fn parent(&self) -> Node {
95 self.parent
96 }
97 #[must_use]
99 pub fn outcome(&self) -> &Outcome {
100 &self.outcome
101 }
102 #[must_use]
104 pub fn profile(&self) -> &structured_cms::cades::Profile<'d> {
105 &self.profile
106 }
107 #[must_use]
110 pub fn identifier(&self) -> Option<&CertificateAgreement> {
111 self.identifier.as_ref()
112 }
113 #[must_use]
115 pub fn signing(&self) -> Option<structured_cms::ess::Bindings> {
116 self.signing
117 }
118 #[must_use]
120 pub fn content(&self) -> Option<&ContentAgreement> {
121 self.content.as_ref()
122 }
123 #[must_use]
125 pub fn signature(&self) -> Option<SignatureAnswer> {
126 self.signature
127 }
128 #[must_use]
130 pub fn restrictions(&self) -> Option<PathRestriction> {
131 self.restrictions
132 }
133}
134
135pub struct Context<'scope, 'model, 'document> {
138 selected: Original<'model, 'document>,
139 signer: &'scope SignerInfo<'document>,
140 parent_signature: &'scope Octets<'document>,
141 parent: Span,
142 reader: &'scope Reader<'document>,
143}
144
145impl<'m, 'd> Context<'_, 'm, 'd> {
146 #[must_use]
148 pub fn signer(&self) -> &SignerInfo<'d> {
149 self.signer
150 }
151
152 pub fn check<'a, 'p, P>(
160 &'a self,
161 provider: &'a P,
162 certificate: &Certificate<'d>,
163 path: &'p CheckedPath<'d>,
164 ) -> Result<Binding<'m, 'd, 'p>, CheckError<P::Error>>
165 where
166 'd: 'a,
167 P: SignatureProvider<'a> + AsnDigest<'a>,
168 {
169 let at = self.signer.span().start;
170 let processing = |error| CheckError::Processing(Error::Cms(error));
171 let work = self.reader.execution();
172 let resource = |cause| CheckError::Processing(Error::resource(cause, "cades.counter", at));
173 let profile =
174 structured_cms::cades::profile(self.signer, Role::Countersignature, self.reader)
175 .map_err(processing)?;
176 let conforms = profile_conforms(self.signer, &profile, self.reader)?;
177 let mut result = Binding::unproven(self.selected, self.parent, path, profile);
178 let Some(target) = path.members().last() else {
179 return Ok(result);
180 };
181 let same_certificate = exact(
182 target.certificate().as_bytes(),
183 certificate.as_bytes(),
184 &work,
185 )
186 .map_err(resource)?;
187 result.identifier = Some(
188 directory::matches_certificate_in(self.signer.sid(), certificate, self.reader)
189 .map_err(CheckError::Identifier)?,
190 );
191 if !same_certificate
192 || !matches!(result.identifier, Some(CertificateAgreement::Matches))
193 || !conforms
194 {
195 return Ok(result);
196 }
197 let plan = prepare::resolve(
198 provider,
199 self.signer,
200 target.key(),
201 self.reader,
202 Scope::CounterSignature,
203 )
204 .map_err(CheckError::Prepare)?;
205 let plan = match plan {
206 prepare::Preparation::Ready(plan) => plan,
207 prepare::Preparation::Unsupported(part) => {
208 result.outcome = Outcome::Unsupported(part);
209 return Ok(result);
210 }
211 prepare::Preparation::Malformed(error) => {
212 result.outcome = Outcome::Malformed(error);
213 return Ok(result);
214 }
215 };
216 let mut requests = work.vector();
217 requests.try_push(plan).map_err(resource)?;
218 let batch = Batch::prepare(provider, requests.into_owned(), &work, "cades.counter", at)
219 .map_err(|source| {
220 CheckError::Verify(structured_cms::stream::StreamError::Callback(source))
221 })?;
222 let mut original_parent = self.parent_signature;
223 let checks = batch
224 .run(&mut original_parent, &work)
225 .map_err(CheckError::Verify)?;
226 let Some(checked) = checks.into_iter().next() else {
227 return Err(CheckError::Processing(Error::Foreign { at }));
228 };
229 let (_, _, content, signature) = checked.into_parts();
230 let math = matches!(
231 content,
232 ContentAgreement::Direct | ContentAgreement::Matches
233 ) && signature == Some(SignatureAnswer::Valid);
234 result.content = Some(content);
235 result.signature = signature;
236 let signing = result.profile.signing();
237 let ess = if signing.v1().is_some() || signing.v2().is_some() {
238 let bindings = signing
239 .bind_certificate(provider, certificate, self.reader)
240 .map_err(CheckError::Ess)?;
241 result.signing = Some(bindings);
242 ess_matches(bindings)
243 } else {
244 true
245 };
246 let restriction = crate::basic::restrictions(signing, Some(path), provider, self.reader)
247 .map_err(CheckError::Restriction)?;
248 result.restrictions = Some(restriction);
249 if math
250 && ess
251 && matches!(
252 restriction,
253 PathRestriction::Unstated | PathRestriction::Matches
254 )
255 {
256 result.outcome = Outcome::Bound;
257 }
258 Ok(result)
259 }
260}
261
262fn profile_conforms<E>(
263 signer: &SignerInfo<'_>,
264 profile: &structured_cms::cades::Profile<'_>,
265 reader: &Reader<'_>,
266) -> Result<bool, CheckError<E>> {
267 let mut conforms = signer.faults().is_empty()
268 && profile.faults().is_empty()
269 && profile.signing().faults().is_empty();
270 for attribute in [profile.signing().v1(), profile.signing().v2()]
271 .into_iter()
272 .flatten()
273 {
274 reader
275 .charge(1)
276 .map_err(|cause| CheckError::Processing(Error::encoding(cause, "cades.counter")))?;
277 if !attribute.faults().is_empty() {
278 conforms = false;
279 }
280 }
281 Ok(conforms)
282}
283
284pub struct Bindings<'model, 'document, 'path> {
287 signature: Original<'model, 'document>,
288 values: OwnedVec<Option<Binding<'model, 'document, 'path>>>,
289 complete: bool,
290}
291
292impl<'m, 'd, 'p> Bindings<'m, 'd, 'p> {
293 #[must_use]
295 pub fn values(&self) -> &[Option<Binding<'m, 'd, 'p>>] {
296 &self.values
297 }
298 #[must_use]
300 pub fn paths(&self) -> Option<Paths<'_, 'm, 'd, 'p>> {
301 if self.complete {
302 Some(Paths { bindings: self })
303 } else {
304 None
305 }
306 }
307}
308
309pub struct Paths<'receipt, 'model, 'document, 'path> {
311 bindings: &'receipt Bindings<'model, 'document, 'path>,
312}
313
314impl<'m, 'd, 'p> CounterReceipts<'p, 'd> for Paths<'_, 'm, 'd, 'p> {
315 type Id = Node;
316 type Document = DocumentId<'m, 'd>;
317 type Generation = ();
318 fn snapshot(&self) -> Snapshot<DocumentId<'m, 'd>, ()> {
319 self.bindings.signature.snapshot
320 }
321 fn signature(&self) -> Node {
322 self.bindings.signature.node
323 }
324 fn len(&self) -> usize {
325 self.bindings.values.len()
326 }
327 fn counter(&self, ordinal: usize) -> Counter<'p, 'd, Node> {
328 let Some(entry) = &self.bindings.values[ordinal] else {
329 unreachable!("only a complete counter set exposes Paths")
330 };
331 Counter {
332 signature: entry.source.node,
333 property: entry.source.node,
334 path: entry.path,
335 }
336 }
337}
338
339pub type BindingsError<E> = WalkError<SelectionError<E>>;
341
342#[derive(Debug)]
344pub enum SelectionError<E> {
345 Callback(E),
347 Foreign {
349 at: u64,
351 },
352}
353
354pub fn bind<'m, 'd, 'p, E>(
359 prepared: &Preparation<'m, 'd>,
360 parent: &Reader<'d>,
361 mut select: impl FnMut(Context<'_, 'm, 'd>) -> Result<Binding<'m, 'd, 'p>, E>,
362) -> Result<Bindings<'m, 'd, 'p>, BindingsError<E>> {
363 let mut complete = true;
364 let values = structured_cms::counter::walk(prepared.signer(), parent, |visit, reader| {
365 let Visit::Signer {
366 signer,
367 signature,
368 parent: owner,
369 } = visit
370 else {
371 complete = false;
374 return Ok(None);
375 };
376 let selected = prepared.document().locate(signer.span());
377 let result = select(Context {
378 selected,
379 signer,
380 parent_signature: signature,
381 parent: owner,
382 reader,
383 })
384 .map_err(SelectionError::Callback)?;
385 if result.source != selected || result.parent != Node::of(owner) {
386 return Err(SelectionError::Foreign {
387 at: signer.span().start,
388 });
389 }
390 if !matches!(result.outcome, Outcome::Bound) {
391 complete = false;
392 }
393 Ok(Some(result))
394 })?;
395 Ok(Bindings {
396 signature: prepared.document().locate(prepared.signer().span()),
397 values,
398 complete,
399 })
400}