xml_sec/xmldsig/
digest.rs1use ring::digest;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum DigestAlgorithm {
16 Sha1,
18 Sha256,
20 Sha384,
22 Sha512,
24}
25
26impl DigestAlgorithm {
27 pub fn from_uri(uri: &str) -> Option<Self> {
40 match uri {
41 "http://www.w3.org/2000/09/xmldsig#sha1" => Some(Self::Sha1),
42 "http://www.w3.org/2001/04/xmlenc#sha256" => Some(Self::Sha256),
43 "http://www.w3.org/2001/04/xmldsig-more#sha384" => Some(Self::Sha384),
44 "http://www.w3.org/2001/04/xmlenc#sha512" => Some(Self::Sha512),
45 _ => None,
46 }
47 }
48
49 pub fn uri(self) -> &'static str {
51 match self {
52 Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
53 Self::Sha256 => "http://www.w3.org/2001/04/xmlenc#sha256",
54 Self::Sha384 => "http://www.w3.org/2001/04/xmldsig-more#sha384",
55 Self::Sha512 => "http://www.w3.org/2001/04/xmlenc#sha512",
56 }
57 }
58
59 pub fn signing_allowed(self) -> bool {
64 !matches!(self, Self::Sha1)
65 }
66
67 pub fn output_len(self) -> usize {
69 match self {
70 Self::Sha1 => 20,
71 Self::Sha256 => 32,
72 Self::Sha384 => 48,
73 Self::Sha512 => 64,
74 }
75 }
76
77 fn ring_algorithm(self) -> &'static digest::Algorithm {
79 match self {
80 Self::Sha1 => &digest::SHA1_FOR_LEGACY_USE_ONLY,
81 Self::Sha256 => &digest::SHA256,
82 Self::Sha384 => &digest::SHA384,
83 Self::Sha512 => &digest::SHA512,
84 }
85 }
86}
87
88pub fn compute_digest(algorithm: DigestAlgorithm, data: &[u8]) -> Vec<u8> {
92 let result = digest::digest(algorithm.ring_algorithm(), data);
93 result.as_ref().to_vec()
94}
95
96pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
105 #[expect(
106 deprecated,
107 reason = "legacy ring constant-time helper is still used here"
108 )]
109 ring::constant_time::verify_slices_are_equal(a, b).is_ok()
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
119 fn from_uri_sha1() {
120 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#sha1");
121 assert_eq!(algo, Some(DigestAlgorithm::Sha1));
122 }
123
124 #[test]
125 fn from_uri_sha256() {
126 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha256");
127 assert_eq!(algo, Some(DigestAlgorithm::Sha256));
128 }
129
130 #[test]
131 fn from_uri_sha384() {
132 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha384");
133 assert_eq!(algo, Some(DigestAlgorithm::Sha384));
134 }
135
136 #[test]
137 fn from_uri_sha512() {
138 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha512");
139 assert_eq!(algo, Some(DigestAlgorithm::Sha512));
140 }
141
142 #[test]
143 fn from_uri_unknown() {
144 assert_eq!(
145 DigestAlgorithm::from_uri("http://example.com/unknown"),
146 None
147 );
148 }
149
150 #[test]
151 fn uri_round_trip() {
152 for algo in [
153 DigestAlgorithm::Sha1,
154 DigestAlgorithm::Sha256,
155 DigestAlgorithm::Sha384,
156 DigestAlgorithm::Sha512,
157 ] {
158 assert_eq!(
159 DigestAlgorithm::from_uri(algo.uri()),
160 Some(algo),
161 "round-trip failed for {algo:?}"
162 );
163 }
164 }
165
166 #[test]
169 fn sha1_verify_only() {
170 assert!(!DigestAlgorithm::Sha1.signing_allowed());
171 }
172
173 #[test]
174 fn sha256_signing_allowed() {
175 assert!(DigestAlgorithm::Sha256.signing_allowed());
176 }
177
178 #[test]
179 fn sha384_signing_allowed() {
180 assert!(DigestAlgorithm::Sha384.signing_allowed());
181 }
182
183 #[test]
184 fn sha512_signing_allowed() {
185 assert!(DigestAlgorithm::Sha512.signing_allowed());
186 }
187
188 #[test]
191 fn output_lengths() {
192 assert_eq!(DigestAlgorithm::Sha1.output_len(), 20);
193 assert_eq!(DigestAlgorithm::Sha256.output_len(), 32);
194 assert_eq!(DigestAlgorithm::Sha384.output_len(), 48);
195 assert_eq!(DigestAlgorithm::Sha512.output_len(), 64);
196 }
197
198 #[test]
202 fn sha1_empty() {
203 let digest = compute_digest(DigestAlgorithm::Sha1, b"");
205 assert_eq!(digest.len(), 20);
206 assert_eq!(hex(&digest), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
207 }
208
209 #[test]
210 fn sha256_empty() {
211 let digest = compute_digest(DigestAlgorithm::Sha256, b"");
213 assert_eq!(digest.len(), 32);
214 assert_eq!(
215 hex(&digest),
216 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
217 );
218 }
219
220 #[test]
221 fn sha384_empty() {
222 let digest = compute_digest(DigestAlgorithm::Sha384, b"");
224 assert_eq!(digest.len(), 48);
225 assert_eq!(
226 hex(&digest),
227 "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
228 );
229 }
230
231 #[test]
232 fn sha512_empty() {
233 let digest = compute_digest(DigestAlgorithm::Sha512, b"");
235 assert_eq!(digest.len(), 64);
236 assert_eq!(
237 hex(&digest),
238 "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
239 );
240 }
241
242 #[test]
243 fn sha256_hello_world() {
244 let digest = compute_digest(DigestAlgorithm::Sha256, b"hello world");
246 assert_eq!(
247 hex(&digest),
248 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
249 );
250 }
251
252 #[test]
253 fn sha1_abc() {
254 let digest = compute_digest(DigestAlgorithm::Sha1, b"abc");
256 assert_eq!(hex(&digest), "a9993e364706816aba3e25717850c26c9cd0d89d");
257 }
258
259 #[test]
262 fn constant_time_eq_identical() {
263 let a = compute_digest(DigestAlgorithm::Sha256, b"test");
264 let b = compute_digest(DigestAlgorithm::Sha256, b"test");
265 assert!(constant_time_eq(&a, &b));
266 }
267
268 #[test]
269 fn constant_time_eq_different_content() {
270 let a = compute_digest(DigestAlgorithm::Sha256, b"test1");
271 let b = compute_digest(DigestAlgorithm::Sha256, b"test2");
272 assert!(!constant_time_eq(&a, &b));
273 }
274
275 #[test]
276 fn constant_time_eq_different_lengths() {
277 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2]));
278 }
279
280 #[test]
281 fn constant_time_eq_empty() {
282 assert!(constant_time_eq(&[], &[]));
283 }
284
285 #[test]
288 fn digest_output_matches_declared_length() {
289 let data = b"test data for length verification";
290 for algo in [
291 DigestAlgorithm::Sha1,
292 DigestAlgorithm::Sha256,
293 DigestAlgorithm::Sha384,
294 DigestAlgorithm::Sha512,
295 ] {
296 let digest = compute_digest(algo, data);
297 assert_eq!(
298 digest.len(),
299 algo.output_len(),
300 "output length mismatch for {algo:?}"
301 );
302 }
303 }
304
305 fn hex(bytes: &[u8]) -> String {
307 bytes.iter().map(|b| format!("{b:02x}")).collect()
308 }
309}