rustls/crypto/
tls13.rs

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 = hmac::Tag::new(b"");
14
15        for (n, chunk) in output
16            .chunks_mut(self.0.tag_len())
17            .enumerate()
18        {
19            term = self
20                .0
21                .sign_concat(term.as_ref(), 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 = [0u8; hmac::Tag::MAX_LEN];
39        let reduced_tag = &mut tag[..self.0.tag_len()];
40        self.expand_unchecked(info, reduced_tag);
41        OkmBlock::new(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 = [0u8; N];
195    expander
196        .expand_slice(info, &mut output)
197        .expect("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 = Self {
214            buf: [0u8; Self::MAX_LEN],
215            used: bytes.len(),
216        };
217        tag.buf[..bytes.len()].copy_from_slice(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    use std::prelude::v1::*;
247
248    struct ByteArray<const N: usize>([u8; N]);
249
250    impl<const N: usize> From<[u8; N]> for ByteArray<N> {
251        fn from(array: [u8; N]) -> Self {
252            Self(array)
253        }
254    }
255
256    /// Test cases from appendix A in the RFC, minus cases requiring SHA1.
257
258    #[test]
259    fn test_case_1() {
260        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
261        let ikm = &[0x0b; 22];
262        let salt = &[
263            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
264        ];
265        let info: &[&[u8]] = &[
266            &[0xf0, 0xf1, 0xf2],
267            &[0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9],
268        ];
269
270        let output: ByteArray<42> = expand(
271            hkdf.extract_from_secret(Some(salt), ikm)
272                .as_ref(),
273            info,
274        );
275
276        assert_eq!(
277            &output.0,
278            &[
279                0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
280                0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
281                0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65
282            ]
283        );
284    }
285
286    #[test]
287    fn test_case_2() {
288        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
289        let ikm: Vec<u8> = (0x00u8..=0x4f).collect();
290        let salt: Vec<u8> = (0x60u8..=0xaf).collect();
291        let info: Vec<u8> = (0xb0u8..=0xff).collect();
292
293        let output: ByteArray<82> = expand(
294            hkdf.extract_from_secret(Some(&salt), &ikm)
295                .as_ref(),
296            &[&info],
297        );
298
299        assert_eq!(
300            &output.0,
301            &[
302                0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a,
303                0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c,
304                0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb,
305                0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8,
306                0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec,
307                0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87
308            ]
309        );
310    }
311
312    #[test]
313    fn test_case_3() {
314        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
315        let ikm = &[0x0b; 22];
316        let salt = &[];
317        let info = &[];
318
319        let output: ByteArray<42> = expand(
320            hkdf.extract_from_secret(Some(salt), ikm)
321                .as_ref(),
322            info,
323        );
324
325        assert_eq!(
326            &output.0,
327            &[
328                0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c,
329                0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f,
330                0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8
331            ]
332        );
333    }
334
335    #[test]
336    fn test_salt_not_provided() {
337        // can't use test case 7, because we don't have (or want) SHA1.
338        //
339        // this output is generated with cryptography.io:
340        //
341        // >>> hkdf.HKDF(algorithm=hashes.SHA384(), length=96, salt=None, info=b"hello").derive(b"\x0b" * 40)
342
343        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA384);
344        let ikm = &[0x0b; 40];
345        let info = &[&b"hel"[..], &b"lo"[..]];
346
347        let output: ByteArray<96> = expand(
348            hkdf.extract_from_secret(None, ikm)
349                .as_ref(),
350            info,
351        );
352
353        assert_eq!(
354            &output.0,
355            &[
356                0xd5, 0x45, 0xdd, 0x3a, 0xff, 0x5b, 0x19, 0x46, 0xd4, 0x86, 0xfd, 0xb8, 0xd8, 0x88,
357                0x2e, 0xe0, 0x1c, 0xc1, 0xa5, 0x48, 0xb6, 0x05, 0x75, 0xe4, 0xd7, 0x5d, 0x0f, 0x5f,
358                0x23, 0x40, 0xee, 0x6c, 0x9e, 0x7c, 0x65, 0xd0, 0xee, 0x79, 0xdb, 0xb2, 0x07, 0x1d,
359                0x66, 0xa5, 0x50, 0xc4, 0x8a, 0xa3, 0x93, 0x86, 0x8b, 0x7c, 0x69, 0x41, 0x6b, 0x3e,
360                0x61, 0x44, 0x98, 0xb8, 0xc2, 0xfc, 0x82, 0x82, 0xae, 0xcd, 0x46, 0xcf, 0xb1, 0x47,
361                0xdc, 0xd0, 0x69, 0x0d, 0x19, 0xad, 0xe6, 0x6c, 0x70, 0xfe, 0x87, 0x92, 0x04, 0xb6,
362                0x82, 0x2d, 0x97, 0x7e, 0x46, 0x80, 0x4c, 0xe5, 0x76, 0x72, 0xb4, 0xb8
363            ]
364        );
365    }
366
367    #[test]
368    fn test_output_length_bounds() {
369        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
370        let ikm = &[];
371        let info = &[];
372
373        let mut output = [0u8; 32 * 255 + 1];
374        assert!(hkdf
375            .extract_from_secret(None, ikm)
376            .expand_slice(info, &mut output)
377            .is_err());
378    }
379}