1use ic_core::traits::Mac;
8use ic_core::{ensure, Result, Zeroize};
9
10const MAX_TAG_LEN: usize = 64;
11
12pub fn kbkdf_counter<M: Mac>(
19 key: &[u8],
20 label: &[u8],
21 context: &[u8],
22 out: &mut [u8],
23) -> Result<()> {
24 ic_core::module::operational()?;
25 ensure!(!out.is_empty(), InvalidLength, "kbkdf output");
26 ensure!(
27 M::TAG_LEN <= MAX_TAG_LEN,
28 InvalidParameter,
29 "mac tag too wide"
30 );
31 ensure!(
32 !label.contains(&0),
33 InvalidParameter,
34 "kbkdf label must not contain a zero byte"
35 );
36
37 let total_bits = (out.len() as u64)
38 .checked_mul(8)
39 .and_then(|b| u32::try_from(b).ok())
40 .ok_or(ic_core::err!(InvalidLength, "kbkdf output too long"))?;
41
42 let blocks = out.len().div_ceil(M::TAG_LEN);
43 ensure!(
44 blocks <= u32::MAX as usize,
45 CounterExhausted,
46 "kbkdf counter"
47 );
48
49 let mut scratch = [0u8; MAX_TAG_LEN];
50 for (i, chunk) in out.chunks_mut(M::TAG_LEN).enumerate() {
51 let counter = (i as u32) + 1;
52 let mut m = M::new(key)?;
53 m.update(&counter.to_be_bytes());
54 m.update(label);
55 m.update(&[0u8]);
56 m.update(context);
57 m.update(&total_bits.to_be_bytes());
58 let t = m.finalize();
59 scratch[..M::TAG_LEN].copy_from_slice(t.as_ref());
60 chunk.copy_from_slice(&scratch[..chunk.len()]);
61 }
62 scratch.zeroize();
63 Ok(())
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use ic_mac::HmacSha256;
70
71 #[test]
72 fn is_deterministic() {
73 let mut a = [0u8; 48];
74 let mut b = [0u8; 48];
75 kbkdf_counter::<HmacSha256>(b"kdk", b"label", b"ctx", &mut a).unwrap();
76 kbkdf_counter::<HmacSha256>(b"kdk", b"label", b"ctx", &mut b).unwrap();
77 assert_eq!(a, b);
78 }
79
80 #[test]
81 fn label_and_context_are_domain_separated() {
82 let mut a = [0u8; 32];
83 let mut b = [0u8; 32];
84 let mut c = [0u8; 32];
85 kbkdf_counter::<HmacSha256>(b"kdk", b"ab", b"c", &mut a).unwrap();
86 kbkdf_counter::<HmacSha256>(b"kdk", b"a", b"bc", &mut b).unwrap();
87 kbkdf_counter::<HmacSha256>(b"kdk", b"ab", b"d", &mut c).unwrap();
88 assert_ne!(a, b);
90 assert_ne!(a, c);
91 }
92
93 #[test]
96 fn output_length_is_bound_into_the_derivation() {
97 let mut short = [0u8; 32];
98 let mut long = [0u8; 64];
99 kbkdf_counter::<HmacSha256>(b"kdk", b"l", b"c", &mut short).unwrap();
100 kbkdf_counter::<HmacSha256>(b"kdk", b"l", b"c", &mut long).unwrap();
101 assert_ne!(&long[..32], &short[..]);
102 }
103
104 #[test]
105 fn blocks_are_distinct() {
106 let mut out = [0u8; 96];
107 kbkdf_counter::<HmacSha256>(b"kdk", b"l", b"c", &mut out).unwrap();
108 assert_ne!(&out[..32], &out[32..64]);
109 assert_ne!(&out[32..64], &out[64..]);
110 }
111
112 #[test]
113 fn rejects_ambiguous_label() {
114 let mut out = [0u8; 32];
115 assert!(kbkdf_counter::<HmacSha256>(b"kdk", b"a\0b", b"c", &mut out).is_err());
116 assert!(kbkdf_counter::<HmacSha256>(b"kdk", b"l", b"c", &mut []).is_err());
117 }
118}