Coverage Report

Created: 2026-02-14 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/aws-lc-rs-1.15.4/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 const HKDF_SHA1_FOR_LEGACY_USE_ONLY: Algorithm = Algorithm(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY);
63
64
/// HKDF using HMAC-SHA-256.
65
pub const HKDF_SHA256: Algorithm = Algorithm(hmac::HMAC_SHA256);
66
67
/// HKDF using HMAC-SHA-384.
68
pub const HKDF_SHA384: Algorithm = Algorithm(hmac::HMAC_SHA384);
69
70
/// HKDF using HMAC-SHA-512.
71
pub const HKDF_SHA512: Algorithm = Algorithm(hmac::HMAC_SHA512);
72
73
/// General Info length's for HKDF don't normally exceed 256 bits.
74
/// We set the default capacity to a value larger than should be needed
75
/// so that the value passed to |`HKDF_expand`| is only allocated once.
76
const HKDF_INFO_DEFAULT_CAPACITY_LEN: usize = 80;
77
78
/// The maximum output size of a PRK computed by |`HKDF_extract`| is the maximum digest
79
/// size that can be outputted by *AWS-LC*.
80
const MAX_HKDF_PRK_LEN: usize = digest::MAX_OUTPUT_LEN;
81
82
impl KeyType for Algorithm {
83
0
    fn len(&self) -> usize {
84
0
        self.0.digest_algorithm().output_len
85
0
    }
86
}
87
88
/// A salt for HKDF operations.
89
pub struct Salt {
90
    algorithm: Algorithm,
91
    bytes: Arc<[u8]>,
92
}
93
94
#[allow(clippy::missing_fields_in_debug)]
95
impl fmt::Debug for Salt {
96
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97
0
        f.debug_struct("hkdf::Salt")
98
0
            .field("algorithm", &self.algorithm.0)
99
0
            .finish()
100
0
    }
101
}
102
103
impl Salt {
104
    /// Constructs a new `Salt` with the given value based on the given digest
105
    /// algorithm.
106
    ///
107
    /// Constructing a `Salt` is relatively expensive so it is good to reuse a
108
    /// `Salt` object instead of re-constructing `Salt`s with the same value.
109
    ///
110
    // # FIPS
111
    // The following conditions must be met:
112
    // * Algorithm is one of the following:
113
    //   * `HKDF_SHA1_FOR_LEGACY_USE_ONLY`
114
    //   * `HKDF_SHA256`
115
    //   * `HKDF_SHA384`
116
    //   * `HKDF_SHA512`
117
    // * `value.len() > 0` is true
118
    //
119
    /// # Panics
120
    /// `new` panics if salt creation fails
121
    #[must_use]
122
0
    pub fn new(algorithm: Algorithm, value: &[u8]) -> Self {
123
0
        Self {
124
0
            algorithm,
125
0
            bytes: Arc::from(value),
126
0
        }
127
0
    }
128
129
    /// The [HKDF-Extract] operation.
130
    ///
131
    /// [HKDF-Extract]: https://tools.ietf.org/html/rfc5869#section-2.2
132
    ///
133
    /// # Panics
134
    /// Panics if the extract operation is unable to be performed
135
    #[inline]
136
    #[must_use]
137
0
    pub fn extract(&self, secret: &[u8]) -> Prk {
138
0
        Prk {
139
0
            algorithm: self.algorithm,
140
0
            mode: PrkMode::ExtractExpand {
141
0
                secret: Arc::new(ZeroizeBoxSlice::from(secret)),
142
0
                salt: Arc::clone(&self.bytes),
143
0
            },
144
0
        }
145
0
    }
146
147
    /// The algorithm used to derive this salt.
148
    #[inline]
149
    #[must_use]
150
0
    pub fn algorithm(&self) -> Algorithm {
151
0
        Algorithm(self.algorithm.hmac_algorithm())
152
0
    }
153
}
154
155
impl From<Okm<'_, Algorithm>> for Salt {
156
0
    fn from(okm: Okm<'_, Algorithm>) -> Self {
157
0
        let algorithm = okm.prk.algorithm;
158
0
        let salt_len = okm.len().len();
159
0
        let mut salt_bytes = vec![0u8; salt_len];
160
0
        okm.fill(&mut salt_bytes).unwrap();
161
0
        Self {
162
0
            algorithm,
163
0
            bytes: Arc::from(salt_bytes.as_slice()),
164
0
        }
165
0
    }
