rustls/crypto/
tls13.rs

1use alloc::boxed::Box;
2use alloc::vec::Vec;
3
4use zeroize::Zeroize;
5
6use super::{ActiveKeyExchange, hmac};
7use crate::error::Error;
8use crate::version::TLS13;
9
10/// Implementation of `HkdfExpander` via `hmac::Key`.
11pub struct HkdfExpanderUsingHmac(Box<dyn hmac::Key>);
12
13impl HkdfExpanderUsingHmac {
14    fn expand_unchecked(&self, info: &[&[u8]], output: &mut [u8]) {
15        let mut term = hmac::Tag::new(b"");
16
17        for (n, chunk) in output
18            .chunks_mut(self.0.tag_len())
19            .enumerate()
20        {
21            term = self
22                .0
23                .sign_concat(term.as_ref(), info, &[(n + 1) as u8]);
24            chunk.copy_from_slice(&term.as_ref()[..chunk.len()]);
25        }
26    }
27}
28
29impl HkdfExpander for HkdfExpanderUsingHmac {
30    fn expand_slice(&self, info: &[&[u8]], output: &mut [u8]) -> Result<(), OutputLengthError> {
31        if output.len() > 255 * self.0.tag_len() {
32            return Err(OutputLengthError);
33        }
34
35        self.expand_unchecked(info, output);
36        Ok(())
37    }
38
39    fn expand_block(&self, info: &[&[u8]]) -> OkmBlock {
40        let mut tag = [0u8; hmac::Tag::MAX_LEN];
41        let reduced_tag = &mut tag[..self.0.tag_len()];
42        self.expand_unchecked(info, reduced_tag);
43        OkmBlock::new(reduced_tag)
44    }
45
46    fn hash_len(&self) -> usize {
47        self.0.tag_len()
48    }
49}
50
51/// Implementation of `Hkdf` (and thence `HkdfExpander`) via `hmac::Hmac`.
52#[allow(clippy::exhaustive_structs)]
53pub struct HkdfUsingHmac<'a>(pub &'a dyn hmac::Hmac);
54
55impl Hkdf for HkdfUsingHmac<'_> {
56    fn extract_from_zero_ikm(&self, salt: Option<&[u8]>) -> Box<dyn HkdfExpander> {
57        let zeroes = [0u8; hmac::Tag::MAX_LEN];
58        Box::new(HkdfExpanderUsingHmac(self.0.with_key(
59            &self.extract_prk_from_secret(salt, &zeroes[..self.0.hash_output_len()]),
60        )))
61    }
62
63    fn extract_from_secret(&self, salt: Option<&[u8]>, secret: &[u8]) -> Box<dyn HkdfExpander> {
64        Box::new(HkdfExpanderUsingHmac(
65            self.0
66                .with_key(&self.extract_prk_from_secret(salt, secret)),
67        ))
68    }
69
70    fn expander_for_okm(&self, okm: &OkmBlock) -> Box<dyn HkdfExpander> {
71        Box::new(HkdfExpanderUsingHmac(self.0.with_key(okm.as_ref())))
72    }
73
74    fn hmac_sign(&self, key: &OkmBlock, message: &[u8]) -> hmac::Tag {
75        self.0
76            .with_key(key.as_ref())
77            .sign(&[message])
78    }
79}
80
81impl HkdfPrkExtract for HkdfUsingHmac<'_> {
82    fn extract_prk_from_secret(&self, salt: Option<&[u8]>, secret: &[u8]) -> Vec<u8> {
83        let zeroes = [0u8; hmac::Tag::MAX_LEN];
84        let salt = match salt {
85            Some(salt) => salt,
86            None => &zeroes[..self.0.hash_output_len()],
87        };
88        self.0
89            .with_key(salt)
90            .sign(&[secret])
91            .as_ref()
92            .to_vec()
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_for_tls_version(peer_pub_key, &TLS13)?
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    /// Return `true` if this is backed by a FIPS-approved implementation.
181    fn fips(&self) -> bool {
182        false
183    }
184}
185
186/// An extended HKDF implementation that supports directly extracting a pseudo-random key (PRK).
187///
188/// The base [`Hkdf`] trait is tailored to the needs of TLS 1.3, where all extracted PRKs
189/// are expanded as-is, and so can be safely encapsulated without exposing the caller
190/// to the key material.
191///
192/// In other contexts (for example, hybrid public key encryption (HPKE)) it may be necessary
193/// to use the extracted PRK directly for purposes other than an immediate expansion.
194/// This trait can be implemented to offer this functionality when it is required.
195pub(crate) trait HkdfPrkExtract: Hkdf {
196    /// `HKDF-Extract(salt, secret)`
197    ///
198    /// A `salt` of `None` should be treated as a sequence of `HashLen` zero bytes.
199    ///
200    /// In most cases you should prefer [`Hkdf::extract_from_secret`] and using the
201    /// returned [HkdfExpander] instead of handling the PRK directly.
202    fn extract_prk_from_secret(&self, salt: Option<&[u8]>, secret: &[u8]) -> Vec<u8>;
203}
204
205/// `HKDF-Expand(PRK, info, L)` to construct any type from a byte array.
206///
207/// - `PRK` is the implicit key material represented by this instance.
208/// - `L := N`; N is the size of the byte array.
209/// - `info` is a slice of byte slices, which should be processed sequentially
210///   (or concatenated if that is not possible).
211///
212/// This is infallible, because the set of types (and therefore their length) is known
213/// at compile time.
214pub fn expand<T, const N: usize>(expander: &dyn HkdfExpander, info: &[&[u8]]) -> T
215where
216    T: From<[u8; N]>,
217{
218    let mut output = [0u8; N];
219    expander
220        .expand_slice(info, &mut output)
221        .expect("expand type parameter T is too large");
222    T::from(output)
223}
224
225/// Output key material from HKDF, as a value type.
226#[derive(Clone)]
227pub struct OkmBlock {
228    buf: [u8; Self::MAX_LEN],
229    used: usize,
230}
231
232impl OkmBlock {
233    /// Build a single OKM block by copying a byte slice.
234    ///
235    /// The slice can be up to [`OkmBlock::MAX_LEN`] bytes in length.
236    pub fn new(bytes: &[u8]) -> Self {
237        let mut tag = Self {
238            buf: [0u8; Self::MAX_LEN],
239            used: bytes.len(),
240        };
241        tag.buf[..bytes.len()].copy_from_slice(bytes);
242        tag
243    }
244
245    /// Maximum supported HMAC tag size: supports up to SHA512.
246    pub const MAX_LEN: usize = 64;
247}
248
249impl Drop for OkmBlock {
250    fn drop(&mut self) {
251        self.buf.zeroize();
252    }
253}
254
255impl AsRef<[u8]> for OkmBlock {
256    fn as_ref(&self) -> &[u8] {
257        &self.buf[..self.used]
258    }
259}
260
261/// An error type used for `HkdfExpander::expand_slice` when
262/// the slice exceeds the maximum HKDF output length.
263#[allow(clippy::exhaustive_structs)]
264#[derive(Debug)]
265pub struct OutputLengthError;
266
267#[cfg(all(test, feature = "ring"))]
268mod tests {
269    use std::prelude::v1::*;
270
271    use super::{Hkdf, HkdfUsingHmac, expand};
272    // nb: crypto::aws_lc_rs provider doesn't provide (or need) hmac,
273    // so cannot be used for this test.
274    use crate::crypto::ring::hmac;
275
276    struct ByteArray<const N: usize>([u8; N]);
277
278    impl<const N: usize> From<[u8; N]> for ByteArray<N> {
279        fn from(array: [u8; N]) -> Self {
280            Self(array)
281        }
282    }
283
284    /// Test cases from appendix A in the RFC, minus cases requiring SHA1.
285
286    #[test]
287    fn test_case_1() {
288        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
289        let ikm = &[0x0b; 22];
290        let salt = &[
291            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
292        ];
293        let info: &[&[u8]] = &[
294            &[0xf0, 0xf1, 0xf2],
295            &[0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9],
296        ];
297
298        let output: ByteArray<42> = expand(
299            hkdf.extract_from_secret(Some(salt), ikm)
300                .as_ref(),
301            info,
302        );
303
304        assert_eq!(
305            &output.0,
306            &[
307                0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
308                0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
309                0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65
310            ]
311        );
312    }
313
314    #[test]
315    fn test_case_2() {
316        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
317        let ikm: Vec<u8> = (0x00u8..=0x4f).collect();
318        let salt: Vec<u8> = (0x60u8..=0xaf).collect();
319        let info: Vec<u8> = (0xb0u8..=0xff).collect();
320
321        let output: ByteArray<82> = expand(
322            hkdf.extract_from_secret(Some(&salt), &ikm)
323                .as_ref(),
324            &[&info],
325        );
326
327        assert_eq!(
328            &output.0,
329            &[
330                0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a,
331                0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c,
332                0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb,
333                0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8,
334                0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec,
335                0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87
336            ]
337        );
338    }
339
340    #[test]
341    fn test_case_3() {
342        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
343        let ikm = &[0x0b; 22];
344        let salt = &[];
345        let info = &[];
346
347        let output: ByteArray<42> = expand(
348            hkdf.extract_from_secret(Some(salt), ikm)
349                .as_ref(),
350            info,
351        );
352
353        assert_eq!(
354            &output.0,
355            &[
356                0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c,
357                0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f,
358                0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8
359            ]
360        );
361    }
362
363    #[test]
364    fn test_salt_not_provided() {
365        // can't use test case 7, because we don't have (or want) SHA1.
366        //
367        // this output is generated with cryptography.io:
368        //
369        // >>> hkdf.HKDF(algorithm=hashes.SHA384(), length=96, salt=None, info=b"hello").derive(b"\x0b" * 40)
370
371        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA384);
372        let ikm = &[0x0b; 40];
373        let info = &[&b"hel"[..], &b"lo"[..]];
374
375        let output: ByteArray<96> = expand(
376            hkdf.extract_from_secret(None, ikm)
377                .as_ref(),
378            info,
379        );
380
381        assert_eq!(
382            &output.0,
383            &[
384                0xd5, 0x45, 0xdd, 0x3a, 0xff, 0x5b, 0x19, 0x46, 0xd4, 0x86, 0xfd, 0xb8, 0xd8, 0x88,
385                0x2e, 0xe0, 0x1c, 0xc1, 0xa5, 0x48, 0xb6, 0x05, 0x75, 0xe4, 0xd7, 0x5d, 0x0f, 0x5f,
386                0x23, 0x40, 0xee, 0x6c, 0x9e, 0x7c, 0x65, 0xd0, 0xee, 0x79, 0xdb, 0xb2, 0x07, 0x1d,
387                0x66, 0xa5, 0x50, 0xc4, 0x8a, 0xa3, 0x93, 0x86, 0x8b, 0x7c, 0x69, 0x41, 0x6b, 0x3e,
388                0x61, 0x44, 0x98, 0xb8, 0xc2, 0xfc, 0x82, 0x82, 0xae, 0xcd, 0x46, 0xcf, 0xb1, 0x47,
389                0xdc, 0xd0, 0x69, 0x0d, 0x19, 0xad, 0xe6, 0x6c, 0x70, 0xfe, 0x87, 0x92, 0x04, 0xb6,
390                0x82, 0x2d, 0x97, 0x7e, 0x46, 0x80, 0x4c, 0xe5, 0x76, 0x72, 0xb4, 0xb8
391            ]
392        );
393    }
394
395    #[test]
396    fn test_output_length_bounds() {
397        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
398        let ikm = &[];
399        let info = &[];
400
401        let mut output = [0u8; 32 * 255 + 1];
402        assert!(
403            hkdf.extract_from_secret(None, ikm)
404                .expand_slice(info, &mut output)
405                .is_err()
406        );
407    }
408}