1use super::hmac;
2use super::ActiveKeyExchange;
3use crate::error::Error;
4
5use alloc::boxed::Box;
6use zeroize::Zeroize;
7
8/// Implementation of `HkdfExpander` via `hmac::Key`.
9pub struct HkdfExpanderUsingHmac(Box<dyn hmac::Key>);
10
11impl HkdfExpanderUsingHmac {
12 fn expand_unchecked(&self, info: &[&[u8]], output: &mut [u8]) {
13 let mut term: Tag = hmac::Tag::new(bytes:b"");
14
15 for (n: usize, chunk: &mut [u8]) in outputChunksMut<'_, u8>
16 .chunks_mut(self.0.tag_len())
17 .enumerate()
18 {
19 term = self
20 .0
21 .sign_concat(first:term.as_ref(), middle:info, &[(n + 1) as u8]);
22 chunk.copy_from_slice(&term.as_ref()[..chunk.len()]);
23 }
24 }
25}
26
27impl HkdfExpander for HkdfExpanderUsingHmac {
28 fn expand_slice(&self, info: &[&[u8]], output: &mut [u8]) -> Result<(), OutputLengthError> {
29 if output.len() > 255 * self.0.tag_len() {
30 return Err(OutputLengthError);
31 }
32
33 self.expand_unchecked(info, output);
34 Ok(())
35 }
36
37 fn expand_block(&self, info: &[&[u8]]) -> OkmBlock {
38 let mut tag: [u8; 64] = [0u8; hmac::Tag::MAX_LEN];
39 let reduced_tag: &mut [u8] = &mut tag[..self.0.tag_len()];
40 self.expand_unchecked(info, output:reduced_tag);
41 OkmBlock::new(bytes:reduced_tag)
42 }
43
44 fn hash_len(&self) -> usize {
45 self.0.tag_len()
46 }
47}
48
49/// Implementation of `Hkdf` (and thence `HkdfExpander`) via `hmac::Hmac`.
50pub struct HkdfUsingHmac<'a>(pub &'a dyn hmac::Hmac);
51
52impl<'a> Hkdf for HkdfUsingHmac<'a> {
53 fn extract_from_zero_ikm(&self, salt: Option<&[u8]>) -> Box<dyn HkdfExpander> {
54 let zeroes = [0u8; hmac::Tag::MAX_LEN];
55 let salt = match salt {
56 Some(salt) => salt,
57 None => &zeroes[..self.0.hash_output_len()],
58 };
59 Box::new(HkdfExpanderUsingHmac(
60 self.0.with_key(
61 self.0
62 .with_key(salt)
63 .sign(&[&zeroes[..self.0.hash_output_len()]])
64 .as_ref(),
65 ),
66 ))
67 }
68
69 fn extract_from_secret(&self, salt: Option<&[u8]>, secret: &[u8]) -> Box<dyn HkdfExpander> {
70 let zeroes = [0u8; hmac::Tag::MAX_LEN];
71 let salt = match salt {
72 Some(salt) => salt,
73 None => &zeroes[..self.0.hash_output_len()],
74 };
75 Box::new(HkdfExpanderUsingHmac(
76 self.0.with_key(
77 self.0
78 .with_key(salt)
79 .sign(&[secret])
80 .as_ref(),
81 ),
82 ))
83 }
84
85 fn expander_for_okm(&self, okm: &OkmBlock) -> Box<dyn HkdfExpander> {
86 Box::new(HkdfExpanderUsingHmac(self.0.with_key(okm.as_ref())))
87 }
88
89 fn hmac_sign(&self, key: &OkmBlock, message: &[u8]) -> hmac::Tag {
90 self.0
91 .with_key(key.as_ref())
92 .sign(&[message])
93 }
94}
95
96/// Implementation of `HKDF-Expand` with an implicitly stored and immutable `PRK`.
97pub trait HkdfExpander: Send + Sync {
98 /// `HKDF-Expand(PRK, info, L)` into a slice.
99 ///
100 /// Where:
101 ///
102 /// - `PRK` is the implicit key material represented by this instance.
103 /// - `L` is `output.len()`.
104 /// - `info` is a slice of byte slices, which should be processed sequentially
105 /// (or concatenated if that is not possible).
106 ///
107 /// Returns `Err(OutputLengthError)` if `L` is larger than `255 * HashLen`.
108 /// Otherwise, writes to `output`.
109 fn expand_slice(&self, info: &[&[u8]], output: &mut [u8]) -> Result<(), OutputLengthError>;
110
111 /// `HKDF-Expand(PRK, info, L=HashLen)` returned as a value.
112 ///
113 /// - `PRK` is the implicit key material represented by this instance.
114 /// - `L := HashLen`.
115 /// - `info` is a slice of byte slices, which should be processed sequentially
116 /// (or concatenated if that is not possible).
117 ///
118 /// This is infallible, because by definition `OkmBlock` is always exactly
119 /// `HashLen` bytes long.
120 fn expand_block(&self, info: &[&[u8]]) -> OkmBlock;
121
122 /// Return what `HashLen` is for this instance.
123 ///
124 /// This must be no larger than [`OkmBlock::MAX_LEN`].
125 fn hash_len(&self) -> usize;
126}
127
128/// A HKDF implementation oriented to the needs of TLS1.3.
129///
130/// See [RFC5869](https://datatracker.ietf.org/doc/html/rfc5869) for the terminology
131/// used in this definition.
132///
133/// You can use [`HkdfUsingHmac`] which implements this trait on top of an implementation
134/// of [`hmac::Hmac`].
135pub trait Hkdf: Send + Sync {
136 /// `HKDF-Extract(salt, 0_HashLen)`
137 ///
138 /// `0_HashLen` is a string of `HashLen` zero bytes.
139 ///
140 /// A `salt` of `None` should be treated as a sequence of `HashLen` zero bytes.
141 fn extract_from_zero_ikm(&self, salt: Option<&[u8]>) -> Box<dyn HkdfExpander>;
142
143 /// `HKDF-Extract(salt, secret)`
144 ///
145 /// A `salt` of `None` should be treated as a sequence of `HashLen` zero bytes.
146 fn extract_from_secret(&self, salt: Option<&[u8]>, secret: &[u8]) -> Box<dyn HkdfExpander>;
147
148 /// `HKDF-Extract(salt, shared_secret)` where `shared_secret` is the result of a key exchange.
149 ///
150 /// Custom implementations should complete the key exchange by calling
151 /// `kx.complete(peer_pub_key)` and then using this as the input keying material to
152 /// `HKDF-Extract`.
153 ///
154 /// A `salt` of `None` should be treated as a sequence of `HashLen` zero bytes.
155 fn extract_from_kx_shared_secret(
156 &self,
157 salt: Option<&[u8]>,
158 kx: Box<dyn ActiveKeyExchange>,
159 peer_pub_key: &[u8],
160 ) -> Result<Box<dyn HkdfExpander>, Error> {
161 Ok(self.extract_from_secret(
162 salt,
163 kx.complete(peer_pub_key)?
164 .secret_bytes(),
165 ))
166 }
167
168 /// Build a `HkdfExpander` using `okm` as the secret PRK.
169 fn expander_for_okm(&self, okm: &OkmBlock) -> Box<dyn HkdfExpander>;
170
171 /// Signs `message` using `key` viewed as a HMAC key.
172 ///
173 /// This should use the same hash function as the HKDF functions in this
174 /// trait.
175 ///
176 /// See [RFC2104](https://datatracker.ietf.org/doc/html/rfc2104) for the
177 /// definition of HMAC.
178 fn hmac_sign(&self, key: &OkmBlock, message: &[u8]) -> hmac::Tag;
179}
180
181/// `HKDF-Expand(PRK, info, L)` to construct any type from a byte array.
182///
183/// - `PRK` is the implicit key material represented by this instance.
184/// - `L := N`; N is the size of the byte array.
185/// - `info` is a slice of byte slices, which should be processed sequentially
186/// (or concatenated if that is not possible).
187///
188/// This is infallible, because the set of types (and therefore their length) is known
189/// at compile time.
190pub fn expand<T, const N: usize>(expander: &dyn HkdfExpander, info: &[&[u8]]) -> T
191where
192 T: From<[u8; N]>,
193{
194 let mut output: [u8; N] = [0u8; N];
195 expander
196 .expand_slice(info, &mut output)
197 .expect(msg:"expand type parameter T is too large");
198 T::from(output)
199}
200
201/// Output key material from HKDF, as a value type.
202#[derive(Clone)]
203pub struct OkmBlock {
204 buf: [u8; Self::MAX_LEN],
205 used: usize,
206}
207
208impl OkmBlock {
209 /// Build a single OKM block by copying a byte slice.
210 ///
211 /// The slice can be up to [`OkmBlock::MAX_LEN`] bytes in length.
212 pub fn new(bytes: &[u8]) -> Self {
213 let mut tag: OkmBlock = Self {
214 buf: [0u8; Self::MAX_LEN],
215 used: bytes.len(),
216 };
217 tag.buf[..bytes.len()].copy_from_slice(src:bytes);
218 tag
219 }
220
221 /// Maximum supported HMAC tag size: supports up to SHA512.
222 pub const MAX_LEN: usize = 64;
223}
224
225impl Drop for OkmBlock {
226 fn drop(&mut self) {
227 self.buf.zeroize();
228 }
229}
230
231impl AsRef<[u8]> for OkmBlock {
232 fn as_ref(&self) -> &[u8] {
233 &self.buf[..self.used]
234 }
235}
236
237/// An error type used for `HkdfExpander::expand_slice` when
238/// the slice exceeds the maximum HKDF output length.
239#[derive(Debug)]
240pub struct OutputLengthError;
241
242#[cfg(all(test, feature = "ring"))]
243mod tests {
244 use super::{expand, Hkdf, HkdfUsingHmac};
245 use crate::test_provider::hmac;
246
247 struct ByteArray<const N: usize>([u8; N]);
248
249 impl<const N: usize> From<[u8; N]> for ByteArray<N> {
250 fn from(array: [u8; N]) -> Self {
251 Self(array)
252 }
253 }
254
255 /// Test cases from appendix A in the RFC, minus cases requiring SHA1.
256
257 #[test]
258 fn test_case_1() {
259 let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
260 let ikm = &[0x0b; 22];
261 let salt = &[
262 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
263 ];
264 let info: &[&[u8]] = &[
265 &[0xf0, 0xf1, 0xf2],
266 &[0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9],
267 ];
268
269 let output: ByteArray<42> = expand(
270 hkdf.extract_from_secret(Some(salt), ikm)
271 .as_ref(),
272 info,
273 );
274
275 assert_eq!(
276 &output.0,
277 &[
278 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
279 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
280 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65
281 ]
282 );
283 }
284
285 #[test]
286 fn test_case_2() {
287 let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
288 let ikm: Vec<u8> = (0x00u8..=0x4f).collect();
289 let salt: Vec<u8> = (0x60u8..=0xaf).collect();
290 let info: Vec<u8> = (0xb0u8..=0xff).collect();
291
292 let output: ByteArray<82> = expand(
293 hkdf.extract_from_secret(Some(&salt), &ikm)
294 .as_ref(),
295 &[&info],
296 );
297
298 assert_eq!(
299 &output.0,
300 &[
301 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a,
302 0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c,
303 0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb,
304 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8,
305 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec,
306 0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87
307 ]
308 );
309 }
310
311 #[test]
312 fn test_case_3() {
313 let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
314 let ikm = &[0x0b; 22];
315 let salt = &[];
316 let info = &[];
317
318 let output: ByteArray<42> = expand(
319 hkdf.extract_from_secret(Some(salt), ikm)
320 .as_ref(),
321 info,
322 );
323
324 assert_eq!(
325 &output.0,
326 &[
327 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c,
328 0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f,
329 0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8
330 ]
331 );
332 }
333
334 #[test]
335 fn test_salt_not_provided() {
336 // can't use test case 7, because we don't have (or want) SHA1.
337 //
338 // this output is generated with cryptography.io:
339 //
340 // >>> hkdf.HKDF(algorithm=hashes.SHA384(), length=96, salt=None, info=b"hello").derive(b"\x0b" * 40)
341
342 let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA384);
343 let ikm = &[0x0b; 40];
344 let info = &[&b"hel"[..], &b"lo"[..]];
345
346 let output: ByteArray<96> = expand(
347 hkdf.extract_from_secret(None, ikm)
348 .as_ref(),
349 info,
350 );
351
352 assert_eq!(
353 &output.0,
354 &[
355 0xd5, 0x45, 0xdd, 0x3a, 0xff, 0x5b, 0x19, 0x46, 0xd4, 0x86, 0xfd, 0xb8, 0xd8, 0x88,
356 0x2e, 0xe0, 0x1c, 0xc1, 0xa5, 0x48, 0xb6, 0x05, 0x75, 0xe4, 0xd7, 0x5d, 0x0f, 0x5f,
357 0x23, 0x40, 0xee, 0x6c, 0x9e, 0x7c, 0x65, 0xd0, 0xee, 0x79, 0xdb, 0xb2, 0x07, 0x1d,
358 0x66, 0xa5, 0x50, 0xc4, 0x8a, 0xa3, 0x93, 0x86, 0x8b, 0x7c, 0x69, 0x41, 0x6b, 0x3e,
359 0x61, 0x44, 0x98, 0xb8, 0xc2, 0xfc, 0x82, 0x82, 0xae, 0xcd, 0x46, 0xcf, 0xb1, 0x47,
360 0xdc, 0xd0, 0x69, 0x0d, 0x19, 0xad, 0xe6, 0x6c, 0x70, 0xfe, 0x87, 0x92, 0x04, 0xb6,
361 0x82, 0x2d, 0x97, 0x7e, 0x46, 0x80, 0x4c, 0xe5, 0x76, 0x72, 0xb4, 0xb8
362 ]
363 );
364 }
365
366 #[test]
367 fn test_output_length_bounds() {
368 let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
369 let ikm = &[];
370 let info = &[];
371
372 let mut output = [0u8; 32 * 255 + 1];
373 assert!(hkdf
374 .extract_from_secret(None, ikm)
375 .expand_slice(info, &mut output)
376 .is_err());
377 }
378}
379