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