blob: 973ed88af6c33280f6bdb3e54d227bfbdd9d6331 [file] [log] [blame]
/* Copyright (c) 2023, Google Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//! 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"));
//! ```
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::Sealed).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,
}
#[allow(clippy::let_unit_value)]
impl Prk {
/// 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
// possibily 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::{
hkdf::{HkdfSha256, HkdfSha512, 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>,
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"),
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",
),
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(),
okm: decode_hex_into_vec(
"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"),
},
];
for Test {
ikm,
salt,
info,
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());
}
}
#[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());
}
}