1use ic_core::traits::internal::Digest;
4
5use ic_core::traits::internal::Mac;
6
7use ic_core::traits::{Algorithm, SelfTest};
8use ic_core::{ensure, Result, Zeroize};
9
10const MAX_BLOCK_LEN: usize = 144;
15
16#[derive(Clone)]
22pub struct Hmac<D: Digest> {
23 inner: D,
24 outer: D,
25}
26
27pub trait HmacDigest: Digest {
33 const HMAC_ID: &'static str;
35 const HMAC_NAME: &'static str;
37}
38
39impl<D: HmacDigest> Algorithm for Hmac<D> {
40 const ID: &'static str = D::HMAC_ID;
41 const NAME: &'static str = D::HMAC_NAME;
42}
43
44impl<D: Digest> Hmac<D> {
45 fn absorb_pads(&mut self, key: &[u8]) {
59 let mut pad = [0x36u8; MAX_BLOCK_LEN];
60 let block = &mut pad[..D::BLOCK_LEN];
61 if key.len() > D::BLOCK_LEN {
62 xor_hashed_key::<D>(key, block);
63 } else {
64 for (p, k) in block.iter_mut().zip(key) {
65 *p ^= k;
66 }
67 }
68
69 self.inner.update(block);
70 for p in block.iter_mut() {
71 *p ^= 0x36 ^ 0x5c;
72 }
73 self.outer.update(block);
74
75 block.zeroize();
76 }
77}
78
79const fn large_state<D>() -> bool {
95 core::mem::size_of::<D>() > 512
96}
97
98#[inline(always)]
100fn keyed<D: Digest>(key: &[u8]) -> Hmac<D> {
101 let mut mac = Hmac {
102 inner: D::new(),
103 outer: D::new(),
104 };
105 mac.absorb_pads(key);
106 mac
107}
108
109#[inline(never)]
110fn keyed_out_of_line<D: Digest>(key: &[u8]) -> Hmac<D> {
111 keyed(key)
112}
113
114#[inline(always)]
116fn tag<D: Digest>(mut mac: Hmac<D>) -> D::Output {
117 let inner_digest = mac.inner.finalize();
118 mac.outer.update(inner_digest.as_ref());
119 mac.outer.finalize()
120}
121
122#[inline(never)]
123fn tag_out_of_line<D: Digest>(mac: Hmac<D>) -> D::Output {
124 tag(mac)
125}
126
127#[inline(always)]
138fn one_shot<D: Digest>(key: &[u8], data: &[u8]) -> D::Output {
139 let mut pad = [0x36u8; MAX_BLOCK_LEN];
140 let block = &mut pad[..D::BLOCK_LEN];
141 if key.len() > D::BLOCK_LEN {
142 xor_hashed_key::<D>(key, block);
143 } else {
144 for (p, k) in block.iter_mut().zip(key) {
145 *p ^= k;
146 }
147 }
148
149 let mut inner = D::new();
150 inner.update(block);
151 for p in block.iter_mut() {
156 *p ^= 0x36 ^ 0x5c;
157 }
158 let mut outer = D::new();
159 outer.update(block);
160 block.zeroize();
161
162 inner.update(data);
163 let inner_digest = inner.finalize();
164 outer.update(inner_digest.as_ref());
165 outer.finalize()
166}
167
168#[inline(never)]
169fn one_shot_out_of_line<D: Digest>(key: &[u8], data: &[u8]) -> D::Output {
170 one_shot::<D>(key, data)
171}
172
173#[inline(never)]
178fn xor_hashed_key<D: Digest>(key: &[u8], block: &mut [u8]) {
179 let mut hashed = D::digest(key);
180 for (p, k) in block.iter_mut().zip(hashed.as_ref()) {
181 *p ^= k;
182 }
183 hashed.as_mut().zeroize();
185}
186
187impl<D: HmacDigest> Mac for Hmac<D> {
188 type Tag = D::Output;
189 const TAG_LEN: usize = D::OUTPUT_LEN;
190
191 fn new(key: &[u8]) -> Result<Self> {
192 ic_core::module::operational()?;
193 ensure!(
194 D::BLOCK_LEN <= MAX_BLOCK_LEN,
195 InvalidParameter,
196 "digest block exceeds hmac buffer"
197 );
198 if large_state::<D>() {
199 Ok(keyed_out_of_line(key))
200 } else {
201 Ok(keyed(key))
202 }
203 }
204
205 fn update(&mut self, data: &[u8]) {
206 self.inner.update(data);
207 }
208
209 fn finalize(self) -> Self::Tag {
210 if large_state::<D>() {
211 tag_out_of_line(self)
212 } else {
213 tag(self)
214 }
215 }
216
217 fn mac(key: &[u8], data: &[u8]) -> Result<Self::Tag> {
218 ic_core::module::operational()?;
219 ensure!(
220 D::BLOCK_LEN <= MAX_BLOCK_LEN,
221 InvalidParameter,
222 "digest block exceeds hmac buffer"
223 );
224 if large_state::<D>() {
225 Ok(one_shot_out_of_line::<D>(key, data))
226 } else {
227 Ok(one_shot::<D>(key, data))
228 }
229 }
230}
231
232macro_rules! hmac_alias {
233 (
234 $name:ident, $digest:ty, $id:literal, $disp:literal, $taglen:literal,
235 $kat_key:expr, $kat_msg:expr, $kat_tag:literal
236 ) => {
237 #[doc = concat!($disp, ".")]
238 pub type $name = Hmac<$digest>;
239
240 impl HmacDigest for $digest {
241 const HMAC_ID: &'static str = $id;
242 const HMAC_NAME: &'static str = $disp;
243 }
244
245 impl SelfTest for Hmac<$digest> {
246 fn self_test() -> Result<()> {
247 let mut key = [0u8; 20];
248 ic_core::codec::hex_decode($kat_key.as_bytes(), &mut key)?;
249 let tag = <Self as Mac>::mac(&key, $kat_msg)?;
250 let mut want = [0u8; $taglen];
251 ic_core::codec::hex_decode($kat_tag.as_bytes(), &mut want)?;
252 ensure!(
253 ic_core::ct::verify(&want, tag.as_ref()),
254 SelfTestFailed,
255 $id
256 );
257 Ok(())
258 }
259 }
260 };
261}
262
263const KAT_KEY: &str = "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b";
265
266hmac_alias!(
273 HmacSha256,
274 ic_hash::Sha256,
275 "hmac-sha2-256",
276 "HMAC-SHA-256",
277 32,
278 KAT_KEY,
279 b"Hi There",
280 "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
281);
282hmac_alias!(
283 HmacSha384,
284 ic_hash::Sha384,
285 "hmac-sha2-384",
286 "HMAC-SHA-384",
287 48,
288 KAT_KEY,
289 b"Hi There",
290 "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6"
291);
292hmac_alias!(
293 HmacSha512,
294 ic_hash::Sha512,
295 "hmac-sha2-512",
296 "HMAC-SHA-512",
297 64,
298 KAT_KEY,
299 b"Hi There",
300 "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
301);
302hmac_alias!(
303 HmacSha512_256,
304 ic_hash::Sha512_256,
305 "hmac-sha2-512-256",
306 "HMAC-SHA-512/256",
307 32,
308 KAT_KEY,
309 b"Hi There",
310 "9f9126c3d9c3c330d760425ca8a217e31feae31bfe70196ff81642b868402eab"
311);
312hmac_alias!(
313 HmacSha3_256,
314 ic_hash::Sha3_256,
315 "hmac-sha3-256",
316 "HMAC-SHA3-256",
317 32,
318 KAT_KEY,
319 b"Hi There",
320 "ba85192310dffa96e2a3a40e69774351140bb7185e1202cdcc917589f95e16bb"
321);
322hmac_alias!(
323 HmacSha3_512,
324 ic_hash::Sha3_512,
325 "hmac-sha3-512",
326 "HMAC-SHA3-512",
327 64,
328 KAT_KEY,
329 b"Hi There",
330 "eb3fbd4b2eaab8f5c504bd3a41465aacec15770a7cabac531e482f860b5ec7ba47ccb2c6f2afce8f88d22b6dc61380f23a668fd3888bb80537c0a0b86407689e"
331);
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use ic_core::codec::hex;
337
338 #[test]
339 fn rfc4231_case_1() {
340 let key = [0x0bu8; 20];
341 assert_eq!(
342 hex(HmacSha256::mac(&key, b"Hi There").unwrap().as_ref()),
343 "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
344 );
345 assert_eq!(
346 hex(HmacSha512::mac(&key, b"Hi There").unwrap().as_ref()),
347 "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
348 );
349 }
350
351 #[test]
352 fn rfc4231_case_2_short_key() {
353 assert_eq!(
354 hex(HmacSha256::mac(b"Jefe", b"what do ya want for nothing?")
355 .unwrap()
356 .as_ref()),
357 "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
358 );
359 }
360
361 #[test]
363 fn rfc4231_case_3_long_data() {
364 let key = [0xaau8; 20];
365 let data = [0xddu8; 50];
366 assert_eq!(
367 hex(HmacSha256::mac(&key, &data).unwrap().as_ref()),
368 "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
369 );
370 }
371
372 #[test]
375 fn rfc4231_case_6_oversized_key() {
376 let key = [0xaau8; 131];
377 assert_eq!(
378 hex(HmacSha256::mac(
379 &key,
380 b"Test Using Larger Than Block-Size Key - Hash Key First"
381 )
382 .unwrap()
383 .as_ref()),
384 "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
385 );
386 }
387
388 #[test]
393 fn rfc4231_case_6_oversized_key_sha2_512_family() {
394 let key = [0xaau8; 131];
395 let msg = b"Test Using Larger Than Block-Size Key - Hash Key First";
396 assert_eq!(
397 hex(HmacSha384::mac(&key, msg).unwrap().as_ref()),
398 "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952"
399 );
400 assert_eq!(
401 hex(HmacSha512::mac(&key, msg).unwrap().as_ref()),
402 "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"
403 );
404 }
405
406 #[test]
407 fn empty_key_and_message() {
408 assert_eq!(
409 hex(HmacSha256::mac(b"", b"").unwrap().as_ref()),
410 "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
411 );
412 }
413
414 #[test]
415 fn streaming_matches_one_shot() {
416 let data: Vec<u8> = (0..200u8).collect();
417 for split in [0usize, 1, 63, 64, 128, 200] {
418 let mut m = HmacSha256::new(b"k").unwrap();
419 m.update(&data[..split]);
420 m.update(&data[split..]);
421 assert_eq!(m.finalize(), HmacSha256::mac(b"k", &data).unwrap());
422 }
423 }
424
425 #[test]
432 fn the_one_shot_path_matches_the_streaming_one() {
433 fn check<D: HmacDigest>() {
434 let block = D::BLOCK_LEN;
435 for key_len in [0, 1, 20, block - 1, block, block + 1, 2 * block + 3] {
436 let key: Vec<u8> = (0..key_len).map(|i| (i * 13 + 5) as u8).collect();
437 for len in [0, 1, 55, 56, 63, 64, 65, 111, 112, 128, 200] {
438 let data: Vec<u8> = (0..len).map(|i| (i * 7 + 1) as u8).collect();
439 let mut m = Hmac::<D>::new(&key).unwrap();
440 m.update(&data);
441 let want = m.finalize();
442 let got = Hmac::<D>::mac(&key, &data).unwrap();
443 assert_eq!(
444 got.as_ref(),
445 want.as_ref(),
446 "{} key {key_len} msg {len}",
447 D::ID
448 );
449 }
450 }
451 }
452 check::<ic_hash::Sha256>();
453 check::<ic_hash::Sha384>();
454 check::<ic_hash::Sha512>();
455 check::<ic_hash::Sha512_256>();
456 check::<ic_hash::Sha3_256>();
457 check::<ic_hash::Sha3_512>();
458 }
459
460 #[test]
461 fn verify_rejects_wrong_tag_and_length() {
462 let tag = HmacSha256::mac(b"k", b"m").unwrap();
463 <HmacSha256 as ic_core::traits::Mac>::verify(b"k", b"m", tag.as_ref()).unwrap();
464 let mut bad = tag;
465 bad[0] ^= 1;
466 assert!(<HmacSha256 as ic_core::traits::Mac>::verify(b"k", b"m", bad.as_ref()).is_err());
467 assert!(
468 <HmacSha256 as ic_core::traits::Mac>::verify(b"k", b"m", &tag.as_ref()[..31]).is_err()
469 );
470 }
471
472 #[test]
473 fn self_tests_pass() {
474 HmacSha256::self_test().unwrap();
475 HmacSha384::self_test().unwrap();
476 HmacSha512::self_test().unwrap();
477 HmacSha512_256::self_test().unwrap();
478 HmacSha3_256::self_test().unwrap();
479 HmacSha3_512::self_test().unwrap();
480 }
481}