Skip to main content

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}