Coverage Report

Created: 2025-05-07 06:59

/rust/registry/src/index.crates.io-6f17d22bba15001f/rustls-0.23.26/src/crypto/tls13.rs
Line
Count
Source (jump to first uncovered line)
1
use alloc::boxed::Box;
2
use alloc::vec::Vec;
3
4
use zeroize::Zeroize;
5
6
use super::{ActiveKeyExchange, hmac};
7
use crate::error::Error;
8
use crate::version::TLS13;
9
10
/// Implementation of `HkdfExpander` via `hmac::Key`.
11
pub struct HkdfExpanderUsingHmac(Box<dyn hmac::Key>);
12
13
impl HkdfExpanderUsingHmac {
14
0
    fn expand_unchecked(&self, info: &[&[u8]], output: &mut [u8]) {
15
0
        let mut term = hmac::Tag::new(b"");
16
17
0
        for (n, chunk) in output
18
0
            .chunks_mut(self.0.tag_len())
19
0
            .enumerate()
20
0
        {
21
0
            term = self
22
0
                .0
23
0
                .sign_concat(term.as_ref(), info, &[(n + 1) as u8]);
24
0
            chunk.copy_from_slice(&term.as_ref()[..chunk.len()]);
25
0
        }
26
0
    }
27
}
28
29
impl HkdfExpander for HkdfExpanderUsingHmac {
30
0
    fn expand_slice(&self, info: &[&[u8]], output: &mut [u8]) -> Result<(), OutputLengthError> {
31
0
        if output.len() > 255 * self.0.tag_len() {
32
0
            return Err(OutputLengthError);
33
0
        }
34
0
35
0
        self.expand_unchecked(info, output);
36
0
        Ok(())
37
0
    }
38
39
0
    fn expand_block(&self, info: &[&[u8]]) -> OkmBlock {
40
0
        let mut tag = [0u8; hmac::Tag::MAX_LEN];
41
0
        let reduced_tag = &mut tag[..self.0.tag_len()];
42
0
        self.expand_unchecked(info, reduced_tag);
43
0
        OkmBlock::new(reduced_tag)
44
0
    }
45
46
0
    fn hash_len(&self) -> usize {
47
0
        self.0.tag_len()
48
0
    }
49
}
50
51
/// Implementation of `Hkdf` (and thence `HkdfExpander`) via `hmac::Hmac`.
52
pub struct HkdfUsingHmac<'a>(pub &'a dyn hmac::Hmac);
53
54
impl Hkdf for HkdfUsingHmac<'_> {
55
0
    fn extract_from_zero_ikm(&self, salt: Option<&[u8]>) -> Box<dyn HkdfExpander> {
56
0
        let zeroes = [0u8; hmac::Tag::MAX_LEN];
57
0
        Box::new(HkdfExpanderUsingHmac(self.0.with_key(
58
0
            &self.extract_prk_from_secret(salt, &zeroes[..self.0.hash_output_len()]),
59
0
        )))
60
0
    }
61
62
0
    fn extract_from_secret(&self, salt: Option<&[u8]>, secret: &[u8]) -> Box<dyn HkdfExpander> {
63
0
        Box::new(HkdfExpanderUsingHmac(
64
0
            self.0
65
0
                .with_key(&self.extract_prk_from_secret(salt, secret)),
66
0
        ))
67
0
    }
68
69
0
    fn expander_for_okm(&self, okm: &OkmBlock) -> Box<dyn HkdfExpander> {
70
0
        Box::new(HkdfExpanderUsingHmac(self.0.with_key(okm.as_ref())))
71
0
    }
72
73
0
    fn hmac_sign(&self, key: &OkmBlock, message: &[u8]) -> hmac::Tag {
74
0
        self.0
75
0
            .with_key(key.as_ref())
76
0
            .sign(&[message])
77
0
    }
78
}
79
80
impl HkdfPrkExtract for HkdfUsingHmac<'_> {
81
0
    fn extract_prk_from_secret(&self, salt: Option<&[u8]>, secret: &[u8]) -> Vec<u8> {
82
0
        let zeroes = [0u8; hmac::Tag::MAX_LEN];
83
0
        let salt = match salt {
84
0
            Some(salt) => salt,
85
0
            None => &zeroes[..self.0.hash_output_len()],
86
        };
87
0
        self.0
88
0
            .with_key(salt)
89
0
            .sign(&[secret])
90
0
            .as_ref()
91
0
            .to_vec()
92
0
    }
93
}
94
95
/// Implementation of `HKDF-Expand` with an implicitly stored and immutable `PRK`.
96
pub 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`].
134
pub 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
0
    fn extract_from_kx_shared_secret(
155
0
        &self,
156
0
        salt: Option<&[u8]>,
157
0
        kx: Box<dyn ActiveKeyExchange>,
158
0
        peer_pub_key: &[u8],
159
0
    ) -> Result<Box<dyn HkdfExpander>, Error> {
160
0
        Ok(self.extract_from_secret(
161
0
            salt,
162
0
            kx.complete_for_tls_version(peer_pub_key, &TLS13)?
163
0
                .secret_bytes(),
164
        ))
165
0
    }
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
0
    fn fips(&self) -> bool {
181
0
        false
182
0
    }
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.
194
pub(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.
213
0
pub fn expand<T, const N: usize>(expander: &dyn HkdfExpander, info: &[&[u8]]) -> T
214
0
where
215
0
    T: From<[u8; N]>,
216
0
{
217
0
    let mut output = [0u8; N];
218
0
    expander
219
0
        .expand_slice(info, &mut output)
220
0
        .expect("expand type parameter T is too large");
221
0
    T::from(output)
222
0
}
Unexecuted instantiation: rustls::crypto::tls13::expand::<[u8; 8], 8>
Unexecuted instantiation: rustls::crypto::tls13::expand::<rustls::crypto::cipher::Iv, 12>
Unexecuted instantiation: rustls::crypto::tls13::expand::<rustls::crypto::cipher::AeadKey, 32>
223
224
/// Output key material from HKDF, as a value type.
225
#[derive(Clone)]
226
pub struct OkmBlock {
227
    buf: [u8; Self::MAX_LEN],
228
    used: usize,
229
}
230
231
impl 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
0
    pub fn new(bytes: &[u8]) -> Self {
236
0
        let mut tag = Self {
237
0
            buf: [0u8; Self::MAX_LEN],
238
0
            used: bytes.len(),
239
0
        };
240
0
        tag.buf[..bytes.len()].copy_from_slice(bytes);
241
0
        tag
242
0
    }
243
244
    /// Maximum supported HMAC tag size: supports up to SHA512.
245
    pub const MAX_LEN: usize = 64;
246
}
247
248
impl Drop for OkmBlock {
249
0
    fn drop(&mut self) {
250
0
        self.buf.zeroize();
251
0
    }
252
}
253
254
impl AsRef<[u8]> for OkmBlock {
255
0
    fn as_ref(&self) -> &[u8] {
256
0
        &self.buf[..self.used]
257
0
    }
258
}
259
260
/// An error type used for `HkdfExpander::expand_slice` when
261
/// the slice exceeds the maximum HKDF output length.
262
#[derive(Debug)]
263
pub struct OutputLengthError;
264
265
#[cfg(all(test, feature = "ring"))]
266
mod tests {
267
    use std::prelude::v1::*;
268
269
    use super::{Hkdf, HkdfUsingHmac, expand};
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!(
401
            hkdf.extract_from_secret(None, ikm)
402
                .expand_slice(info, &mut output)
403
                .is_err()
404
        );
405
    }
406
}