| // Copyright 2023 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| //! Implements the HMAC-based Key Derivation Function from | |
| //! <https://datatracker.ietf.org/doc/html/rfc5869>. | |
| //! | |
| //! One-shot operation: | |
| //! | |
| //! ``` | |
| //! use bssl_crypto::{hkdf, hkdf::HkdfSha256}; | |
| //! | |
| //! let key: [u8; 32] = HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"), | |
| //! b"info"); | |
| //! ``` | |
| //! | |
| //! If deriving several keys that vary only in the `info` parameter, then part | |
| //! of the computation can be shared by calculating the "pseudo-random key". | |
| //! This is purely a performance optimisation. | |
| //! | |
| //! ``` | |
| //! use bssl_crypto::{hkdf, hkdf::HkdfSha256}; | |
| //! | |
| //! let prk = HkdfSha256::extract(b"secret", hkdf::Salt::NonEmpty(b"salt")); | |
| //! let key1 : [u8; 32] = prk.expand(b"info1"); | |
| //! let key2 : [u8; 32] = prk.expand(b"info2"); | |
| //! | |
| //! assert_eq!(key1, HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"), | |
| //! b"info1")); | |
| //! assert_eq!(key2, HkdfSha256::derive(b"secret", hkdf::Salt::NonEmpty(b"salt"), | |
| //! b"info2")); | |
| //! ``` | |
| //! | |
| //! The above examples assume that the size of the outputs is known at compile | |
| //! time. (And only output lengths less than 256 bytes are supported.) | |
| //! | |
| //! ```compile_fail | |
| //! use bssl_crypto::{hkdf, hkdf::HkdfSha256}; | |
| //! | |
| //! let key: [u8; 256] = HkdfSha256::derive(b"secret", hkdf::Salt::None, b"info"); | |
| //! ``` | |
| //! | |
| //! To use HKDF with longer, or run-time, lengths, use `derive_into` and | |
| //! `extract_into`: | |
| //! | |
| //! ``` | |
| //! use bssl_crypto::{hkdf, hkdf::HkdfSha256}; | |
| //! | |
| //! let mut out = [0u8; 50]; | |
| //! HkdfSha256::derive_into(b"secret", hkdf::Salt::None, b"info", &mut out).expect( | |
| //! "HKDF can't produce that much"); | |
| //! | |
| //! assert_eq!(out, HkdfSha256::derive(b"secret", hkdf::Salt::None, b"info")); | |
| //! ``` | |
| //! | |
| //! To expand output from the explicit bytes of a PRK, use `Prk::new`: | |
| //! | |
| //! ``` | |
| //! use bssl_crypto::{digest::Sha256, digest::Algorithm, hkdf}; | |
| //! | |
| //! let prk: [u8; Sha256::OUTPUT_LEN] = bssl_crypto::rand_array(); | |
| //! // unwrap: only fails if the input is not equal to the digest length, which | |
| //! // cannot happen here. | |
| //! let prk = hkdf::Prk::new::<Sha256>(&prk).unwrap(); | |
| //! let mut out = vec![0u8; 42]; | |
| //! prk.expand_into(b"info", &mut out)?; | |
| //! # Ok::<(), hkdf::TooLong>(()) | |
| //! ``` | |
| use crate::{digest, sealed, with_output_array, FfiMutSlice, FfiSlice, ForeignTypeRef}; | |
| use core::marker::PhantomData; | |
| /// Implementation of HKDF-SHA-256 | |
| pub type HkdfSha256 = Hkdf<digest::Sha256>; | |
| /// Implementation of HKDF-SHA-512 | |
| pub type HkdfSha512 = Hkdf<digest::Sha512>; | |
| /// Error type returned when too much output is requested from an HKDF operation. | |
| #[derive(Debug)] | |
| pub struct TooLong; | |
| /// HKDF's optional salt values. See <https://datatracker.ietf.org/doc/html/rfc5869#section-3.1> | |
| pub enum Salt<'a> { | |
| /// No salt. | |
| None, | |
| /// An explicit salt. Note that an empty value here is interpreted the same | |
| /// as if passing `None`. | |
| NonEmpty(&'a [u8]), | |
| } | |
| impl Salt<'_> { | |
| fn as_ffi_ptr(&self) -> *const u8 { | |
| match self { | |
| Salt::None => core::ptr::null(), | |
| Salt::NonEmpty(salt) => salt.as_ffi_ptr(), | |
| } | |
| } | |
| fn len(&self) -> usize { | |
| match self { | |
| Salt::None => 0, | |
| Salt::NonEmpty(salt) => salt.len(), | |
| } | |
| } | |
| } | |
| /// HKDF for any of the implemented hash functions. The aliases [`HkdfSha256`] | |
| /// and [`HkdfSha512`] are provided for the most common cases. | |
| pub struct Hkdf<MD: digest::Algorithm>(PhantomData<MD>); | |
| impl<MD: digest::Algorithm> Hkdf<MD> { | |
| /// The maximum number of bytes of key material that can be produced. | |
| pub const MAX_OUTPUT_LEN: usize = MD::OUTPUT_LEN * 255; | |
| /// Derive key material from the given secret, salt, and info. Attempting | |
| /// to derive more than 255 bytes is a compile-time error, see `derive_into` | |
| /// for longer outputs. | |
| /// | |
| /// The semantics of the arguments are complex. See | |
| /// <https://datatracker.ietf.org/doc/html/rfc5869#section-3>. | |
| pub fn derive<const N: usize>(secret: &[u8], salt: Salt, info: &[u8]) -> [u8; N] { | |
| Self::extract(secret, salt).expand(info) | |
| } | |
| /// Derive key material from the given secret, salt, and info. Attempting | |
| /// to derive more than `MAX_OUTPUT_LEN` bytes is a run-time error. | |
| /// | |
| /// The semantics of the arguments are complex. See | |
| /// <https://datatracker.ietf.org/doc/html/rfc5869#section-3>. | |
| pub fn derive_into( | |
| secret: &[u8], | |
| salt: Salt, | |
| info: &[u8], | |
| out: &mut [u8], | |
| ) -> Result<(), TooLong> { | |
| Self::extract(secret, salt).expand_into(info, out) | |
| } | |
| /// Extract a pseudo-random key from the given secret and salt. This can | |
| /// be used to avoid redoing computation when computing several keys that | |
| /// vary only in the `info` parameter. | |
| pub fn extract(secret: &[u8], salt: Salt) -> Prk { | |
| let mut prk = [0u8; bssl_sys::EVP_MAX_MD_SIZE as usize]; | |
| let mut prk_len = 0usize; | |
| let evp_md = MD::get_md(sealed::SealedType).as_ptr(); | |
| unsafe { | |
| // Safety: `EVP_MAX_MD_SIZE` is the maximum output size of | |
| // `HKDF_extract` so it'll never overrun the buffer. | |
| bssl_sys::HKDF_extract( | |
| prk.as_mut_ffi_ptr(), | |
| &mut prk_len, | |
| evp_md, | |
| secret.as_ffi_ptr(), | |
| secret.len(), | |
| salt.as_ffi_ptr(), | |
| salt.len(), | |
| ); | |
| } | |
| // This is documented to be always be true. | |
| assert!(prk_len <= prk.len()); | |
| Prk { | |
| prk, | |
| len: prk_len, | |
| evp_md, | |
| } | |
| } | |
| } | |
| /// A pseudo-random key, an intermediate value in the HKDF computation. | |
| pub struct Prk { | |
| prk: [u8; bssl_sys::EVP_MAX_MD_SIZE as usize], | |
| len: usize, | |
| evp_md: *const bssl_sys::EVP_MD, | |
| } | |
| // Safety: `EVP_MD`s are actually thread-safe because it is a descriptor of | |
| // digest algorithm input and output specification plus a virtual table. | |
| // It will remain read-only throughout program lifetime. | |
| unsafe impl Sync for Prk {} | |
| // Safety: the `EVP_MD` descriptor is constructed once and shared through-out | |
| // the program lifetime. | |
| unsafe impl Send for Prk {} | |
| #[allow(clippy::let_unit_value, clippy::unwrap_used)] | |
| impl Prk { | |
| /// Creates a Prk from bytes. | |
| pub fn new<MD: digest::Algorithm>(prk_bytes: &[u8]) -> Option<Self> { | |
| if prk_bytes.len() != MD::OUTPUT_LEN { | |
| return None; | |
| } | |
| let mut prk = [0u8; bssl_sys::EVP_MAX_MD_SIZE as usize]; | |
| prk.get_mut(..MD::OUTPUT_LEN) | |
| // unwrap: `EVP_MAX_MD_SIZE` must be greater than the length of any | |
| // digest function thus this is always successful. | |
| .unwrap() | |
| .copy_from_slice(prk_bytes); | |
| Some(Prk { | |
| prk, | |
| len: MD::OUTPUT_LEN, | |
| evp_md: MD::get_md(sealed::SealedType).as_ptr(), | |
| }) | |
| } | |
| /// Returns the bytes of the pseudorandom key. | |
| pub fn as_bytes(&self) -> &[u8] { | |
| self.prk | |
| .get(..self.len) | |
| // unwrap:`self.len` must be less than the length of `self.prk` thus | |
| // this is always in bounds. | |
| .unwrap() | |
| } | |
| /// Derive key material for the given info parameter. Attempting | |
| /// to derive more than 255 bytes is a compile-time error, see `expand_into` | |
| /// for longer outputs. | |
| pub fn expand<const N: usize>(&self, info: &[u8]) -> [u8; N] { | |
| // This is the odd way to write a static assertion that uses a const | |
| // parameter in Rust. Even then, Rust cannot reference `MAX_OUTPUT_LEN`. | |
| // But if we safely assume that all hash functions output at least a | |
| // byte then 255 is a safe lower bound on `MAX_OUTPUT_LEN`. | |
| // A doctest at the top of the module checks that this assert is effective. | |
| struct StaticAssert<const N: usize, const BOUND: usize>; | |
| impl<const N: usize, const BOUND: usize> StaticAssert<N, BOUND> { | |
| const BOUNDS_CHECK: () = assert!(N < BOUND, "Large outputs not supported"); | |
| } | |
| let _ = StaticAssert::<N, 256>::BOUNDS_CHECK; | |
| unsafe { | |
| with_output_array(|out, out_len| { | |
| // Safety: `HKDF_expand` writes exactly `out_len` bytes or else | |
| // returns zero. `evp_md` is valid by construction. | |
| let result = bssl_sys::HKDF_expand( | |
| out, | |
| out_len, | |
| self.evp_md, | |
| self.prk.as_ffi_ptr(), | |
| self.len, | |
| info.as_ffi_ptr(), | |
| info.len(), | |
| ); | |
| // The output length is known to be within bounds so the only other | |
| // possibility is an allocation failure, which we don't attempt to | |
| // handle. | |
| assert_eq!(result, 1); | |
| }) | |
| } | |
| } | |
| /// Derive key material from the given info parameter. Attempting | |
| /// to derive more than the HKDF's `MAX_OUTPUT_LEN` bytes is a run-time | |
| /// error. | |
| pub fn expand_into(&self, info: &[u8], out: &mut [u8]) -> Result<(), TooLong> { | |
| // Safety: writes at most `out.len()` bytes into `out`. | |
| // `evp_md` is valid by construction. | |
| let result = unsafe { | |
| bssl_sys::HKDF_expand( | |
| out.as_mut_ffi_ptr(), | |
| out.len(), | |
| self.evp_md, | |
| self.prk.as_ffi_ptr(), | |
| self.len, | |
| info.as_ffi_ptr(), | |
| info.len(), | |
| ) | |
| }; | |
| if result == 1 { | |
| Ok(()) | |
| } else { | |
| Err(TooLong) | |
| } | |
| } | |
| } | |
| #[cfg(test)] | |
| #[allow( | |
| clippy::expect_used, | |
| clippy::panic, | |
| clippy::indexing_slicing, | |
| clippy::unwrap_used | |
| )] | |
| mod tests { | |
| use crate::{ | |
| digest::Sha256, | |
| hkdf::{HkdfSha256, HkdfSha512, Prk, Salt}, | |
| test_helpers::{decode_hex, decode_hex_into_vec}, | |
| }; | |
| #[test] | |
| fn sha256() { | |
| let ikm = decode_hex_into_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); | |
| let salt_vec = decode_hex_into_vec("000102030405060708090a0b0c"); | |
| let salt = Salt::NonEmpty(&salt_vec); | |
| let info = decode_hex_into_vec("f0f1f2f3f4f5f6f7f8f9"); | |
| let okm: [u8; 42] = HkdfSha256::derive(ikm.as_slice(), salt, info.as_slice()); | |
| let expected = decode_hex( | |
| "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865", | |
| ); | |
| assert_eq!(okm, expected); | |
| } | |
| #[test] | |
| fn sha512() { | |
| let ikm = decode_hex_into_vec("5d3db20e8238a90b62a600fa57fdb318"); | |
| let salt_vec = decode_hex_into_vec("1d6f3b38a1e607b5e6bcd4af1800a9d3"); | |
| let salt = Salt::NonEmpty(&salt_vec); | |
| let info = decode_hex_into_vec("2bc5f39032b6fc87da69ba8711ce735b169646fd"); | |
| let okm: [u8; 42] = HkdfSha512::derive(ikm.as_slice(), salt, info.as_slice()); | |
| let expected = decode_hex( | |
| "8c3cf7122dcb5eb7efaf02718f1faf70bca20dcb75070e9d0871a413a6c05fc195a75aa9ffc349d70aae", | |
| ); | |
| assert_eq!(okm, expected); | |
| } | |
| // Test Vectors from https://tools.ietf.org/html/rfc5869. | |
| #[test] | |
| fn rfc5869_sha256() { | |
| struct Test { | |
| ikm: Vec<u8>, | |
| salt: Vec<u8>, | |
| info: Vec<u8>, | |
| prk: Vec<u8>, | |
| okm: Vec<u8>, | |
| } | |
| let tests = [ | |
| Test { | |
| // Test Case 1 | |
| ikm: decode_hex_into_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), | |
| salt: decode_hex_into_vec("000102030405060708090a0b0c"), | |
| info: decode_hex_into_vec("f0f1f2f3f4f5f6f7f8f9"), | |
| prk: decode_hex_into_vec( | |
| "077709362c2e32df0ddc3f0dc47bba63\ | |
| 90b6c73bb50f9c3122ec844ad7c2b3e5", | |
| ), | |
| okm: decode_hex_into_vec("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865") | |
| }, | |
| Test { | |
| // Test Case 2 | |
| ikm: decode_hex_into_vec( | |
| "000102030405060708090a0b0c0d0e0f\ | |
| 101112131415161718191a1b1c1d1e1f\ | |
| 202122232425262728292a2b2c2d2e2f\ | |
| 303132333435363738393a3b3c3d3e3f\ | |
| 404142434445464748494a4b4c4d4e4f", | |
| ), | |
| salt: decode_hex_into_vec( | |
| "606162636465666768696a6b6c6d6e6f\ | |
| 707172737475767778797a7b7c7d7e7f\ | |
| 808182838485868788898a8b8c8d8e8f\ | |
| 909192939495969798999a9b9c9d9e9f\ | |
| a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", | |
| ), | |
| info: decode_hex_into_vec( | |
| "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf\ | |
| c0c1c2c3c4c5c6c7c8c9cacbcccdcecf\ | |
| d0d1d2d3d4d5d6d7d8d9dadbdcdddedf\ | |
| e0e1e2e3e4e5e6e7e8e9eaebecedeeef\ | |
| f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", | |
| ), | |
| prk: decode_hex_into_vec( | |
| "06a6b88c5853361a06104c9ceb35b45c\ | |
| ef760014904671014a193f40c15fc244", | |
| ), | |
| okm: decode_hex_into_vec( | |
| "b11e398dc80327a1c8e7f78c596a4934\ | |
| 4f012eda2d4efad8a050cc4c19afa97c\ | |
| 59045a99cac7827271cb41c65e590e09\ | |
| da3275600c2f09b8367793a9aca3db71\ | |
| cc30c58179ec3e87c14c01d5c1f3434f\ | |
| 1d87", | |
| ) | |
| }, | |
| Test { | |
| // Test Case 3 | |
| ikm: decode_hex_into_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), | |
| salt: Vec::new(), | |
| info: Vec::new(), | |
| prk: decode_hex_into_vec( | |
| "19ef24a32c717b167f33a91d6f648bdf\ | |
| 96596776afdb6377ac434c1c293ccb04", | |
| ), | |
| okm: decode_hex_into_vec( | |
| "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"), | |
| }, | |
| ]; | |
| for Test { | |
| ikm, | |
| salt, | |
| info, | |
| prk, | |
| okm, | |
| } in tests.iter() | |
| { | |
| let salt = if salt.is_empty() { | |
| Salt::None | |
| } else { | |
| Salt::NonEmpty(&salt) | |
| }; | |
| let mut okm2 = vec![0u8; okm.len()]; | |
| assert!( | |
| HkdfSha256::derive_into(ikm.as_slice(), salt, info.as_slice(), &mut okm2).is_ok() | |
| ); | |
| assert_eq!(okm2.as_slice(), okm.as_slice()); | |
| let prk2 = Prk::new::<Sha256>(prk.as_slice()).unwrap(); | |
| assert_eq!(prk2.as_bytes(), prk.as_slice()); | |
| let mut okm3 = vec![0u8; okm.len()]; | |
| let _ = prk2.expand_into(info.as_slice(), &mut okm3); | |
| assert_eq!(okm3.as_slice(), okm.as_slice()); | |
| } | |
| } | |
| #[test] | |
| fn max_output() { | |
| let hkdf = HkdfSha256::extract(b"", Salt::None); | |
| let mut longest = vec![0u8; HkdfSha256::MAX_OUTPUT_LEN]; | |
| assert!(hkdf.expand_into(b"", &mut longest).is_ok()); | |
| let mut too_long = vec![0u8; HkdfSha256::MAX_OUTPUT_LEN + 1]; | |
| assert!(hkdf.expand_into(b"", &mut too_long).is_err()); | |
| } | |
| #[test] | |
| fn wrong_prk_len() { | |
| assert!(Prk::new::<Sha256>( | |
| decode_hex_into_vec("077709362c2e32df0ddc3f0dc47bba63").as_slice() | |
| ) | |
| .is_none()); | |
| assert!(Prk::new::<Sha256>( | |
| decode_hex_into_vec("077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e590b6c73bb50f9c3122ec844ad7c2b3e5").as_slice()) | |
| .is_none() | |
| ); | |
| } | |
| } |