Coverage Report

Created: 2025-12-12 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/aws-lc-rs-1.14.1/src/hkdf.rs
Line
Count
Source
1
// Copyright 2015 Brian Smith.
2
// SPDX-License-Identifier: ISC
3
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4
// SPDX-License-Identifier: Apache-2.0 OR ISC
5
6
//! HMAC-based Extract-and-Expand Key Derivation Function.
7
//!
8
//! HKDF is specified in [RFC 5869].
9
//!
10
//! [RFC 5869]: https://tools.ietf.org/html/rfc5869
11
//!
12
//! # Example
13
//! ```
14
//! use aws_lc_rs::{aead, hkdf, hmac, rand};
15
//!
16
//! // Generate a (non-secret) salt value
17
//! let mut salt_bytes = [0u8; 32];
18
//! rand::fill(&mut salt_bytes).unwrap();
19
//!
20
//! // Extract pseudo-random key from secret keying materials
21
//! let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, &salt_bytes);
22
//! let pseudo_random_key = salt.extract(b"secret input keying material");
23
//!
24
//! // Derive HMAC key
25
//! let hmac_key_material = pseudo_random_key
26
//!     .expand(
27
//!         &[b"hmac contextual info"],
28
//!         hkdf::HKDF_SHA256.hmac_algorithm(),
29
//!     )
30
//!     .unwrap();
31
//! let hmac_key = hmac::Key::from(hmac_key_material);
32
//!
33
//! // Derive UnboundKey for AES-128-GCM
34
//! let aes_keying_material = pseudo_random_key
35
//!     .expand(&[b"aes contextual info"], &aead::AES_128_GCM)
36
//!     .unwrap();
37
//! let aead_unbound_key = aead::UnboundKey::from(aes_keying_material);
38
//! ```
39
40
use crate::aws_lc::{HKDF_expand, HKDF};
41
use crate::error::Unspecified;
42
use crate::fips::indicator_check;
43
use crate::{digest, hmac};
44
use alloc::sync::Arc;
45
use core::fmt;
46
use zeroize::Zeroize;
47
48
/// An HKDF algorithm.
49
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50
pub struct Algorithm(hmac::Algorithm);
51
52
impl Algorithm {
53
    /// The underlying HMAC algorithm.
54
    #[inline]
55
    #[must_use]
56
0
    pub fn hmac_algorithm(&self) -> hmac::Algorithm {
57
0
        self.0
58
0
    }
59
}
60
61
/// HKDF using HMAC-SHA-1. Obsolete.
62
pub static HKDF_SHA1_FOR_LEGACY_USE_ONLY: Algorithm =
63
    Algorithm(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY);
64
65
/// HKDF using HMAC-SHA-256.
66
pub static HKDF_SHA256: Algorithm = Algorithm(hmac::HMAC_SHA256);
67
68
/// HKDF using HMAC-SHA-384.
69
pub static HKDF_SHA384: Algorithm = Algorithm(hmac::HMAC_SHA384);
70
71
/// HKDF using HMAC-SHA-512.
72
pub static HKDF_SHA512: Algorithm = Algorithm(hmac::HMAC_SHA512);
73
74
/// General Salt length's for HKDF don't normally exceed 256 bits.
75
/// We set the limit to something tolerable, so that the Salt structure can be stack allocatable.
76
const MAX_HKDF_SALT_LEN: usize = 80;
77
78
/// General Info length's for HKDF don't normally exceed 256 bits.
79
/// We set the default capacity to a value larger than should be needed
80
/// so that the value passed to |`HKDF_expand`| is only allocated once.
81
const HKDF_INFO_DEFAULT_CAPACITY_LEN: usize = 300;
82
83
/// The maximum output size of a PRK computed by |`HKDF_extract`| is the maximum digest
84
/// size that can be outputted by *AWS-LC*.
85
const MAX_HKDF_PRK_LEN: usize = digest::MAX_OUTPUT_LEN;
86
87
impl KeyType for Algorithm {
88
0
    fn len(&self) -> usize {
89
0
        self.0.digest_algorithm().output_len
90
0
    }
91
}
92
93
/// A salt for HKDF operations.
94
pub struct Salt {
95
    algorithm: Algorithm,
96
    bytes: [u8; MAX_HKDF_SALT_LEN],
97
    len: usize,
98
}
99
100
#[allow(clippy::missing_fields_in_debug)]
101
impl fmt::Debug for Salt {
102
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103
0
        f.debug_struct("hkdf::Salt")
104
0
            .field("algorithm", &self.algorithm.0)
105
0
            .finish()
106
0
    }
