xml_sec/xmldsig/
digest.rs1use sha1::Sha1;
9use sha2::{Digest, Sha256, Sha384, Sha512};
10use subtle::ConstantTimeEq;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum DigestAlgorithm {
18 Sha1,
20 Sha256,
22 Sha384,
24 Sha512,
26}
27
28impl DigestAlgorithm {
29 pub fn from_uri(uri: &str) -> Option<Self> {
42 match uri {
43 "http://www.w3.org/2000/09/xmldsig#sha1" => Some(Self::Sha1),
44 "http://www.w3.org/2001/04/xmlenc#sha256" => Some(Self::Sha256),
45 "http://www.w3.org/2001/04/xmldsig-more#sha384" => Some(Self::Sha384),
46 "http://www.w3.org/2001/04/xmlenc#sha512" => Some(Self::Sha512),
47 _ => None,
48 }
49 }
50
51 pub fn uri(self) -> &'static str {
53 match self {
54 Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
55 Self::Sha256 => "http://www.w3.org/2001/04/xmlenc#sha256",
56 Self::Sha384 => "http://www.w3.org/2001/04/xmldsig-more#sha384",
57 Self::Sha512 => "http://www.w3.org/2001/04/xmlenc#sha512",
58 }
59 }
60
61 pub fn signing_allowed(self) -> bool {
66 !matches!(self, Self::Sha1)
67 }
68
69 pub fn output_len(self) -> usize {
71 match self {
72 Self::Sha1 => 20,
73 Self::Sha256 => 32,
74 Self::Sha384 => 48,
75 Self::Sha512 => 64,
76 }
77 }
78}
79
80pub fn compute_digest(algorithm: DigestAlgorithm, data: &[u8]) -> Vec<u8> {
84 match algorithm {
85 DigestAlgorithm::Sha1 => Sha1::digest(data).to_vec(),
86 DigestAlgorithm::Sha256 => Sha256::digest(data).to_vec(),
87 DigestAlgorithm::Sha384 => Sha384::digest(data).to_vec(),
88 DigestAlgorithm::Sha512 => Sha512::digest(data).to_vec(),
89 }
90}
91
92pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
101 a.ct_eq(b).into()
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
111 fn from_uri_sha1() {
112 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#sha1");
113 assert_eq!(algo, Some(DigestAlgorithm::Sha1));
114 }
115
116 #[test]
117 fn from_uri_sha256() {
118 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha256");
119 assert_eq!(algo, Some(DigestAlgorithm::Sha256));
120 }
121
122 #[test]
123 fn from_uri_sha384() {
124 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha384");
125 assert_eq!(algo, Some(DigestAlgorithm::Sha384));
126 }
127
128 #[test]
129 fn from_uri_sha512() {
130 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha512");
131 assert_eq!(algo, Some(DigestAlgorithm::Sha512));
132 }
133
134 #[test]
135 fn from_uri_unknown() {
136 assert_eq!(
137 DigestAlgorithm::from_uri("http://example.com/unknown"),
138 None
139 );
140 }
141
142 #[test]
143 fn uri_round_trip() {
144 for algo in [
145 DigestAlgorithm::Sha1,
146 DigestAlgorithm::Sha256,
147 DigestAlgorithm::Sha384,
148 DigestAlgorithm::Sha512,
149 ] {
150 assert_eq!(
151 DigestAlgorithm::from_uri(algo.uri()),
152 Some(algo),
153 "round-trip failed for {algo:?}"
154 );
155 }
156 }
157
158 #[test]
161 fn sha1_verify_only() {
162 assert!(!DigestAlgorithm::Sha1.signing_allowed());
163 }
164
165 #[test]
166 fn sha256_signing_allowed() {
167 assert!(DigestAlgorithm::Sha256.signing_allowed());
168 }
169
170 #[test]
171 fn sha384_signing_allowed() {
172 assert!(DigestAlgorithm::Sha384.signing_allowed());
173 }
174
175 #[test]
176 fn sha512_signing_allowed() {
177 assert!(DigestAlgorithm::Sha512.signing_allowed());
178 }
179
180 #[test]
183 fn output_lengths() {
184 assert_eq!(DigestAlgorithm::Sha1.output_len(), 20);
185 assert_eq!(DigestAlgorithm::Sha256.output_len(), 32);
186 assert_eq!(DigestAlgorithm::Sha384.output_len(), 48);
187 assert_eq!(DigestAlgorithm::Sha512.output_len(), 64);
188 }
189
190 #[test]
194 fn sha1_empty() {
195 let digest = compute_digest(DigestAlgorithm::Sha1, b"");
197 assert_eq!(digest.len(), 20);
198 assert_eq!(hex(&digest), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
199 }
200
201 #[test]
202 fn sha256_empty() {
203 let digest = compute_digest(DigestAlgorithm::Sha256, b"");
205 assert_eq!(digest.len(), 32);
206 assert_eq!(
207 hex(&digest),
208 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
209 );
210 }
211
212 #[test]
213 fn sha384_empty() {
214 let digest = compute_digest(DigestAlgorithm::Sha384, b"");
216 assert_eq!(digest.len(), 48);
217 assert_eq!(
218 hex(&digest),
219 "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
220 );
221 }
222
223 #[test]
224 fn sha512_empty() {
225 let digest = compute_digest(DigestAlgorithm::Sha512, b"");
227 assert_eq!(digest.len(), 64);
228 assert_eq!(
229 hex(&digest),
230 "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
231 );
232 }
233
234 #[test]
235 fn sha256_hello_world() {
236 let digest = compute_digest(DigestAlgorithm::Sha256, b"hello world");
238 assert_eq!(
239 hex(&digest),
240 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
241 );
242 }
243
244 #[test]
245 fn sha1_abc() {
246 let digest = compute_digest(DigestAlgorithm::Sha1, b"abc");
248 assert_eq!(hex(&digest), "a9993e364706816aba3e25717850c26c9cd0d89d");
249 }
250
251 #[test]
254 fn constant_time_eq_identical() {
255 let a = compute_digest(DigestAlgorithm::Sha256, b"test");
256 let b = compute_digest(DigestAlgorithm::Sha256, b"test");
257 assert!(constant_time_eq(&a, &b));
258 }
259
260 #[test]
261 fn constant_time_eq_different_content() {
262 let a = compute_digest(DigestAlgorithm::Sha256, b"test1");
263 let b = compute_digest(DigestAlgorithm::Sha256, b"test2");
264 assert!(!constant_time_eq(&a, &b));
265 }
266
267 #[test]
268 fn constant_time_eq_different_lengths() {
269 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2]));
270 }
271
272 #[test]
273 fn constant_time_eq_empty() {
274 assert!(constant_time_eq(&[], &[]));
275 }
276
277 #[test]
280 fn digest_output_matches_declared_length() {
281 let data = b"test data for length verification";
282 for algo in [
283 DigestAlgorithm::Sha1,
284 DigestAlgorithm::Sha256,
285 DigestAlgorithm::Sha384,
286 DigestAlgorithm::Sha512,
287 ] {
288 let digest = compute_digest(algo, data);
289 assert_eq!(
290 digest.len(),
291 algo.output_len(),
292 "output length mismatch for {algo:?}"
293 );
294 }
295 }
296
297 fn hex(bytes: &[u8]) -> String {
299 bytes.iter().map(|b| format!("{b:02x}")).collect()
300 }
301}