xml_sec/xmldsig/verify/session/
hash_uses.rs1use super::*;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum HashUseRole {
8 SignatureMessage,
10 MaskGeneration,
12 Reference(ReferenceParent),
14}
15
16#[cfg(test)]
17mod tests {
18 use super::*;
19
20 #[test]
21 fn every_advertised_method_has_explicit_message_hash_semantics() {
22 for algorithm in SignatureAlgorithm::ALL {
25 assert!(
26 !matches!(message_hash(algorithm), HashUseAlgorithm::Unsupported(_)),
27 "{algorithm:?}"
28 );
29 }
30 }
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum HashUseAlgorithm<'a> {
36 Digest(DigestAlgorithm),
38 Shake256_512,
40 Intrinsic(SignatureAlgorithm),
42 Unsupported(Option<&'a str>),
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum HashUseState {
49 Complete,
51 Unsupported,
53 Unprocessed,
55}
56
57#[derive(Debug)]
59pub struct SignatureHashUse<'a> {
60 pub signature: NodeIdentity,
62 pub instruction: NodeIdentity,
64 pub method: Option<NodeIdentity>,
66 pub role: HashUseRole,
68 pub algorithm: HashUseAlgorithm<'a>,
70 pub state: HashUseState,
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum HashUseCompleteness {
77 Complete,
79 Unsupported(NodeIdentity),
81 Unprocessed(NodeIdentity),
83}
84
85fn message_hash(algorithm: SignatureAlgorithm) -> HashUseAlgorithm<'static> {
86 if let Some(parameters) = algorithm.rsa_pss_parameters() {
87 return HashUseAlgorithm::Digest(parameters.digest);
88 }
89 if let Some(digest) = algorithm.ecdsa_digest() {
90 return HashUseAlgorithm::Digest(digest);
91 }
92 use DigestAlgorithm as D;
93 use SignatureAlgorithm as S;
94 let digest = match algorithm {
95 S::RsaSha1 | S::DsaSha1 | S::HmacSha1 => D::Sha1,
96 S::RsaSha224 | S::HmacSha224 => D::Sha224,
97 S::RsaSha256 | S::DsaSha256 | S::HmacSha256 => D::Sha256,
98 S::RsaSha384 | S::HmacSha384 => D::Sha384,
99 S::RsaSha512 | S::HmacSha512 | S::Ed25519Ph => D::Sha512,
100 #[cfg(feature = "legacy-algorithms")]
101 S::RsaMd5 | S::HmacMd5 => D::Md5,
102 #[cfg(feature = "legacy-algorithms")]
103 S::RsaRipemd160 | S::HmacRipemd160 => D::Ripemd160,
104 S::Ed448Ph => return HashUseAlgorithm::Shake256_512,
108 S::Ed25519 | S::Ed25519Ctx | S::Ed448 | S::PostQuantum(_) => {
109 return HashUseAlgorithm::Intrinsic(algorithm);
110 }
111 _ => return HashUseAlgorithm::Unsupported(Some(algorithm.uri())),
112 };
113 HashUseAlgorithm::Digest(digest)
114}
115
116struct ReferenceHashVisitor<'a, W, F> {
119 signature: NodeIdentity,
120 instruction: NodeIdentity,
121 parent: ReferenceParent,
122 completed: bool,
123 work: &'a W,
124 completeness: &'a mut HashUseCompleteness,
125 visitor: &'a mut F,
126}
127
128impl<W: Work, F> ReferenceHashVisitor<'_, W, F> {
129 fn emit<E>(
130 &mut self,
131 algorithm: HashUseAlgorithm<'_>,
132 method: Option<NodeIdentity>,
133 ) -> Result<(), CatalogueError<E>>
134 where
135 F: for<'a> FnMut(SignatureHashUse<'a>) -> Result<(), E>,
136 {
137 let state = if matches!(algorithm, HashUseAlgorithm::Unsupported(_)) {
138 *self.completeness = HashUseCompleteness::Unsupported(self.instruction);
139 HashUseState::Unsupported
140 } else if self.completed {
141 HashUseState::Complete
142 } else {
143 if matches!(self.completeness, HashUseCompleteness::Complete) {
144 *self.completeness = HashUseCompleteness::Unprocessed(self.instruction);
145 }
146 HashUseState::Unprocessed
147 };
148 self.work
149 .charge_work(1)
150 .map_err(|error| CatalogueError::Processing(error.into()))?;
151 (self.visitor)(SignatureHashUse {
152 signature: self.signature,
153 instruction: self.instruction,
154 method,
155 role: HashUseRole::Reference(self.parent),
156 algorithm,
157 state,
158 })
159 .map_err(CatalogueError::Visitor)
160 }
161}
162
163impl<W: Work, Q: Work> SelectedSignature<'_, '_, '_, '_, W, Q> {
164 pub fn signature_method(&self) -> Result<SelectedSignatureMethod, DsigError> {
166 self.session.signature_method_in(self.index, self.work)
167 }
168
169 pub fn visit_hash_uses<E>(
172 &self,
173 mut visitor: impl for<'a> FnMut(SignatureHashUse<'a>) -> Result<(), E>,
174 ) -> Result<HashUseCompleteness, CatalogueError<E>> {
175 let method = self
176 .signature_method()
177 .map_err(CatalogueError::Processing)?;
178 let signature = self.session.evidence.signatures()[self.index].identity;
179 let mut completeness = HashUseCompleteness::Complete;
180 let algorithm = message_hash(method.algorithm);
181 let state = if matches!(algorithm, HashUseAlgorithm::Unsupported(_)) {
182 completeness = HashUseCompleteness::Unsupported(method.identity);
183 HashUseState::Unsupported
184 } else {
185 HashUseState::Complete
186 };
187 self.work
188 .charge_work(1)
189 .map_err(|error| CatalogueError::Processing(error.into()))?;
190 visitor(SignatureHashUse {
191 signature,
192 instruction: method.identity,
193 method: Some(method.identity),
194 role: HashUseRole::SignatureMessage,
195 algorithm,
196 state,
197 })
198 .map_err(CatalogueError::Visitor)?;
199 if let Some(parameters) = method.algorithm.rsa_pss_parameters() {
203 self.work
204 .charge_work(1)
205 .map_err(|error| CatalogueError::Processing(error.into()))?;
206 visitor(SignatureHashUse {
207 signature,
208 instruction: method.identity,
209 method: Some(method.identity),
210 role: HashUseRole::MaskGeneration,
211 algorithm: HashUseAlgorithm::Digest(parameters.mgf_digest),
212 state: HashUseState::Complete,
213 })
214 .map_err(CatalogueError::Visitor)?;
215 }
216 let catalogue = self.visit_reference_instructions(|node, parent, method| {
217 let reference = self.session.view.node_identity(node);
218 let digest_receipt = self.session.operations[self.index]
219 .capture
220 .digests
221 .borrow()
222 .get(&node.id().get_usize(), self.work)
223 .map_err(|error| CatalogueError::Processing(error.into()))?
224 .copied();
225 let mut emitter = ReferenceHashVisitor {
226 signature,
227 instruction: reference,
228 parent,
229 completed: digest_receipt.is_some_and(|receipt| receipt.computed),
230 work: self.work,
231 completeness: &mut completeness,
232 visitor: &mut visitor,
233 };
234 let Some(method) = method else {
235 return emitter.emit(HashUseAlgorithm::Unsupported(None), None);
236 };
237 let method_identity = self.session.view.node_identity(method);
238 if let Some(receipt) = digest_receipt {
239 return emitter.emit(
240 HashUseAlgorithm::Digest(receipt.algorithm),
241 Some(method_identity),
242 );
243 }
244 let mut found = false;
245 let inspected = super::super::super::extension::visit_direct_attributes(
246 self.session.view,
247 method_identity,
248 self.work,
249 |attribute| {
250 if attribute.namespace.is_none() && attribute.local_name == "Algorithm" {
251 found = true;
252 let comparisons = DigestAlgorithm::ALL
255 .iter()
256 .map(|algorithm| algorithm.uri().len() as u64 + 1)
257 .sum();
258 self.work
259 .charge_work(comparisons)
260 .map_err(|error| CatalogueError::Processing(error.into()))?;
261 let algorithm = DigestAlgorithm::from_uri(attribute.value).map_or(
262 HashUseAlgorithm::Unsupported(Some(attribute.value)),
263 HashUseAlgorithm::Digest,
264 );
265 emitter.emit(algorithm, Some(method_identity))?;
266 }
267 Ok(())
268 },
269 );
270 match inspected {
271 Ok(()) => {}
272 Err(super::super::super::extension::InspectionError::Visitor(error)) => {
273 return Err(error);
274 }
275 Err(super::super::super::extension::InspectionError::Processing(error)) => {
276 return Err(CatalogueError::Processing(match error {
277 super::super::super::extension::ScalarError::Resource(error) => {
278 error.into()
279 }
280 super::super::super::extension::ScalarError::Document(error) => {
281 error.into()
282 }
283 super::super::super::extension::ScalarError::NotElement => {
284 DsigError::InvalidRequest {
285 reason: "DigestMethod is not an element",
286 }
287 }
288 }));
289 }
290 }
291 if !found {
292 emitter.emit(HashUseAlgorithm::Unsupported(None), Some(method_identity))?;
293 }
294 Ok(())
295 });
296 match catalogue {
297 Err(CatalogueError::Visitor(error)) => Err(error),
298 Err(CatalogueError::Processing(error)) => Err(CatalogueError::Processing(error)),
299 Ok(status) => {
300 if matches!(completeness, HashUseCompleteness::Complete) {
301 match status {
302 CatalogueCompleteness::Complete => {}
303 CatalogueCompleteness::Incomplete(node)
304 | CatalogueCompleteness::Unchecked(node) => {
305 completeness = HashUseCompleteness::Unprocessed(node)
306 }
307 }
308 }
309 Ok(completeness)
310 }
311 }
312 }
313}