1use subtle::ConstantTimeEq;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum DigestAlgorithm {
17 #[cfg(feature = "legacy-algorithms")]
19 Md5,
20 #[cfg(feature = "legacy-algorithms")]
22 Ripemd160,
23 Sha1,
25 Sha224,
27 Sha256,
29 Sha384,
31 Sha512,
33 Sha3_224,
35 Sha3_256,
37 Sha3_384,
39 Sha3_512,
41}
42
43impl DigestAlgorithm {
44 pub const ALL: [Self; 9 + if cfg!(feature = "legacy-algorithms") {
46 2
47 } else {
48 0
49 }] = [
50 #[cfg(feature = "legacy-algorithms")]
51 Self::Md5,
52 #[cfg(feature = "legacy-algorithms")]
53 Self::Ripemd160,
54 Self::Sha1,
55 Self::Sha224,
56 Self::Sha256,
57 Self::Sha384,
58 Self::Sha512,
59 Self::Sha3_224,
60 Self::Sha3_256,
61 Self::Sha3_384,
62 Self::Sha3_512,
63 ];
64 pub fn from_uri(uri: &str) -> Option<Self> {
80 match uri {
81 #[cfg(feature = "legacy-algorithms")]
84 "http://www.w3.org/2001/04/xmldsig-more#md5" => Some(Self::Md5),
85 #[cfg(feature = "legacy-algorithms")]
86 "http://www.w3.org/2001/04/xmlenc#ripemd160" => Some(Self::Ripemd160),
87 "http://www.w3.org/2000/09/xmldsig#sha1" => Some(Self::Sha1),
88 "http://www.w3.org/2001/04/xmldsig-more#sha224" => Some(Self::Sha224),
89 "http://www.w3.org/2001/04/xmlenc#sha256" => Some(Self::Sha256),
90 "http://www.w3.org/2001/04/xmldsig-more#sha384" => Some(Self::Sha384),
91 "http://www.w3.org/2001/04/xmlenc#sha512" => Some(Self::Sha512),
92 "http://www.w3.org/2007/05/xmldsig-more#sha3-224" => Some(Self::Sha3_224),
95 "http://www.w3.org/2007/05/xmldsig-more#sha3-256" => Some(Self::Sha3_256),
96 "http://www.w3.org/2007/05/xmldsig-more#sha3-384" => Some(Self::Sha3_384),
97 "http://www.w3.org/2007/05/xmldsig-more#sha3-512" => Some(Self::Sha3_512),
98 _ => None,
99 }
100 }
101
102 pub fn uri(self) -> &'static str {
104 match self {
105 #[cfg(feature = "legacy-algorithms")]
106 Self::Md5 => "http://www.w3.org/2001/04/xmldsig-more#md5",
107 #[cfg(feature = "legacy-algorithms")]
108 Self::Ripemd160 => "http://www.w3.org/2001/04/xmlenc#ripemd160",
109 Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
110 Self::Sha224 => "http://www.w3.org/2001/04/xmldsig-more#sha224",
111 Self::Sha256 => "http://www.w3.org/2001/04/xmlenc#sha256",
112 Self::Sha384 => "http://www.w3.org/2001/04/xmldsig-more#sha384",
113 Self::Sha512 => "http://www.w3.org/2001/04/xmlenc#sha512",
114 Self::Sha3_224 => "http://www.w3.org/2007/05/xmldsig-more#sha3-224",
115 Self::Sha3_256 => "http://www.w3.org/2007/05/xmldsig-more#sha3-256",
116 Self::Sha3_384 => "http://www.w3.org/2007/05/xmldsig-more#sha3-384",
117 Self::Sha3_512 => "http://www.w3.org/2007/05/xmldsig-more#sha3-512",
118 }
119 }
120
121 pub fn signing_allowed(self) -> bool {
126 !matches!(self, Self::Sha1) && !self.requires_explicit_permission()
127 }
128
129 pub const fn requires_explicit_permission(self) -> bool {
131 match self {
132 #[cfg(feature = "legacy-algorithms")]
133 Self::Md5 | Self::Ripemd160 => true,
134 _ => false,
135 }
136 }
137
138 pub fn output_len(self) -> usize {
140 match self {
141 #[cfg(feature = "legacy-algorithms")]
142 Self::Md5 => 16,
143 #[cfg(feature = "legacy-algorithms")]
144 Self::Ripemd160 => 20,
145 Self::Sha1 => 20,
146 Self::Sha224 | Self::Sha3_224 => 28,
147 Self::Sha256 | Self::Sha3_256 => 32,
148 Self::Sha384 | Self::Sha3_384 => 48,
149 Self::Sha512 | Self::Sha3_512 => 64,
150 }
151 }
152}
153
154pub fn compute_digest(algorithm: DigestAlgorithm, data: &[u8]) -> Vec<u8> {
158 compute_digest_with_provider(crate::provider::default_provider(), algorithm, data)
159 .expect("default provider advertises every XMLDSig digest")
160}
161
162pub fn compute_digest_with_provider(
164 provider: &dyn crate::provider::CryptoProvider,
165 algorithm: DigestAlgorithm,
166 data: &[u8],
167) -> Result<Vec<u8>, crate::provider::ProviderError> {
168 let mut state = begin_digest(provider, algorithm)?;
169 state.update(data)?;
170 finish_digest(algorithm, state)
171}
172
173pub(crate) fn begin_digest<'p>(
175 provider: &'p dyn crate::provider::CryptoProvider,
176 algorithm: DigestAlgorithm,
177) -> Result<Box<dyn crate::provider::DigestState + 'p>, crate::provider::ProviderError> {
178 provider.require_capability(crate::provider::ProviderCapability::Digest(algorithm))?;
179 provider.begin_digest(algorithm)
180}
181
182pub(crate) fn finish_digest(
185 algorithm: DigestAlgorithm,
186 state: Box<dyn crate::provider::DigestState + '_>,
187) -> Result<Vec<u8>, crate::provider::ProviderError> {
188 let digest = state.finalize()?;
189 let expected = algorithm.output_len();
190 if digest.len() != expected {
191 return Err(crate::provider::ProviderError::InvalidOutputSize {
192 operation: crate::provider::ProviderOperation::Digest,
193 expected,
194 actual: digest.len(),
195 });
196 }
197 Ok(digest)
198}
199
200pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
209 a.ct_eq(b).into()
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 struct RejectingDigestProvider {
217 output: Option<Vec<u8>>,
218 }
219
220 struct FixedDigest(Vec<u8>);
222
223 impl crate::provider::DigestState for FixedDigest {
224 fn update(&mut self, _: &[u8]) -> Result<(), crate::provider::ProviderError> {
225 Ok(())
226 }
227 fn finalize(self: Box<Self>) -> Result<Vec<u8>, crate::provider::ProviderError> {
228 Ok(self.0)
229 }
230 }
231
232 impl crate::provider::CryptoProvider for RejectingDigestProvider {
233 fn name(&self) -> &'static str {
234 "rejecting-digest"
235 }
236
237 fn supports(&self, capability: crate::provider::ProviderCapability<'_>) -> bool {
238 if self.output.is_none()
239 && matches!(capability, crate::provider::ProviderCapability::Digest(_))
240 {
241 return false;
242 }
243 crate::provider::default_provider().supports(capability)
244 }
245
246 fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
247 crate::provider::default_provider().fill_random(output)
248 }
249
250 fn derive_key(
251 &self,
252 parameters: &crate::provider::KdfParameters<'_>,
253 secret: &[u8],
254 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
255 crate::provider::default_provider().derive_key(parameters, secret)
256 }
257
258 fn begin_digest(
259 &self,
260 _algorithm: DigestAlgorithm,
261 ) -> Result<Box<dyn crate::provider::DigestState + '_>, crate::provider::ProviderError>
262 {
263 if let Some(output) = &self.output {
264 return Ok(Box::new(FixedDigest(output.clone())));
265 }
266 panic!("an unavailable digest capability must not be dispatched")
267 }
268
269 fn sign(
270 &self,
271 key: &dyn super::super::SigningKey,
272 algorithm: super::super::SignatureAlgorithm,
273 data: &[u8],
274 ) -> Result<Vec<u8>, super::super::SigningKeyError> {
275 crate::provider::default_provider().sign(key, algorithm, data)
276 }
277
278 fn verify(
279 &self,
280 key: &dyn super::super::VerifyingKey,
281 algorithm: super::super::SignatureAlgorithm,
282 data: &[u8],
283 signature: &[u8],
284 ) -> Result<bool, super::super::DsigError> {
285 crate::provider::default_provider().verify(key, algorithm, data, signature)
286 }
287
288 #[cfg(feature = "xmlenc")]
289 fn encrypt_data(
290 &self,
291 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
292 key: &[u8],
293 plaintext: &[u8],
294 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
295 crate::provider::default_provider().encrypt_data(algorithm, key, plaintext)
296 }
297
298 #[cfg(feature = "xmlenc")]
299 fn decrypt_data(
300 &self,
301 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
302 key: &[u8],
303 ciphertext: &[u8],
304 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
305 crate::provider::default_provider().decrypt_data(algorithm, key, ciphertext)
306 }
307
308 #[cfg(feature = "xmlenc")]
309 fn wrap_key(
310 &self,
311 algorithm: crate::xmlenc::KeyWrapAlgorithm,
312 kek: &[u8],
313 key: &[u8],
314 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
315 crate::provider::default_provider().wrap_key(algorithm, kek, key)
316 }
317
318 #[cfg(feature = "xmlenc")]
319 fn unwrap_key(
320 &self,
321 algorithm: crate::xmlenc::KeyWrapAlgorithm,
322 kek: &[u8],
323 wrapped: &[u8],
324 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
325 crate::provider::default_provider().unwrap_key(algorithm, kek, wrapped)
326 }
327
328 #[cfg(feature = "xmlenc")]
329 fn transport_key(
330 &self,
331 key: &dyn crate::provider::KeyTransportKey,
332 parameters: &crate::xmlenc::RsaOaepParameters,
333 plaintext: &[u8],
334 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
335 crate::provider::default_provider().transport_key(key, parameters, plaintext)
336 }
337
338 #[cfg(feature = "xmlenc")]
339 fn recover_key(
340 &self,
341 key: &dyn crate::provider::KeyRecoveryKey,
342 parameters: &crate::xmlenc::RsaOaepParameters,
343 ciphertext: &[u8],
344 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
345 crate::provider::default_provider().recover_key(key, parameters, ciphertext)
346 }
347 }
348
349 #[test]
350 fn explicit_provider_digest_failures_are_returned() {
351 assert!(matches!(
354 compute_digest_with_provider(
355 &RejectingDigestProvider { output: None },
356 DigestAlgorithm::Sha256,
357 b"x"
358 ),
359 Err(crate::provider::ProviderError::Unsupported { .. })
360 ));
361 }
362
363 #[test]
364 fn explicit_provider_digest_output_must_match_the_algorithm() {
365 for actual in [0, 31, 33] {
368 assert!(matches!(
369 compute_digest_with_provider(
370 &RejectingDigestProvider {
371 output: Some(vec![0_u8; actual]),
372 },
373 DigestAlgorithm::Sha256,
374 b"x",
375 ),
376 Err(crate::provider::ProviderError::InvalidOutputSize {
377 operation: crate::provider::ProviderOperation::Digest,
378 expected: 32,
379 actual: output_len,
380 }) if output_len == actual
381 ));
382 }
383
384 assert_eq!(
385 compute_digest_with_provider(
386 &RejectingDigestProvider {
387 output: Some(vec![7_u8; 32]),
388 },
389 DigestAlgorithm::Sha256,
390 b"x",
391 ),
392 Ok(vec![7_u8; 32]),
393 );
394 }
395
396 #[test]
399 fn from_uri_sha1() {
400 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#sha1");
401 assert_eq!(algo, Some(DigestAlgorithm::Sha1));
402 }
403
404 #[test]
405 fn from_uri_sha256() {
406 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha256");
407 assert_eq!(algo, Some(DigestAlgorithm::Sha256));
408 }
409
410 #[test]
411 fn from_uri_sha384() {
412 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha384");
413 assert_eq!(algo, Some(DigestAlgorithm::Sha384));
414 }
415
416 #[test]
417 fn from_uri_sha512() {
418 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha512");
419 assert_eq!(algo, Some(DigestAlgorithm::Sha512));
420 }
421
422 #[test]
423 fn from_uri_unknown() {
424 assert_eq!(
425 DigestAlgorithm::from_uri("http://example.com/unknown"),
426 None
427 );
428 }
429
430 #[test]
431 fn uri_round_trip() {
432 for algo in [
433 DigestAlgorithm::Sha1,
434 DigestAlgorithm::Sha256,
435 DigestAlgorithm::Sha384,
436 DigestAlgorithm::Sha512,
437 ] {
438 assert_eq!(
439 DigestAlgorithm::from_uri(algo.uri()),
440 Some(algo),
441 "round-trip failed for {algo:?}"
442 );
443 }
444 }
445
446 #[test]
449 fn sha1_verify_only() {
450 assert!(!DigestAlgorithm::Sha1.signing_allowed());
451 }
452
453 #[test]
454 fn sha256_signing_allowed() {
455 assert!(DigestAlgorithm::Sha256.signing_allowed());
456 }
457
458 #[test]
459 fn sha384_signing_allowed() {
460 assert!(DigestAlgorithm::Sha384.signing_allowed());
461 }
462
463 #[test]
464 fn sha512_signing_allowed() {
465 assert!(DigestAlgorithm::Sha512.signing_allowed());
466 }
467
468 #[test]
471 fn output_lengths() {
472 assert_eq!(DigestAlgorithm::Sha1.output_len(), 20);
473 assert_eq!(DigestAlgorithm::Sha256.output_len(), 32);
474 assert_eq!(DigestAlgorithm::Sha384.output_len(), 48);
475 assert_eq!(DigestAlgorithm::Sha512.output_len(), 64);
476 }
477
478 #[test]
479 fn sha224_uri_round_trips_and_has_standard_width() {
480 let algorithm = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha224")
481 .expect("XMLDSig 1.1 SHA-224 must be recognized");
482
483 assert_eq!(
484 algorithm.uri(),
485 "http://www.w3.org/2001/04/xmldsig-more#sha224"
486 );
487 assert_eq!(algorithm.output_len(), 28);
488 assert!(algorithm.signing_allowed());
489 }
490
491 #[test]
495 fn sha1_empty() {
496 let digest = compute_digest(DigestAlgorithm::Sha1, b"");
498 assert_eq!(digest.len(), 20);
499 assert_eq!(hex(&digest), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
500 }
501
502 #[test]
503 fn sha256_empty() {
504 let digest = compute_digest(DigestAlgorithm::Sha256, b"");
506 assert_eq!(digest.len(), 32);
507 assert_eq!(
508 hex(&digest),
509 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
510 );
511 }
512
513 #[test]
514 fn sha224_empty() {
515 let digest = compute_digest(
516 DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha224")
517 .expect("SHA-224 URI must be recognized"),
518 b"",
519 );
520 assert_eq!(
521 hex(&digest),
522 "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"
523 );
524 }
525
526 #[test]
527 fn sha384_empty() {
528 let digest = compute_digest(DigestAlgorithm::Sha384, b"");
530 assert_eq!(digest.len(), 48);
531 assert_eq!(
532 hex(&digest),
533 "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
534 );
535 }
536
537 #[test]
538 fn sha512_empty() {
539 let digest = compute_digest(DigestAlgorithm::Sha512, b"");
541 assert_eq!(digest.len(), 64);
542 assert_eq!(
543 hex(&digest),
544 "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
545 );
546 }
547
548 #[test]
549 fn sha256_hello_world() {
550 let digest = compute_digest(DigestAlgorithm::Sha256, b"hello world");
552 assert_eq!(
553 hex(&digest),
554 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
555 );
556 }
557
558 #[test]
559 fn sha1_abc() {
560 let digest = compute_digest(DigestAlgorithm::Sha1, b"abc");
562 assert_eq!(hex(&digest), "a9993e364706816aba3e25717850c26c9cd0d89d");
563 }
564
565 #[test]
568 fn constant_time_eq_identical() {
569 let a = compute_digest(DigestAlgorithm::Sha256, b"test");
570 let b = compute_digest(DigestAlgorithm::Sha256, b"test");
571 assert!(constant_time_eq(&a, &b));
572 }
573
574 #[test]
575 fn constant_time_eq_different_content() {
576 let a = compute_digest(DigestAlgorithm::Sha256, b"test1");
577 let b = compute_digest(DigestAlgorithm::Sha256, b"test2");
578 assert!(!constant_time_eq(&a, &b));
579 }
580
581 #[test]
582 fn constant_time_eq_different_lengths() {
583 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2]));
584 }
585
586 #[test]
587 fn constant_time_eq_empty() {
588 assert!(constant_time_eq(&[], &[]));
589 }
590
591 #[test]
594 fn digest_output_matches_declared_length() {
595 let data = b"test data for length verification";
596 for algo in [
597 DigestAlgorithm::Sha1,
598 DigestAlgorithm::Sha256,
599 DigestAlgorithm::Sha384,
600 DigestAlgorithm::Sha512,
601 ] {
602 let digest = compute_digest(algo, data);
603 assert_eq!(
604 digest.len(),
605 algo.output_len(),
606 "output length mismatch for {algo:?}"
607 );
608 }
609 }
610
611 fn hex(bytes: &[u8]) -> String {
613 bytes.iter().map(|b| format!("{b:02x}")).collect()
614 }
615}