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}