1use core::marker::PhantomData;
4use ic_core::traits::{Algorithm, Kdf, Mac, SelfTest};
5use ic_core::{ensure, Result, Zeroize};
6
7const MAX_PRK_LEN: usize = 64;
9
10pub struct Hkdf<M: Mac>(PhantomData<M>);
12
13impl<M: Mac> Hkdf<M> {
14 pub fn extract(salt: &[u8], ikm: &[u8], prk: &mut [u8]) -> Result<()> {
18 ic_core::module::operational()?;
19 ensure!(prk.len() == M::TAG_LEN, InvalidLength, "hkdf prk buffer");
20 let zeros = [0u8; MAX_PRK_LEN];
21 let salt = if salt.is_empty() {
22 &zeros[..M::TAG_LEN]
23 } else {
24 salt
25 };
26 let tag = M::mac(salt, ikm)?;
27 prk.copy_from_slice(tag.as_ref());
28 Ok(())
29 }
30
31 pub fn expand(prk: &[u8], info: &[u8], out: &mut [u8]) -> Result<()> {
38 ic_core::module::operational()?;
39 ensure!(prk.len() >= M::TAG_LEN, InvalidLength, "hkdf prk too short");
40 Self::expand_with(|| M::new(prk), info, out)
41 }
42
43 pub fn expand_from(keyed: &M, info: &[u8], out: &mut [u8]) -> Result<()>
55 where
56 M: Clone,
57 {
58 ic_core::module::operational()?;
59 Self::expand_with(|| Ok(keyed.clone()), info, out)
60 }
61
62 fn expand_with(
65 mut keyed: impl FnMut() -> Result<M>,
66 info: &[u8],
67 out: &mut [u8],
68 ) -> Result<()> {
69 let n = M::TAG_LEN;
70 ensure!(
72 out.len() <= 255 * n,
73 InvalidLength,
74 "hkdf output exceeds 255*HashLen"
75 );
76
77 let mut previous = [0u8; MAX_PRK_LEN];
78 let mut previous_len = 0usize;
79 let mut counter: u8 = 1;
80
81 for chunk in out.chunks_mut(n) {
82 let mut m = keyed()?;
83 m.update(&previous[..previous_len]);
84 m.update(info);
85 m.update(&[counter]);
86 let t = m.finalize();
87 chunk.copy_from_slice(&t.as_ref()[..chunk.len()]);
88 previous[..n].copy_from_slice(t.as_ref());
89 previous_len = n;
90 counter = counter.wrapping_add(1);
91 }
92 previous.zeroize();
93 Ok(())
94 }
95}
96
97impl<M: Mac> Algorithm for Hkdf<M> {
98 const ID: &'static str = M::ID;
99 const NAME: &'static str = "HKDF";
100}
101
102impl<M: Mac> Kdf for Hkdf<M> {
103 fn derive(secret: &[u8], salt: &[u8], info: &[u8], out: &mut [u8]) -> Result<()> {
105 ic_core::module::operational()?;
106 let mut prk = [0u8; MAX_PRK_LEN];
107 Self::extract(salt, secret, &mut prk[..M::TAG_LEN])?;
108 let r = Self::expand(&prk[..M::TAG_LEN], info, out);
109 prk.zeroize();
110 r
111 }
112}
113
114impl SelfTest for Hkdf<ic_mac::HmacSha256> {
115 fn self_test() -> Result<()> {
116 let ikm = [0x0bu8; 22];
118 let mut salt = [0u8; 13];
119 ic_core::codec::hex_decode(b"000102030405060708090a0b0c", &mut salt)?;
120 let mut info = [0u8; 10];
121 ic_core::codec::hex_decode(b"f0f1f2f3f4f5f6f7f8f9", &mut info)?;
122
123 let mut okm = [0u8; 42];
124 <Self as Kdf>::derive(&ikm, &salt, &info, &mut okm)?;
125
126 let mut want = [0u8; 42];
127 ic_core::codec::hex_decode(
128 b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
129 &mut want,
130 )?;
131 ensure!(
132 ic_core::ct::verify(&want, &okm),
133 SelfTestFailed,
134 "hkdf-sha2-256"
135 );
136 Ok(())
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use ic_core::codec::{hex, unhex};
144 use ic_mac::{HmacSha256, HmacSha512};
145
146 #[test]
148 fn rfc5869_case_1() {
149 let ikm = [0x0bu8; 22];
150 let salt = unhex("000102030405060708090a0b0c").unwrap();
151 let info = unhex("f0f1f2f3f4f5f6f7f8f9").unwrap();
152
153 let mut prk = [0u8; 32];
154 Hkdf::<HmacSha256>::extract(&salt, &ikm, &mut prk).unwrap();
155 assert_eq!(
156 hex(&prk),
157 "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"
158 );
159
160 let mut okm = [0u8; 42];
161 Hkdf::<HmacSha256>::expand(&prk, &info, &mut okm).unwrap();
162 assert_eq!(
163 hex(&okm),
164 "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"
165 );
166 }
167
168 #[test]
170 fn rfc5869_case_3_empty_salt_and_info() {
171 let ikm = [0x0bu8; 22];
172 let mut prk = [0u8; 32];
173 Hkdf::<HmacSha256>::extract(b"", &ikm, &mut prk).unwrap();
174 assert_eq!(
175 hex(&prk),
176 "19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04"
177 );
178
179 let mut okm = [0u8; 42];
180 Hkdf::<HmacSha256>::expand(&prk, b"", &mut okm).unwrap();
181 assert_eq!(
182 hex(&okm),
183 "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"
184 );
185 }
186
187 #[test]
189 fn rfc5869_case_2_long_inputs() {
190 let ikm: Vec<u8> = (0..80u8).collect();
191 let salt: Vec<u8> = (0x60..0xb0u8).collect();
192 let info: Vec<u8> = (0xb0..=0xffu8).collect();
193
194 let mut okm = [0u8; 82];
195 Hkdf::<HmacSha256>::derive(&ikm, &salt, &info, &mut okm).unwrap();
196 assert_eq!(
197 hex(&okm),
198 "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87"
199 );
200 }
201
202 #[test]
203 fn derive_matches_extract_then_expand() {
204 let mut a = [0u8; 40];
205 Hkdf::<HmacSha512>::derive(b"ikm", b"salt", b"info", &mut a).unwrap();
206
207 let mut prk = [0u8; 64];
208 Hkdf::<HmacSha512>::extract(b"salt", b"ikm", &mut prk).unwrap();
209 let mut b = [0u8; 40];
210 Hkdf::<HmacSha512>::expand(&prk, b"info", &mut b).unwrap();
211 assert_eq!(a, b);
212 }
213
214 #[test]
215 fn info_separates_derived_keys() {
216 let mut a = [0u8; 32];
217 let mut b = [0u8; 32];
218 Hkdf::<HmacSha256>::derive(b"ikm", b"salt", b"context-a", &mut a).unwrap();
219 Hkdf::<HmacSha256>::derive(b"ikm", b"salt", b"context-b", &mut b).unwrap();
220 assert_ne!(a, b, "distinct info must yield independent keys");
221 }
222
223 #[test]
228 fn expand_from_a_keyed_mac_matches_expand() {
229 let mut checked = 0;
230 for prk in [[0x0bu8; 32], [0xa5; 32]] {
231 let keyed = HmacSha256::new(&prk).unwrap();
232 for len in [1usize, 31, 32, 33, 64, 65, 200, 255 * 32] {
233 let mut a = std::vec![0u8; len];
234 let mut b = std::vec![0u8; len];
235 Hkdf::<HmacSha256>::expand(&prk, b"tls13 key", &mut a).unwrap();
236 Hkdf::<HmacSha256>::expand_from(&keyed, b"tls13 key", &mut b).unwrap();
237 assert_eq!(a, b, "length {len}");
238 checked += 1;
239 }
240 }
241 let prk = [0x5cu8; 64];
242 let keyed = HmacSha512::new(&prk).unwrap();
243 let (mut a, mut b) = ([0u8; 200], [0u8; 200]);
244 Hkdf::<HmacSha512>::expand(&prk, b"info", &mut a).unwrap();
245 Hkdf::<HmacSha512>::expand_from(&keyed, b"info", &mut b).unwrap();
246 assert_eq!(a, b);
247 assert!(checked >= 16);
248 }
249
250 #[test]
252 fn expand_from_reproduces_rfc5869_case_1() {
253 let ikm = [0x0bu8; 22];
254 let salt: std::vec::Vec<u8> = (0x00..=0x0c).collect();
255 let info: std::vec::Vec<u8> = (0xf0..=0xf9).collect();
256 let mut prk = [0u8; 32];
257 Hkdf::<HmacSha256>::extract(&salt, &ikm, &mut prk).unwrap();
258 let keyed = HmacSha256::new(&prk).unwrap();
259 let mut okm = [0u8; 42];
260 Hkdf::<HmacSha256>::expand_from(&keyed, &info, &mut okm).unwrap();
261 let mut expected = [0u8; 42];
262 Hkdf::<HmacSha256>::expand(&prk, &info, &mut expected).unwrap();
263 assert_eq!(okm, expected);
264 assert_eq!(&okm[..4], &[0x3c, 0xb2, 0x5f, 0x25]);
266 }
267
268 #[test]
269 fn rejects_output_beyond_255_blocks() {
270 let mut too_long = vec![0u8; 255 * 32 + 1];
271 assert!(Hkdf::<HmacSha256>::expand(&[0u8; 32], b"", &mut too_long).is_err());
272 let mut at_limit = vec![0u8; 255 * 32];
273 assert!(Hkdf::<HmacSha256>::expand(&[0u8; 32], b"", &mut at_limit).is_ok());
274 }
275
276 #[test]
277 fn self_test_passes() {
278 Hkdf::<HmacSha256>::self_test().unwrap();
279 }
280}