107
}
108
109
impl Drop for Salt {
110
0
    fn drop(&mut self) {
111
0
        self.bytes.zeroize();
112
0
    }
113
}
114
115
impl Salt {
116
    /// Constructs a new `Salt` with the given value based on the given digest
117
    /// algorithm.
118
    ///
119
    /// Constructing a `Salt` is relatively expensive so it is good to reuse a
120
    /// `Salt` object instead of re-constructing `Salt`s with the same value.
121
    ///
122
    // # FIPS
123
    // The following conditions must be met:
124
    // * Algorithm is one of the following:
125
    //   * `HKDF_SHA1_FOR_LEGACY_USE_ONLY`
126
    //   * `HKDF_SHA256`
127
    //   * `HKDF_SHA384`
128
    //   * `HKDF_SHA512`
129
    // * `value.len() > 0` is true
130
    //
131
    /// # Panics
132
    /// `new` panics if the salt length exceeds the limit
133
    #[must_use]
134
0
    pub fn new(algorithm: Algorithm, value: &[u8]) -> Self {
135
0
        Salt::try_new(algorithm, value).expect("Salt length limit exceeded.")
136
0
    }
137
138
0
    fn try_new(algorithm: Algorithm, value: &[u8]) -> Result<Salt, Unspecified> {
139
0
        let salt_len = value.len();
140
0
        if salt_len > MAX_HKDF_SALT_LEN {
141
0
            return Err(Unspecified);
142
0
        }
143
0
        let mut salt_bytes = [0u8; MAX_HKDF_SALT_LEN];
144
0
        salt_bytes[0..salt_len].copy_from_slice(value);
145
0
        Ok(Self {
146
0
            algorithm,
147
0
            bytes: salt_bytes,
148
0
            len: salt_len,
149
0
        })
150
0
    }
151
152
    /// The [HKDF-Extract] operation.
153
    ///
154
    /// [HKDF-Extract]: https://tools.ietf.org/html/rfc5869#section-2.2
155
    ///
156
    /// # Panics
157
    /// Panics if the extract operation is unable to be performed
158
    #[inline]
159
    #[must_use]
160
0
    pub fn extract(&self, secret: &[u8]) -> Prk {
161
0
        Prk {
162
0
            algorithm: self.algorithm,
163
0
            mode: PrkMode::ExtractExpand {
164
0
                secret: Arc::from(ZeroizeBoxSlice::from(secret)),
165
0
                salt: self.bytes,
166
0
                salt_len: self.len,
167
0
            },
168
0
        }
169
0
    }
170
171
    /// The algorithm used to derive this salt.
172
    #[inline]
173
    #[must_use]
174
0
    pub fn algorithm(&self) -> Algorithm {
175
0
        Algorithm(self.algorithm.hmac_algorithm())
176
0
    }
177
}
178
179
#[allow(clippy::assertions_on_constants)]
180
const _: () = assert!(MAX_HKDF_PRK_LEN <= MAX_HKDF_SALT_LEN);
181
182
impl From<Okm<'_, Algorithm>> for Salt {
183
0
    fn from(okm: Okm<'_, Algorithm>) -> Self {
184
0
        let algorithm = okm.prk.algorithm;
185
0
        let mut salt_bytes = [0u8; MAX_HKDF_SALT_LEN];
186
0
        let salt_len = okm.len().len();
187
0
        okm.fill(&mut salt_bytes[..salt_len]).unwrap();
188
0
        Self {
189
0
            algorithm,
190
0
            bytes: salt_bytes,
191
0
            len: salt_len,
192
0
        }
193
0
    }
