structured_xades/validation/ref_digest.rs
1//! Exact original referenced-artifact digests, without authority inference.
2
3use super::{ReferenceKind, ReferenceProperty};
4use crate::{
5 binary::{self, BinaryXml, Location},
6 certificate::{Agreement, DigestProvider},
7 digest, material,
8 xml::Access,
9};
10use structured_asn1::memory::Work;
11use structured_cms::stream::RangesError;
12
13/// Original bytes available for reference comparison. Independently supplied
14/// path certificates/CRLs do not need fabricated XML material nodes. OCSP must
15/// retain its WHOLE envelope; a bare basic response is intentionally not a target.
16///
17/// ```compile_fail
18/// use structured_xades::validation::ReferenceTarget;
19/// fn wrong(basic: &structured_pkix::ocsp::BasicResponse<'_>) {
20/// let _: ReferenceTarget<'_, '_, usize, u8, u8> = ReferenceTarget::Ocsp(basic);
21/// }
22/// ```
23#[derive(Clone, Copy)]
24pub enum ReferenceTarget<'view, 'document, N, D, G> {
25 /// Original parsed certificate, including an independently retained anchor.
26 Certificate(&'view structured_pkix::Certificate<'document>),
27 /// Original complete or delta list, without serialization or a numeric ID.
28 Crl(&'view structured_pkix::crl::CertificateList<'document>),
29 /// Whole original successful OCSP envelope, never only its basic payload.
30 Ocsp(&'view structured_pkix::ocsp::ResponseBytes<'document>),
31 /// Original XML-incorporated whole certificate/CRL/OCSPResponse source.
32 Encapsulated(&'view material::Source<N, D, G>),
33}
34
35/// Exact digest processing, independent of identifier/path/authority checks.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ReferenceDigest {
38 /// The original kind and complete declared digest were compared.
39 Compared(Agreement),
40 /// The supplied material has another schema kind. No provider was invoked.
41 DifferentKind,
42 /// This OCSP reference has no digest; its identifier still requires checking.
43 IdentifierOnly,
44 /// `OtherRef` has external format semantics; no generic hash is invented.
45 OtherFormat,
46}
47
48/// Caller/source misuse and actual execution failures are terminal.
49#[derive(Debug)]
50pub enum ReferenceDigestError<PE> {
51 /// Root selected-key/certificate/path binding was not established; supplied
52 /// candidate bytes cannot stand for an actual signature participant.
53 UnboundPath,
54 /// No original reference exists at this caller-selected ordinal.
55 Index {
56 /// Requested ordinal, not a document byte location.
57 index: usize,
58 },
59 /// The material/view belongs to another selected signature or immutable
60 /// XML source generation.
61 Foreign,
62 /// The shared digest driver preserves acquisition/provider/resource causes.
63 Digest(digest::Error<RangesError, PE>),
64}
65
66/// Compare a retained original reference with a retained original artifact.
67/// EN319132-1 A.1.1/A.1.2/A.1.3/A.1.4 use whole certificate/CRL DER and
68/// whole `OCSPResponse`, NOT merely its `BasicOCSPResponse` payload.
69/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
70///
71/// Reuses the existing streaming/method/width/MD5 core without serialization,
72/// copying, implicit acquisition or a second algorithm dispatcher. Source
73/// retention alone does not certify valid DER; parse/authenticate the actual
74/// material through the material/PKIX pipeline separately. Neither agreement
75/// nor a URI proves incorporation, issuer identity, path use or a baseline.
76pub fn compare_reference_digest<'p, X: BinaryXml, P: DigestProvider<'p, X>, W: Work>(
77 xml: &X,
78 references: &ReferenceProperty<X::Id, X::Document, X::Generation>,
79 index: usize,
80 source: &material::Material<X>,
81 provider: &'p P,
82 execution: &W,
83) -> binary::BinaryResult<ReferenceDigest, X, ReferenceDigestError<P::Error>> {
84 compare_reference_target(
85 xml,
86 references,
87 index,
88 ReferenceTarget::Encapsulated(source),
89 provider,
90 execution,
91 )
92}
93
94/// Compare an original reference against an actual external parsed certificate
95/// or CRL, or a retained whole XML envelope, through the SAME digest kernel.
96/// External acquisition/path trust stays with the caller; only the CURRENT
97/// operation pays this comparison and original storage ownership is unchanged.
98/// This does not imply that an external value is incorporated in the signature.
99/// The Annex A full-DER/whole-response and separate identifier obligations are
100/// identical to [`compare_reference_digest`]; no basic-only OCSP hash is allowed.
101pub fn compare_reference_target<'p, X: BinaryXml, P: DigestProvider<'p, X>, W: Work>(
102 xml: &X,
103 references: &ReferenceProperty<X::Id, X::Document, X::Generation>,
104 index: usize,
105 target: ReferenceTarget<'_, '_, X::Id, X::Document, X::Generation>,
106 provider: &'p P,
107 execution: &W,
108) -> binary::BinaryResult<ReferenceDigest, X, ReferenceDigestError<P::Error>> {
109 let location = Location {
110 snapshot: references.snapshot(),
111 node: references.node(),
112 };
113 let access = Access::new(xml, references.snapshot(), execution);
114 access
115 .query(references.node(), |_, _, _| Ok(()))
116 .map_err(|cause| binary::Error::Access { location, cause })?;
117 let (kind, bytes) = match target {
118 ReferenceTarget::Certificate(value) => (material::Kind::Certificate, value.as_bytes()),
119 ReferenceTarget::Crl(value) => (material::Kind::Crl, value.as_bytes()),
120 ReferenceTarget::Ocsp(value) => (material::Kind::Ocsp, value.as_bytes()),
121 ReferenceTarget::Encapsulated(source) => {
122 if source.location().snapshot != references.snapshot() {
123 return Err(binary::Error::Callback {
124 location,
125 cause: ReferenceDigestError::Foreign,
126 });
127 }
128 (source.kind(), source.bytes())
129 }
130 };
131 let Some(entry) = references.entries().get(index) else {
132 return Err(binary::Error::Callback {
133 location,
134 cause: ReferenceDigestError::Index { index },
135 });
136 };
137 let expected_kind = match entry.kind() {
138 ReferenceKind::Certificate => material::Kind::Certificate,
139 ReferenceKind::Crl => material::Kind::Crl,
140 ReferenceKind::Ocsp => material::Kind::Ocsp,
141 ReferenceKind::Other => return Ok(ReferenceDigest::OtherFormat),
142 };
143 if kind != expected_kind {
144 return Ok(ReferenceDigest::DifferentKind);
145 }
146 let Some((method, value)) = entry.digest() else {
147 return Ok(ReferenceDigest::IdentifierOnly);
148 };
149 digest::check(
150 xml,
151 digest::Reference {
152 snapshot: references.snapshot(),
153 method,
154 value,
155 },
156 &mut [bytes],
157 provider,
158 execution,
159 )
160 .map(ReferenceDigest::Compared)
161 .map_err(|cause| cause.map_callback(ReferenceDigestError::Digest))
162}