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