194
}
195
196
/// The length of the OKM (Output Keying Material) for a `Prk::expand()` call.
197
#[allow(clippy::len_without_is_empty)]
198
pub trait KeyType {
199
    /// The length that `Prk::expand()` should expand its input to.
200
    fn len(&self) -> usize;
201
}
202
203
#[derive(Clone)]
204
enum PrkMode {
205
    Expand {
206
        key_bytes: [u8; MAX_HKDF_PRK_LEN],
207
        key_len: usize,
208
    },
209
    ExtractExpand {
210
        secret: Arc<ZeroizeBoxSlice<u8>>,
211
        salt: [u8; MAX_HKDF_SALT_LEN],
212
        salt_len: usize,
213
    },
214
}
215
216
impl PrkMode {
217
0
    fn fill(&self, algorithm: Algorithm, out: &mut [u8], info: &[u8]) -> Result<(), Unspecified> {
218
0
        let digest = *digest::match_digest_type(&algorithm.0.digest_algorithm().id);
219
220
0
        match &self {
221
0
            PrkMode::Expand { key_bytes, key_len } => unsafe {
222
0
                if 1 != indicator_check!(HKDF_expand(
223
0
                    out.as_mut_ptr(),
224
0
                    out.len(),
225
0
                    digest,
226
0
                    key_bytes.as_ptr(),
227
0
                    *key_len,
228
0
                    info.as_ptr(),
229
0
                    info.len(),
230
0
                )) {
231
0
                    return Err(Unspecified);
232
0
                }
233
            },
234
            PrkMode::ExtractExpand {
235
0
                secret,
236
0
                salt,
237
0
                salt_len,
238
            } => {
239
0
                if 1 != indicator_check!(unsafe {
240
0
                    HKDF(
241
0
                        out.as_mut_ptr(),
242
0
                        out.len(),
243
0
                        digest,
244
0
                        secret.as_ptr(),
245
0
                        secret.len(),
246
0
                        salt.as_ptr(),
247
0
                        *salt_len,
248
0
                        info.as_ptr(),
249
0
                        info.len(),
250
0
                    )
251
0
                }) {
252
0
                    return Err(Unspecified);
253
0
                }
254
            }
255
        }
256
257
0
        Ok(())
258
0
    }
259
}
260
261
impl fmt::Debug for PrkMode {
262
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263
0
        match self {
264
0
            Self::Expand { .. } => f.debug_struct("Expand").finish_non_exhaustive(),
265
0
            Self::ExtractExpand { .. } => f.debug_struct("ExtractExpand").finish_non_exhaustive(),
266
        }
267
0
    }
268
}
269
270
struct ZeroizeBoxSlice<T: Zeroize>(Box<[T]>);
271
272
impl<T: Zeroize> core::ops::Deref for ZeroizeBoxSlice<T> {
273
    type Target = [T];
274
275
0
    fn deref(&self) -> &Self::Target {
276
0
        &self.0
277
0
    }
278
}
279
280
impl<T: Clone + Zeroize> From<&[T]> for ZeroizeBoxSlice<T> {
281
0
    fn from(value: &[T]) -> Self {
282
0
        Self(Vec::from(value).into_boxed_slice())
283
0
    }
284
}
285
286
impl<T: Zeroize> Drop for ZeroizeBoxSlice<T> {
287
0
    fn drop(&mut self) {
288
0
        self.0.zeroize();
289
0
    }
290
}
291
292
/// A HKDF PRK (pseudorandom key).
293
#[derive(Clone)]
294
pub struct Prk {
295
    algorithm: Algorithm,
296
    mode: PrkMode,
297
}
298
299
#[allow(clippy::missing_fields_in_debug)]
300
impl fmt::Debug for Prk {
301
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302
0
        f.debug_struct("hkdf::Prk")
303
0
            .field("algorithm", &self.algorithm.0)
304
0
            .field("mode", &self.mode)
305
0
            .finish()
306
0
    }