166
}
167
168
/// The length of the OKM (Output Keying Material) for a `Prk::expand()` call.
169
#[allow(clippy::len_without_is_empty)]
170
pub trait KeyType {
171
    /// The length that `Prk::expand()` should expand its input to.
172
    fn len(&self) -> usize;
173
}
174
175
#[derive(Clone)]
176
enum PrkMode {
177
    Expand {
178
        key_bytes: [u8; MAX_HKDF_PRK_LEN],
179
        key_len: usize,
180
    },
181
    ExtractExpand {
182
        secret: Arc<ZeroizeBoxSlice<u8>>,
183
        salt: Arc<[u8]>,
184
    },
185
}
186
187
impl PrkMode {
188
0
    fn fill(&self, algorithm: Algorithm, out: &mut [u8], info: &[u8]) -> Result<(), Unspecified> {
189
0
        let digest = digest::match_digest_type(&algorithm.0.digest_algorithm().id).as_const_ptr();
190
191
0
        match &self {
192
0
            PrkMode::Expand { key_bytes, key_len } => unsafe {
193
0
                if 1 != indicator_check!(HKDF_expand(
194
0
                    out.as_mut_ptr(),
195
0
                    out.len(),
196
0
                    digest,
197
0
                    key_bytes.as_ptr(),
198
0
                    *key_len,
199
0
                    info.as_ptr(),
200
0
                    info.len(),
201
0
                )) {
202
0
                    return Err(Unspecified);
203
0
                }
204
            },
205
0
            PrkMode::ExtractExpand { secret, salt } => {
206
0
                if 1 != indicator_check!(unsafe {
207
0
                    HKDF(
208
0
                        out.as_mut_ptr(),
209
0
                        out.len(),
210
0
                        digest,
211
0
                        secret.as_ptr(),
212
0
                        secret.len(),
213
0
                        salt.as_ptr(),
214
0
                        salt.len(),
215
0
                        info.as_ptr(),
216
0
                        info.len(),
217
0
                    )
218
0
                }) {
219
0
                    return Err(Unspecified);
220
0
                }
221
            }
222
        }
223
224
0
        Ok(())
225
0
    }
226
}
227
228
impl fmt::Debug for PrkMode {
229
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230
0
        match self {
231
0
            Self::Expand { .. } => f.debug_struct("Expand").finish_non_exhaustive(),
232
0
            Self::ExtractExpand { .. } => f.debug_struct("ExtractExpand").finish_non_exhaustive(),
233
        }
234
0
    }
235
}
236
237
struct ZeroizeBoxSlice<T: Zeroize>(Box<[T]>);
238
239
impl<T: Zeroize> core::ops::Deref for ZeroizeBoxSlice<T> {
240
    type Target = [T];
241
242
0
    fn deref(&self) -> &Self::Target {
243
0
        &self.0
244
0
    }
245
}
246
247
impl<T: Clone + Zeroize> From<&[T]> for ZeroizeBoxSlice<T> {
248
0
    fn from(value: &[T]) -> Self {
249
0
        Self(Vec::from(value).into_boxed_slice())
250
0
    }
251
}
252
253
impl<T: Zeroize> Drop for ZeroizeBoxSlice<T> {
254
0
    fn drop(&mut self) {
255
0
        self.0.zeroize();
256
0
    }
257
}
258
259
/// A HKDF PRK (pseudorandom key).
260
#[derive(Clone)]
261
pub struct Prk {
262
    algorithm: Algorithm,
263
    mode: PrkMode,
264
}
265
266
impl Drop for Prk {
267
0
    fn drop(&mut self) {
268
        if let PrkMode::Expand {
269
0
            ref mut key_bytes, ..
270
0
        } = self.mode
271
0
        {
272
0
            key_bytes.zeroize();
273
0
        }
274
0
    }
275
}
276
277
#[allow(clippy::missing_fields_in_debug)]
278
impl fmt::Debug for Prk {
279
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280
0
        f.debug_struct("hkdf::Prk")
281
0
            .field("algorithm", &self.algorithm.0)
282
0
            .field("mode", &self.mode)
283
0
            .finish()
284
0
    }
