1use crate::Error;
4use crate::prepare::Preparation;
5use crate::source::{DocumentId, Node, Original};
6use structured_asn1::Reader;
7use structured_asn1::memory::Work;
8use structured_cms::directory::{self, CertificateAgreement};
9use structured_cms::ess::{Binding, BindingError, Bindings};
10use structured_cms::provider::SignatureAnswer;
11use structured_cms::verify::{Checked, ContentAgreement, DocumentChecks};
12use structured_ltv::pki::CounterReceipts;
13use structured_ltv::pki::{CheckedPath, SignerReceipt};
14use structured_pkix::Certificate;
15use structured_pkix::algorithm::digest::AsnDigest;
16
17pub struct Basic<'receipt, 'prepared, 'model, 'document, 'path> {
21 signer: &'receipt SignerBinding<'prepared, 'model, 'document, 'path>,
22 counters: crate::counters::Paths<'receipt, 'model, 'document, 'path>,
23 complete: bool,
24}
25
26impl<'r, 'p, 'm, 'd, 'v> Basic<'r, 'p, 'm, 'd, 'v> {
27 #[must_use]
29 pub fn binding(&self) -> &'r SignerBinding<'p, 'm, 'd, 'v> {
30 self.signer
31 }
32 #[must_use]
34 pub fn counter_paths(&self) -> &crate::counters::Paths<'r, 'm, 'd, 'v> {
35 &self.counters
36 }
37 #[must_use]
40 pub fn all_basic_requirements_met(&self) -> bool {
41 self.complete
42 }
43}
44
45impl<'r, 'p, 'm, 'd, 'v> structured_ltv::baseline::BasicStage<'v, 'd>
46 for Basic<'r, 'p, 'm, 'd, 'v>
47{
48 type Id = Node;
49 type Document = DocumentId<'m, 'd>;
50 type Generation = ();
51 type Signer = SignerBinding<'p, 'm, 'd, 'v>;
52 type Counters = crate::counters::Paths<'r, 'm, 'd, 'v>;
53 fn signer(&self) -> &Self::Signer {
54 self.signer
55 }
56 fn counters(&self) -> &Self::Counters {
57 &self.counters
58 }
59 fn all_requirements_met(&self) -> bool {
60 self.complete
61 }
62}
63
64pub fn basic<'r, 'p, 'm, 'd, 'v>(
68 signer: &'r SignerBinding<'p, 'm, 'd, 'v>,
69 counters: crate::counters::Paths<'r, 'm, 'd, 'v>,
70 operation: &Reader<'_>,
71) -> crate::Result<Basic<'r, 'p, 'm, 'd, 'v>> {
72 let prepared = signer.prepared();
73 prepared
74 .document()
75 .message()
76 .require_session(operation, "cades.basic")?;
77 let selected = prepared.document().locate(prepared.signer().span());
78 operation
79 .charge(1)
80 .map_err(|cause| Error::encoding(cause, "cades.basic"))?;
81 if counters.snapshot() != selected.snapshot
82 || counters.signature() != selected.node
83 || counters.len() != prepared.counters().len()
84 {
85 return Err(Error::Foreign {
86 at: selected.node.span().start,
87 });
88 }
89 for (ordinal, expected) in prepared.counters().iter().enumerate() {
90 operation
91 .charge(1)
92 .map_err(|cause| Error::encoding(cause, "cades.basic"))?;
93 if counters.counter(ordinal).signature != expected.node() {
94 return Err(Error::Foreign {
95 at: expected.node().span().start,
96 });
97 }
98 }
99 Ok(Basic {
100 signer,
101 counters,
102 complete: prepared.basic_format_requirements_met() && signer.is_bound(),
103 })
104}
105
106#[derive(Debug)]
108pub enum BindError<E> {
109 Processing(Error),
111 Identifier(directory::Error),
113 Ess(BindingError<E>),
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum PathRestriction {
120 Unstated,
122 Matches,
124 PathUnavailable,
126 Differs,
128 Unproven,
130}
131
132pub struct SignerBinding<'receipt, 'model, 'document, 'path> {
137 prepared: &'receipt Preparation<'model, 'document>,
138 path: Option<&'path CheckedPath<'document>>,
139 certificate: &'path Certificate<'document>,
140 identifier: CertificateAgreement,
141 ess: Bindings,
142 key_matches: bool,
143 included: Option<Original<'model, 'document>>,
144 mathematical: bool,
145 restrictions: PathRestriction,
146}
147
148impl<'r, 'm, 'd, 'p> SignerBinding<'r, 'm, 'd, 'p> {
149 #[must_use]
151 pub fn prepared(&self) -> &'r Preparation<'m, 'd> {
152 self.prepared
153 }
154 #[must_use]
156 pub fn certificate(&self) -> &'p Certificate<'d> {
157 self.certificate
158 }
159 #[must_use]
161 pub fn identifier(&self) -> &CertificateAgreement {
162 &self.identifier
163 }
164 #[must_use]
166 pub fn ess(&self) -> Bindings {
167 self.ess
168 }
169 #[must_use]
171 pub fn key_matches(&self) -> bool {
172 self.key_matches
173 }
174 #[must_use]
176 pub fn included(&self) -> Option<Original<'m, 'd>> {
177 self.included
178 }
179 #[must_use]
182 pub fn mathematical(&self) -> bool {
183 self.mathematical
184 }
185 #[must_use]
187 pub fn restrictions(&self) -> PathRestriction {
188 self.restrictions
189 }
190 #[must_use]
193 pub fn is_bound(&self) -> bool {
194 self.mathematical
195 && self.key_matches
196 && self.included.is_some()
197 && self.prepared.profile().signing().faults().is_empty()
201 && matches!(self.identifier, CertificateAgreement::Matches)
202 && ess_matches(self.ess)
203 && matches!(
204 self.restrictions,
205 PathRestriction::Unstated | PathRestriction::Matches
206 )
207 }
208}
209
210impl<'m, 'd, 'p> SignerReceipt<'p, 'd> for SignerBinding<'_, 'm, 'd, 'p> {
211 type Id = Node;
212 type Document = DocumentId<'m, 'd>;
213 type Generation = ();
214 fn signature(&self) -> Original<'m, 'd> {
215 self.prepared
216 .document()
217 .locate(self.prepared.signer().span())
218 }
219 fn path(&self) -> Option<&'p CheckedPath<'d>> {
220 if self.is_bound() { self.path } else { None }
221 }
222}
223
224pub fn bind<'a, 'r, 'm, 'd, 'p, P: AsnDigest<'a>>(
233 prepared: &'r Preparation<'m, 'd>,
234 document_checks: &DocumentChecks<'_>,
235 checked: &Checked<'_>,
236 certificate: &'p Certificate<'d>,
237 path: Option<&'p CheckedPath<'d>>,
238 provider: &'a P,
239 parent: &Reader<'a>,
240) -> Result<SignerBinding<'r, 'm, 'd, 'p>, BindError<P::Error>>
241where
242 'd: 'a,
243{
244 let at = prepared.signer().span().start;
245 let processing = BindError::Processing;
246 prepared
247 .document()
248 .message()
249 .require_session(parent, "cades.basic")
250 .map_err(|error| processing(Error::Cms(error)))?;
251 if !core::ptr::eq(checked.signer(), prepared.signer()) {
252 return Err(processing(Error::Foreign { at }));
253 }
254 let work = parent.execution();
255 document_checks
256 .require(prepared.document().message(), checked, &work)
257 .map_err(|error| processing(Error::Cms(error)))?;
258 let refusal = |cause| processing(Error::resource(cause, "cades.basic", at));
259 work.charge_work(1).map_err(refusal)?;
260 let mathematical = matches!(
261 checked.content(),
262 ContentAgreement::Matches | ContentAgreement::Direct
263 ) && checked.signature() == Some(SignatureAnswer::Valid);
264 let identifier =
265 directory::matches_certificate_in(prepared.signer().sid(), certificate, parent)
266 .map_err(BindError::Identifier)?;
267 let mut key_matches = exact(
268 checked.key().key().as_bytes(),
269 certificate.tbs().public_key().as_bytes(),
270 &work,
271 )
272 .map_err(refusal)?;
273 if let Some(path) = path {
274 if let Some(target) = path.members().last() {
275 key_matches &= exact(
276 target.certificate().as_bytes(),
277 certificate.as_bytes(),
278 &work,
279 )
280 .map_err(refusal)?;
281 key_matches &=
282 same_parameters(checked.key().parameters(), target.key().parameters(), &work)
283 .map_err(refusal)?;
284 } else {
285 key_matches = false;
286 }
287 } else {
288 key_matches &= same_parameters(
289 checked.key().parameters(),
290 certificate.tbs().public_key().algorithm().parameters(),
291 &work,
292 )
293 .map_err(refusal)?;
294 }
295 let mut included = None;
296 for original in prepared.document().message().certificates() {
297 work.charge_work(1).map_err(refusal)?;
298 if exact(original.as_bytes(), certificate.as_bytes(), &work).map_err(refusal)? {
299 included = Some(prepared.document().locate(original.span()));
300 break;
301 }
302 }
303 let ess = prepared
304 .profile()
305 .signing()
306 .bind_certificate(provider, certificate, parent)
307 .map_err(BindError::Ess)?;
308 let restrictions = restrictions(prepared.profile().signing(), path, provider, parent)?;
309 Ok(SignerBinding {
310 prepared,
311 path,
312 certificate,
313 identifier,
314 ess,
315 key_matches,
316 included,
317 mathematical,
318 restrictions,
319 })
320}
321
322pub(crate) fn restrictions<'a, P: AsnDigest<'a>>(
323 signing: &structured_cms::ess::SigningAttributes<'a>,
324 path: Option<&CheckedPath<'_>>,
325 provider: &'a P,
326 parent: &Reader<'a>,
327) -> Result<PathRestriction, BindError<P::Error>> {
328 let mut result = PathRestriction::Unstated;
329 for set in [signing.v1(), signing.v2()].into_iter().flatten() {
330 parent.charge(1).map_err(|cause| {
331 BindError::Processing(Error::encoding(cause, "cades.ess.restrictions"))
332 })?;
333 if set.certificates().len() <= 1 {
334 continue;
335 }
336 if !set.faults().is_empty() {
337 return Ok(PathRestriction::Unproven);
338 }
339 let Some(path) = path else {
340 return Ok(PathRestriction::PathUnavailable);
341 };
342 result = PathRestriction::Matches;
343 let Some((_, authorities)) = path.members().split_last() else {
348 return Err(BindError::Processing(Error::Foreign {
349 at: set.element().span().start,
350 }));
351 };
352 for certificate in authorities.iter().map(|member| member.certificate()) {
357 let answers = set
358 .bind_validation_certificate(provider, certificate, parent)
359 .map_err(BindError::Ess)?;
360 let mut matched = false;
361 let mut unsupported = false;
362 for answer in &answers {
363 parent.charge(1).map_err(|cause| {
364 BindError::Processing(Error::encoding(cause, "cades.ess.restrictions"))
365 })?;
366 if *answer == Binding::Matches {
367 matched = true;
368 }
369 if *answer == Binding::Unsupported {
370 unsupported = true;
371 }
372 }
373 if !matched {
374 return Ok(if unsupported {
375 PathRestriction::Unproven
376 } else {
377 PathRestriction::Differs
378 });
379 }
380 }
381 }
382 Ok(result)
383}
384
385pub(crate) fn ess_matches(bindings: Bindings) -> bool {
386 (bindings.v1.is_some() || bindings.v2.is_some())
387 && bindings.v1.is_none_or(|value| value == Binding::Matches)
388 && bindings.v2.is_none_or(|value| value == Binding::Matches)
389}
390
391pub(crate) fn exact(
392 left: &[u8],
393 right: &[u8],
394 work: &impl Work,
395) -> Result<bool, structured_asn1::memory::MemoryError> {
396 work.charge_work(1)?;
397 if core::ptr::eq(left, right) {
398 return Ok(true);
399 }
400 if left.len() != right.len() {
401 return Ok(false);
402 }
403 work.charge_work(left.len() as u64)?;
404 Ok(left == right)
405}
406
407fn same_parameters(
408 left: Option<&[u8]>,
409 right: Option<&[u8]>,
410 work: &impl Work,
411) -> Result<bool, structured_asn1::memory::MemoryError> {
412 work.charge_work(1)?;
413 match (left, right) {
414 (None, None) => Ok(true),
415 (Some(left), Some(right)) => exact(left, right, work),
416 _ => Ok(false),
417 }
418}