#![forbid(unsafe_code)]
use core::{fmt, marker::PhantomData};
use generic_array::GenericArray;
use typenum::{Prod, U255};
use crate::{
block::BlockSize,
hash::Hash,
hmac::{Hmac, HmacKey},
kdf::{KdfError, Prk},
keys::SecretKeyBytes,
};
pub type MaxOutput<D> = Prod<U255, D>;
pub struct Hkdf<H>(PhantomData<fn() -> H>);
impl<H: Hash + BlockSize> Hkdf<H> {
pub const MAX_OUTPUT: usize = 255 * H::DIGEST_SIZE;
pub const PRK_SIZE: usize = H::DIGEST_SIZE;
#[inline]
pub fn extract(ikm: &[u8], salt: &[u8]) -> Prk<H::DigestSize> {
Self::extract_multi(&[ikm], salt)
}
#[inline]
pub fn extract_multi<I>(ikm: I, salt: &[u8]) -> Prk<H::DigestSize>
where
I: IntoIterator,
I::Item: AsRef<[u8]>,
{
let salt = if salt.is_empty() {
let zero = GenericArray::<u8, H::DigestSize>::default();
HmacKey::new(zero.as_slice())
} else {
HmacKey::new(salt)
};
let prk = Hmac::<H>::mac_multi(&salt, ikm).into_array();
Prk::new(SecretKeyBytes::new(prk))
}
#[inline]
pub fn expand(out: &mut [u8], prk: &Prk<H::DigestSize>, info: &[u8]) -> Result<(), KdfError> {
Self::expand_multi(out, prk, &[info])
}
pub fn expand_multi<I>(
out: &mut [u8],
prk: &Prk<H::DigestSize>,
info: I,
) -> Result<(), KdfError>
where
I: IntoIterator<Item: AsRef<[u8]>, IntoIter: Clone>,
{
if out.len() > Self::MAX_OUTPUT {
return Err(KdfError::OutputTooLong);
}
let key = HmacKey::<H>::new(prk.as_bytes());
let expander = Hmac::<H>::new(&key);
let info = info.into_iter();
let mut n = 0u8;
let mut chunks = out.chunks_exact_mut(H::DIGEST_SIZE);
let mut prev = None;
for chunk in chunks.by_ref() {
n = n.wrapping_add(1);
let mut expander = expander.clone();
if let Some(prev) = prev {
expander.update(prev);
}
info.clone().for_each(|s| {
expander.update(s.as_ref());
});
expander.update(&[n]);
let tag = expander.tag();
chunk.copy_from_slice(tag.as_bytes());
prev = Some(chunk);
}
let chunk = chunks.into_remainder();
if !chunk.is_empty() {
let mut expander = expander.clone();
if let Some(prev) = prev {
expander.update(prev);
}
info.clone().for_each(|s| {
expander.update(s.as_ref());
});
expander.update(&[n.wrapping_add(1)]);
let tag = expander.tag();
chunk.copy_from_slice(&tag.as_bytes()[..chunk.len()]);
}
Ok(())
}
}
impl<H> fmt::Debug for Hkdf<H> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Hkdf").finish()
}
}
#[macro_export]
macro_rules! hkdf_impl {
(
$name:ident, $doc_name:expr, $hash:ident
$(, oid = $oid:ident )?
$(, kdf_id = $kdf_id:expr)?
$(,)?
) => {
#[doc = concat!($doc_name, ".")]
#[derive(Copy, Clone, Debug)]
pub struct $name;
impl $crate::kdf::Kdf for $name {
type MaxOutput = $crate::hkdf::MaxOutput<<$hash as $crate::hash::Hash>::DigestSize>;
type PrkSize = <$hash as $crate::hash::Hash>::DigestSize;
fn extract_multi<'a, I>(ikm: I, salt: &[u8]) -> $crate::kdf::Prk<Self::PrkSize>
where
I: ::core::iter::IntoIterator<Item = &'a [u8]>,
{
$crate::hkdf::Hkdf::<$hash>::extract_multi(ikm, salt)
}
fn expand_multi<'a, I>(
out: &mut [u8],
prk: &$crate::kdf::Prk<Self::PrkSize>,
info: I,
) -> Result<(), $crate::kdf::KdfError>
where
I: ::core::iter::IntoIterator<Item = &'a [u8]>,
I::IntoIter: ::core::clone::Clone,
{
$crate::hkdf::Hkdf::<$hash>::expand_multi(out, prk, info)
}
}
$(impl $crate::oid::Identified for $name {
const OID: &$crate::oid::Oid = $oid;
})?
$(impl $crate::hpke::HpkeKdf for $name {
const ID: $crate::hpke::KdfId = $kdf_id;
})?
};
}
pub(crate) use hkdf_impl;
#[cfg(test)]
#[allow(clippy::wildcard_imports)]
mod tests {
macro_rules! hkdf_tests {
() => {
use crate::{
hpke::KdfId,
oid::consts::{HKDF_WITH_SHA2_256, HKDF_WITH_SHA2_384, HKDF_WITH_SHA2_512},
test_util::test_kdf,
};
hkdf_impl!(
HkdfSha256,
"HKDF-SHA256",
Sha256,
oid = HKDF_WITH_SHA2_256,
kdf_id = KdfId::HkdfSha256,
);
hkdf_impl!(
HkdfSha384,
"HKDF-SHA384",
Sha384,
oid = HKDF_WITH_SHA2_384,
kdf_id = KdfId::HkdfSha384,
);
hkdf_impl!(
HkdfSha512,
"HKDF-SHA512",
Sha512,
oid = HKDF_WITH_SHA2_512,
kdf_id = KdfId::HkdfSha512,
);
test_kdf!(hkdf_sha256, HkdfSha256, HkdfTest::HkdfSha256);
test_kdf!(hkdf_sha384, HkdfSha384, HkdfTest::HkdfSha384);
test_kdf!(hkdf_sha512, HkdfSha512, HkdfTest::HkdfSha512);
};
}
#[cfg(feature = "bearssl")]
mod bearssl {
use crate::bearssl::{Sha256, Sha384, Sha512};
hkdf_tests!();
}
mod rust {
use crate::rust::{Sha256, Sha384, Sha512};
hkdf_tests!();
}
}