307
}
308
309
impl Prk {
310
    /// Construct a new `Prk` directly with the given value.
311
    ///
312
    /// Usually one can avoid using this. It is useful when the application
313
    /// intentionally wants to leak the PRK secret, e.g. to implement
314
    /// `SSLKEYLOGFILE` functionality.
315
    ///
316
    // # FIPS
317
    // This function must not be used.
318
    //
319
    // See [`Salt::extract`].
320
    //
321
    /// # Panics
322
    /// Panics if the given Prk length exceeds the limit
323
    #[must_use]
324
0
    pub fn new_less_safe(algorithm: Algorithm, value: &[u8]) -> Self {
325
0
        Prk::try_new_less_safe(algorithm, value).expect("Prk length limit exceeded.")
326
0
    }
327
328
0
    fn try_new_less_safe(algorithm: Algorithm, value: &[u8]) -> Result<Prk, Unspecified> {
329
0
        let key_len = value.len();
330
0
        if key_len > MAX_HKDF_PRK_LEN {
331
0
            return Err(Unspecified);
332
0
        }
333
0
        let mut key_bytes = [0u8; MAX_HKDF_PRK_LEN];
334
0
        key_bytes[0..key_len].copy_from_slice(value);
335
0
        Ok(Self {
336
0
            algorithm,
337
0
            mode: PrkMode::Expand { key_bytes, key_len },
338
0
        })
339
0
    }
340
341
    /// The [HKDF-Expand] operation.
342
    ///
343
    /// [HKDF-Expand]: https://tools.ietf.org/html/rfc5869#section-2.3
344
    ///
345
    /// # Errors
346
    /// Returns `error::Unspecified` if:
347
    ///   * `len` is more than 255 times the digest algorithm's output length.
348
    // # FIPS
349
    // The following conditions must be met:
350
    // * `Prk` must be constructed using `Salt::extract` prior to calling
351
    // this method.
352
    // * After concatination of the `info` slices the resulting `[u8].len() > 0` is true.
353
    #[inline]
354
0
    pub fn expand<'a, L: KeyType>(
355
0
        &'a self,
356
0
        info: &'a [&'a [u8]],
357
0
        len: L,
358
0
    ) -> Result<Okm<'a, L>, Unspecified> {
359
0
        let len_cached = len.len();
360
0
        if len_cached > 255 * self.algorithm.0.digest_algorithm().output_len {
361
0
            return Err(Unspecified);
362
0
        }
363
0
        let mut info_bytes: Vec<u8> = Vec::with_capacity(HKDF_INFO_DEFAULT_CAPACITY_LEN);
364
0
        let mut info_len = 0;
365
0
        for &byte_ary in info {
366
0
            info_bytes.extend_from_slice(byte_ary);
367
0
            info_len += byte_ary.len();
368
0
        }
369
0
        let info_bytes = info_bytes.into_boxed_slice();
370
0
        Ok(Okm {
371
0
            prk: self,
372
0
            info_bytes,
373
0
            info_len,
374
0
            len,
375
0
        })
376
0
    }
377
}
378
379
impl From<Okm<'_, Algorithm>> for Prk {
380
0
    fn from(okm: Okm<Algorithm>) -> Self {
381
0
        let algorithm = okm.len;
382
0
        let key_len = okm.len.len();
383
0
        let mut key_bytes = [0u8; MAX_HKDF_PRK_LEN];
384
0
        okm.fill(&mut key_bytes[0..key_len]).unwrap();
385
386
0
        Self {
387
0
            algorithm,
388
0
            mode: PrkMode::Expand { key_bytes, key_len },
389
0
        }
390
0
    }
391
}
392
393
/// An HKDF OKM (Output Keying Material)
394
///
395
/// Intentionally not `Clone` or `Copy` as an OKM is generally only safe to
396
/// use once.
397
pub struct Okm<'a, L: KeyType> {
398
    prk: &'a Prk,
399
    info_bytes: Box<[u8]>,
400
    info_len: usize,
401
    len: L,
402
}
403
404
impl<L: KeyType> fmt::Debug for Okm<'_, L> {
405
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
406
0
        f.debug_struct("hkdf::Okm").field("prk", &self.prk).finish()
407
0
    }
408
}
409
410
impl<L: KeyType> Drop for Okm<'_, L> {
411
0
    fn drop(&mut self) {
412
0
        self.info_bytes.zeroize();
413
0
    }
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<aws_lc_rs::hkdf::Algorithm> as core::ops::drop::Drop>::drop
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<aws_lc_rs::hmac::Algorithm> as core::ops::drop::Drop>::drop
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::Algorithm> as core::ops::drop::Drop>::drop
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::cipher::Algorithm> as core::ops::drop::Drop>::drop
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::quic::Algorithm> as core::ops::drop::Drop>::drop
414
}
415
416
impl<L: KeyType> Okm<'_, L> {
417
    /// The `OkmLength` given to `Prk::expand()`.
418
    #[inline]
