#![no_std]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use hmac::{
Hmac, SimpleHmac,
digest::{Output, OutputSizeUser, array::typenum::Unsigned},
};
mod errors;
mod hmac_impl;
pub use errors::{InvalidLength, InvalidPrkLength};
pub use hmac;
pub use hmac_impl::HmacImpl;
#[cfg(feature = "kdf")]
pub use kdf::{self, Kdf};
pub type HkdfExtract<H> = GenericHkdfExtract<Hmac<H>>;
pub type Hkdf<H> = GenericHkdf<Hmac<H>>;
pub type SimpleHkdfExtract<H> = GenericHkdfExtract<SimpleHmac<H>>;
pub type SimpleHkdf<H> = GenericHkdf<SimpleHmac<H>>;
#[derive(Clone, Debug)]
pub struct GenericHkdfExtract<H: HmacImpl> {
hmac: H,
}
impl<H: HmacImpl> GenericHkdfExtract<H> {
#[must_use]
pub fn new(salt: Option<&[u8]>) -> Self {
let default_salt = Output::<H>::default();
let salt = salt.unwrap_or(&default_salt);
let hmac = H::new_from_slice(salt);
Self { hmac }
}
pub fn input_ikm(&mut self, ikm: &[u8]) {
self.hmac.update(ikm);
}
#[allow(clippy::missing_panics_doc, reason = "PRK size is correct")]
pub fn finalize(self) -> (Output<H>, GenericHkdf<H>) {
let prk = self.hmac.finalize();
let hkdf = GenericHkdf::<H>::from_prk(&prk).expect("PRK size is correct");
(prk, hkdf)
}
}
#[cfg(feature = "kdf")]
impl<H: HmacImpl> Kdf for GenericHkdfExtract<H> {
fn derive_key(&self, secret: &[u8], info: &[u8], out: &mut [u8]) -> kdf::Result<()> {
let mut extract = self.clone();
extract.input_ikm(secret);
let (_, hkdf) = extract.finalize();
hkdf.expand(info, out).map_err(|_| kdf::Error)
}
}
#[derive(Clone, Debug)]
pub struct GenericHkdf<H: HmacImpl> {
hmac: H,
}
impl<H: HmacImpl> GenericHkdf<H> {
#[must_use]
pub fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self {
let (_, hkdf) = Self::extract(salt, ikm);
hkdf
}
pub fn from_prk(prk: &[u8]) -> Result<Self, InvalidPrkLength> {
let hash_len = <H as OutputSizeUser>::OutputSize::to_usize();
if prk.len() < hash_len {
return Err(InvalidPrkLength);
}
let hmac = H::new_from_slice(prk);
Ok(Self { hmac })
}
#[must_use]
pub fn extract(salt: Option<&[u8]>, ikm: &[u8]) -> (Output<H>, Self) {
let mut extract_ctx = GenericHkdfExtract::<H>::new(salt);
extract_ctx.input_ikm(ikm);
extract_ctx.finalize()
}
#[allow(clippy::missing_panics_doc, reason = "expect should not fail")]
pub fn expand_multi_info(
&self,
info_components: &[&[u8]],
okm: &mut [u8],
) -> Result<(), InvalidLength> {
let mut prev: Option<Output<H>> = None;
let chunk_len = <H as OutputSizeUser>::OutputSize::USIZE;
if okm.len() > chunk_len * 255 {
return Err(InvalidLength);
}
for (block_n, block) in okm.chunks_mut(chunk_len).enumerate() {
let mut hmac = self.hmac.clone();
if let Some(ref prev) = prev {
hmac.update(prev);
};
for info in info_components {
hmac.update(info);
}
hmac.update(&[u8::try_from(block_n).expect("should convert") + 1]);
let output = hmac.finalize();
let block_len = block.len();
block.copy_from_slice(&output[..block_len]);
prev = Some(output);
}
Ok(())
}
pub fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> {
self.expand_multi_info(&[info], okm)
}
}