Skip to main content

structured_xades/validation/
ref_ocsp.rs

1//! Original OCSP identifier against one actually authenticated response signer.
2
3use super::{
4    ReferenceIdentifier, ReferenceKeyResult, ReferenceNameError, ReferenceProperty, ReferenceTime,
5    ReferenceTimeError, ResponderIdentifier, ResponderKeyError, matches_reference_key,
6    matches_reference_name, matches_reference_time,
7};
8use crate::{
9    binary::{self, BinaryXml, Location},
10    properties::CalendarXml,
11    scalar::TextXml,
12    xml::{Access, AccessError, Xml},
13};
14use structured_asn1::Reader;
15use structured_pkix::algorithm::digest::AsnDigest;
16use structured_pkix::ocsp::{ResponderMatch, SignedResponse};
17
18/// Completed identifier facts, separate from whole-response digest, target
19/// authorization, status usability, actual participant use and incorporation.
20pub struct OcspReferenceIdentity {
21    responder: Option<ResponderMatch>,
22    produced_at: ReferenceTime,
23}
24
25impl OcspReferenceIdentity {
26    /// Both `ByName` and `ByKey` identify the same authenticated signer;
27    /// the branch written inside `ResponseData` need not match the XML branch.
28    /// None means mandatory time syntax was malformed or unavailable, so no
29    /// responder normalization/digest callback was attempted.
30    #[must_use]
31    pub fn responder(&self) -> Option<ResponderMatch> {
32        self.responder
33    }
34
35    /// Complete original production-time interpretation, including unstated
36    /// timezone, malformed or unavailable schema facts.
37    #[must_use]
38    pub fn produced_at(&self) -> ReferenceTime {
39        self.produced_at
40    }
41}
42
43/// Exact stage/cause; processing/resource refusal is not identity disagreement.
44#[derive(Debug)]
45pub enum OcspReferenceError<E, PE, N, D, G> {
46    /// Original source/generation/current-operation query refused.
47    Access(AccessError<E, N>),
48    /// No reference exists at this ordinal.
49    Index {
50        /// Requested ordinal, not an XML byte offset.
51        index: usize,
52    },
53    /// Original RFC4514/Name comparison failure.
54    Name(ReferenceNameError<E, N>),
55    /// Original DER `ByKey` or selected digest-provider failure.
56    Key(binary::Error<E, ResponderKeyError<structured_pkix::error::CallbackError<PE>>, N, D, G>),
57    /// Original XML production-time or current comparison failure.
58    Time(ReferenceTimeError<E, N>),
59}
60
61/// One complete original identifier result in this XML owner's source domain.
62pub type OcspReferenceResult<X, PE> = Result<
63    Option<binary::LocatedValue<OcspReferenceIdentity, X>>,
64    OcspReferenceError<
65        <X as Xml>::Error,
66        PE,
67        <X as Xml>::Id,
68        <X as Xml>::Document,
69        <X as Xml>::Generation,
70    >,
71>;
72
73/// Compare both fields of an original `OCSPIdentifier` with a response whose
74/// mathematical signature and ResponderID-to-signer binding already succeeded.
75/// Annex A.1.2 requires the actual responder and producedAt of the referenced
76/// response. RFC6960 ยง4.2.1 permits either responder identifier spelling; a
77/// `ByName` XML selector can identify a byKey response and conversely through the
78/// SAME authenticated signer. No signature/path/hash of the response is repeated.
79/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
80/// <https://www.rfc-editor.org/rfc/rfc6960#section-4.2.1>
81///
82/// All XML decoding, name work, key hash callbacks and calendar comparison use
83/// the current operation. No successful prefix is returned on a later refusal.
84/// None means a different reference kind, never agreement. The selected response
85/// still needs whole-envelope digest association, issuer authorization, status
86/// validity, actual use and incorporation checks; this is not a baseline grade.
87///
88/// # Panics
89/// Only if an immutable privately constructed reference changes its selector
90/// kind between the original field selection and the same field kernel.
91pub fn check_ocsp_reference<'k, X, D, PE>(
92    xml: &X,
93    references: &ReferenceProperty<X::Id, X::Document, X::Generation>,
94    index: usize,
95    signed: SignedResponse<'_, '_>,
96    digest: &'k D,
97    operation: &Reader<'_>,
98) -> OcspReferenceResult<X, PE>
99where
100    X: BinaryXml + TextXml + CalendarXml,
101    D: AsnDigest<'k, Error = PE>,
102{
103    let work = operation.execution();
104    let access = Access::new(xml, references.snapshot(), &work);
105    access
106        .query(references.node(), |_, _, _| Ok(()))
107        .map_err(OcspReferenceError::Access)?;
108    let Some(entry) = references.entries().get(index) else {
109        return Err(OcspReferenceError::Index { index });
110    };
111    let ReferenceIdentifier::Ocsp {
112        node, responder, ..
113    } = entry.identifier()
114    else {
115        return Ok(None);
116    };
117    let produced_at = matches_reference_time(
118        xml,
119        references,
120        index,
121        signed.response().produced_at(),
122        operation,
123    )
124    .map_err(OcspReferenceError::Time)?
125    .expect("original OCSP time entry");
126    // Mandatory scalar syntax precedes provider work. A completed semantic
127    // time mismatch/unstated zone is still readable and preserves both facts;
128    // malformed/unavailable syntax cannot admit a digest resolver side effect.
129    let responder = if matches!(
130        produced_at,
131        ReferenceTime::Malformed | ReferenceTime::Unsupported
132    ) {
133        None
134    } else {
135        Some(match responder {
136            ResponderIdentifier::Name(_) => {
137                match matches_reference_name(
138                    xml,
139                    references,
140                    index,
141                    signed.signer().tbs().subject(),
142                    operation,
143                )
144                .map_err(OcspReferenceError::Name)?
145                {
146                    Some(true) => ResponderMatch::Matches,
147                    Some(false) => ResponderMatch::Differs,
148                    None => unreachable!("original ByName entry"),
149                }
150            }
151            ResponderIdentifier::Key(_) => {
152                let result: ReferenceKeyResult<X, PE> = matches_reference_key(
153                    xml,
154                    references,
155                    index,
156                    signed.signer(),
157                    digest,
158                    operation,
159                );
160                result
161                    .map_err(OcspReferenceError::Key)?
162                    .expect("original ByKey entry")
163                    .value
164            }
165        })
166    };
167    access
168        .query(node, |_, _, _| Ok(()))
169        .map_err(OcspReferenceError::Access)?;
170    Ok(Some(binary::Located {
171        location: Location {
172            snapshot: references.snapshot(),
173            node,
174        },
175        value: OcspReferenceIdentity {
176            responder,
177            produced_at,
178        },
179    }))
180}