clia_rustls_mod/crypto/
tls13.rs

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