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_interface::key_material::{KeyMaterial256, KeyType};
44//! use bouncycastle_core_interface::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 [KeyMaterial], 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_interface::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialInternal, KeyType};
100//! use bouncycastle_core_interface::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 = KeyMaterialInternal::<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_interface::key_material::{KeyMaterial, KeyMaterial256, KeyMaterialInternal, 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 = KeyMaterialInternal::<200>::new();
145//! let _bytes_written = HKDF_SHA256::extract_and_expand_out(&salt, &ikm, info, 200, &mut okm).unwrap();
146//! ```
147
148
149#![forbid(unsafe_code)]
150
151use std::marker::PhantomData;
152use bouncycastle_core_interface::errors::{KDFError, MACError};
153use bouncycastle_core_interface::key_material::{KeyMaterial0, KeyMaterial512, KeyMaterialInternal, KeyType};
154use bouncycastle_core_interface::traits::{Hash, KDF, KeyMaterial, MAC, HashAlgParams, SecurityStrength};
155use bouncycastle_hmac::HMAC;
156use bouncycastle_sha2::{SHA256, SHA512};
157use bouncycastle_utils::{max, min};
158
159// Imports needed only for docs
160#[allow(unused_imports)]
161use bouncycastle_core_interface::traits::XOF;
162// end doc-only imports
163
164
165/*** Constants ***/
166// Slightly hacky, but set this to accomodate the underlying hash primitive with the largest output size.
167// Would be better to somehow pull that at compile time from H, but I'm not sure how to do that.
168const HMAC_BLOCK_LEN: usize = 64;
169
170/*** String constants ***/
171
172pub const HKDF_SHA256_NAME: &str = "HKDF-SHA256";
173pub const HKDF_SHA512_NAME: &str = "HKDF-SHA512";
174
175
176/*** Types ***/
177#[allow(non_camel_case_types)]
178pub type HKDF_SHA256 = HKDF<SHA256>;
179#[allow(non_camel_case_types)]
180pub type HKDF_SHA512 = HKDF<SHA512>;
181
182pub struct HKDF<H: Hash + HashAlgParams + Default> {
183    hmac: Option<HMAC<H>>,  // Optional because we can't construct an HMAC until they give us a key
184                            // to initialize it with.
185                            // None should correspond to a state of Uninitialized.
186    entropy: HkdfEntropyTracker<H>,
187    state: HkdfStates,
188}
189
190// Note: does not need to impl Drop because HKDF itself does not hold any sensitive state data.
191
192#[derive(Clone, Debug, PartialOrd, PartialEq)]
193enum HkdfStates {
194    /// waiting for salt
195    Uninitialized,
196
197    /// Salt set, waiting for IKMs or do_final
198    Initialized,
199
200    /// [HKDF::do_extract_update_key] has been called, after which no more credited IKMs can be given.
201    /// This is in conformance with NIST SP 800-133 which requires all keys to come before other inputs.
202    TakingAdditionalInfo,
203}
204
205struct HkdfEntropyTracker<H: Hash + HashAlgParams + Default> {
206    _phantomhash: PhantomData<H>,
207    entropy: usize,
208    security_strength: SecurityStrength,
209}
210
211impl<H: Hash + HashAlgParams + Default> HkdfEntropyTracker<H> {
212    fn new() -> Self { Self { _phantomhash: PhantomData, entropy: 0, security_strength: SecurityStrength::None } }
213
214    /// Takes in a KeyMaterial that is being mixed and figures out how much entropy to credit.
215    /// Returns the amount of entropy credited.
216    fn credit_entropy(&mut self, key: &impl KeyMaterial) -> usize {
217        let additional_entropy = if key.is_full_entropy() { key.key_len() } else { 0 };
218        self.entropy += additional_entropy;
219        self.security_strength = max(&self.security_strength, &key.security_strength()).clone();
220        self.security_strength = min(&self.security_strength, &SecurityStrength::from_bytes(H::OUTPUT_LEN/2)).clone();
221        additional_entropy
222    }
223
224    pub fn get_entropy(&self) -> usize {
225        self.entropy
226    }
227
228    // According to NIST SP 800-56Cr2, a KDF is fully seeded when its underlying hash primitive has a full block.
229    pub fn is_fully_seeded(&self) -> bool {
230        self.entropy >= H::OUTPUT_LEN
231    }
232
233    /// Either [KeyMaterial::BytesLowEntropy] or [KeyMaterial::BytesFullEntropy] depending on
234    /// whether enough input key material was provided for the internal hash function to have a full block.
235    fn get_output_key_type(&self) -> KeyType {
236        if self.is_fully_seeded() {
237            KeyType::BytesFullEntropy
238        } else {
239            KeyType::BytesLowEntropy
240        }
241    }
242}
243
244// Since I don't want this struct to be public, the tests have to go here.
245#[test]
246fn test_entropy_tracker() {
247    let mut entropy = HkdfEntropyTracker::<SHA256>::new();
248
249    assert_eq!(entropy.get_entropy(), 0);
250    assert_eq!(entropy.get_output_key_type(), KeyType::BytesLowEntropy);
251
252    let key = KeyMaterial512::from_bytes_as_type(b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", KeyType::BytesFullEntropy).unwrap();
253    entropy.credit_entropy(&key);
254    assert_eq!(entropy.get_entropy(), 16);
255    assert_eq!(entropy.is_fully_seeded(), false);
256    assert_eq!(entropy.get_output_key_type(), KeyType::BytesLowEntropy);
257
258    entropy.credit_entropy(&key);
259    assert_eq!(entropy.get_entropy(), 32);
260    assert_eq!(entropy.is_fully_seeded(), true);
261    assert_eq!(entropy.get_output_key_type(), KeyType::BytesFullEntropy);
262}
263
264impl<H: Hash + HashAlgParams + Default> Default for HKDF<H> {
265    fn default() -> Self {
266        Self::new()
267    }
268}
269
270impl<H: Hash + HashAlgParams + Default> HKDF<H> {
271    pub fn new() -> Self {
272        Self { hmac: None, entropy: HkdfEntropyTracker::new(), state: HkdfStates::Uninitialized }
273    }
274
275    /// Returns the amount of entropy currently credited from the keys inputted so far.
276    pub fn get_entropy(&self) -> usize {
277        self.entropy.get_entropy()
278    }
279
280    /// Has the entropy input so far met the threshold for this object to be considered fully seeded?
281    pub fn is_fully_seeded(&self) -> bool {
282        self.entropy.is_fully_seeded()
283    }
284
285    /// HKDF-Extract(salt, IKM) -> PRK
286    ///    Options:
287    ///       Hash     a hash function; HashLen denotes the length of the
288    ///                hash function output in octets
289    ///
290    ///    Inputs:
291    ///       salt     optional salt value (a non-secret random value);
292    ///                if not provided, it is set to a string of HashLen zeros.
293    ///       IKM      input keying material
294    ///
295    ///    Output:
296    ///       PRK      a pseudorandom key (of HashLen octets)
297    ///
298    /// The KeyMaterial input parameters can be of any [KeyType]; but the type of the output will be set accordingly.
299    /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any
300    /// underlying hash function, but the actual key length will be appropriate to the underlying hash function.
301    ///
302    /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
303    /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
304    pub fn extract(
305        salt: &impl KeyMaterial,
306        ikm: &impl KeyMaterial,
307    ) -> Result<impl KeyMaterial, MACError> {
308        let mut prk = KeyMaterialInternal::<HMAC_BLOCK_LEN>::new();
309        Self::extract_out(salt, ikm, &mut prk)?;
310        Ok(prk)
311    }
312
313    /// Same as [HKDF::extract], but writes the output to a provided KeyMaterial buffer.
314    pub fn extract_out(
315        salt: &impl KeyMaterial,
316        ikm: &impl KeyMaterial,
317        prk: &mut impl KeyMaterial,
318    ) -> Result<usize, MACError> {
319        // PRK = HMAC-Hash(salt, IKM)
320
321        let mut hkdf = Self::new();
322        hkdf.do_extract_init(salt)?;
323        hkdf.do_extract_update_key(ikm)?;
324        let bytes_written = hkdf.do_extract_final_out(prk)?;
325
326        Ok(bytes_written)
327    }
328
329    /// The definition of HKDF-Expand from RFC5869 is as follows:
330    /// HKDF-Expand(PRK, info, L) -> OKM
331    ///    Options:
332    ///       Hash     a hash function; HashLen denotes the length of the
333    ///                hash function output in octets
334    ///    Inputs:
335    ///       PRK      a pseudorandom key of at least HashLen octets
336    ///                (usually, the output from the extract step)
337    ///       info     optional context and application specific information
338    ///                (can be a zero-length string)
339    ///       L        length of output keying material in octets
340    ///                (<= 255*HashLen)
341    ///
342    ///   Output:
343    ///       OKM      output keying material (of L octets)
344    ///
345    /// Due to the details of the KeyMaterial object needing to compile to a known size, there is (currently)
346    /// no way (within a no_std context) to dynamically allocate a KeyMaterial object according to the given 'L',
347    /// therefore this function is provided only as expand_out(), filling the provided KeyMaterial object,
348    /// and no analogous expand() is provided.
349    ///
350    /// The KeyMaterial input parameters can be of any KeyType; but the type of the output will be set accordingly.
351    ///
352    /// L is the output length. This will throw a [MACError::InvalidLength] if the provided KeyMaterial is too small to hold the requested output.
353    ///
354    /// Returns the number of bytes written.
355    #[allow(non_snake_case)] // for L
356    pub fn expand_out(
357        prk: &impl KeyMaterial,
358        info: &[u8],
359        L: usize,
360        okm: &mut impl KeyMaterial,
361    ) -> Result<usize, KDFError> {
362        // From RFC5896
363        //    N = ceil(L/HashLen)
364        //    T = T(1) | T(2) | T(3) | ... | T(N)
365        //    OKM = first L octets of T
366        //
367        //    where:
368        //    T(0) = empty string (zero length)
369        //    T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
370        //    T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
371        //    T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
372        //    ...
373        //
374        //    (where the constant concatenated to the end of each T(n) is a
375        //    single octet.)
376
377        let hash_len = H::OUTPUT_LEN;
378        if L > 255 * hash_len {
379            return Err(KDFError::InvalidLength(
380                "HMAC can not produce more than 255*HashLen bytes out output",
381            ));
382        }
383
384        if L > okm.capacity() {
385            return Err(KDFError::InvalidLength(
386                "Provided KeyMaterial is too small to hold the requested output length.",
387            ));
388        }
389
390        let mut entropy = HkdfEntropyTracker::<H>::new();
391        entropy.credit_entropy(prk);
392
393        #[allow(non_snake_case)]
394        let N = L.div_ceil(hash_len) as u8;
395        let mut bytes_written: usize = 0;
396
397        okm.allow_hazardous_operations();
398        let out: &mut [u8] = okm.mut_ref_to_bytes()?;
399        // Could potentially speed this up by unrolling T(0) and T(1)
400
401        // We're gonna have to kludge the prk key type to MACKey to make HMAC happy, but we'll set it back to the original value afterwards.
402        let prk_as_mac_key = KeyMaterialInternal::<HMAC_BLOCK_LEN>::from_bytes_as_type(prk.ref_to_bytes(), KeyType::MACKey)?;
403
404        #[allow(non_snake_case)]
405        let mut T = [0u8; HMAC_BLOCK_LEN];
406        let mut t_len: usize = 0;
407        let mut i = 1u8;
408        while i < N {
409            // todo: might need this to be new_allow_weak_key()
410            let mut hmac = HMAC::<H>::new(&prk_as_mac_key)?;
411            hmac.do_update(&T[..t_len]);
412            hmac.do_update(info);
413            hmac.do_update(&[i]);
414
415            t_len = hmac.do_final_out(&mut T)?;
416            debug_assert_eq!(t_len, hash_len); // this will be true for every iteration after T(0) / T(1)
417            out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]);
418            bytes_written += t_len;
419            i += 1;
420        }
421
422        // On the last iteration, we don't take all of the output.
423        let remaining = L - bytes_written;
424        // todo: might need this to be new_allow_weak_key()
425        let mut hmac = HMAC::<H>::new(&prk_as_mac_key)?;
426        hmac.do_update(&T[..t_len]);
427        hmac.do_update(info);
428        hmac.do_update(&[i]);
429
430        t_len = hmac.do_final_out(&mut T[..remaining])?;
431        debug_assert_eq!(t_len, remaining); // this will be true for every iteration after T(0) / T(1)
432        out[bytes_written..bytes_written + t_len].copy_from_slice(&T[..t_len]);
433        bytes_written += t_len;
434
435        // set the KeyType of the output
436        // since we've done some computation, the result will not actually be zeroized, even if all input key material was zeroized.
437        if prk.key_type() == KeyType::Zeroized {
438            okm.set_key_type(KeyType::BytesLowEntropy)?;
439        } else {
440            okm.set_key_type(prk.key_type().clone())?;
441        }
442        okm.set_key_len(bytes_written)?;
443        if okm.key_type() <= KeyType::BytesLowEntropy {
444            okm.set_security_strength(SecurityStrength::None)?;
445        } else {
446            okm.set_security_strength(min(&SecurityStrength::from_bytes(okm.key_len()), &entropy.security_strength).clone())?;
447        }
448        okm.drop_hazardous_operations();
449        Ok(bytes_written)
450    }
451
452    /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
453    /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
454    #[allow(non_snake_case)]
455    pub fn extract_and_expand_out(
456        salt: &impl KeyMaterial,
457        ikm: &impl KeyMaterial,
458        info: &[u8],
459        L: usize,
460        okm: &mut impl KeyMaterial,
461    ) -> Result<usize, KDFError> {
462        let prk = Self::extract(salt, ikm)?;
463        Self::expand_out(&prk, info, L, okm)
464    }
465
466    /// This, together with [HKDF::do_extract_update_key], [HKDF::do_extract_update_bytes] and [HKDF::do_extract_final]
467    /// provide a streaming interface for very long values of `ikm`.
468    /// In this mode, the entropy of `ikm` is untracked, and so only the entropy ef `salt` is taken into account
469    /// when computing the entropy of the output `prk`.
470    /// The KeyMaterial input parameters can be of any [KeyType]; but the type of the output will be set accordingly.
471    /// The output KeyMaterial will be of fixed size, with a capacity large enough to cover any
472    /// underlying hash function, but the actual key length will be appropriate to the underlying hash function.
473    ///
474    /// Salt is optional, which is indicated by providing an uninitialized KeyMaterial object of length zero,
475    /// the capacity is irrelevant, so KeyMateriol256::new() or KeyMaterial_internal::<0>::new() would both count as an absent salt.
476    ///
477    /// Returns the number of bits of entropy credited to this input key material.
478    pub fn do_extract_init(&mut self, salt: &impl KeyMaterial) -> Result<usize, MACError> {
479        if self.state >= HkdfStates::Initialized {
480            return Err(MACError::InvalidState("Initialized twice"));
481        };
482
483        // Often HMAC is initialized with a zero salt,
484        // So we're gonna ignore key strength errors here
485        // This will all be tabulated correctly via entropy.credit_entropy()
486        self.hmac = Some(HMAC::<H>::new_allow_weak_key(salt)?);
487
488        let additional_entropy = self.entropy.credit_entropy(salt);
489        self.state = HkdfStates::Initialized;
490
491        Ok(additional_entropy)
492    }
493
494    /// An update function that allows adding an IKM as a [KeyMaterial].
495    /// Credits the entropy contained in the IKM.
496    /// This function may be called zero or more times in a workflow.
497    /// In particular, this function may be called multiple times to add more than one IKM.
498    ///
499    /// Returns the number of bits of entropy credited to this input key material.
500    pub fn do_extract_update_key(&mut self, ikm: &impl KeyMaterial) -> Result<usize, MACError> {
501        if self.state == HkdfStates::Uninitialized {
502            return Err(MACError::InvalidState(
503                "Must call do_extract_init() before calling do_extract_update_key()",
504            ));
505        };
506
507        if self.state == HkdfStates::TakingAdditionalInfo {
508            return Err(MACError::InvalidState(
509                "Cannot accept more credited IKMs via do_extract_update_key(&KeyMaterial) after an uncredited key has been provided via do_extract_update(&[u8])",
510            ));
511        }
512        debug_assert_eq!(self.state, HkdfStates::Initialized);
513        debug_assert!(self.hmac.is_some());
514
515        let additional_entropy = self.entropy.credit_entropy(ikm);
516        let hmac_ref: &mut HMAC<H> = self.hmac.as_mut().unwrap();
517        hmac_ref.do_update(ikm.ref_to_bytes());
518        // self.hmac.as_mut().unwrap().do_update(ikm.ref_to_bytes());
519
520        Ok(additional_entropy)
521    }
522
523    /// An update function that allows streaming of the IKM as bytes.
524    /// Note that since this interface takes the IKM as raw bytes, it cannot track its entropy
525    /// and therefore any IKM material provided through this interface will not count towards
526    /// the entropy of the output key.
527    ///
528    /// State machine: this function must be called after [HKDF::do_extract_init], followed by
529    /// zero or more calls of [HKDF::do_extract_update_key], and before [HKDF::do_extract_final].
530    ///
531    /// Returns the number of bits of entropy credited to this input key material, which is always 0 for this function.
532    pub fn do_extract_update_bytes(&mut self, ikm_chunk: &[u8]) -> Result<usize, MACError> {
533        if self.state == HkdfStates::Uninitialized {
534            return Err(MACError::InvalidState(
535                "Must call do_extract_init() before calling do_extract_update()",
536            ));
537        };
538        self.state = HkdfStates::TakingAdditionalInfo;
539
540        self.hmac.as_mut().unwrap().do_update(ikm_chunk);
541        Ok(0)
542    }
543
544    #[allow(non_snake_case)]
545    pub fn do_extract_final(self) -> Result<impl KeyMaterial, MACError> {
546        let mut okm = KeyMaterialInternal::<HMAC_BLOCK_LEN>::new();
547        self.do_extract_final_out(&mut okm)?;
548        Ok(okm)
549    }
550
551    #[allow(non_snake_case)]
552    pub fn do_extract_final_out(self, okm: &mut impl KeyMaterial) -> Result<usize, MACError> {
553        if self.state == HkdfStates::Uninitialized {
554            return Err(MACError::InvalidState(
555                "Must call do_extract_init() before calling do_extract_complete().",
556            ));
557        };
558        debug_assert!(self.hmac.is_some());
559
560        let output_key_type = self.entropy.get_output_key_type(); // need to do this above self.hmac.do_final_out, which will consume self.
561
562        okm.allow_hazardous_operations();  // doing it here to get mut_ref_to_bytes
563        let bytes_written = self.hmac.unwrap().do_final_out(&mut okm.mut_ref_to_bytes().unwrap())?;
564        okm.set_key_len(bytes_written)?;
565        okm.set_key_type(output_key_type)?;
566        if output_key_type <= KeyType::BytesLowEntropy {
567            okm.set_security_strength(SecurityStrength::None)?;
568        } else {
569            okm.set_security_strength(min(&SecurityStrength::from_bytes(okm.key_len()), &self.entropy.security_strength).clone())?;
570        }
571        okm.drop_hazardous_operations();
572        Ok(bytes_written)
573    }
574}
575
576/// As per NIST SP 800-56Cr2 section 5.1, HKDF extract_and_expand can be used as a KDF.
577/// Additionally, section 4.1 says that when using HMAC as a KDF, the salt may be set to
578/// a string of HashLen zeros. All key material and additional_input is mapped to HKDF's ikm input.
579/// While this is not the only mode in which HKDF can be used as a KDF, this is considered the default mode
580/// that is exposed through [KDF::derive_key] and [KDF::derive_key_out].
581/// More advanced control of the inputs to HKDF can be achieved by using [KDF::derive_key_from_multiple] and
582/// [KDF::derive_key_from_multiple_out], or by using the [HKDF] impl directly.
583///
584/// Entropy tracking: this implementation will map entropy from the input keys to the output key.
585impl<H: Hash + HashAlgParams + Default> KDF for HKDF<H> {
586    /// This invokes [HKDF::extract_and_expand_out] with a zero salt and using the provided key as ikm.
587    /// This provides a fixed-length output, which may be truncated as needed.
588    fn derive_key(
589        self,
590        key: &impl KeyMaterial,
591        additional_input: &[u8],
592    ) -> Result<Box<dyn KeyMaterial>, KDFError> {
593        let mut output_key = KeyMaterial512::new();
594        _ = self.derive_key_out(key, additional_input, &mut output_key)?;
595        output_key.truncate(H::OUTPUT_LEN)?;
596        Ok(Box::new(output_key))
597    }
598
599    /// This invokes [HKDF::extract_and_expand_out] with a zero salt and using the provided key as ikm.
600    /// This fills the provided [KeyMaterial] object in place of exposing a Length parameter.
601    fn derive_key_out(
602        self,
603        key: &impl KeyMaterial,
604        additional_input: &[u8],
605        output_key: &mut impl KeyMaterial,
606    ) -> Result<usize, KDFError> {
607        let bytes_written = HKDF::<H>::extract_and_expand_out(&KeyMaterialInternal::<0>::new(), key, additional_input, output_key.capacity(), output_key)?;
608        Ok(bytes_written)
609    }
610
611    /// As with [KDF::derive_key] and [KDF::derive_key_out],
612    /// This invokes HKDF in the extract_and_expand mode and maps the provided keys in the following way:
613    /// - The first (0'th) key is used as the salt for HKDF.extract.
614    /// - The remaining keys are concatenated to form HKDF's ikm parameter.
615    /// - Entropy of all provided keys are tracked to determine the output key's entropy.
616    ///
617    /// Therefore, derive_key_from_multiple(&[KeyMaterial0::new(), &key], &info) is equivalent to derive_key(&key, &info).
618    ///
619    /// This provides a fixed-length output, which may be truncated as needed.
620    fn derive_key_from_multiple(
621        self,
622        keys: &[&impl KeyMaterial],
623        additional_input: &[u8],
624    ) -> Result<Box<dyn KeyMaterial>, KDFError> {
625        let mut output_key = KeyMaterial512::new();
626        _ = self.derive_key_from_multiple_out(keys, additional_input, &mut output_key)?;
627        output_key.truncate(*min(&output_key.key_len(), &H::OUTPUT_LEN))?;
628        Ok(Box::new(output_key))
629    }
630
631
632    /// This behaves the same as [KDF::derive_key_from_multiple], except that it fills the provided
633    /// [KeyMaterial] object in place of exposing a Length parameter.
634    fn derive_key_from_multiple_out(
635        self,
636        keys: &[&impl KeyMaterial],
637        additional_input: &[u8],
638        output_key: &mut impl KeyMaterial,
639    ) -> Result<usize, KDFError> {
640        let mut hkdf = HKDF::<H>::new();
641        let mut entropy = HkdfEntropyTracker::<H>::new();
642
643        if keys.len() >= 1 {
644            hkdf.do_extract_init(keys[0])?;
645            entropy.credit_entropy(keys[0]);
646        } else {
647            hkdf.do_extract_init(&KeyMaterial0::new())?;
648        };
649
650        if keys.len() != 0 {
651            for key in &keys[1..] {
652                hkdf.do_extract_update_bytes(key.ref_to_bytes())?;
653                entropy.credit_entropy(*key);
654            }
655        }
656        let mut prk = KeyMaterialInternal::<HMAC_BLOCK_LEN>::new();
657        _ = hkdf.do_extract_final_out(&mut prk)?;
658        let bytes_written = HKDF::<H>::expand_out(&prk, additional_input, output_key.capacity(), output_key)?;
659
660        output_key.allow_hazardous_operations();
661        output_key.set_key_type(entropy.get_output_key_type())?;
662        output_key.set_security_strength(min(&SecurityStrength::from_bytes(output_key.key_len()), &entropy.security_strength).clone())?;
663        output_key.drop_hazardous_operations();
664
665        Ok(bytes_written)
666    }
667
668    fn max_security_strength(&self) -> SecurityStrength {
669        H::default().max_security_strength()
670    }
671}