1use ic_core::traits::{Algorithm, Digest, Mac, SelfTest};
4use ic_core::{ensure, Result, Zeroize};
5
6const MAX_BLOCK_LEN: usize = 144;
11
12#[derive(Clone)]
18pub struct Hmac<D: Digest> {
19 inner: D,
20 outer: D,
21}
22
23pub trait HmacDigest: Digest {
29 const HMAC_ID: &'static str;
31 const HMAC_NAME: &'static str;
33}
34
35impl<D: HmacDigest> Algorithm for Hmac<D> {
36 const ID: &'static str = D::HMAC_ID;
37 const NAME: &'static str = D::HMAC_NAME;
38}
39
40impl<D: Digest> Hmac<D> {
41 fn absorb_pads(&mut self, key: &[u8]) {
55 let mut pad = [0x36u8; MAX_BLOCK_LEN];
56 let block = &mut pad[..D::BLOCK_LEN];
57 if key.len() > D::BLOCK_LEN {
58 xor_hashed_key::<D>(key, block);
59 } else {
60 for (p, k) in block.iter_mut().zip(key) {
61 *p ^= k;
62 }
63 }
64
65 self.inner.update(block);
66 for p in block.iter_mut() {
67 *p ^= 0x36 ^ 0x5c;
68 }
69 self.outer.update(block);
70
71 block.zeroize();
72 }
73}
74
75const fn large_state<D>() -> bool {
91 core::mem::size_of::<D>() > 512
92}
93
94#[inline(always)]
96fn keyed<D: Digest>(key: &[u8]) -> Hmac<D> {
97 let mut mac = Hmac {
98 inner: D::new(),
99 outer: D::new(),
100 };
101 mac.absorb_pads(key);
102 mac
103}
104
105#[inline(never)]
106fn keyed_out_of_line<D: Digest>(key: &[u8]) -> Hmac<D> {
107 keyed(key)
108}
109
110#[inline(always)]
112fn tag<D: Digest>(mut mac: Hmac<D>) -> D::Output {
113 let inner_digest = mac.inner.finalize();
114 mac.outer.update(inner_digest.as_ref());
115 mac.outer.finalize()
116}
117
118#[inline(never)]
119fn tag_out_of_line<D: Digest>(mac: Hmac<D>) -> D::Output {
120 tag(mac)
121}
122
123#[inline(never)]
128fn xor_hashed_key<D: Digest>(key: &[u8], block: &mut [u8]) {
129 let mut hashed = D::digest(key);
130 for (p, k) in block.iter_mut().zip(hashed.as_ref()) {
131 *p ^= k;
132 }
133 hashed.as_mut().zeroize();
135}
136
137impl<D: HmacDigest> Mac for Hmac<D> {
138 type Tag = D::Output;
139 const TAG_LEN: usize = D::OUTPUT_LEN;
140
141 fn new(key: &[u8]) -> Result<Self> {
142 ic_core::module::operational()?;
143 ensure!(
144 D::BLOCK_LEN <= MAX_BLOCK_LEN,
145 InvalidParameter,
146 "digest block exceeds hmac buffer"
147 );
148 if large_state::<D>() {
149 Ok(keyed_out_of_line(key))
150 } else {
151 Ok(keyed(key))
152 }
153 }
154
155 fn update(&mut self, data: &[u8]) {
156 self.inner.update(data);
157 }
158
159 fn finalize(self) -> Self::Tag {
160 if large_state::<D>() {
161 tag_out_of_line(self)
162 } else {
163 tag(self)
164 }
165 }
166}
167
168macro_rules! hmac_alias {
169 (
170 $name:ident, $digest:ty, $id:literal, $disp:literal, $taglen:literal,
171 $kat_key:expr, $kat_msg:expr, $kat_tag:literal
172 ) => {
173 #[doc = concat!($disp, ".")]
174 pub type $name = Hmac<$digest>;
175
176 impl HmacDigest for $digest {
177 const HMAC_ID: &'static str = $id;
178 const HMAC_NAME: &'static str = $disp;
179 }
180
181 impl SelfTest for Hmac<$digest> {
182 fn self_test() -> Result<()> {
183 let mut key = [0u8; 20];
184 ic_core::codec::hex_decode($kat_key.as_bytes(), &mut key)?;
185 let tag = <Self as Mac>::mac(&key, $kat_msg)?;
186 let mut want = [0u8; $taglen];
187 ic_core::codec::hex_decode($kat_tag.as_bytes(), &mut want)?;
188 ensure!(
189 ic_core::ct::verify(&want, tag.as_ref()),
190 SelfTestFailed,
191 $id
192 );
193 Ok(())
194 }
195 }
196 };
197}
198
199const KAT_KEY: &str = "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b";
201
202hmac_alias!(
209 HmacSha256,
210 ic_hash::Sha256,
211 "hmac-sha2-256",
212 "HMAC-SHA-256",
213 32,
214 KAT_KEY,
215 b"Hi There",
216 "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
217);
218hmac_alias!(
219 HmacSha384,
220 ic_hash::Sha384,
221 "hmac-sha2-384",
222 "HMAC-SHA-384",
223 48,
224 KAT_KEY,
225 b"Hi There",
226 "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6"
227);
228hmac_alias!(
229 HmacSha512,
230 ic_hash::Sha512,
231 "hmac-sha2-512",
232 "HMAC-SHA-512",
233 64,
234 KAT_KEY,
235 b"Hi There",
236 "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
237);
238hmac_alias!(
239 HmacSha512_256,
240 ic_hash::Sha512_256,
241 "hmac-sha2-512-256",
242 "HMAC-SHA-512/256",
243 32,
244 KAT_KEY,
245 b"Hi There",
246 "9f9126c3d9c3c330d760425ca8a217e31feae31bfe70196ff81642b868402eab"
247);
248hmac_alias!(
249 HmacSha3_256,
250 ic_hash::Sha3_256,
251 "hmac-sha3-256",
252 "HMAC-SHA3-256",
253 32,
254 KAT_KEY,
255 b"Hi There",
256 "ba85192310dffa96e2a3a40e69774351140bb7185e1202cdcc917589f95e16bb"
257);
258hmac_alias!(
259 HmacSha3_512,
260 ic_hash::Sha3_512,
261 "hmac-sha3-512",
262 "HMAC-SHA3-512",
263 64,
264 KAT_KEY,
265 b"Hi There",
266 "eb3fbd4b2eaab8f5c504bd3a41465aacec15770a7cabac531e482f860b5ec7ba47ccb2c6f2afce8f88d22b6dc61380f23a668fd3888bb80537c0a0b86407689e"
267);
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use ic_core::codec::hex;
273
274 #[test]
275 fn rfc4231_case_1() {
276 let key = [0x0bu8; 20];
277 assert_eq!(
278 hex(HmacSha256::mac(&key, b"Hi There").unwrap().as_ref()),
279 "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
280 );
281 assert_eq!(
282 hex(HmacSha512::mac(&key, b"Hi There").unwrap().as_ref()),
283 "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
284 );
285 }
286
287 #[test]
288 fn rfc4231_case_2_short_key() {
289 assert_eq!(
290 hex(HmacSha256::mac(b"Jefe", b"what do ya want for nothing?")
291 .unwrap()
292 .as_ref()),
293 "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
294 );
295 }
296
297 #[test]
299 fn rfc4231_case_3_long_data() {
300 let key = [0xaau8; 20];
301 let data = [0xddu8; 50];
302 assert_eq!(
303 hex(HmacSha256::mac(&key, &data).unwrap().as_ref()),
304 "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
305 );
306 }
307
308 #[test]
311 fn rfc4231_case_6_oversized_key() {
312 let key = [0xaau8; 131];
313 assert_eq!(
314 hex(HmacSha256::mac(
315 &key,
316 b"Test Using Larger Than Block-Size Key - Hash Key First"
317 )
318 .unwrap()
319 .as_ref()),
320 "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
321 );
322 }
323
324 #[test]
329 fn rfc4231_case_6_oversized_key_sha2_512_family() {
330 let key = [0xaau8; 131];
331 let msg = b"Test Using Larger Than Block-Size Key - Hash Key First";
332 assert_eq!(
333 hex(HmacSha384::mac(&key, msg).unwrap().as_ref()),
334 "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952"
335 );
336 assert_eq!(
337 hex(HmacSha512::mac(&key, msg).unwrap().as_ref()),
338 "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"
339 );
340 }
341
342 #[test]
343 fn empty_key_and_message() {
344 assert_eq!(
345 hex(HmacSha256::mac(b"", b"").unwrap().as_ref()),
346 "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
347 );
348 }
349
350 #[test]
351 fn streaming_matches_one_shot() {
352 let data: Vec<u8> = (0..200u8).collect();
353 for split in [0usize, 1, 63, 64, 128, 200] {
354 let mut m = HmacSha256::new(b"k").unwrap();
355 m.update(&data[..split]);
356 m.update(&data[split..]);
357 assert_eq!(m.finalize(), HmacSha256::mac(b"k", &data).unwrap());
358 }
359 }
360
361 #[test]
362 fn verify_rejects_wrong_tag_and_length() {
363 let tag = HmacSha256::mac(b"k", b"m").unwrap();
364 HmacSha256::verify(b"k", b"m", tag.as_ref()).unwrap();
365 let mut bad = tag;
366 bad[0] ^= 1;
367 assert!(HmacSha256::verify(b"k", b"m", bad.as_ref()).is_err());
368 assert!(HmacSha256::verify(b"k", b"m", &tag.as_ref()[..31]).is_err());
369 }
370
371 #[test]
372 fn self_tests_pass() {
373 HmacSha256::self_test().unwrap();
374 HmacSha384::self_test().unwrap();
375 HmacSha512::self_test().unwrap();
376 HmacSha512_256::self_test().unwrap();
377 HmacSha3_256::self_test().unwrap();
378 HmacSha3_512::self_test().unwrap();
379 }
380}