portable_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    #[cfg(unstable_api_not_supported)] // [FIPS REMOVED FROM THIS FORK]
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#[derive(Debug)]
264pub struct OutputLengthError;
265
266#[cfg(all(test, feature = "ring"))]
267mod tests {
268    use std::prelude::v1::*;
269
270    use super::{expand, Hkdf, HkdfUsingHmac};
271    // nb: crypto::aws_lc_rs provider doesn't provide (or need) hmac,
272    // so cannot be used for this test.
273    use crate::crypto::ring::hmac;
274
275    struct ByteArray<const N: usize>([u8; N]);
276
277    impl<const N: usize> From<[u8; N]> for ByteArray<N> {
278        fn from(array: [u8; N]) -> Self {
279            Self(array)
280        }
281    }
282
283    /// Test cases from appendix A in the RFC, minus cases requiring SHA1.
284
285    #[test]
286    fn test_case_1() {
287        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
288        let ikm = &[0x0b; 22];
289        let salt = &[
290            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
291        ];
292        let info: &[&[u8]] = &[
293            &[0xf0, 0xf1, 0xf2],
294            &[0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9],
295        ];
296
297        let output: ByteArray<42> = expand(
298            hkdf.extract_from_secret(Some(salt), ikm)
299                .as_ref(),
300            info,
301        );
302
303        assert_eq!(
304            &output.0,
305            &[
306                0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
307                0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
308                0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65
309            ]
310        );
311    }
312
313    #[test]
314    fn test_case_2() {
315        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
316        let ikm: Vec<u8> = (0x00u8..=0x4f).collect();
317        let salt: Vec<u8> = (0x60u8..=0xaf).collect();
318        let info: Vec<u8> = (0xb0u8..=0xff).collect();
319
320        let output: ByteArray<82> = expand(
321            hkdf.extract_from_secret(Some(&salt), &ikm)
322                .as_ref(),
323            &[&info],
324        );
325
326        assert_eq!(
327            &output.0,
328            &[
329                0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a,
330                0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c,
331                0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb,
332                0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8,
333                0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec,
334                0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87
335            ]
336        );
337    }
338
339    #[test]
340    fn test_case_3() {
341        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
342        let ikm = &[0x0b; 22];
343        let salt = &[];
344        let info = &[];
345
346        let output: ByteArray<42> = expand(
347            hkdf.extract_from_secret(Some(salt), ikm)
348                .as_ref(),
349            info,
350        );
351
352        assert_eq!(
353            &output.0,
354            &[
355                0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c,
356                0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f,
357                0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8
358            ]
359        );
360    }
361
362    #[test]
363    fn test_salt_not_provided() {
364        // can't use test case 7, because we don't have (or want) SHA1.
365        //
366        // this output is generated with cryptography.io:
367        //
368        // >>> hkdf.HKDF(algorithm=hashes.SHA384(), length=96, salt=None, info=b"hello").derive(b"\x0b" * 40)
369
370        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA384);
371        let ikm = &[0x0b; 40];
372        let info = &[&b"hel"[..], &b"lo"[..]];
373
374        let output: ByteArray<96> = expand(
375            hkdf.extract_from_secret(None, ikm)
376                .as_ref(),
377            info,
378        );
379
380        assert_eq!(
381            &output.0,
382            &[
383                0xd5, 0x45, 0xdd, 0x3a, 0xff, 0x5b, 0x19, 0x46, 0xd4, 0x86, 0xfd, 0xb8, 0xd8, 0x88,
384                0x2e, 0xe0, 0x1c, 0xc1, 0xa5, 0x48, 0xb6, 0x05, 0x75, 0xe4, 0xd7, 0x5d, 0x0f, 0x5f,
385                0x23, 0x40, 0xee, 0x6c, 0x9e, 0x7c, 0x65, 0xd0, 0xee, 0x79, 0xdb, 0xb2, 0x07, 0x1d,
386                0x66, 0xa5, 0x50, 0xc4, 0x8a, 0xa3, 0x93, 0x86, 0x8b, 0x7c, 0x69, 0x41, 0x6b, 0x3e,
387                0x61, 0x44, 0x98, 0xb8, 0xc2, 0xfc, 0x82, 0x82, 0xae, 0xcd, 0x46, 0xcf, 0xb1, 0x47,
388                0xdc, 0xd0, 0x69, 0x0d, 0x19, 0xad, 0xe6, 0x6c, 0x70, 0xfe, 0x87, 0x92, 0x04, 0xb6,
389                0x82, 0x2d, 0x97, 0x7e, 0x46, 0x80, 0x4c, 0xe5, 0x76, 0x72, 0xb4, 0xb8
390            ]
391        );
392    }
393
394    #[test]
395    fn test_output_length_bounds() {
396        let hkdf = HkdfUsingHmac(&hmac::HMAC_SHA256);
397        let ikm = &[];
398        let info = &[];
399
400        let mut output = [0u8; 32 * 255 + 1];
401        assert!(hkdf
402            .extract_from_secret(None, ikm)
403            .expand_slice(info, &mut output)
404            .is_err());
405    }
406}