285
}
286
287
impl Prk {
288
    /// Construct a new `Prk` directly with the given value.
289
    ///
290
    /// Usually one can avoid using this. It is useful when the application
291
    /// intentionally wants to leak the PRK secret, e.g. to implement
292
    /// `SSLKEYLOGFILE` functionality.
293
    ///
294
    // # FIPS
295
    // The following conditions must be met:
296
    // * Algorithm is one of the following:
297
    //   * `HKDF_SHA1_FOR_LEGACY_USE_ONLY`
298
    //   * `HKDF_SHA256`
299
    //   * `HKDF_SHA384`
300
    //   * `HKDF_SHA512`
301
    // * The `info_len` from [`Prk::expand`] is non-zero.
302
    //
303
    /// # Panics
304
    /// Panics if the given Prk length exceeds the limit
305
    #[must_use]
306
0
    pub fn new_less_safe(algorithm: Algorithm, value: &[u8]) -> Self {
307
0
        Prk::try_new_less_safe(algorithm, value).expect("Prk length limit exceeded.")
308
0
    }
309
310
0
    fn try_new_less_safe(algorithm: Algorithm, value: &[u8]) -> Result<Prk, Unspecified> {
311
0
        let key_len = value.len();
312
0
        if key_len > MAX_HKDF_PRK_LEN {
313
0
            return Err(Unspecified);
314
0
        }
315
0
        let mut key_bytes = [0u8; MAX_HKDF_PRK_LEN];
316
0
        key_bytes[0..key_len].copy_from_slice(value);
317
0
        Ok(Self {
318
0
            algorithm,
319
0
            mode: PrkMode::Expand { key_bytes, key_len },
320
0
        })
321
0
    }
322
323
    /// The [HKDF-Expand] operation.
324
    ///
325
    /// [HKDF-Expand]: https://tools.ietf.org/html/rfc5869#section-2.3
326
    ///
327
    /// # Errors
328
    /// Returns `error::Unspecified` if:
329
    ///   * `len` is more than 255 times the digest algorithm's output length.
330
    // # FIPS
331
    // The following conditions must be met:
332
    // * `Prk` must be constructed using `Salt::extract` prior to calling
333
    // this method.
334
    // * After concatination of the `info` slices the resulting `[u8].len() > 0` is true.
335
    #[inline]
336
0
    pub fn expand<'a, L: KeyType>(
337
0
        &'a self,
338
0
        info: &'a [&'a [u8]],
339
0
        len: L,
340
0
    ) -> Result<Okm<'a, L>, Unspecified> {
341
0
        let len_cached = len.len();
342
0
        if len_cached > 255 * self.algorithm.0.digest_algorithm().output_len {
343
0
            return Err(Unspecified);
344
0
        }
345
0
        Ok(Okm {
346
0
            prk: self,
347
0
            info,
348
0
            len,
349
0
        })
350
0
    }
351
}
352
353
impl From<Okm<'_, Algorithm>> for Prk {
354
0
    fn from(okm: Okm<Algorithm>) -> Self {
355
0
        let algorithm = okm.len;
356
0
        let key_len = okm.len.len();
357
0
        let mut key_bytes = [0u8; MAX_HKDF_PRK_LEN];
358
0
        okm.fill(&mut key_bytes[0..key_len]).unwrap();
359
360
0
        Self {
361
0
            algorithm,
362
0
            mode: PrkMode::Expand { key_bytes, key_len },
363
0
        }
364
0
    }
365
}
366
367
/// An HKDF OKM (Output Keying Material)
368
///
369
/// Intentionally not `Clone` or `Copy` as an OKM is generally only safe to
370
/// use once.
371
pub struct Okm<'a, L: KeyType> {
372
    prk: &'a Prk,
373
    info: &'a [&'a [u8]],
374
    len: L,
375
}
376
377
impl<L: KeyType> fmt::Debug for Okm<'_, L> {
378
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
379
0
        f.debug_struct("hkdf::Okm").field("prk", &self.prk).finish()
380
0
    }
381
}
382
383
/// Concatenates info slices into a contiguous buffer for HKDF operations.
384
/// Uses stack allocation for typical cases, heap allocation for large info.
385
/// Info is public context data per RFC 5869, so no zeroization is needed.
386
#[inline]
387
0
fn concatenate_info<F, R>(info: &[&[u8]], f: F) -> R
388
0
where
389
0
    F: FnOnce(&[u8]) -> R,
