1use crate::binary::{self, BinaryXml, Location};
4use crate::binding::{Bound, SignatureXml};
5use crate::container::XADES;
6use crate::properties::{Error, PropertyResult, Reading};
7use crate::xml::{Access, AccessError, Name, Snapshot, Xml};
8use structured_asn1::digest::{Digest, OnceError, Resolution, matches_once};
9use structured_asn1::{
10 Reader, Tag,
11 memory::{MemoryError, OwnedVec, Work},
12 universal,
13};
14use structured_cms::ess::IssuerSerialReading;
15use structured_pkix::{Certificate, path::WorkingKey};
16
17mod path;
18pub use path::{
19 CheckedPathCertificate, PathCheckResult, PathDigest, PathDigestResult, PathDigests, check_path,
20 match_path,
21};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum KeyAssociation {
28 Same,
30 Differs,
32 Unproven,
34}
35
36pub trait CertificateXml: SignatureXml {
38 fn key_association<W: Work>(
49 &self,
50 snapshot: Snapshot<Self::Document, Self::Generation>,
51 signature: Self::Id,
52 certificate: &Certificate<'_>,
53 key: WorkingKey<'_>,
54 execution: &W,
55 ) -> Result<KeyAssociation, Self::Error>;
56}
57
58pub trait DigestProvider<'a, X: Xml>: Digest<'a> {
67 fn resolve<W: Work>(
69 &self,
70 access: &Access<'_, X, W>,
71 method: X::Id,
72 ) -> Result<Resolution<Self::Identity>, Self::Error>;
73
74 fn classify(&self, identity: &Self::Identity) -> crate::hashes::HashClass;
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum Agreement {
86 ProhibitedAlgorithm,
88 Matches,
90 Differs,
92 Unsupported,
94 MalformedMethod,
96 MalformedValue,
98}
99
100#[derive(Debug)]
102pub enum DigestError<E> {
103 ReferenceIndex {
106 index: usize,
108 },
109 Provider(E),
111 Resource(MemoryError),
113 OutputWidth {
116 expected: usize,
118 actual: usize,
120 },
121}
122
123#[derive(Debug, Clone, Copy)]
127pub struct Reference<N> {
128 node: N,
129 method: N,
130 digest: N,
131 issuer: Option<N>,
132}
133
134impl<N: Copy> Reference<N> {
135 #[must_use]
137 pub fn node(&self) -> N {
138 self.node
139 }
140 #[must_use]
142 pub fn method(&self) -> N {
143 self.method
144 }
145 #[must_use]
147 pub fn digest(&self) -> N {
148 self.digest
149 }
150 #[must_use]
152 pub fn issuer_serial(&self) -> Option<N> {
153 self.issuer
154 }
155}
156
157pub struct References<N, D, G> {
161 snapshot: Snapshot<D, G>,
162 signature: N,
163 node: N,
164 entries: OwnedVec<Reference<N>>,
165}
166
167impl<N: Copy, D: Copy, G: Copy> References<N, D, G> {
168 #[must_use]
170 pub fn snapshot(&self) -> Snapshot<D, G> {
171 self.snapshot
172 }
173 #[must_use]
175 pub fn signature(&self) -> N {
176 self.signature
177 }
178 #[must_use]
180 pub fn node(&self) -> N {
181 self.node
182 }
183 #[must_use]
185 pub fn entries(&self) -> &[Reference<N>] {
186 &self.entries
187 }
188}
189
190pub fn compare<'p, X: BinaryXml, P: DigestProvider<'p, X>, W: Work>(
196 xml: &X,
197 reference: &Catalogue<X>,
198 index: usize,
199 certificate: &Certificate<'_>,
200 provider: &'p P,
201 execution: &W,
202) -> binary::BinaryResult<Agreement, X, DigestError<P::Error>> {
203 let Some(entry) = reference.entries.get(index) else {
206 return Err(binary::Error::Callback {
207 location: Location {
208 snapshot: reference.snapshot,
209 node: reference.node,
210 },
211 cause: DigestError::ReferenceIndex { index },
212 });
213 };
214 let location = Location {
215 snapshot: reference.snapshot,
216 node: entry.method,
217 };
218 let access = Access::new(xml, reference.snapshot, execution);
219 if let Some(answer) = preflight_method(&access, entry.method)
220 .map_err(|cause| binary::Error::Access { location, cause })?
221 {
222 return Ok(answer);
223 }
224 binary::with_octets(
225 xml,
226 Location {
227 snapshot: reference.snapshot,
228 node: entry.digest,
229 },
230 execution,
231 |expected| {
232 Ok::<_, core::convert::Infallible>(compare_expected(
233 &access,
234 entry,
235 certificate,
236 provider,
237 expected,
238 ))
239 },
240 )
241 .map_err(|error| error.map_callback(|never| match never {}))?
242}
243
244fn compare_expected<'p, X: Xml, P: DigestProvider<'p, X>, W: Work>(
245 access: &Access<'_, X, W>,
246 entry: &Reference<X::Id>,
247 certificate: &Certificate<'_>,
248 provider: &'p P,
249 expected: &[u8],
250) -> binary::BinaryResult<Agreement, X, DigestError<P::Error>> {
251 let identity = match resolve_expected(access, entry, provider, expected)? {
252 Expected::Ready(identity) => identity,
253 Expected::Answer(answer) => return Ok(answer),
254 };
255 compare_resolved(access, entry, certificate, provider, &identity, expected)
256}
257
258pub(crate) enum Expected<I> {
259 Ready(FormatDigest<I>),
260 Answer(Agreement),
261}
262
263pub(crate) struct FormatDigest<I>(I);
267
268impl<I> FormatDigest<I> {
269 pub(crate) fn identity(&self) -> &I {
270 &self.0
271 }
272}
273
274pub(crate) fn baseline_digest<'p, X: Xml, P: DigestProvider<'p, X>>(
275 identity: P::Identity,
276 provider: &P,
277) -> Expected<P::Identity> {
278 match resolved_digest::<true, X, P>(identity, provider) {
279 Ok(identity) => Expected::Ready(FormatDigest(identity)),
280 Err(answer) => Expected::Answer(answer),
281 }
282}
283
284pub(crate) fn resolved_digest<'p, const BASELINE: bool, X: Xml, P: DigestProvider<'p, X>>(
285 identity: P::Identity,
286 provider: &P,
287) -> Result<P::Identity, Agreement> {
288 match provider.classify(&identity) {
294 crate::hashes::HashClass::Md5 if BASELINE => Err(Agreement::ProhibitedAlgorithm),
295 crate::hashes::HashClass::Intrinsic => Err(Agreement::MalformedMethod),
296 crate::hashes::HashClass::Md5 | crate::hashes::HashClass::OtherKnown => Ok(identity),
297 }
298}
299
300fn resolve_expected<'p, X: Xml, P: DigestProvider<'p, X>, W: Work>(
301 access: &Access<'_, X, W>,
302 entry: &Reference<X::Id>,
303 provider: &P,
304 expected: &[u8],
305) -> binary::BinaryResult<Expected<P::Identity>, X, DigestError<P::Error>> {
306 let location = Location {
307 snapshot: access.snapshot(),
308 node: entry.method,
309 };
310 let resolved = access
311 .query(entry.method, |_, _, _| {
312 Ok(provider.resolve(access, entry.method))
313 })
314 .map_err(|cause| binary::Error::Access { location, cause })?
315 .map_err(|cause| binary::Error::Callback {
316 location,
317 cause: DigestError::Provider(cause),
318 })?;
319 let identity = match resolved {
320 Resolution::Supported(identity) => identity,
321 Resolution::Unsupported => return Ok(Expected::Answer(Agreement::Unsupported)),
322 Resolution::Malformed => return Ok(Expected::Answer(Agreement::MalformedMethod)),
323 };
324 let identity = match baseline_digest::<X, P>(identity, provider) {
325 Expected::Ready(identity) => identity,
326 answer @ Expected::Answer(_) => return Ok(answer),
327 };
328 if let Some(width) = provider.width(identity.identity())
329 && expected.len() != width
330 {
331 return Ok(Expected::Answer(Agreement::MalformedValue));
332 }
333 Ok(Expected::Ready(identity))
334}
335
336fn compare_resolved<'p, X: Xml, P: DigestProvider<'p, X>, W: Work>(
337 access: &Access<'_, X, W>,
338 entry: &Reference<X::Id>,
339 certificate: &Certificate<'_>,
340 provider: &'p P,
341 identity: &FormatDigest<P::Identity>,
342 expected: &[u8],
343) -> binary::BinaryResult<Agreement, X, DigestError<P::Error>> {
344 let execution = access.execution();
345 let location = Location {
346 snapshot: access.snapshot(),
347 node: entry.digest,
348 };
349 let check = || {
350 execution.charge_work(1).map_err(DigestError::Resource)?;
351 let answer = matches_once(
354 provider,
355 identity.identity(),
356 certificate.as_bytes(),
357 expected,
358 execution,
359 )
360 .map_err(|error| match error {
361 OnceError::Provider(cause) => DigestError::Provider(cause),
362 OnceError::OutputWidth { expected, actual } => {
363 DigestError::OutputWidth { expected, actual }
364 }
365 })?;
366 Ok(match answer {
367 Resolution::Supported(true) => Agreement::Matches,
368 Resolution::Supported(false) => Agreement::Differs,
369 Resolution::Unsupported => Agreement::Unsupported,
370 Resolution::Malformed => Agreement::MalformedMethod,
371 })
372 };
373 check().map_err(|cause| binary::Error::Callback { location, cause })
374}
375
376pub(crate) fn preflight_method<X: Xml, W: Work>(
377 access: &Access<'_, X, W>,
378 node: X::Id,
379) -> Result<Option<Agreement>, AccessError<X::Error, X::Id>> {
380 access
381 .attribute(
382 node,
383 Name {
384 namespace: "",
385 local: "Algorithm",
386 },
387 |value| {
388 let Some(value) = value else {
389 return Ok(Some(Agreement::MalformedMethod));
390 };
391 let value = access.trim_xml_whitespace(value)?;
392 preflight_algorithm(value, access.execution())
393 },
394 )?
395 .map_err(|cause| AccessError::Resource { node, cause })
396}
397
398pub(crate) fn preflight_algorithm<W: Work>(
399 algorithm: &str,
400 work: &W,
401) -> Result<Option<Agreement>, MemoryError> {
402 work.charge_work(algorithm.len() as u64)?;
408 Ok(
409 if algorithm == "http://www.w3.org/2001/04/xmldsig-more#md5" {
410 Some(Agreement::ProhibitedAlgorithm)
411 } else {
412 None
413 },
414 )
415}
416
417pub type Catalogue<X> = References<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
419
420pub struct CheckedCertificate<'view, 'document, N, D, G> {
425 reference: &'view References<N, D, G>,
426 certificate: &'view Certificate<'document>,
427 key: KeyAssociation,
428 digest: Agreement,
429}
430
431impl<'view, 'document, N, D, G> CheckedCertificate<'view, 'document, N, D, G> {
432 #[must_use]
434 pub fn certificate(&self) -> &'view Certificate<'document> {
435 self.certificate
436 }
437 #[must_use]
439 pub fn reference(&self) -> &'view References<N, D, G> {
440 self.reference
441 }
442 #[must_use]
444 pub fn key(&self) -> KeyAssociation {
445 self.key
446 }
447 #[must_use]
449 pub fn digest(&self) -> Agreement {
450 self.digest
451 }
452 #[must_use]
455 pub fn is_bound(&self) -> bool {
456 self.key == KeyAssociation::Same && self.digest == Agreement::Matches
457 }
458}
459
460pub type Checked<'view, 'document, X> = CheckedCertificate<
462 'view,
463 'document,
464 <X as Xml>::Id,
465 <X as Xml>::Document,
466 <X as Xml>::Generation,
467>;
468
469#[derive(Debug)]
471pub enum CheckError<E, PE, N, D, G> {
472 ForeignCatalogue {
475 signature: N,
477 property: N,
479 },
480 ForbiddenCertificateUri {
482 location: Location<N, D, G>,
484 },
485 Access(AccessError<E, N>),
487 Digest(binary::Error<E, DigestError<PE>, N, D, G>),
489}
490
491pub type CheckResult<'v, 'd, X, PE> = CertificateResult<Checked<'v, 'd, X>, X, PE>;
493
494pub type CertificateResult<T, X, PE> = Result<
496 T,
497 CheckError<<X as Xml>::Error, PE, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
498>;
499
500#[derive(Debug)]
502pub enum IssuerError<E> {
503 ReferenceIndex {
505 index: usize,
507 },
508 Encoding(structured_asn1::Error),
510 Reading(structured_cms::Error),
512 Trailing {
514 offset: u64,
516 },
517 Callback(E),
519}
520
521pub fn with_issuer_serial<X: BinaryXml, R, E>(
527 xml: &X,
528 reference: &Catalogue<X>,
529 index: usize,
530 operation: &Reader<'_>,
531 visitor: impl for<'source> FnOnce(IssuerSerialReading<'source>) -> Result<R, E>,
532) -> binary::BinaryResult<Option<binary::LocatedValue<R, X>>, X, IssuerError<E>> {
533 let location = Location {
534 snapshot: reference.snapshot,
535 node: reference.node,
536 };
537 let Some(entry) = reference.entries.get(index) else {
538 return Err(binary::Error::Callback {
539 location,
540 cause: IssuerError::ReferenceIndex { index },
541 });
542 };
543 let execution = operation.execution();
544 Access::new(xml, reference.snapshot, &execution)
545 .query(entry.node, |_, _, _| Ok(()))
546 .map_err(|cause| binary::Error::Access { location, cause })?;
547 let Some(node) = entry.issuer else {
548 return Ok(None);
549 };
550 let location = Location {
551 snapshot: reference.snapshot,
552 node,
553 };
554 with_issuer_serial_at(xml, location, operation, visitor).map(Some)
555}
556
557pub(crate) fn with_issuer_serial_at<X: BinaryXml, R, E>(
558 xml: &X,
559 location: Location<X::Id, X::Document, X::Generation>,
560 operation: &Reader<'_>,
561 visitor: impl for<'source> FnOnce(IssuerSerialReading<'source>) -> Result<R, E>,
562) -> binary::BinaryResult<binary::LocatedValue<R, X>, X, IssuerError<E>> {
563 binary::with_der_reader(xml, location, operation, |reader| {
564 let element = reader
565 .expect(Tag::constructed(universal::SEQUENCE))
566 .map_err(IssuerError::Encoding)?;
567 let reading = IssuerSerialReading::read(reader, &element, "xades.issuerSerialV2")
568 .map_err(IssuerError::Reading)?;
569 if !reader.is_empty() {
570 return Err(IssuerError::Trailing {
571 offset: reader.offset(),
572 });
573 }
574 visitor(reading)
575 .map(|value| binary::Located { location, value })
576 .map_err(IssuerError::Callback)
577 })
578}
579
580pub fn check<
586 'view,
587 'document,
588 'p,
589 X: BinaryXml + CertificateXml,
590 P: DigestProvider<'p, X>,
591 W: Work,
592>(
593 xml: &X,
594 bound: &Bound<X::Id, X::Document, X::Generation>,
595 reference: &'view Catalogue<X>,
596 certificate: &'view Certificate<'document>,
597 provider: &'p P,
598 execution: &W,
599) -> CheckResult<'view, 'document, X, P::Error> {
600 let key = bind_key::<X, P::Error, W>(
601 xml,
602 bound,
603 reference,
604 certificate,
605 WorkingKey::standing(certificate.tbs().public_key()),
606 execution,
607 )?;
608 let digest =
609 compare(xml, reference, 0, certificate, provider, execution).map_err(CheckError::Digest)?;
610 Ok(CheckedCertificate {
611 reference,
612 certificate,
613 key,
614 digest,
615 })
616}
617
618fn bind_key<X: CertificateXml, PE, W: Work>(
619 xml: &X,
620 bound: &Bound<X::Id, X::Document, X::Generation>,
621 reference: &Catalogue<X>,
622 certificate: &Certificate<'_>,
623 key: WorkingKey<'_>,
624 execution: &W,
625) -> CertificateResult<KeyAssociation, X, PE> {
626 let source = bound.containers();
627 if reference.snapshot != source.snapshot() {
628 return Err(CheckError::ForeignCatalogue {
629 signature: source.signature(),
630 property: reference.node,
631 });
632 }
633 let access = Access::new(xml, source.snapshot(), execution);
634 if !access
635 .equal_id(reference.signature, source.signature())
636 .map_err(|cause| {
637 CheckError::Access(AccessError::Resource {
638 node: reference.node,
639 cause,
640 })
641 })?
642 {
643 return Err(CheckError::ForeignCatalogue {
644 signature: source.signature(),
645 property: reference.node,
646 });
647 }
648 for index in 0..reference.entries.len() {
649 access
650 .query(reference.node, |_, _, work| Ok(work.charge_work(1)))
651 .map_err(CheckError::Access)?
652 .map_err(|cause| {
653 CheckError::Access(AccessError::Resource {
654 node: reference.node,
655 cause,
656 })
657 })?;
658 let entry = &reference.entries[index];
659 let written = access
660 .attribute(
661 entry.node,
662 Name {
663 namespace: "",
664 local: "URI",
665 },
666 |value| value.is_some(),
667 )
668 .map_err(CheckError::Access)?;
669 if written {
675 return Err(CheckError::ForbiddenCertificateUri {
676 location: Location {
677 snapshot: reference.snapshot,
678 node: entry.node,
679 },
680 });
681 }
682 }
683 access
684 .query(source.signature(), |xml, snapshot, work| {
685 xml.key_association(snapshot, source.signature(), certificate, key, work)
686 })
687 .map_err(CheckError::Access)
688}
689
690pub fn read<X: Xml, W: Work>(
697 access: &Access<'_, X, W>,
698 signed: &Reading<X>,
699) -> PropertyResult<Catalogue<X>, X> {
700 let node = signed.signing_certificate();
701 if signed.snapshot() != access.snapshot() {
702 return Err(Error::Access(AccessError::Changed { node }));
703 }
704 let mut entries = access
705 .query(node, |_, _, work| Ok(work.vector()))
706 .map_err(Error::Access)?;
707 for child in access.children(node) {
708 let child = child.map_err(Error::Child)?;
709 expect_name(access, child, XADES, "Cert")?;
710 let entry = read_entry(access, child)?;
711 entries
712 .try_push(entry)
713 .map_err(|cause| Error::Resource { node: child, cause })?;
714 }
715 if entries.is_empty() {
716 return Err(Error::Missing {
717 parent: node,
718 local: "Cert",
719 });
720 }
721 Ok(References {
722 snapshot: signed.snapshot(),
723 signature: signed.signature(),
724 node,
725 entries: entries.into_owned(),
726 })
727}
728
729pub(crate) fn read_entry<X: Xml, W: Work>(
730 access: &Access<'_, X, W>,
731 node: X::Id,
732) -> PropertyResult<Reference<X::Id>, X> {
733 let mut children = access.children(node);
734 let digest = required(&mut children, node, "CertDigest")?;
735 expect_name(access, digest, XADES, "CertDigest")?;
736 let issuer = children.next().transpose().map_err(Error::Child)?;
737 if let Some(issuer) = issuer {
738 expect_name(access, issuer, XADES, "IssuerSerialV2")?;
739 }
740 if let Some(extra) = children.next() {
741 return Err(Error::Unexpected {
742 node: extra.map_err(Error::Child)?,
743 });
744 }
745 let (method, value) = crate::properties::digest_value(access, digest)?;
746 Ok(Reference {
747 node,
748 method,
749 digest: value,
750 issuer,
751 })
752}
753
754fn required<X: Xml, W: Work>(
755 children: &mut crate::xml::Children<'_, '_, X, W>,
756 parent: X::Id,
757 local: &'static str,
758) -> PropertyResult<X::Id, X> {
759 children
760 .next()
761 .transpose()
762 .map_err(Error::Child)?
763 .ok_or(Error::Missing { parent, local })
764}
765
766fn expect_name<X: Xml, W: Work>(
767 access: &Access<'_, X, W>,
768 node: X::Id,
769 namespace: &str,
770 local: &str,
771) -> PropertyResult<(), X> {
772 if !access
773 .has_name(node, Name { namespace, local })
774 .map_err(Error::Access)?
775 {
776 return Err(Error::Unexpected { node });
777 }
778 Ok(())
779}