419
0
    pub fn len(&self) -> &L {
420
0
        &self.len
421
0
    }
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<aws_lc_rs::hkdf::Algorithm>>::len
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<aws_lc_rs::hmac::Algorithm>>::len
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::Algorithm>>::len
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::cipher::Algorithm>>::len
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::quic::Algorithm>>::len
422
423
    /// Fills `out` with the output of the HKDF-Expand operation for the given
424
    /// inputs.
425
    ///
426
    // # FIPS
427
    // The following conditions must be met:
428
    // * Algorithm is one of the following:
429
    //    * `HKDF_SHA1_FOR_LEGACY_USE_ONLY`
430
    //    * `HKDF_SHA256`
431
    //    * `HKDF_SHA384`
432
    //    * `HKDF_SHA512`
433
    // * The [`Okm`] was constructed from a [`Prk`] created with [`Salt::extract`] and:
434
    //    * The `value.len()` passed to [`Salt::new`] was non-zero.
435
    //    * The `info_len` from [`Prk::expand`] was non-zero.
436
    //
437
    /// # Errors
438
    /// `error::Unspecified` if the requested output length differs from the length specified by
439
    /// `L: KeyType`.
440
    #[inline]
441
0
    pub fn fill(self, out: &mut [u8]) -> Result<(), Unspecified> {
442
0
        if out.len() != self.len.len() {
443
0
            return Err(Unspecified);
444
0
        }
445
446
0
        self.prk
447
0
            .mode
448
0
            .fill(self.prk.algorithm, out, &self.info_bytes[..self.info_len])?;
449
450
0
        Ok(())
451
0
    }
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<aws_lc_rs::hkdf::Algorithm>>::fill
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<aws_lc_rs::hmac::Algorithm>>::fill
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::Algorithm>>::fill
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::cipher::Algorithm>>::fill
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::quic::Algorithm>>::fill
452
}
453
454
#[cfg(test)]
455
mod tests {
456
    use crate::hkdf::{Salt, HKDF_SHA256, HKDF_SHA384};
457
458
    #[cfg(feature = "fips")]
459
    mod fips;
460
461
    #[test]
462
    fn hkdf_coverage() {
463
        // Something would have gone horribly wrong for this to not pass, but we test this so our
464
        // coverage reports will look better.
465
        assert_ne!(HKDF_SHA256, HKDF_SHA384);
466
        assert_eq!("Algorithm(Algorithm(SHA256))", format!("{HKDF_SHA256:?}"));
467
    }
468
469
    #[test]
470
    fn test_debug() {
471
        const SALT: &[u8; 32] = &[
472
            29, 113, 120, 243, 11, 202, 39, 222, 206, 81, 163, 184, 122, 153, 52, 192, 98, 195,
473
            240, 32, 34, 19, 160, 128, 178, 111, 97, 232, 113, 101, 221, 143,
474
        ];
475
        const SECRET1: &[u8; 32] = &[
476
            157, 191, 36, 107, 110, 131, 193, 6, 175, 226, 193, 3, 168, 133, 165, 181, 65, 120,
477
            194, 152, 31, 92, 37, 191, 73, 222, 41, 112, 207, 236, 196, 174,
478
        ];
479
480
        const INFO1: &[&[u8]] = &[
481
            &[
482
                2, 130, 61, 83, 192, 248, 63, 60, 211, 73, 169, 66, 101, 160, 196, 212, 250, 113,
483
            ],
484
            &[
485
                80, 46, 248, 123, 78, 204, 171, 178, 67, 204, 96, 27, 131, 24,
486
            ],
487
        ];
488
489
        let alg = HKDF_SHA256;
490
        let salt = Salt::new(alg, SALT);
491
        let prk = salt.extract(SECRET1);
492
        let okm = prk.expand(INFO1, alg).unwrap();
493
494
        assert_eq!(
495
            "hkdf::Salt { algorithm: Algorithm(SHA256) }",
496
            format!("{salt:?}")
497
        );
498
        assert_eq!(
499
            "hkdf::Prk { algorithm: Algorithm(SHA256), mode: ExtractExpand { .. } }",
500
            format!("{prk:?}")
501
        );
502
        assert_eq!(
503
            "hkdf::Okm { prk: hkdf::Prk { algorithm: Algorithm(SHA256), mode: ExtractExpand { .. } } }",
504
            format!("{okm:?}")
505
        );
506
    }
507
}