Skip to main content

bssl_crypto/
hkdf.rs

1/* Copyright 2023 The BoringSSL Authors
2 *
3 * Permission to use, copy, modify, and/or distribute this software for any
4 * purpose with or without fee is hereby granted, provided that the above
5 * copyright notice and this permission notice appear in all copies.
6 *
7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 */
15
16//! Implements the HMAC-based Key Derivation Function from
17//! <https://datatracker.ietf.org/doc/html/rfc5869>.
18//!
19//! One-shot operation:
20//!
21//! ```
22//! use bssl_crypto::{hkdf, hkdf::HkdfSha256};
23//!
24//! let key: [u8; 32] = HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"),
25//!                                        b"info");
26//! ```
27//!
28//! If deriving several keys that vary only in the `info` parameter, then part
29//! of the computation can be shared by calculating the "pseudo-random key".
30//! This is purely a performance optimisation.
31//!
32//! ```
33//! use bssl_crypto::{hkdf, hkdf::HkdfSha256};
34//!
35//! let prk = HkdfSha256::extract(b"secret", hkdf::Salt::NonEmpty(b"salt"));
36//! let key1 : [u8; 32] = prk.expand(b"info1");
37//! let key2 : [u8; 32] = prk.expand(b"info2");
38//!
39//! assert_eq!(key1, HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"),
40//!                                     b"info1"));
41//! assert_eq!(key2, HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"),
42//!                                     b"info2"));
43//! ```
44//!
45//! The above examples assume that the size of the outputs is known at compile
46//! time. (And only output lengths less than 256 bytes are supported.)
47//!
48//! ```compile_fail
49//! use bssl_crypto::{hkdf, hkdf::HkdfSha256};
50//!
51//! let key: [u8; 256] = HkdfSha256::derive(b"secret", hkdf::Salt::None, b"info");
52//! ```
53//!
54//! To use HKDF with longer, or run-time, lengths, use `derive_into` and
55//! `extract_into`:
56//!
57//! ```
58//! use bssl_crypto::{hkdf, hkdf::HkdfSha256};
59//!
60//! let mut out = [0u8; 50];
61//! HkdfSha256::derive_into(b"secret", hkdf::Salt::None, b"info", &mut out).expect(
62//!    "HKDF can't produce that much");
63//!
64//! assert_eq!(out, HkdfSha256::derive(b"secret", hkdf::Salt::None, b"info"));
65//! ```
66//!
67//! To expand output from the explicit bytes of a PRK, use `Prk::new`:
68//!
69//! ```
70//! use bssl_crypto::{digest::Sha256, digest::Algorithm, hkdf};
71//!
72//! let prk: [u8; Sha256::OUTPUT_LEN] = bssl_crypto::rand_array();
73//! // unwrap: only fails if the input is not equal to the digest length, which
74//! // cannot happen here.
75//! let prk = hkdf::Prk::new::<Sha256>(&prk).unwrap();
76//! let mut out = vec![0u8; 42];
77//! prk.expand_into(b"info", &mut out)?;
78//! # Ok::<(), hkdf::TooLong>(())
79//! ```
80
81use crate::{digest, sealed, with_output_array, FfiMutSlice, FfiSlice, ForeignTypeRef};
82use core::marker::PhantomData;
83
84/// Implementation of HKDF-SHA-256
85pub type HkdfSha256 = Hkdf<digest::Sha256>;
86
87/// Implementation of HKDF-SHA-512
88pub type HkdfSha512 = Hkdf<digest::Sha512>;
89
90/// Error type returned when too much output is requested from an HKDF operation.
91#[derive(Debug)]
92pub struct TooLong;
93
94/// HKDF's optional salt values. See <https://datatracker.ietf.org/doc/html/rfc5869#section-3.1>
95pub enum Salt<'a> {
96    /// No salt.
97    None,
98    /// An explicit salt. Note that an empty value here is interpreted the same
99    /// as if passing `None`.
100    NonEmpty(&'a [u8]),
101}
102
103impl Salt<'_> {
104    fn as_ffi_ptr(&self) -> *const u8 {
105        match self {
106            Salt::None => core::ptr::null(),
107            Salt::NonEmpty(salt) => salt.as_ffi_ptr(),
108        }
109    }
110
111    fn len(&self) -> usize {
112        match self {
113            Salt::None => 0,
114            Salt::NonEmpty(salt) => salt.len(),
115        }
116    }
117}
118
119/// HKDF for any of the implemented hash functions. The aliases [`HkdfSha256`]
120/// and [`HkdfSha512`] are provided for the most common cases.
121pub struct Hkdf<MD: digest::Algorithm>(PhantomData<MD>);
122
123impl<MD: digest::Algorithm> Hkdf<MD> {
124    /// The maximum number of bytes of key material that can be produced.
125    pub const MAX_OUTPUT_LEN: usize = MD::OUTPUT_LEN * 255;
126
127    /// Derive key material from the given secret, salt, and info. Attempting
128    /// to derive more than 255 bytes is a compile-time error, see `derive_into`
129    /// for longer outputs.
130    ///
131    /// The semantics of the arguments are complex. See
132    /// <https://datatracker.ietf.org/doc/html/rfc5869#section-3>.
133    pub fn derive<const N: usize>(secret: &[u8], salt: Salt, info: &[u8]) -> [u8; N] {
134        Self::extract(secret, salt).expand(info)
135    }
136
137    /// Derive key material from the given secret, salt, and info. Attempting
138    /// to derive more than `MAX_OUTPUT_LEN` bytes is a run-time error.
139    ///
140    /// The semantics of the arguments are complex. See
141    /// <https://datatracker.ietf.org/doc/html/rfc5869#section-3>.
142    pub fn derive_into(
143        secret: &[u8],
144        salt: Salt,
145        info: &[u8],
146        out: &mut [u8],
147    ) -> Result<(), TooLong> {
148        Self::extract(secret, salt).expand_into(info, out)
149    }
150
151    /// Extract a pseudo-random key from the given secret and salt. This can
152    /// be used to avoid redoing computation when computing several keys that
153    /// vary only in the `info` parameter.
154    pub fn extract(secret: &[u8], salt: Salt) -> Prk {
155        let mut prk = [0u8; bssl_sys::EVP_MAX_MD_SIZE as usize];
156        let mut prk_len = 0usize;
157        let evp_md = MD::get_md(sealed::Sealed).as_ptr();
158        unsafe {
159            // Safety: `EVP_MAX_MD_SIZE` is the maximum output size of
160            // `HKDF_extract` so it'll never overrun the buffer.
161            bssl_sys::HKDF_extract(
162                prk.as_mut_ffi_ptr(),
163                &mut prk_len,
164                evp_md,
165                secret.as_ffi_ptr(),
166                secret.len(),
167                salt.as_ffi_ptr(),
168                salt.len(),
169            );
170        }
171        // This is documented to be always be true.
172        assert!(prk_len <= prk.len());
173        Prk {
174            prk,
175            len: prk_len,
176            evp_md,
177        }
178    }
179}
180
181/// A pseudo-random key, an intermediate value in the HKDF computation.
182pub struct Prk {
183    prk: [u8; bssl_sys::EVP_MAX_MD_SIZE as usize],
184    len: usize,
185    evp_md: *const bssl_sys::EVP_MD,
186}
187
188#[allow(clippy::let_unit_value,
189        clippy::unwrap_used,
190)]
191impl Prk {
192    /// Creates a Prk from bytes.
193    pub fn new<MD: digest::Algorithm>(prk_bytes: &[u8]) -> Option<Self> {
194        if prk_bytes.len() != MD::OUTPUT_LEN {
195            return None;
196        }
197
198        let mut prk = [0u8; bssl_sys::EVP_MAX_MD_SIZE as usize];
199        prk.get_mut(..MD::OUTPUT_LEN)
200            // unwrap: `EVP_MAX_MD_SIZE` must be greater than the length of any
201            // digest function thus this is always successful.
202            .unwrap()
203            .copy_from_slice(prk_bytes);
204
205        Some(Prk {
206            prk,
207            len: MD::OUTPUT_LEN,
208            evp_md: MD::get_md(sealed::Sealed).as_ptr(),
209        })
210    }
211
212    /// Returns the bytes of the pseudorandom key.
213    pub fn as_bytes(&self) -> &[u8] {
214        self.prk.get(..self.len)
215        // unwrap:`self.len` must be less than the length of `self.prk` thus
216        // this is always in bounds.
217        .unwrap()
218    }
219
220    /// Derive key material for the given info parameter. Attempting
221    /// to derive more than 255 bytes is a compile-time error, see `expand_into`
222    /// for longer outputs.
223    pub fn expand<const N: usize>(&self, info: &[u8]) -> [u8; N] {
224        // This is the odd way to write a static assertion that uses a const
225        // parameter in Rust. Even then, Rust cannot reference `MAX_OUTPUT_LEN`.
226        // But if we safely assume that all hash functions output at least a
227        // byte then 255 is a safe lower bound on `MAX_OUTPUT_LEN`.
228        // A doctest at the top of the module checks that this assert is effective.
229        struct StaticAssert<const N: usize, const BOUND: usize>;
230        impl<const N: usize, const BOUND: usize> StaticAssert<N, BOUND> {
231            const BOUNDS_CHECK: () = assert!(N < BOUND, "Large outputs not supported");
232        }
233        let _ = StaticAssert::<N, 256>::BOUNDS_CHECK;
234
235        unsafe {
236            with_output_array(|out, out_len| {
237                // Safety: `HKDF_expand` writes exactly `out_len` bytes or else
238                // returns zero. `evp_md` is valid by construction.
239                let result = bssl_sys::HKDF_expand(
240                    out,
241                    out_len,
242                    self.evp_md,
243                    self.prk.as_ffi_ptr(),
244                    self.len,
245                    info.as_ffi_ptr(),
246                    info.len(),
247                );
248                // The output length is known to be within bounds so the only other
249                // possibily is an allocation failure, which we don't attempt to
250                // handle.
251                assert_eq!(result, 1);
252            })
253        }
254    }
255
256    /// Derive key material from the given info parameter. Attempting
257    /// to derive more than the HKDF's `MAX_OUTPUT_LEN` bytes is a run-time
258    /// error.
259    pub fn expand_into(&self, info: &[u8], out: &mut [u8]) -> Result<(), TooLong> {
260        // Safety: writes at most `out.len()` bytes into `out`.
261        // `evp_md` is valid by construction.
262        let result = unsafe {
263            bssl_sys::HKDF_expand(
264                out.as_mut_ffi_ptr(),
265                out.len(),
266                self.evp_md,
267                self.prk.as_ffi_ptr(),
268                self.len,
269                info.as_ffi_ptr(),
270                info.len(),
271            )
272        };
273        if result == 1 {
274            Ok(())
275        } else {
276            Err(TooLong)
277        }
278    }
279}
280
281#[cfg(test)]
282#[allow(
283    clippy::expect_used,
284    clippy::panic,
285    clippy::indexing_slicing,
286    clippy::unwrap_used
287)]
288mod tests {
289    use crate::{
290        digest::Sha256,
291        hkdf::{HkdfSha256, HkdfSha512, Prk, Salt},
292        test_helpers::{decode_hex, decode_hex_into_vec},
293    };
294
295    #[test]
296    fn sha256() {
297        let ikm = decode_hex_into_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
298        let salt_vec = decode_hex_into_vec("000102030405060708090a0b0c");
299        let salt = Salt::NonEmpty(&salt_vec);
300        let info = decode_hex_into_vec("f0f1f2f3f4f5f6f7f8f9");
301        let okm: [u8; 42] = HkdfSha256::derive(ikm.as_slice(), salt, info.as_slice());
302        let expected = decode_hex(
303            "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
304        );
305        assert_eq!(okm, expected);
306    }
307
308    #[test]
309    fn sha512() {
310        let ikm = decode_hex_into_vec("5d3db20e8238a90b62a600fa57fdb318");
311        let salt_vec = decode_hex_into_vec("1d6f3b38a1e607b5e6bcd4af1800a9d3");
312        let salt = Salt::NonEmpty(&salt_vec);
313        let info = decode_hex_into_vec("2bc5f39032b6fc87da69ba8711ce735b169646fd");
314        let okm: [u8; 42] = HkdfSha512::derive(ikm.as_slice(), salt, info.as_slice());
315        let expected = decode_hex(
316            "8c3cf7122dcb5eb7efaf02718f1faf70bca20dcb75070e9d0871a413a6c05fc195a75aa9ffc349d70aae",
317        );
318        assert_eq!(okm, expected);
319    }
320
321    // Test Vectors from https://tools.ietf.org/html/rfc5869.
322    #[test]
323    fn rfc5869_sha256() {
324        struct Test {
325            ikm: Vec<u8>,
326            salt: Vec<u8>,
327            info: Vec<u8>,
328            prk: Vec<u8>,
329            okm: Vec<u8>,
330        }
331        let tests = [
332            Test {
333                // Test Case 1
334                ikm: decode_hex_into_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
335                salt: decode_hex_into_vec("000102030405060708090a0b0c"),
336                info: decode_hex_into_vec("f0f1f2f3f4f5f6f7f8f9"),
337                prk: decode_hex_into_vec(
338                    "077709362c2e32df0ddc3f0dc47bba63\
339                    90b6c73bb50f9c3122ec844ad7c2b3e5",
340                ),
341                okm: decode_hex_into_vec("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865")
342            },
343            Test {
344                // Test Case 2
345                ikm: decode_hex_into_vec(
346                    "000102030405060708090a0b0c0d0e0f\
347                    101112131415161718191a1b1c1d1e1f\
348                    202122232425262728292a2b2c2d2e2f\
349                    303132333435363738393a3b3c3d3e3f\
350                    404142434445464748494a4b4c4d4e4f",
351                ),
352                salt: decode_hex_into_vec(
353                    "606162636465666768696a6b6c6d6e6f\
354                    707172737475767778797a7b7c7d7e7f\
355                    808182838485868788898a8b8c8d8e8f\
356                    909192939495969798999a9b9c9d9e9f\
357                    a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
358                ),
359                info: decode_hex_into_vec(
360                    "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf\
361                    c0c1c2c3c4c5c6c7c8c9cacbcccdcecf\
362                    d0d1d2d3d4d5d6d7d8d9dadbdcdddedf\
363                    e0e1e2e3e4e5e6e7e8e9eaebecedeeef\
364                    f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
365                ),
366                prk: decode_hex_into_vec(
367                    "06a6b88c5853361a06104c9ceb35b45c\
368                    ef760014904671014a193f40c15fc244",
369                ),
370                okm: decode_hex_into_vec(
371                    "b11e398dc80327a1c8e7f78c596a4934\
372                    4f012eda2d4efad8a050cc4c19afa97c\
373                    59045a99cac7827271cb41c65e590e09\
374                    da3275600c2f09b8367793a9aca3db71\
375                    cc30c58179ec3e87c14c01d5c1f3434f\
376                    1d87",
377                )
378            },
379            Test {
380                // Test Case 3
381                ikm: decode_hex_into_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
382                salt: Vec::new(),
383                info: Vec::new(),
384                prk: decode_hex_into_vec(
385                    "19ef24a32c717b167f33a91d6f648bdf\
386                    96596776afdb6377ac434c1c293ccb04",
387                ),
388                okm: decode_hex_into_vec(
389                    "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"),
390            },
391        ];
392
393        for Test {
394            ikm,
395            salt,
396            info,
397            prk,
398            okm,
399        } in tests.iter()
400        {
401            let salt = if salt.is_empty() {
402                Salt::None
403            } else {
404                Salt::NonEmpty(&salt)
405            };
406            let mut okm2 = vec![0u8; okm.len()];
407            assert!(
408                HkdfSha256::derive_into(ikm.as_slice(), salt, info.as_slice(), &mut okm2).is_ok()
409            );
410            assert_eq!(okm2.as_slice(), okm.as_slice());
411
412            let prk2 = Prk::new::<Sha256>(prk.as_slice()).unwrap();
413            assert_eq!(prk2.as_bytes(), prk.as_slice());
414            let mut okm3 = vec![0u8; okm.len()];
415            let _ = prk2.expand_into(info.as_slice(), &mut okm3);
416            assert_eq!(okm3.as_slice(), okm.as_slice());
417        }
418    }
419
420    #[test]
421    fn max_output() {
422        let hkdf = HkdfSha256::extract(b"", Salt::None);
423        let mut longest = vec![0u8; HkdfSha256::MAX_OUTPUT_LEN];
424        assert!(hkdf.expand_into(b"", &mut longest).is_ok());
425
426        let mut too_long = vec![0u8; HkdfSha256::MAX_OUTPUT_LEN + 1];
427        assert!(hkdf.expand_into(b"", &mut too_long).is_err());
428    }
429
430    #[test]
431    fn wrong_prk_len() {
432        assert!(Prk::new::<Sha256>(
433            decode_hex_into_vec("077709362c2e32df0ddc3f0dc47bba63").as_slice()
434        )
435        .is_none());
436        assert!(Prk::new::<Sha256>(
437            decode_hex_into_vec("077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e590b6c73bb50f9c3122ec844ad7c2b3e5").as_slice())
438            .is_none()
439        );
440    }
441}