use crate::{HmacAlgorithm, HmacProvider};
#[derive(Debug, PartialEq, Eq)]
pub enum HkdfError {
OutputTooLong,
}
pub trait HkdfProvider: HmacProvider {
fn hkdf_extract(
&mut self,
alg: <Self as HmacProvider>::Algorithm,
salt: Option<&[u8]>,
ikm: &[u8],
) -> Result<impl AsRef<[u8]> + use<Self>, HkdfError>;
fn hkdf_expand(
&mut self,
alg: <Self as HmacProvider>::Algorithm,
prk: &[u8],
info: &[u8],
okm: &mut [u8],
) -> Result<(), HkdfError>;
fn hkdf(
&mut self,
alg: <Self as HmacProvider>::Algorithm,
salt: Option<&[u8]>,
ikm: &[u8],
info: &[u8],
okm: &mut [u8],
) -> Result<(), HkdfError> {
let prk = self.hkdf_extract(alg.clone(), salt, ikm)?;
self.hkdf_expand(alg, prk.as_ref(), info, okm)
}
}
impl<H: HmacProvider> HkdfProvider for H {
fn hkdf_extract(
&mut self,
alg: <Self as HmacProvider>::Algorithm,
salt: Option<&[u8]>,
ikm: &[u8],
) -> Result<impl AsRef<[u8]> + use<H>, HkdfError> {
let mut zero_salt = <<H as HmacProvider>::Algorithm as HmacAlgorithm>::MaxLenBuf::default();
let zero_salt = zero_salt.as_mut();
let hash_len = alg.len();
debug_assert!(
hash_len <= zero_salt.len(),
"algorithm length is longer than type's announced maximum HMAC length"
);
let salt_bytes = salt.unwrap_or(&zero_salt[..hash_len]);
Ok(self.hmac_with_keydata(alg, salt_bytes, ikm))
}
fn hkdf_expand(
&mut self,
alg: <Self as HmacProvider>::Algorithm,
prk: &[u8],
info: &[u8],
okm: &mut [u8],
) -> Result<(), HkdfError> {
let hash_len = alg.len();
if okm.len() > 255 * hash_len {
return Err(HkdfError::OutputTooLong);
}
let mut t = <<H as HmacProvider>::Algorithm as HmacAlgorithm>::MaxLenBuf::default();
let t = t.as_mut();
debug_assert!(
hash_len <= t.len(),
"algorithm length is longer than type's announced maximum HMAC length"
);
let mut t_len = 0usize;
let mut pos = 0usize;
while pos < okm.len() {
let counter = (pos / hash_len + 1) as u8;
let mut state = self.init_with_keydata(alg.clone(), prk);
if t_len > 0 {
HmacProvider::update(self, &mut state, &t[..t_len]);
}
HmacProvider::update(self, &mut state, info);
HmacProvider::update(self, &mut state, &[counter]);
let result = HmacProvider::finalize(self, state);
let result_bytes = result.as_ref();
debug_assert_eq!(
result_bytes.len(),
hash_len,
"algorithm did not produce its announced fixed length as output"
);
t[..hash_len].copy_from_slice(result_bytes);
t_len = hash_len;
let take = (okm.len() - pos).min(hash_len);
okm[pos..pos + take].copy_from_slice(&t[..take]);
pos += take;
}
Ok(())
}
}