390
{
391
0
    let info_len: usize = info.iter().map(|s| s.len()).sum();
Unexecuted instantiation: aws_lc_rs::hkdf::concatenate_info::<<aws_lc_rs::hkdf::Okm<aws_lc_rs::hkdf::Algorithm>>::fill::{closure#0}, core::result::Result<(), aws_lc_rs::error::Unspecified>>::{closure#0}
Unexecuted instantiation: aws_lc_rs::hkdf::concatenate_info::<<aws_lc_rs::hkdf::Okm<aws_lc_rs::hmac::Algorithm>>::fill::{closure#0}, core::result::Result<(), aws_lc_rs::error::Unspecified>>::{closure#0}
Unexecuted instantiation: aws_lc_rs::hkdf::concatenate_info::<<aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::Algorithm>>::fill::{closure#0}, core::result::Result<(), aws_lc_rs::error::Unspecified>>::{closure#0}
Unexecuted instantiation: aws_lc_rs::hkdf::concatenate_info::<<aws_lc_rs::hkdf::Okm<&aws_lc_rs::cipher::Algorithm>>::fill::{closure#0}, core::result::Result<(), aws_lc_rs::error::Unspecified>>::{closure#0}
Unexecuted instantiation: aws_lc_rs::hkdf::concatenate_info::<<aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::quic::Algorithm>>::fill::{closure#0}, core::result::Result<(), aws_lc_rs::error::Unspecified>>::{closure#0}
392
393
    // Info is public; no need to zeroize.
394
0
    if info_len <= HKDF_INFO_DEFAULT_CAPACITY_LEN {
395
        // Use stack buffer for typical case (avoids heap allocation)
396
0
        let mut stack_buf = [0u8; HKDF_INFO_DEFAULT_CAPACITY_LEN];
397
0
        let mut pos = 0;
398
0
        for &slice in info {
399
0
            stack_buf[pos..pos + slice.len()].copy_from_slice(slice);
400
0
            pos += slice.len();
401
0
        }
402
403
0
        f(&stack_buf[..info_len])
404
    } else {
405
        // Heap allocation for rare large info case
406
0
        let mut heap_buf = Vec::with_capacity(info_len);
407
0
        for &slice in info {
408
0
            heap_buf.extend_from_slice(slice);
409
0
        }
410
411
0
        f(&heap_buf)
412
    }
413
0
}
Unexecuted instantiation: aws_lc_rs::hkdf::concatenate_info::<<aws_lc_rs::hkdf::Okm<aws_lc_rs::hkdf::Algorithm>>::fill::{closure#0}, core::result::Result<(), aws_lc_rs::error::Unspecified>>
Unexecuted instantiation: aws_lc_rs::hkdf::concatenate_info::<<aws_lc_rs::hkdf::Okm<aws_lc_rs::hmac::Algorithm>>::fill::{closure#0}, core::result::Result<(), aws_lc_rs::error::Unspecified>>
Unexecuted instantiation: aws_lc_rs::hkdf::concatenate_info::<<aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::Algorithm>>::fill::{closure#0}, core::result::Result<(), aws_lc_rs::error::Unspecified>>
Unexecuted instantiation: aws_lc_rs::hkdf::concatenate_info::<<aws_lc_rs::hkdf::Okm<&aws_lc_rs::cipher::Algorithm>>::fill::{closure#0}, core::result::Result<(), aws_lc_rs::error::Unspecified>>
Unexecuted instantiation: aws_lc_rs::hkdf::concatenate_info::<<aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::quic::Algorithm>>::fill::{closure#0}, core::result::Result<(), aws_lc_rs::error::Unspecified>>
414
415
impl<L: KeyType> Okm<'_, L> {
416
    /// The `OkmLength` given to `Prk::expand()`.
417
    #[inline]
418
0
    pub fn len(&self) -> &L {
419
0
        &self.len
420
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
421
422
    /// Fills `out` with the output of the HKDF-Expand operation for the given
423
    /// inputs.
424
    ///
425
    // # FIPS
426
    // The following conditions must be met:
427
    // * Algorithm is one of the following:
428
    //    * `HKDF_SHA1_FOR_LEGACY_USE_ONLY`
429
    //    * `HKDF_SHA256`
430
    //    * `HKDF_SHA384`
431
    //    * `HKDF_SHA512`
432
    // * The [`Okm`] was constructed from a [`Prk`] created with [`Salt::extract`] and:
433
    //    * The `value.len()` passed to [`Salt::new`] was non-zero.
434
    //    * The `info_len` from [`Prk::expand`] was non-zero.
435
    //
436
    /// # Errors
437
    /// `error::Unspecified` if the requested output length differs from the length specified by
438
    /// `L: KeyType`.
439
    #[inline]
440
0
    pub fn fill(self, out: &mut [u8]) -> Result<(), Unspecified> {
441
0
        if out.len() != self.len.len() {
442
0
            return Err(Unspecified);
443
0
        }
444
445
0
        concatenate_info(self.info, |info_bytes| {
446
0
            self.prk.mode.fill(self.prk.algorithm, out, info_bytes)
447
0
        })
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<aws_lc_rs::hkdf::Algorithm>>::fill::{closure#0}
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<aws_lc_rs::hmac::Algorithm>>::fill::{closure#0}
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::Algorithm>>::fill::{closure#0}
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::cipher::Algorithm>>::fill::{closure#0}
Unexecuted instantiation: <aws_lc_rs::hkdf::Okm<&aws_lc_rs::aead::quic::Algorithm>>::fill::{closure#0}
448
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
449
}
450
451
#[cfg(test)]
452
mod tests {
453
    use crate::hkdf::{Salt, HKDF_SHA256, HKDF_SHA384};
454
455
    #[cfg(feature = "fips")]
456
    mod fips;
457
458
    #[test]
459
    fn hkdf_coverage() {
460
        // Something would have gone horribly wrong for this to not pass, but we test this so our
461
        // coverage reports will look better.
462
        assert_ne!(HKDF_SHA256, HKDF_SHA384);
463
        assert_eq!("Algorithm(Algorithm(SHA256))", format!("{HKDF_SHA256:?}"));
464
    }
465
466
    #[test]
467
    fn test_debug() {
468
        const SALT: &[u8; 32] = &[
469
            29, 113, 120, 243, 11, 202, 39, 222, 206, 81, 163, 184, 122, 153, 52, 192, 98, 195,
470
            240, 32, 34, 19, 160, 128, 178, 111, 97, 232, 113, 101, 221, 143,
471
        ];
472
        const SECRET1: &[u8; 32] = &[
473
            157, 191, 36, 107, 110, 131, 193, 6, 175, 226, 193, 3, 168, 133, 165, 181, 65, 120,
474
            194, 152, 31, 92, 37, 191, 73, 222, 41, 112, 207, 236, 196, 174,
475
        ];
476
477
        const INFO1: &[&[u8]] = &[
478
            &[
479
                2, 130, 61, 83, 192, 248, 63, 60, 211, 73, 169, 66, 101, 160, 196, 212, 250, 113,
480
            ],
481
            &[
482
                80, 46, 248, 123, 78, 204, 171, 178, 67, 204, 96, 27, 131, 24,
483
            ],
484
        ];
485
486
        let alg = HKDF_SHA256;
487
        let salt = Salt::new(alg, SALT);
488
        let prk = salt.extract(SECRET1);
489
        let okm = prk.expand(INFO1, alg).unwrap();
490
491
        assert_eq!(
492
            "hkdf::Salt { algorithm: Algorithm(SHA256) }",
493
            format!("{salt:?}")
494
        );
495
        assert_eq!(
496
            "hkdf::Prk { algorithm: Algorithm(SHA256), mode: ExtractExpand { .. } }",
497
            format!("{prk:?}")
498
        );
499
        assert_eq!(
500
            "hkdf::Okm { prk: hkdf::Prk { algorithm: Algorithm(SHA256), mode: ExtractExpand { .. } } }",
501
            format!("{okm:?}")
502
        );
503
    }
504
505
    #[test]
506
    fn test_long_salt() {
507
        // Test with a salt longer than the previous 80-byte limit
508
        let long_salt = vec![0x42u8; 100];
509
510
        // This should work now that we removed the MAX_HKDF_SALT_LEN restriction
511
        let salt = Salt::new(HKDF_SHA256, &long_salt);
512
513
        // Test the extract operation still works
514
        let secret = b"test secret key material";
515
        let prk = salt.extract(secret);
516
517
        // Test expand operation
518
        let info_data = b"test context info";
519
        let info = [info_data.as_slice()];
520
        let okm = prk.expand(&info, HKDF_SHA256).unwrap();
521
522
        // Fill output buffer
523
        let mut output = [0u8; 32];
524
        okm.fill(&mut output).unwrap();
525
526
        // Test with an even longer salt to demonstrate flexibility
527
        let very_long_salt = vec![0x55u8; 500];
528
        let very_long_salt_obj = Salt::new(HKDF_SHA256, &very_long_salt);
529
        let prk2 = very_long_salt_obj.extract(secret);
530
        let okm2 = prk2.expand(&info, HKDF_SHA256).unwrap();
531
        let mut output2 = [0u8; 32];
532
        okm2.fill(&mut output2).unwrap();
533
534
        // Verify outputs are different (they should be due to different salts)
535
        assert_ne!(output, output2);
536
    }
537
}