#![forbid(unsafe_code)]
#![cfg_attr(docsrs, doc(cfg(not(fips))))]
#![cfg(not(fips))]
use core::marker::PhantomData;
use aranya_buggy::BugExt;
use generic_array::GenericArray;
use typenum::{Prod, U255};
use crate::{
hash::Hash,
hmac::{Hmac, Tag},
kdf::{KdfError, Prk},
keys::SecretKeyBytes,
};
pub type MaxOutput<D> = Prod<U255, D>;
pub struct Hkdf<H>(PhantomData<H>);
impl<H: Hash> 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 zero = GenericArray::<u8, H::DigestSize>::default();
let salt = if salt.is_empty() { &zero } else { 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,
I::Item: AsRef<[u8]>,
I::IntoIter: Clone,
{
if out.len() > Self::MAX_OUTPUT {
return Err(KdfError::OutputTooLong);
}
let expander = Hmac::<H>::new(prk.as_bytes());
let info = info.into_iter();
let mut prev: Option<Tag<H::DigestSize>> = None;
for (i, chunk) in out.chunks_mut(H::DIGEST_SIZE).enumerate() {
let mut expander = expander.clone();
if let Some(prev) = prev {
expander.update(prev.as_bytes());
}
for s in info.clone() {
expander.update(s.as_ref());
}
let next = i.checked_add(1).assume("i + 1 must not wrap")?;
expander.update(&[next as u8]);
let tag = expander.tag();
chunk.copy_from_slice(&tag.as_bytes()[..chunk.len()]);
prev = Some(tag);
}
Ok(())
}
}
#[cfg_attr(feature = "hazmat", macro_export)]
#[cfg_attr(docsrs, doc(cfg(feature = "hazmat")))]
macro_rules! hkdf_impl {
($name:ident, $doc_name:expr, $hash:ident) => {
#[doc = concat!($doc_name, ".")]
pub struct $name;
impl $crate::kdf::Kdf for $name {
const ID: $crate::kdf::KdfId = $crate::kdf::KdfId::$name;
type MaxOutput = $crate::hkdf::MaxOutput<<$hash as $crate::hash::Hash>::DigestSize>;
type PrkSize = <$hash as $crate::hash::Hash>::DigestSize;
fn extract_multi<I>(ikm: I, salt: &[u8]) -> $crate::kdf::Prk<Self::PrkSize>
where
I: ::core::iter::IntoIterator,
I::Item: ::core::convert::AsRef<[u8]>,
{
$crate::hkdf::Hkdf::<$hash>::extract_multi(ikm, salt)
}
fn expand_multi<I>(
out: &mut [u8],
prk: &$crate::kdf::Prk<Self::PrkSize>,
info: I,
) -> Result<(), $crate::kdf::KdfError>
where
I: ::core::iter::IntoIterator,
I::Item: ::core::convert::AsRef<[u8]>,
I::IntoIter: ::core::clone::Clone,
{
$crate::hkdf::Hkdf::<$hash>::expand_multi(out, prk, info)
}
}
};
}
pub(crate) use hkdf_impl;
#[cfg(test)]
#[allow(clippy::wildcard_imports)]
mod tests {
macro_rules! hkdf_tests {
() => {
use crate::test_util::test_kdf;
hkdf_impl!(HkdfSha256, "HKDF-SHA256", Sha256);
hkdf_impl!(HkdfSha384, "HKDF-SHA384", Sha384);
hkdf_impl!(HkdfSha512, "HKDF-SHA512", Sha512);
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!();
}
}