Skip to main content

bouncycastle_hkdf/
lib.rs

1//! HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as per RFC5859, as allowed by
2//! NIST SP 800-56Cr2.
3//!
4//! # Usage
5//!
6//! Since HKDF uses `HMAC<HASH>` as its underlying primitive, most of what is said in the [`HMAC`] crate docs
7//! about instantiating HMAC objects applies here as well. Unlike HMAC, an HKDF object is created without
8//! an initial key, and will self-initialize the internal HMAC object as part of the [`HKDF::extract`] phase.
9//!
10//!
11//! # Examples
12//! ## Constructing an object
13//!
14//! HMAC objects can be constructed with any underlying hash function that implements [`Hash`].
15//! Type aliases are provided for the common HKDF-HASH algorithms.
16//!
17//! The following object instantiations are equivalent:
18//!
19//! ```
20//! use bouncycastle_hkdf::HKDF_SHA256;
21//!
22//! let hkdf = HKDF_SHA256::new();
23//! ```
24//! and
25//! ```
26//! use bouncycastle_hkdf::HKDF;
27//! use bouncycastle_sha2::SHA256;
28//!
29//! let hkdf = HKDF::<SHA256>::new();
30//! ```
31//!
32//! ## Deriving a key via the [`KDF`] trait
33//! Being a Key Derivation Function (KDF), the objective of HKDF is to take input key material which is not
34//! directly usable for its intended purpose and transform into a suitable output key.
35//! Typically, this takes one or both of the following forms:
36//!
37//! * Starting with a seed and mixing in additional input to diversify the output key (ie make it unique). An example of this would be starting with a secret seed and mixing in a public ID or URL to generate keys which are unique per URL.
38//! * Starting with a full-entropy seed which is at the correct security level for the application, but which is not long enough. An example could be starting with a 128-bit seed and mixing it with the strings "read" and "write" to produce one AES-128 key for each of the two directions of a communication channel.
39//!
40//! The simplest usage is via the one-shot functions provided by the [`KDF`] trait.
41//!
42//! ```
43//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
44//! use bouncycastle_core::traits::{KDF };
45//! use bouncycastle_hkdf::HKDF_SHA256;
46//!
47//! let key = KeyMaterial256::from_bytes_as_type(
48//!             b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
49//!             KeyType::Seed).unwrap();
50//!
51//! let hkdf = HKDF_SHA256::new();
52//! let key = hkdf.derive_key(&key, b"extra input").unwrap();
53//! ```
54//!
55//! [`KDF::derive_key`] will produce a key the same length as the underlying hash function.
56//! Longer output can be requested by instead using [`KDF::derive_key_out`] and providing a larger output buffer,
57//! which will be filled.
58//!
59//! As with other uses of [`KeyMaterialTrait`], the [`KDF::derive_key`] function will track the entropy of the input
60//! key material, and will set the entropy of the output key material accordingly.
61//!
62//! The [`KDF`] trait also provides the [`KDF::derive_key_from_multiple`] and [`KDF::derive_key_from_multiple_out`]
63//! functions, which allows for multiple inputs to be mixed into a single output key, and which allows
64//! for some advanced control of the underlying HKDF primitive.
65//!
66//!
67//! ## HKDF Extract-and-Expand
68//!
69//! The HKDF algorithm defined in RFC5896 and SP 800-56Cr2 is a two-step KDF, broken into an Extract step
70//! which essentially absorbs entropy from the input key material,
71//! and an Expand step which produces the output key material of any requested size.
72//! This interface is essentially a pre-cursor to the [`XOF`] API which was introduced with SHA3; the main
73//! difference being that HKDF-Expand needs to be told up-front how much output to produce, whereas XOFs
74//! can stream output as needed.
75//!
76//! Naturally, the full two-step HKDF-Extract and HKDF-Expand interface is provided by the [`HKDF`] struct,
77//! and exposes additional HKDF-specific parameters beyond what is exposed by the functions of the [`KDF`] trait.
78//!
79//! The usage pattern here is flexible, but generally follows the pattern of first calling [`HKDF::extract`]
80//! with a `salt` and an input key material `ikm`, which produces a pseudorandom key `prk`.
81//! The `prk` will have a [`KeyType`] and [`SecurityStrength`] that results from combining the two provided input keys,
82//! The `prk` may be! used directly as a full-entropy cryptographic key.
83//!
84//! Since the extract step may be called with any number of input keys, a streaming interface is provided
85//! whereby streaming mode in initialized with a call to [`HKDF::do_extract_init`], and then
86//! repeated calls to [`HKDF::do_extract_update_key`] and [`HKDF::do_extract_update_bytes`] may be made.
87//! Entropy from the inputs keys provided via [`HKDF::do_extract_update_key`] are credited towards the output key,
88//! while bytes provided via [`HKDF::do_extract_update_bytes`] are not.
89//! One restriction here is that once you start provided un-credited bytes via [`HKDF::do_extract_update_bytes`],
90//! no more calls to [`HKDF::do_extract_update_key`] may be made.
91//! The streaming API is completed with a call to either [`HKDF::do_extract_final`] or [`HKDF::do_extract_final_out`].
92//!
93//! The second stage, [`HKDF::expand_out`] stretches the `prk` into a longer output key, still of the same [`KeyType`]
94//! and [`SecurityStrength`].
95//!
96//! A typical flow looks like this:
97//!
98//! ```
99//! use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial256, KeyMaterial, KeyType};
100//! use bouncycastle_core::traits::KDF;
101//! use bouncycastle_hkdf::{HKDF, HKDF_SHA256};
102//! use bouncycastle_sha2::{SHA256};
103//!
104//! // setup variables
105//! let salt = KeyMaterial256::from_bytes_as_type(
106//!             b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
107//!             KeyType::MACKey).unwrap();
108//!
109//!  let ikm = KeyMaterial256::from_bytes_as_type(
110//!             b"\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00",
111//!             KeyType::MACKey).unwrap();
112//!
113//! let info = b"some extra context info";
114//!
115//!  // Use the streaming API to derive an output key of length 200 bytes.
116//!  let mut okm = KeyMaterial::<200>::new();
117//!  let mut hkdf = HKDF::<SHA256>::default();
118//!  hkdf.do_extract_init(&salt).unwrap();
119//!  hkdf.do_extract_update_bytes(ikm.ref_to_bytes()).unwrap();
120//!  let prk = hkdf.do_extract_final().unwrap();
121//!  HKDF_SHA256::expand_out(&prk, info, 200, &mut okm).unwrap();
122//! ```
123//!
124//! Various convenience wrapper functions are provided which can reduce the amount of boilerplate code
125//! for common cases.
126//! For example, the above code can be condensed to:
127//!
128//! ```
129//! use bouncycastle_core::key_material::{KeyMaterialTrait, KeyMaterial256, KeyMaterial, KeyType};
130//! use bouncycastle_hkdf::{HKDF_SHA256};
131//!
132//! // setup variables
133//! let salt = KeyMaterial256::from_bytes_as_type(
134//!             b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
135//!             KeyType::MACKey).unwrap();
136//!
137//!  let ikm = KeyMaterial256::from_bytes_as_type(
138//!             b"\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00",
139//!             KeyType::MACKey).unwrap();
140//!
141//! let info = b"some extra context info";
142//!
143//! // Use the one-shot API to derive an output key of length 200 bytes.
144//! let mut okm = KeyMaterial::<200>::new();
145//! let _bytes_written = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 200, &mut okm).unwrap();
146//! ```
147//!
148//! # Suspending and resuming execution
149//!
150//! The *HKDF-Extract* phase supports a streaming API whereby any amount of additional input keying
151//! material can be provided either via [`HKDF::do_extract_update_key`] -- which will
152//! credit the entropy of the provided [`KeyMaterial`] -- or as raw uncredited bytes via
153//! [`HKDF::do_extract_update_bytes`].
154//!
155//! As such, The *HKDF-Extract* phase can be suspended to a cache and resumed later via the
156//! [`SuspendableKeyed`] trait.
157//!
158//! The HKDF algorithm is keyed by a `salt`, which is required twice: once at initialization and again
159//! during finalization. Suspension and resumption are supported via the [`SuspendableKeyed`] trait
160//! which requires the caller to store the salt securely and provide it again during resumption.
161//! Note that providing a different salt during resumption cannot be detected by the library and
162//! would silently produce a different PRK.
163//!
164//! ```rust
165//! use bouncycastle_hkdf::HKDF_SHA256;
166//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
167//! use bouncycastle_core::traits::SuspendableKeyed;
168//!
169//! let salt = KeyMaterial256::from_bytes_as_type(
170//!             b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
171//!             KeyType::MACKey).unwrap();
172//! let ikm_part1 = b"input keying material part 1";
173//! let ikm_part2 = b" ...and part 2";
174//!
175//! let mut hkdf = HKDF_SHA256::new();
176//! hkdf.do_extract_init(&salt).unwrap();
177//! hkdf.do_extract_update_bytes(ikm_part1).unwrap();
178//!
179//! // suspend the in-progress extract (the salt is NOT included in the serialized state)
180//! let serialized_state = hkdf.suspend();
181//!
182//! // ...
183//! // do other things in the meantime
184//! // ...
185//!
186//! // ... later, possibly on another host: resume from the serialized state by re-supplying
187//! // the same salt (make sure you store it securely!).
188//! let mut hkdf = HKDF_SHA256::from_suspended(serialized_state, &salt).unwrap();
189//! hkdf.do_extract_update_bytes(ikm_part2).unwrap();
190//! let _prk = hkdf.do_extract_final().unwrap();
191//! ```
192
193#![forbid(unsafe_code)]
194#![forbid(missing_docs)]
195
196use bouncycastle_core::errors::{KDFError, KeyMaterialError, MACError, SuspendableError};
197use bouncycastle_core::key_material;
198use bouncycastle_core::key_material::{
199    KeyMaterial, KeyMaterial0, KeyMaterial512, KeyMaterialTrait, KeyType,
200};
201use bouncycastle_core::suspendable_state::{add_lib_ver, check_lib_ver};
202use bouncycastle_core::traits::{
203    Hash, HashAlgParams, KDF, MAC, SecurityStrength, SuspendableKeyed,
204};
205use bouncycastle_hmac::{HMAC, SUSPENDED_HMAC_SHA256_STATE_LEN, SUSPENDED_HMAC_SHA512_STATE_LEN};
206use bouncycastle_sha2::{SHA256, SHA512};
207use bouncycastle_utils::{max, min};
208use std::marker::PhantomData;
209// Imports needed only for docs
210#[allow(unused_imports)]
211use bouncycastle_core::traits::XOF;
212// end doc-only imports
213
214/*** Constants ***/
215/// The size of the output key material from the HKDF-Extract phase `prk`, in bytes.
216/// This has been sized so that the output KeyMaterial has enough capacity to accommodate the
217/// underlying hash primitive with the largest output size.
218/// If the given hash function has a smaller output size, then the output KeyMaterial will be
219/// under-full (ie have a key_len that does not use its full capacity).
220/// TODO: This is a dirty dirty hack because correctly sizing the output key
221///       really requires the generic_const_exprs feature, which is currently only available on
222///       nightly Rust, and not on stable. Once they merge that feature, we will be able to get rid of this
223///       and declare `prk: &mut KeyMaterial<H::OUTPUT_LEN>` instead of this hack.
224pub const MAX_HMAC_OUTPUT_LEN: usize = 64;
225
226/*** String constants ***/
227
228///
229pub const HKDF_SHA256_NAME: &str = "HKDF-SHA256";
230///
231pub const HKDF_SHA512_NAME: &str = "HKDF-SHA512";
232
233/*** Types ***/
234/// Public type for HKDF using SHA256.
235#[allow(non_camel_case_types)]
236pub type HKDF_SHA256 = HKDF<SHA256>;
237/// Public type for HKDF using SHA512.
238#[allow(non_camel_case_types)]
239pub type HKDF_SHA512 = HKDF<SHA512>;
240
241/// Internal struct for HKDF.
242/// Can, in theory, be instantiated with hash functions other than the ones provided by this crate (even custom ones).
243#[derive(Clone)]
244pub struct HKDF<H: Hash + HashAlgParams + Default> {
245    // Optional because an HMAC cannot be constructed until a key is provided
246    // to initialize it with.
247    // None must correspond to a state of Uninitialized.
248    hmac: Option<HMAC<H>>,
249    entropy: HkdfEntropyTracker<H>,
250    state: HkdfStates,
251}
252
253// Note: does not need to impl Drop because HKDF itself does not hold any sensitive state data.
254
255#[derive(Clone, Debug, PartialOrd, PartialEq)]
256#[repr(u8)]
257enum HkdfStates {
258    /// waiting for salt
259    Uninitialized = 0,
260
261    /// Salt set, waiting for IKMs or do_final
262    Initialized = 1,
263
264    /// [`HKDF::do_extract_update_key`] has been called, after which no more credited IKMs can be given.
265    /// This is in conformance with NIST SP 800-133 which requires all keys to come before other inputs.
266    TakingAdditionalInfo = 2,
267}
268
269impl TryFrom<u8> for HkdfStates {
270    type Error = SuspendableError;
271
272    /// Inverse of `self as u8`; rejects unrecognized discriminants with [`SuspendableError::InvalidData`].
273    fn try_from(value: u8) -> Result<Self, Self::Error> {
274        Ok(match value {
275            0 => Self::Uninitialized,
276            1 => Self::Initialized,
277            2 => Self::TakingAdditionalInfo,
278            _ => return Err(SuspendableError::InvalidData),
279        })
280    }
281}
282
283#[derive(Clone)]
284struct HkdfEntropyTracker<H: Hash + HashAlgParams + Default> {
285    _phantomhash: PhantomData<H>,
286    entropy: usize,
287    security_strength: SecurityStrength,
288}
289
290impl<H: Hash + HashAlgParams + Default> HkdfEntropyTracker<H> {
291    fn new() -> Self {
292        Self { _phantomhash: PhantomData, entropy: 0, security_strength: SecurityStrength::None }
293    }
294
295    /// Takes in a KeyMaterial that is being mixed and figures out how much entropy to credit.
296    /// Returns the amount of entropy credited.
297    fn credit_entropy(&mut self, key: &impl KeyMaterialTrait) -> usize {
298        let additional_entropy = if key.is_full_entropy() { key.key_len() } else { 0 };
299        self.entropy += additional_entropy;
300        self.security_strength = max(&self.security_strength, &key.security_strength()).clone();
301        self.security_strength =
302            min(&self.security_strength, &SecurityStrength::from_bytes(H::OUTPUT_LEN / 2)).clone();
303        additional_entropy
304    }
305
306    pub fn get_entropy(&self) -> usize {
307        self.entropy
308    }
309
310    // According to NIST SP 800-56Cr2, a KDF is fully seeded when its underlying hash primitive has a full block.
311    pub fn is_fully_seeded(&self) -> bool {
312        self.entropy >= H::OUTPUT_LEN
313    }
314
315    /// Either [`KeyMaterialTrait::BytesLowEntropy`] or [`KeyMaterialTrait::BytesFullEntropy`] depending on
316    /// whether enough input key material was provided for the internal hash function to have a full block.
317    fn get_output_key_type(&self) -> KeyType {
318        if self.is_fully_seeded() { KeyType::CryptographicRandom } else { KeyType::Unknown }
319    }
320}
321
322// Because this struct is not public, the tests have to go here.
323#[test]
324fn test_entropy_tracker() {
325    let mut entropy = HkdfEntropyTracker::<SHA256>::new();
326
327    assert_eq!(entropy.get_entropy(), 0);
328    assert_eq!(entropy.get_output_key_type(), KeyType::Unknown);
329
330    let key = KeyMaterial512::from_bytes_as_type(
331        b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
332        KeyType::CryptographicRandom,
333    )
334    .unwrap();
335    entropy.credit_entropy(&key);
336    assert_eq!(entropy.get_entropy(), 16);
337    assert_eq!(entropy.is_fully_seeded(), false);
338    assert_eq!(entropy.get_output_key_type(), KeyType::Unknown);
339
340    entropy.credit_entropy(&key);
341    assert_eq!(entropy.get_entropy(), 32);
342    assert_eq!(entropy.is_fully_seeded(), true);
343    assert_eq!(entropy.get_output_key_type(), KeyType::CryptographicRandom);
344}
345
346impl<H: Hash + HashAlgParams + Default> Default for HKDF<H> {
347    fn default() -> Self {
348        Self::new()
349    }
350}
351
352impl<H: Hash + HashAlgParams + Default> HKDF<H> {
353    /// Get a new, uninstantiated HKDF object.
354    pub fn new() -> Self {
355        Self { hmac: None, entropy: HkdfEntropyTracker::new(), state: HkdfStates::Uninitialized }
356    }
357
358    /// Returns the amount of entropy currently credited from the keys inputted so far.
359    pub fn get_entropy(&self) -> usize {
360        self.entropy.get_entropy()
361    }
362
363    /// Check whether the entropy input so far met the threshold for this object to be considered fully seeded
364    pub fn is_fully_seeded(&self) -> bool {
365        self.entropy.is_fully_seeded()
366    }
367
368    /// HKDF-Extract(salt, IKM) -> PRK
369    ///    Options:
370    ///       Hash     a hash function; HashLen denotes the length of the
371    ///                hash function output in octets
372    ///
373    ///    Inputs:
374    ///       salt     optional salt value (a non-secret random value);
375    ///                if not provided, it is set to a string of HashLen zeros.
376    ///       IKM      input keying material
377    ///
378    ///    Output:
379    ///       PRK      a pseudorandom key (of HashLen octets)
380    ///
381    /// The KeyMaterial input parameters can be of any [`KeyType`]; but the type of the output will be set accordingly.
382    /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any
383    /// underlying hash function, but the actual key length will be appropriate to the underlying hash function.
384    ///
385    /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
386    /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
387    pub fn extract(
388        salt: &impl KeyMaterialTrait,
389        ikm: &impl KeyMaterialTrait,
390    ) -> Result<impl KeyMaterialTrait, MACError> {
391        let mut prk = KeyMaterial::<MAX_HMAC_OUTPUT_LEN>::new();
392        Self::extract_out(salt, ikm, &mut prk)?;
393        Ok(prk)
394    }
395
396    /// Same as [`HKDF::extract`], but writes the output to a provided KeyMaterial buffer.
397    /// Note that the provided KeyMaterial must be correctly sized to the hash function output length.
398    pub fn extract_out(
399        salt: &impl KeyMaterialTrait,
400        ikm: &impl KeyMaterialTrait,
401        prk: &mut KeyMaterial<MAX_HMAC_OUTPUT_LEN>,
402    ) -> Result<usize, MACError> {
403        // PRK = HMAC-Hash(salt, IKM)
404
405        let mut hkdf = Self::new();
406        hkdf.do_extract_init(salt)?;
407        hkdf.do_extract_update_key(ikm)?;
408        let bytes_written = hkdf.do_extract_final_out(prk)?;
409
410        Ok(bytes_written)
411    }
412
413    /// The definition of HKDF-Expand from RFC5869 is as follows:
414    /// HKDF-Expand(PRK, info, L) -> OKM
415    ///    Options:
416    ///       Hash     a hash function; HashLen denotes the length of the
417    ///                hash function output in octets
418    ///    Inputs:
419    ///       PRK      a pseudorandom key of at least HashLen octets
420    ///                (usually, the output from the extract step)
421    ///       info     optional context and application specific information
422    ///                (can be a zero-length string)
423    ///       L        length of output keying material in octets
424    ///                (<= 255*HashLen)
425    ///
426    ///   Output:
427    ///       OKM      output keying material (of L octets)
428    ///
429    /// Due to the details of the KeyMaterial object needing to compile to a known size, there is (currently)
430    /// no way (within a no_std context) to dynamically allocate a KeyMaterial object according to the given 'L',
431    /// therefore this function is provided only as expand_out(), filling the provided KeyMaterial object,
432    /// and no analogous expand() is provided.
433    ///
434    /// The KeyMaterial input parameters can be of any KeyType; but the type of the output will be set accordingly.
435    ///
436    /// L is the output length. This will throw a [`MACError::InvalidLength`] if the provided KeyMaterial is too small to hold the requested output.
437    ///
438    /// Returns the number of bytes written.
439    #[allow(non_snake_case)] // for L
440    pub fn expand_out(
441        prk: &impl KeyMaterialTrait,
442        info: &[u8],
443        L: usize,
444        okm: &mut impl KeyMaterialTrait,
445    ) -> Result<usize, KDFError> {
446        // From RFC5896
447        //    N = ceil(L/HashLen)
448        //    T = T(1) | T(2) | T(3) | ... | T(N)
449        //    OKM = first L octets of T
450        //
451        //    where:
452        //    T(0) = empty string (zero length)
453        //    T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
454        //    T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
455        //    T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
456        //    ...
457        //
458        //    (where the constant concatenated to the end of each T(n) is a
459        //    single octet.)
460
461        let hash_len = H::OUTPUT_LEN;
462        if L > 255 * hash_len {
463            return Err(KDFError::InvalidLength(
464                "HMAC can not produce more than 255*HashLen bytes out output",
465            ));
466        }
467
468        if L > okm.capacity() {
469            return Err(KDFError::InvalidLength(
470                "Provided KeyMaterial is too small to hold the requested output length.",
471            ));
472        }
473
474        let mut entropy = HkdfEntropyTracker::<H>::new();
475        entropy.credit_entropy(prk);
476
477        #[allow(non_snake_case)]
478        let N = L.div_ceil(hash_len) as u8;
479        let mut bytes_written: usize = 0;
480
481        // Could potentially speed this up by unrolling T(0) and T(1)
482
483        // The prk key type must be temporarily changed to MACKey to satisfy HMAC, then restored afterwards.
484        let prk_as_mac_key = KeyMaterial::<MAX_HMAC_OUTPUT_LEN>::from_bytes_as_type(
485            prk.ref_to_bytes(),
486            KeyType::MACKey,
487        )?;
488
489        #[allow(non_snake_case)]
490        let mut T = [0u8; MAX_HMAC_OUTPUT_LEN];
491        let mut t_len: usize = 0;
492        let mut i = 1u8;
493
494        key_material::do_hazardous_operations(okm, |okm| {
495            let out = okm.ref_to_bytes_mut()?;
496            while i < N {
497                let mut hmac = HMAC::<H>::new(&prk_as_mac_key)
498                    .map_err(|_| KeyMaterialError::GenericError("HMAC initialization failed"))?;
499                hmac.do_update(&T[..t_len]);
500                hmac.do_update(info);
501                hmac.do_update(&[i]);
502
503                t_len = hmac
504                    .do_final_out(&mut T)
505                    .map_err(|_| KeyMaterialError::GenericError("HMAC finalization failed"))?;
506                debug_assert_eq!(t_len, hash_len); // this will be true for every iteration after T(0) / T(1)
507                out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]);
508                bytes_written += t_len;
509                i += 1;
510            }
511            Ok(())
512        })?;
513
514        // Part of the output is not taken on the last iteration
515        let remaining = L - bytes_written;
516        let mut hmac = HMAC::<H>::new(&prk_as_mac_key)?;
517        hmac.do_update(&T[..t_len]);
518        hmac.do_update(info);
519        hmac.do_update(&[i]);
520
521        t_len = hmac.do_final_out(&mut T[..remaining])?;
522        debug_assert_eq!(t_len, remaining); // this will be true for every iteration after T(0) / T(1)
523
524        key_material::do_hazardous_operations(okm, |okm| {
525            let out = okm.ref_to_bytes_mut()?;
526            out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]);
527            Ok(())
528        })?;
529        bytes_written += t_len;
530
531        // Set the KeyType of the output
532        // Since some computation has been performed, the result will not actually be zeroized, even if all input key material was zeroized.
533        key_material::do_hazardous_operations(okm, |okm| {
534            if prk.key_type() == KeyType::Zeroized {
535                okm.set_key_type(KeyType::Unknown)?;
536            } else {
537                okm.set_key_type(prk.key_type().clone())?;
538            }
539            okm.set_key_len(bytes_written)?;
540            if okm.key_type() <= KeyType::Unknown {
541                okm.set_security_strength(SecurityStrength::None)
542            } else {
543                okm.set_security_strength(
544                    min(&SecurityStrength::from_bytes(okm.key_len()), &entropy.security_strength)
545                        .clone(),
546                )
547            }
548        })?;
549
550        Ok(bytes_written)
551    }
552
553    /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
554    /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
555    #[allow(non_snake_case)]
556    pub fn extract_and_expand_out(
557        salt: &impl KeyMaterialTrait,
558        ikm: &impl KeyMaterialTrait,
559        info: &[u8],
560        L: usize,
561        okm: &mut impl KeyMaterialTrait,
562    ) -> Result<usize, KDFError> {
563        let prk = Self::extract(salt, ikm)?;
564        Self::expand_out(&prk, info, L, okm)
565    }
566
567    /// This, together with [`HKDF::do_extract_update_key`], [`HKDF::do_extract_update_bytes`] and [`HKDF::do_extract_final`]
568    /// provide a streaming interface for very long values of `ikm`.
569    /// In this mode, the entropy of `ikm` is untracked, and so only the entropy ef `salt` is taken into account
570    /// when computing the entropy of the output `prk`.
571    /// The KeyMaterial input parameters can be of any [`KeyType`]; but the type of the output will be set accordingly.
572    /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any
573    /// underlying hash function, but the actual key length will be appropriate to the underlying hash function.
574    ///
575    /// Salt is optional; to omit it, provide a KeyMaterial0, which will cause HKDF to use the default all-zero salt.
576    ///
577    /// Returns the number of bits of entropy credited to this input key material.
578    pub fn do_extract_init(&mut self, salt: &impl KeyMaterialTrait) -> Result<usize, MACError> {
579        if self.state >= HkdfStates::Initialized {
580            return Err(MACError::InvalidState("Initialized twice"));
581        };
582
583        // Often HMAC is initialized with a zero salt,
584        // Key strength errors are ignored here.
585        // This will all be tabulated correctly via entropy.credit_entropy()
586        self.hmac = Some(HMAC::<H>::new_allow_weak_key(salt)?);
587
588        let additional_entropy = self.entropy.credit_entropy(salt);
589        self.state = HkdfStates::Initialized;
590
591        Ok(additional_entropy)
592    }
593
594    /// An update function that allows adding an IKM as a [`KeyMaterialTrait`].
595    /// Credits the entropy contained in the IKM.
596    /// This function may be called zero or more times in a workflow.
597    /// In particular, this function may be called multiple times to add more than one IKM.
598    ///
599    /// Returns the number of bits of entropy credited to this input key material.
600    pub fn do_extract_update_key(
601        &mut self,
602        ikm: &impl KeyMaterialTrait,
603    ) -> Result<usize, MACError> {
604        if self.state == HkdfStates::Uninitialized {
605            return Err(MACError::InvalidState(
606                "Must call do_extract_init() before calling do_extract_update_key()",
607            ));
608        };
609
610        if self.state == HkdfStates::TakingAdditionalInfo {
611            return Err(MACError::InvalidState(
612                "Cannot accept more credited IKMs via do_extract_update_key(&KeyMaterial) after an uncredited key has been provided via do_extract_update(&[u8])",
613            ));
614        }
615        debug_assert_eq!(self.state, HkdfStates::Initialized);
616        debug_assert!(self.hmac.is_some());
617
618        let additional_entropy = self.entropy.credit_entropy(ikm);
619        let hmac_ref: &mut HMAC<H> = self.hmac.as_mut().unwrap();
620        hmac_ref.do_update(ikm.ref_to_bytes());
621        // self.hmac.as_mut().unwrap().do_update(ikm.ref_to_bytes());
622
623        Ok(additional_entropy)
624    }
625
626    /// An update function that allows streaming of the IKM as bytes.
627    /// Note that since this interface takes the IKM as raw bytes, it cannot track its entropy
628    /// and therefore any IKM material provided through this interface will not count towards
629    /// the entropy of the output key.
630    ///
631    /// State machine: this function must be called after [`HKDF::do_extract_init`], followed by
632    /// zero or more calls of [`HKDF::do_extract_update_key`], and before [`HKDF::do_extract_final`].
633    ///
634    /// Returns the number of bits of entropy credited to this input key material, which is always 0 for this function.
635    pub fn do_extract_update_bytes(&mut self, ikm_chunk: &[u8]) -> Result<usize, MACError> {
636        if self.state == HkdfStates::Uninitialized {
637            return Err(MACError::InvalidState(
638                "Must call do_extract_init() before calling do_extract_update()",
639            ));
640        };
641        self.state = HkdfStates::TakingAdditionalInfo;
642
643        self.hmac.as_mut().unwrap().do_update(ikm_chunk);
644        Ok(0)
645    }
646
647    /// Finish the HKDF-Extract phase and produce the output `prk`.
648    #[allow(non_snake_case)]
649    pub fn do_extract_final(self) -> Result<KeyMaterial<MAX_HMAC_OUTPUT_LEN>, MACError> {
650        let mut prk = KeyMaterial::<MAX_HMAC_OUTPUT_LEN>::new();
651        self.do_extract_final_out(&mut prk)?;
652        Ok(prk)
653    }
654
655    /// Finish the HKDF-Extract phase and fill the provided `prk`.
656    /// Note that the provided KeyMaterial must be correctly sized to the HMAC block length.
657    #[allow(non_snake_case)]
658    pub fn do_extract_final_out(
659        self,
660        prk: &mut KeyMaterial<MAX_HMAC_OUTPUT_LEN>,
661    ) -> Result<usize, MACError> {
662        if self.state == HkdfStates::Uninitialized {
663            return Err(MACError::InvalidState(
664                "Must call do_extract_init() before calling do_extract_final().",
665            ));
666        };
667        debug_assert!(self.hmac.is_some());
668
669        let output_key_type = self.entropy.get_output_key_type(); // need to do this above self.hmac.do_final_out, which will consume self.
670
671        let mut bytes_written = 0;
672        key_material::do_hazardous_operations(prk, |okm| {
673            bytes_written = self
674                .hmac
675                .unwrap()
676                .do_final_out(&mut okm.ref_to_bytes_mut()?)
677                .map_err(|_| KeyMaterialError::GenericError("HMAC do_final_out failed"))?;
678            okm.set_key_len(bytes_written)?;
679            okm.set_key_type(output_key_type)?;
680            if output_key_type <= KeyType::Unknown {
681                okm.set_security_strength(SecurityStrength::None)
682            } else {
683                okm.set_security_strength(
684                    min(
685                        &SecurityStrength::from_bytes(okm.key_len()),
686                        &self.entropy.security_strength,
687                    )
688                    .clone(),
689                )
690            }
691        })?;
692        // By RFC5869, the output size of prk is HashLen denotes the length of the
693        //                hash function output in octets
694        debug_assert_eq!(prk.key_len(), H::OUTPUT_LEN);
695        Ok(bytes_written)
696    }
697}
698
699/// As per NIST SP 800-56Cr2 section 5.1, HKDF extract_and_expand can be used as a KDF.
700/// Additionally, section 4.1 says that when using HMAC as a KDF, the salt may be set to
701/// a string of HashLen zeros. All key material and additional_input is mapped to HKDF's ikm input.
702/// While this is not the only mode in which HKDF can be used as a KDF, this is considered the default mode
703/// that is exposed through [`KDF::derive_key`] and [`KDF::derive_key_out`].
704/// More advanced control of the inputs to HKDF can be achieved by using [`KDF::derive_key_from_multiple`] and
705/// [`KDF::derive_key_from_multiple_out`], or by using the [`HKDF`] impl directly.
706///
707/// Entropy tracking: this implementation will map entropy from the input keys to the output key.
708impl<H: Hash + HashAlgParams + Default> KDF for HKDF<H> {
709    /// This invokes [`HKDF::extract_and_expand_out`] with a zero salt and using the provided key as ikm.
710    /// This provides a fixed-length output, which may be truncated as needed.
711    fn derive_key(
712        self,
713        key: &impl KeyMaterialTrait,
714        additional_input: &[u8],
715    ) -> Result<Box<dyn KeyMaterialTrait>, KDFError> {
716        let mut output_key = KeyMaterial512::new();
717        _ = self.derive_key_out(key, additional_input, &mut output_key)?;
718        output_key.set_key_len(H::OUTPUT_LEN)?;
719        Ok(Box::new(output_key))
720    }
721
722    /// This invokes [`HKDF::extract_and_expand_out`] with a zero salt and using the provided key as ikm.
723    /// This fills the provided [`KeyMaterialTrait`] object in place of exposing a Length parameter.
724    fn derive_key_out(
725        self,
726        key: &impl KeyMaterialTrait,
727        additional_input: &[u8],
728        output_key: &mut impl KeyMaterialTrait,
729    ) -> Result<usize, KDFError> {
730        let bytes_written = HKDF::<H>::extract_and_expand_out(
731            &KeyMaterial::<0>::new(),
732            key,
733            additional_input,
734            output_key.capacity(),
735            output_key,
736        )?;
737        Ok(bytes_written)
738    }
739
740    /// As with [`KDF::derive_key`] and [`KDF::derive_key_out`],
741    /// This invokes HKDF in the extract_and_expand mode and maps the provided keys in the following way:
742    /// - The first (0'th) key is used as the salt for HKDF.extract.
743    /// - The remaining keys are concatenated to form HKDF's ikm parameter.
744    /// - Entropy of all provided keys are tracked to determine the output key's entropy.
745    ///
746    /// Therefore, derive_key_from_multiple(&[KeyMaterial0::new(), &key], &info) is equivalent to derive_key(&key, &info).
747    ///
748    /// This provides a fixed-length output, which may be truncated as needed.
749    fn derive_key_from_multiple(
750        self,
751        keys: &[&impl KeyMaterialTrait],
752        additional_input: &[u8],
753    ) -> Result<Box<dyn KeyMaterialTrait>, KDFError> {
754        let mut output_key = KeyMaterial512::new();
755        _ = self.derive_key_from_multiple_out(keys, additional_input, &mut output_key)?;
756        output_key.set_key_len(*min(&output_key.key_len(), &H::OUTPUT_LEN))?;
757        Ok(Box::new(output_key))
758    }
759
760    /// This behaves the same as [`KDF::derive_key_from_multiple`], except that it fills the provided
761    /// [`KeyMaterialTrait`] object in place of exposing a Length parameter.
762    fn derive_key_from_multiple_out(
763        self,
764        keys: &[&impl KeyMaterialTrait],
765        additional_input: &[u8],
766        output_key: &mut impl KeyMaterialTrait,
767    ) -> Result<usize, KDFError> {
768        let mut hkdf = HKDF::<H>::new();
769        let mut entropy = HkdfEntropyTracker::<H>::new();
770
771        if keys.len() >= 1 {
772            hkdf.do_extract_init(keys[0])?;
773            entropy.credit_entropy(keys[0]);
774        } else {
775            hkdf.do_extract_init(&KeyMaterial0::new())?;
776        };
777
778        if keys.len() != 0 {
779            for key in &keys[1..] {
780                hkdf.do_extract_update_bytes(key.ref_to_bytes())?;
781                entropy.credit_entropy(*key);
782            }
783        }
784        let mut prk = KeyMaterial::<MAX_HMAC_OUTPUT_LEN>::new();
785        _ = hkdf.do_extract_final_out(&mut prk)?;
786        let bytes_written =
787            HKDF::<H>::expand_out(&prk, additional_input, output_key.capacity(), output_key)?;
788
789        key_material::do_hazardous_operations(output_key, |output_key| {
790            output_key.set_key_type(entropy.get_output_key_type())?;
791            output_key.set_security_strength(
792                min(
793                    &SecurityStrength::from_bytes(output_key.key_len()),
794                    &entropy.security_strength,
795                )
796                .clone(),
797            )
798        })?;
799
800        Ok(bytes_written)
801    }
802
803    fn max_security_strength(&self) -> SecurityStrength {
804        H::default().max_security_strength()
805    }
806}
807
808/// Length in bytes of the serialized state of [`HKDF_SHA256`].
809pub const SUSPENDED_HKDF_SHA256_STATE_LEN: usize = SUSPENDED_HMAC_SHA256_STATE_LEN + 14;
810/// Length in bytes of the serialized state of [`HKDF_SHA512`].
811pub const SUSPENDED_HKDF_SHA512_STATE_LEN: usize = SUSPENDED_HMAC_SHA512_STATE_LEN + 14;
812
813/// HKDF is *keyed by its salt* -- the salt keys the extract-phase HMAC -- so it implements
814/// [`SuspendableKeyed`] (not [`SerializableState`]). An in-progress
815/// extract operation can be suspended and resumed, but the salt is NOT written into the serialized
816/// state and must be re-supplied to [`SuspendableKeyed::from_serialized_state`].
817///
818/// Only the extract phase carries resumable state (expand is a one-shot static operation). As with
819/// HMAC, resuming with the wrong salt cannot be detected and will silently produce a wrong PRK.
820///
821/// Serialized layout: HKDF writes its own 3-byte library version header first and checks it before
822/// parsing anything else. This matters because the inner HMAC blob (which carries its own header) is
823/// absent before extract is initialized -- without HKDF's own header, a pre-init state would have no
824/// version tag at all. Using `B` = the inner HMAC blob length:
825///   [0 .. 3)             HKDF library version header (checked on resume)
826///   [3]                  inner-HMAC present flag (0 = extract not yet initialized)
827///   [4 .. 4 + B)         the inner HMAC's SuspendableKeyed blob (salt excluded); zeroed when absent
828///   [4 + B]              state-machine tag (see `HkdfStates`)
829///   [5 + B .. 13 + B)    entropy counter (usize serialized as u64, little-endian)
830///   [13 + B]             accumulated security strength (1-byte tag)
831/// So the total per HKDF variant is the 3-byte version header + 11 bytes of HKDF bookkeeping
832/// (present flag, state tag, entropy counter, security strength) + the inner HMAC's blob = `B + 14`.
833macro_rules! impl_suspendable_keyed_state_for_hkdf {
834    // $hash: the concrete hash; $serialized_hmac_len: the inner HMAC's serialized-state length for that
835    // hash; $serialized_hkdf_len: the full HKDF serialized-state length (= 3 + 11 + $serialized_hmac_len).
836    ($hash:ty, $serialized_hmac_len:expr, $serialized_hkdf_len:expr) => {
837        impl SuspendableKeyed<{ $serialized_hkdf_len }> for HKDF<$hash> {
838            // HMAC accepts any key material, so the key type is the trait object `dyn KeyMaterialTrait`
839            // rather than a single concrete key type. The key is only used (by reference) to reload the key
840            // bytes at from_serialized_state, so dynamic dispatch here is negligible.
841            type Key = dyn KeyMaterialTrait;
842
843            fn suspend(self) -> [u8; $serialized_hkdf_len] {
844                debug_assert_eq!($serialized_hkdf_len, $serialized_hmac_len + 14);
845                let mut state = [0u8; $serialized_hkdf_len];
846
847                // HKDF's own library version header comes first: the inner HMAC blob is absent before
848                // extract is initialized, so we can't rely on its header being present.
849                add_lib_ver(&mut state);
850
851                // The present flag, then (when present) the inner salt-keyed HMAC blob right after it.
852                if let Some(hmac) = self.hmac {
853                    state[3] = 1; // present flag
854                    state[4..4 + $serialized_hmac_len].copy_from_slice(&hmac.suspend());
855                }
856                // else None:
857                //  the presence flag = 0
858                //  the content = [u8; 0]
859                // which is how it already is, so nothing to do.
860
861                state[4 + $serialized_hmac_len] = self.state as u8;
862                state[5 + $serialized_hmac_len..13 + $serialized_hmac_len]
863                    .copy_from_slice(&(self.entropy.entropy as u64).to_le_bytes());
864                state[13 + $serialized_hmac_len] = self.entropy.security_strength as u8;
865
866                state
867            }
868
869            fn from_suspended(
870                state: [u8; $serialized_hkdf_len],
871                salt: &Self::Key,
872            ) -> Result<Self, SuspendableError> {
873                // Check HKDF's own version header before parsing anything else.
874                check_lib_ver(&state, None)?;
875
876                // Rebuild the salt-keyed HMAC (when present) by re-supplying the salt.
877                let hmac = match state[3] {
878                    0 => None,
879                    // infallible: the sub-slice is exactly $serialized_hmac_len bytes by const construction.
880                    1 => Some(HMAC::<$hash>::from_suspended(
881                        state[4..4 + $serialized_hmac_len].try_into().unwrap(),
882                        salt,
883                    )?),
884                    _ => return Err(SuspendableError::InvalidData),
885                };
886
887                let hkdf_state = HkdfStates::try_from(state[4 + $serialized_hmac_len])?;
888
889                // check that the hkdf_state aligns with the presence of an hmac
890                if
891                    // an hmac object should not be present in the init state.
892                    (hmac.is_some() && hkdf_state == HkdfStates::Uninitialized) ||
893                    // any other state must have an hmac object.
894                    (hmac.is_none() && hkdf_state != HkdfStates::Uninitialized)
895                {
896                    return Err(SuspendableError::InvalidData);
897                }
898
899                // infallible: the sub-slice is exactly 8 bytes by const construction.
900                let entropy = u64::from_le_bytes(
901                    state[5 + $serialized_hmac_len..13 + $serialized_hmac_len].try_into().unwrap(),
902                ) as usize;
903                let security_strength =
904                    SecurityStrength::try_from(state[13 + $serialized_hmac_len])?;
905
906                Ok(HKDF {
907                    hmac,
908                    entropy: HkdfEntropyTracker {
909                        _phantomhash: PhantomData,
910                        entropy,
911                        security_strength,
912                    },
913                    state: hkdf_state,
914                })
915            }
916        }
917    };
918}
919
920impl_suspendable_keyed_state_for_hkdf!(
921    SHA256,
922    SUSPENDED_HMAC_SHA256_STATE_LEN,
923    SUSPENDED_HKDF_SHA256_STATE_LEN
924);
925impl_suspendable_keyed_state_for_hkdf!(
926    SHA512,
927    SUSPENDED_HMAC_SHA512_STATE_LEN,
928    SUSPENDED_HKDF_SHA512_STATE_LEN
929);