rustls/crypto/
tls13.rs

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