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 provider.require_capability(crate::provider::ProviderCapability::Digest(algorithm))?;
169 let digest = provider.digest(algorithm, data)?;
170 let expected = algorithm.output_len();
171 if digest.len() != expected {
172 return Err(crate::provider::ProviderError::InvalidOutputSize {
173 operation: crate::provider::ProviderOperation::Digest,
174 expected,
175 actual: digest.len(),
176 });
177 }
178 Ok(digest)
179}
180
181pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
190 a.ct_eq(b).into()
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 struct RejectingDigestProvider {
198 output: Option<Vec<u8>>,
199 }
200
201 impl crate::provider::CryptoProvider for RejectingDigestProvider {
202 fn name(&self) -> &'static str {
203 "rejecting-digest"
204 }
205
206 fn supports(&self, capability: crate::provider::ProviderCapability<'_>) -> bool {
207 if self.output.is_none()
208 && matches!(capability, crate::provider::ProviderCapability::Digest(_))
209 {
210 return false;
211 }
212 crate::provider::default_provider().supports(capability)
213 }
214
215 fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
216 crate::provider::default_provider().fill_random(output)
217 }
218
219 fn derive_key(
220 &self,
221 parameters: &crate::provider::KdfParameters<'_>,
222 secret: &[u8],
223 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
224 crate::provider::default_provider().derive_key(parameters, secret)
225 }
226
227 fn digest(
228 &self,
229 _algorithm: DigestAlgorithm,
230 _data: &[u8],
231 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
232 if let Some(output) = &self.output {
233 return Ok(output.clone());
234 }
235 panic!("an unavailable digest capability must not be dispatched")
236 }
237
238 fn sign(
239 &self,
240 key: &dyn super::super::SigningKey,
241 algorithm: super::super::SignatureAlgorithm,
242 data: &[u8],
243 ) -> Result<Vec<u8>, super::super::SigningKeyError> {
244 crate::provider::default_provider().sign(key, algorithm, data)
245 }
246
247 fn verify(
248 &self,
249 key: &dyn super::super::VerifyingKey,
250 algorithm: super::super::SignatureAlgorithm,
251 data: &[u8],
252 signature: &[u8],
253 ) -> Result<bool, super::super::DsigError> {
254 crate::provider::default_provider().verify(key, algorithm, data, signature)
255 }
256
257 #[cfg(feature = "xmlenc")]
258 fn encrypt_data(
259 &self,
260 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
261 key: &[u8],
262 plaintext: &[u8],
263 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
264 crate::provider::default_provider().encrypt_data(algorithm, key, plaintext)
265 }
266
267 #[cfg(feature = "xmlenc")]
268 fn decrypt_data(
269 &self,
270 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
271 key: &[u8],
272 ciphertext: &[u8],
273 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
274 crate::provider::default_provider().decrypt_data(algorithm, key, ciphertext)
275 }
276
277 #[cfg(feature = "xmlenc")]
278 fn wrap_key(
279 &self,
280 algorithm: crate::xmlenc::KeyWrapAlgorithm,
281 kek: &[u8],
282 key: &[u8],
283 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
284 crate::provider::default_provider().wrap_key(algorithm, kek, key)
285 }
286
287 #[cfg(feature = "xmlenc")]
288 fn unwrap_key(
289 &self,
290 algorithm: crate::xmlenc::KeyWrapAlgorithm,
291 kek: &[u8],
292 wrapped: &[u8],
293 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
294 crate::provider::default_provider().unwrap_key(algorithm, kek, wrapped)
295 }
296
297 #[cfg(feature = "xmlenc")]
298 fn transport_key(
299 &self,
300 key: &dyn crate::provider::KeyTransportKey,
301 parameters: &crate::xmlenc::RsaOaepParameters,
302 plaintext: &[u8],
303 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
304 crate::provider::default_provider().transport_key(key, parameters, plaintext)
305 }
306
307 #[cfg(feature = "xmlenc")]
308 fn recover_key(
309 &self,
310 key: &dyn crate::provider::KeyRecoveryKey,
311 parameters: &crate::xmlenc::RsaOaepParameters,
312 ciphertext: &[u8],
313 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
314 crate::provider::default_provider().recover_key(key, parameters, ciphertext)
315 }
316 }
317
318 #[test]
319 fn explicit_provider_digest_failures_are_returned() {
320 assert!(matches!(
323 compute_digest_with_provider(
324 &RejectingDigestProvider { output: None },
325 DigestAlgorithm::Sha256,
326 b"x"
327 ),
328 Err(crate::provider::ProviderError::Unsupported { .. })
329 ));
330 }
331
332 #[test]
333 fn explicit_provider_digest_output_must_match_the_algorithm() {
334 for actual in [0, 31, 33] {
337 assert!(matches!(
338 compute_digest_with_provider(
339 &RejectingDigestProvider {
340 output: Some(vec![0_u8; actual]),
341 },
342 DigestAlgorithm::Sha256,
343 b"x",
344 ),
345 Err(crate::provider::ProviderError::InvalidOutputSize {
346 operation: crate::provider::ProviderOperation::Digest,
347 expected: 32,
348 actual: output_len,
349 }) if output_len == actual
350 ));
351 }
352
353 assert_eq!(
354 compute_digest_with_provider(
355 &RejectingDigestProvider {
356 output: Some(vec![7_u8; 32]),
357 },
358 DigestAlgorithm::Sha256,
359 b"x",
360 ),
361 Ok(vec![7_u8; 32]),
362 );
363 }
364
365 #[test]
368 fn from_uri_sha1() {
369 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#sha1");
370 assert_eq!(algo, Some(DigestAlgorithm::Sha1));
371 }
372
373 #[test]
374 fn from_uri_sha256() {
375 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha256");
376 assert_eq!(algo, Some(DigestAlgorithm::Sha256));
377 }
378
379 #[test]
380 fn from_uri_sha384() {
381 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha384");
382 assert_eq!(algo, Some(DigestAlgorithm::Sha384));
383 }
384
385 #[test]
386 fn from_uri_sha512() {
387 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha512");
388 assert_eq!(algo, Some(DigestAlgorithm::Sha512));
389 }
390
391 #[test]
392 fn from_uri_unknown() {
393 assert_eq!(
394 DigestAlgorithm::from_uri("http://example.com/unknown"),
395 None
396 );
397 }
398
399 #[test]
400 fn uri_round_trip() {
401 for algo in [
402 DigestAlgorithm::Sha1,
403 DigestAlgorithm::Sha256,
404 DigestAlgorithm::Sha384,
405 DigestAlgorithm::Sha512,
406 ] {
407 assert_eq!(
408 DigestAlgorithm::from_uri(algo.uri()),
409 Some(algo),
410 "round-trip failed for {algo:?}"
411 );
412 }
413 }
414
415 #[test]
418 fn sha1_verify_only() {
419 assert!(!DigestAlgorithm::Sha1.signing_allowed());
420 }
421
422 #[test]
423 fn sha256_signing_allowed() {
424 assert!(DigestAlgorithm::Sha256.signing_allowed());
425 }
426
427 #[test]
428 fn sha384_signing_allowed() {
429 assert!(DigestAlgorithm::Sha384.signing_allowed());
430 }
431
432 #[test]
433 fn sha512_signing_allowed() {
434 assert!(DigestAlgorithm::Sha512.signing_allowed());
435 }
436
437 #[test]
440 fn output_lengths() {
441 assert_eq!(DigestAlgorithm::Sha1.output_len(), 20);
442 assert_eq!(DigestAlgorithm::Sha256.output_len(), 32);
443 assert_eq!(DigestAlgorithm::Sha384.output_len(), 48);
444 assert_eq!(DigestAlgorithm::Sha512.output_len(), 64);
445 }
446
447 #[test]
448 fn sha224_uri_round_trips_and_has_standard_width() {
449 let algorithm = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha224")
450 .expect("XMLDSig 1.1 SHA-224 must be recognized");
451
452 assert_eq!(
453 algorithm.uri(),
454 "http://www.w3.org/2001/04/xmldsig-more#sha224"
455 );
456 assert_eq!(algorithm.output_len(), 28);
457 assert!(algorithm.signing_allowed());
458 }
459
460 #[test]
464 fn sha1_empty() {
465 let digest = compute_digest(DigestAlgorithm::Sha1, b"");
467 assert_eq!(digest.len(), 20);
468 assert_eq!(hex(&digest), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
469 }
470
471 #[test]
472 fn sha256_empty() {
473 let digest = compute_digest(DigestAlgorithm::Sha256, b"");
475 assert_eq!(digest.len(), 32);
476 assert_eq!(
477 hex(&digest),
478 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
479 );
480 }
481
482 #[test]
483 fn sha224_empty() {
484 let digest = compute_digest(
485 DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha224")
486 .expect("SHA-224 URI must be recognized"),
487 b"",
488 );
489 assert_eq!(
490 hex(&digest),
491 "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"
492 );
493 }
494
495 #[test]
496 fn sha384_empty() {
497 let digest = compute_digest(DigestAlgorithm::Sha384, b"");
499 assert_eq!(digest.len(), 48);
500 assert_eq!(
501 hex(&digest),
502 "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
503 );
504 }
505
506 #[test]
507 fn sha512_empty() {
508 let digest = compute_digest(DigestAlgorithm::Sha512, b"");
510 assert_eq!(digest.len(), 64);
511 assert_eq!(
512 hex(&digest),
513 "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
514 );
515 }
516
517 #[test]
518 fn sha256_hello_world() {
519 let digest = compute_digest(DigestAlgorithm::Sha256, b"hello world");
521 assert_eq!(
522 hex(&digest),
523 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
524 );
525 }
526
527 #[test]
528 fn sha1_abc() {
529 let digest = compute_digest(DigestAlgorithm::Sha1, b"abc");
531 assert_eq!(hex(&digest), "a9993e364706816aba3e25717850c26c9cd0d89d");
532 }
533
534 #[test]
537 fn constant_time_eq_identical() {
538 let a = compute_digest(DigestAlgorithm::Sha256, b"test");
539 let b = compute_digest(DigestAlgorithm::Sha256, b"test");
540 assert!(constant_time_eq(&a, &b));
541 }
542
543 #[test]
544 fn constant_time_eq_different_content() {
545 let a = compute_digest(DigestAlgorithm::Sha256, b"test1");
546 let b = compute_digest(DigestAlgorithm::Sha256, b"test2");
547 assert!(!constant_time_eq(&a, &b));
548 }
549
550 #[test]
551 fn constant_time_eq_different_lengths() {
552 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2]));
553 }
554
555 #[test]
556 fn constant_time_eq_empty() {
557 assert!(constant_time_eq(&[], &[]));
558 }
559
560 #[test]
563 fn digest_output_matches_declared_length() {
564 let data = b"test data for length verification";
565 for algo in [
566 DigestAlgorithm::Sha1,
567 DigestAlgorithm::Sha256,
568 DigestAlgorithm::Sha384,
569 DigestAlgorithm::Sha512,
570 ] {
571 let digest = compute_digest(algo, data);
572 assert_eq!(
573 digest.len(),
574 algo.output_len(),
575 "output length mismatch for {algo:?}"
576 );
577 }
578 }
579
580 fn hex(bytes: &[u8]) -> String {
582 bytes.iter().map(|b| format!("{b:02x}")).collect()
583 }
584}