1use subtle::ConstantTimeEq;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum DigestAlgorithm {
16 Sha1,
18 Sha224,
20 Sha256,
22 Sha384,
24 Sha512,
26}
27
28impl DigestAlgorithm {
29 pub fn from_uri(uri: &str) -> Option<Self> {
43 match uri {
44 "http://www.w3.org/2000/09/xmldsig#sha1" => Some(Self::Sha1),
45 "http://www.w3.org/2001/04/xmldsig-more#sha224" => Some(Self::Sha224),
46 "http://www.w3.org/2001/04/xmlenc#sha256" => Some(Self::Sha256),
47 "http://www.w3.org/2001/04/xmldsig-more#sha384" => Some(Self::Sha384),
48 "http://www.w3.org/2001/04/xmlenc#sha512" => Some(Self::Sha512),
49 _ => None,
50 }
51 }
52
53 pub fn uri(self) -> &'static str {
55 match self {
56 Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
57 Self::Sha224 => "http://www.w3.org/2001/04/xmldsig-more#sha224",
58 Self::Sha256 => "http://www.w3.org/2001/04/xmlenc#sha256",
59 Self::Sha384 => "http://www.w3.org/2001/04/xmldsig-more#sha384",
60 Self::Sha512 => "http://www.w3.org/2001/04/xmlenc#sha512",
61 }
62 }
63
64 pub fn signing_allowed(self) -> bool {
69 !matches!(self, Self::Sha1)
70 }
71
72 pub fn output_len(self) -> usize {
74 match self {
75 Self::Sha1 => 20,
76 Self::Sha224 => 28,
77 Self::Sha256 => 32,
78 Self::Sha384 => 48,
79 Self::Sha512 => 64,
80 }
81 }
82}
83
84pub fn compute_digest(algorithm: DigestAlgorithm, data: &[u8]) -> Vec<u8> {
88 compute_digest_with_provider(crate::provider::default_provider(), algorithm, data)
89 .expect("default provider advertises every XMLDSig digest")
90}
91
92pub fn compute_digest_with_provider(
94 provider: &dyn crate::provider::CryptoProvider,
95 algorithm: DigestAlgorithm,
96 data: &[u8],
97) -> Result<Vec<u8>, crate::provider::ProviderError> {
98 provider.require_capability(crate::provider::ProviderCapability::Digest(algorithm))?;
99 let digest = provider.digest(algorithm, data)?;
100 let expected = algorithm.output_len();
101 if digest.len() != expected {
102 return Err(crate::provider::ProviderError::InvalidOutputSize {
103 operation: crate::provider::ProviderOperation::Digest,
104 expected,
105 actual: digest.len(),
106 });
107 }
108 Ok(digest)
109}
110
111pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
120 a.ct_eq(b).into()
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 struct RejectingDigestProvider {
128 output: Option<Vec<u8>>,
129 }
130
131 impl crate::provider::CryptoProvider for RejectingDigestProvider {
132 fn name(&self) -> &'static str {
133 "rejecting-digest"
134 }
135
136 fn supports(&self, capability: crate::provider::ProviderCapability<'_>) -> bool {
137 if self.output.is_none()
138 && matches!(capability, crate::provider::ProviderCapability::Digest(_))
139 {
140 return false;
141 }
142 crate::provider::default_provider().supports(capability)
143 }
144
145 fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
146 crate::provider::default_provider().fill_random(output)
147 }
148
149 fn derive_key(
150 &self,
151 parameters: &crate::provider::KdfParameters<'_>,
152 secret: &[u8],
153 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
154 crate::provider::default_provider().derive_key(parameters, secret)
155 }
156
157 fn digest(
158 &self,
159 _algorithm: DigestAlgorithm,
160 _data: &[u8],
161 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
162 if let Some(output) = &self.output {
163 return Ok(output.clone());
164 }
165 panic!("an unavailable digest capability must not be dispatched")
166 }
167
168 fn sign(
169 &self,
170 key: &dyn super::super::SigningKey,
171 algorithm: super::super::SignatureAlgorithm,
172 data: &[u8],
173 ) -> Result<Vec<u8>, super::super::SigningKeyError> {
174 crate::provider::default_provider().sign(key, algorithm, data)
175 }
176
177 fn verify(
178 &self,
179 key: &dyn super::super::VerifyingKey,
180 algorithm: super::super::SignatureAlgorithm,
181 data: &[u8],
182 signature: &[u8],
183 ) -> Result<bool, super::super::DsigError> {
184 crate::provider::default_provider().verify(key, algorithm, data, signature)
185 }
186
187 #[cfg(feature = "xmlenc")]
188 fn encrypt_data(
189 &self,
190 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
191 key: &[u8],
192 plaintext: &[u8],
193 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
194 crate::provider::default_provider().encrypt_data(algorithm, key, plaintext)
195 }
196
197 #[cfg(feature = "xmlenc")]
198 fn decrypt_data(
199 &self,
200 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
201 key: &[u8],
202 ciphertext: &[u8],
203 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
204 crate::provider::default_provider().decrypt_data(algorithm, key, ciphertext)
205 }
206
207 #[cfg(feature = "xmlenc")]
208 fn wrap_key(
209 &self,
210 algorithm: crate::xmlenc::KeyWrapAlgorithm,
211 kek: &[u8],
212 key: &[u8],
213 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
214 crate::provider::default_provider().wrap_key(algorithm, kek, key)
215 }
216
217 #[cfg(feature = "xmlenc")]
218 fn unwrap_key(
219 &self,
220 algorithm: crate::xmlenc::KeyWrapAlgorithm,
221 kek: &[u8],
222 wrapped: &[u8],
223 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
224 crate::provider::default_provider().unwrap_key(algorithm, kek, wrapped)
225 }
226
227 #[cfg(feature = "xmlenc")]
228 fn transport_key(
229 &self,
230 key: &dyn crate::provider::KeyTransportKey,
231 parameters: &crate::xmlenc::RsaOaepParameters,
232 plaintext: &[u8],
233 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
234 crate::provider::default_provider().transport_key(key, parameters, plaintext)
235 }
236
237 #[cfg(feature = "xmlenc")]
238 fn recover_key(
239 &self,
240 key: &dyn crate::provider::KeyRecoveryKey,
241 parameters: &crate::xmlenc::RsaOaepParameters,
242 ciphertext: &[u8],
243 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
244 crate::provider::default_provider().recover_key(key, parameters, ciphertext)
245 }
246 }
247
248 #[test]
249 fn explicit_provider_digest_failures_are_returned() {
250 assert!(matches!(
253 compute_digest_with_provider(
254 &RejectingDigestProvider { output: None },
255 DigestAlgorithm::Sha256,
256 b"x"
257 ),
258 Err(crate::provider::ProviderError::Unsupported { .. })
259 ));
260 }
261
262 #[test]
263 fn explicit_provider_digest_output_must_match_the_algorithm() {
264 for actual in [0, 31, 33] {
267 assert!(matches!(
268 compute_digest_with_provider(
269 &RejectingDigestProvider {
270 output: Some(vec![0_u8; actual]),
271 },
272 DigestAlgorithm::Sha256,
273 b"x",
274 ),
275 Err(crate::provider::ProviderError::InvalidOutputSize {
276 operation: crate::provider::ProviderOperation::Digest,
277 expected: 32,
278 actual: output_len,
279 }) if output_len == actual
280 ));
281 }
282
283 assert_eq!(
284 compute_digest_with_provider(
285 &RejectingDigestProvider {
286 output: Some(vec![7_u8; 32]),
287 },
288 DigestAlgorithm::Sha256,
289 b"x",
290 ),
291 Ok(vec![7_u8; 32]),
292 );
293 }
294
295 #[test]
298 fn from_uri_sha1() {
299 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#sha1");
300 assert_eq!(algo, Some(DigestAlgorithm::Sha1));
301 }
302
303 #[test]
304 fn from_uri_sha256() {
305 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha256");
306 assert_eq!(algo, Some(DigestAlgorithm::Sha256));
307 }
308
309 #[test]
310 fn from_uri_sha384() {
311 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha384");
312 assert_eq!(algo, Some(DigestAlgorithm::Sha384));
313 }
314
315 #[test]
316 fn from_uri_sha512() {
317 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha512");
318 assert_eq!(algo, Some(DigestAlgorithm::Sha512));
319 }
320
321 #[test]
322 fn from_uri_unknown() {
323 assert_eq!(
324 DigestAlgorithm::from_uri("http://example.com/unknown"),
325 None
326 );
327 }
328
329 #[test]
330 fn uri_round_trip() {
331 for algo in [
332 DigestAlgorithm::Sha1,
333 DigestAlgorithm::Sha256,
334 DigestAlgorithm::Sha384,
335 DigestAlgorithm::Sha512,
336 ] {
337 assert_eq!(
338 DigestAlgorithm::from_uri(algo.uri()),
339 Some(algo),
340 "round-trip failed for {algo:?}"
341 );
342 }
343 }
344
345 #[test]
348 fn sha1_verify_only() {
349 assert!(!DigestAlgorithm::Sha1.signing_allowed());
350 }
351
352 #[test]
353 fn sha256_signing_allowed() {
354 assert!(DigestAlgorithm::Sha256.signing_allowed());
355 }
356
357 #[test]
358 fn sha384_signing_allowed() {
359 assert!(DigestAlgorithm::Sha384.signing_allowed());
360 }
361
362 #[test]
363 fn sha512_signing_allowed() {
364 assert!(DigestAlgorithm::Sha512.signing_allowed());
365 }
366
367 #[test]
370 fn output_lengths() {
371 assert_eq!(DigestAlgorithm::Sha1.output_len(), 20);
372 assert_eq!(DigestAlgorithm::Sha256.output_len(), 32);
373 assert_eq!(DigestAlgorithm::Sha384.output_len(), 48);
374 assert_eq!(DigestAlgorithm::Sha512.output_len(), 64);
375 }
376
377 #[test]
378 fn sha224_uri_round_trips_and_has_standard_width() {
379 let algorithm = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha224")
380 .expect("XMLDSig 1.1 SHA-224 must be recognized");
381
382 assert_eq!(
383 algorithm.uri(),
384 "http://www.w3.org/2001/04/xmldsig-more#sha224"
385 );
386 assert_eq!(algorithm.output_len(), 28);
387 assert!(algorithm.signing_allowed());
388 }
389
390 #[test]
394 fn sha1_empty() {
395 let digest = compute_digest(DigestAlgorithm::Sha1, b"");
397 assert_eq!(digest.len(), 20);
398 assert_eq!(hex(&digest), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
399 }
400
401 #[test]
402 fn sha256_empty() {
403 let digest = compute_digest(DigestAlgorithm::Sha256, b"");
405 assert_eq!(digest.len(), 32);
406 assert_eq!(
407 hex(&digest),
408 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
409 );
410 }
411
412 #[test]
413 fn sha224_empty() {
414 let digest = compute_digest(
415 DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha224")
416 .expect("SHA-224 URI must be recognized"),
417 b"",
418 );
419 assert_eq!(
420 hex(&digest),
421 "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"
422 );
423 }
424
425 #[test]
426 fn sha384_empty() {
427 let digest = compute_digest(DigestAlgorithm::Sha384, b"");
429 assert_eq!(digest.len(), 48);
430 assert_eq!(
431 hex(&digest),
432 "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
433 );
434 }
435
436 #[test]
437 fn sha512_empty() {
438 let digest = compute_digest(DigestAlgorithm::Sha512, b"");
440 assert_eq!(digest.len(), 64);
441 assert_eq!(
442 hex(&digest),
443 "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
444 );
445 }
446
447 #[test]
448 fn sha256_hello_world() {
449 let digest = compute_digest(DigestAlgorithm::Sha256, b"hello world");
451 assert_eq!(
452 hex(&digest),
453 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
454 );
455 }
456
457 #[test]
458 fn sha1_abc() {
459 let digest = compute_digest(DigestAlgorithm::Sha1, b"abc");
461 assert_eq!(hex(&digest), "a9993e364706816aba3e25717850c26c9cd0d89d");
462 }
463
464 #[test]
467 fn constant_time_eq_identical() {
468 let a = compute_digest(DigestAlgorithm::Sha256, b"test");
469 let b = compute_digest(DigestAlgorithm::Sha256, b"test");
470 assert!(constant_time_eq(&a, &b));
471 }
472
473 #[test]
474 fn constant_time_eq_different_content() {
475 let a = compute_digest(DigestAlgorithm::Sha256, b"test1");
476 let b = compute_digest(DigestAlgorithm::Sha256, b"test2");
477 assert!(!constant_time_eq(&a, &b));
478 }
479
480 #[test]
481 fn constant_time_eq_different_lengths() {
482 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2]));
483 }
484
485 #[test]
486 fn constant_time_eq_empty() {
487 assert!(constant_time_eq(&[], &[]));
488 }
489
490 #[test]
493 fn digest_output_matches_declared_length() {
494 let data = b"test data for length verification";
495 for algo in [
496 DigestAlgorithm::Sha1,
497 DigestAlgorithm::Sha256,
498 DigestAlgorithm::Sha384,
499 DigestAlgorithm::Sha512,
500 ] {
501 let digest = compute_digest(algo, data);
502 assert_eq!(
503 digest.len(),
504 algo.output_len(),
505 "output length mismatch for {algo:?}"
506 );
507 }
508 }
509
510 fn hex(bytes: &[u8]) -> String {
512 bytes.iter().map(|b| format!("{b:02x}")).collect()
513 }
514}