use std::marker::PhantomData;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::classic::crypto_kdf::{
crypto_kdf_hkdf_sha256_expand, crypto_kdf_hkdf_sha256_extract, crypto_kdf_hkdf_sha512_expand,
crypto_kdf_hkdf_sha512_extract,
};
use crate::constants::{
CRYPTO_KDF_HKDF_SHA256_BYTES_MAX, CRYPTO_KDF_HKDF_SHA256_BYTES_MIN,
CRYPTO_KDF_HKDF_SHA256_KEYBYTES, CRYPTO_KDF_HKDF_SHA512_BYTES_MAX,
CRYPTO_KDF_HKDF_SHA512_BYTES_MIN, CRYPTO_KDF_HKDF_SHA512_KEYBYTES,
};
use crate::error::Error;
use crate::types::*;
pub type HkdfSha256Prk = StackByteArray<CRYPTO_KDF_HKDF_SHA256_KEYBYTES>;
pub type HkdfSha512Prk = StackByteArray<CRYPTO_KDF_HKDF_SHA512_KEYBYTES>;
pub type HkdfSha256 = Hkdf<HkdfSha256Variant, HkdfSha256Prk, CRYPTO_KDF_HKDF_SHA256_KEYBYTES>;
pub type HkdfSha512 = Hkdf<HkdfSha512Variant, HkdfSha512Prk, CRYPTO_KDF_HKDF_SHA512_KEYBYTES>;
#[cfg_attr(
feature = "serde",
derive(Zeroize, Clone, Debug, Serialize, Deserialize)
)]
#[cfg_attr(not(feature = "serde"), derive(Zeroize, Clone, Debug))]
pub struct Hkdf<Variant, Prk, const PRK_LENGTH: usize>
where
Variant: HkdfVariant<PRK_LENGTH>,
Prk: ByteArray<PRK_LENGTH> + Zeroize + ZeroizeOnDrop,
{
prk: Prk,
_variant: PhantomData<Variant>,
}
pub type HkdfSha256Expander<Prk> = Hkdf<HkdfSha256Variant, Prk, CRYPTO_KDF_HKDF_SHA256_KEYBYTES>;
pub type HkdfSha512Expander<Prk> = Hkdf<HkdfSha512Variant, Prk, CRYPTO_KDF_HKDF_SHA512_KEYBYTES>;
#[derive(Clone, Copy, Debug, Default)]
pub struct HkdfSha256Variant;
#[derive(Clone, Copy, Debug, Default)]
pub struct HkdfSha512Variant;
#[cfg(any(all(feature = "protected", any(unix, windows)), all(doc, not(doctest))))]
#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
pub mod protected {
use super::*;
pub use crate::protected::*;
pub type HkdfSha256Prk = HeapByteArray<CRYPTO_KDF_HKDF_SHA256_KEYBYTES>;
pub type HkdfSha512Prk = HeapByteArray<CRYPTO_KDF_HKDF_SHA512_KEYBYTES>;
pub type LockedHkdfSha256 = HkdfSha256Expander<Locked<HkdfSha256Prk>>;
pub type LockedHkdfSha512 = HkdfSha512Expander<Locked<HkdfSha512Prk>>;
}
pub trait HkdfVariant<const PRK_LENGTH: usize> {
type Prk: NewByteArray<PRK_LENGTH> + Zeroize + ZeroizeOnDrop;
const OUTPUT_BYTES_MIN: usize;
const OUTPUT_BYTES_MAX: usize;
fn extract(prk: &mut [u8; PRK_LENGTH], salt: Option<&[u8]>, ikm: &[u8]);
fn expand(output: &mut [u8], context: &[u8], prk: &[u8; PRK_LENGTH]) -> Result<(), Error>;
fn validate_output_len(output_len: usize) -> Result<(), Error> {
if output_len < Self::OUTPUT_BYTES_MIN || output_len > Self::OUTPUT_BYTES_MAX {
Err(dryoc_error!(format!(
"invalid output length {}, should be at least {} and no more than {}",
output_len,
Self::OUTPUT_BYTES_MIN,
Self::OUTPUT_BYTES_MAX
)))
} else {
Ok(())
}
}
}
macro_rules! impl_hkdf_variant {
(
$variant:ty,
$prk_len:expr,
$prk:ty,
$bytes_min:expr,
$bytes_max:expr,
$extract:path,
$expand:path
) => {
impl HkdfVariant<$prk_len> for $variant {
type Prk = $prk;
const OUTPUT_BYTES_MAX: usize = $bytes_max;
const OUTPUT_BYTES_MIN: usize = $bytes_min;
fn extract(prk: &mut [u8; $prk_len], salt: Option<&[u8]>, ikm: &[u8]) {
$extract(prk, salt, ikm);
}
fn expand(
output: &mut [u8],
context: &[u8],
prk: &[u8; $prk_len],
) -> Result<(), Error> {
$expand(output, context, prk)
}
}
};
}
impl_hkdf_variant!(
HkdfSha256Variant,
CRYPTO_KDF_HKDF_SHA256_KEYBYTES,
HkdfSha256Prk,
CRYPTO_KDF_HKDF_SHA256_BYTES_MIN,
CRYPTO_KDF_HKDF_SHA256_BYTES_MAX,
crypto_kdf_hkdf_sha256_extract,
crypto_kdf_hkdf_sha256_expand
);
impl_hkdf_variant!(
HkdfSha512Variant,
CRYPTO_KDF_HKDF_SHA512_KEYBYTES,
HkdfSha512Prk,
CRYPTO_KDF_HKDF_SHA512_BYTES_MIN,
CRYPTO_KDF_HKDF_SHA512_BYTES_MAX,
crypto_kdf_hkdf_sha512_extract,
crypto_kdf_hkdf_sha512_expand
);
impl<Variant, Prk, const PRK_LENGTH: usize> Hkdf<Variant, Prk, PRK_LENGTH>
where
Variant: HkdfVariant<PRK_LENGTH>,
Prk: NewByteArray<PRK_LENGTH> + Zeroize + ZeroizeOnDrop,
{
pub fn generate() -> Self {
Self {
prk: Prk::generate(),
_variant: PhantomData,
}
}
#[deprecated(note = "use generate() instead")]
pub fn r#gen() -> Self {
Self::generate()
}
pub fn extract<Salt: Bytes + ?Sized, Ikm: Bytes + ?Sized>(
salt: Option<&Salt>,
ikm: &Ikm,
) -> Self {
let mut prk = Prk::new_byte_array();
Variant::extract(
prk.as_mut_array(),
salt.map(|s| s.as_slice()),
ikm.as_slice(),
);
Self {
prk,
_variant: PhantomData,
}
}
pub fn extract_and_expand<
const OUTPUT_LENGTH: usize,
Salt: Bytes + ?Sized,
Ikm: Bytes + ?Sized,
Context: Bytes + ?Sized,
Output: NewByteArray<OUTPUT_LENGTH>,
>(
salt: Option<&Salt>,
ikm: &Ikm,
context: &Context,
) -> Result<Output, Error> {
Self::extract(salt, ikm).expand(context)
}
pub fn extract_and_expand_to_vec<
Salt: Bytes + ?Sized,
Ikm: Bytes + ?Sized,
Context: Bytes + ?Sized,
>(
output_len: usize,
salt: Option<&Salt>,
ikm: &Ikm,
context: &Context,
) -> Result<Vec<u8>, Error> {
Self::extract(salt, ikm).expand_to_vec(output_len, context)
}
pub fn extract_and_expand_to_bytes<
Salt: Bytes + ?Sized,
Ikm: Bytes + ?Sized,
Context: Bytes + ?Sized,
Output: NewBytes + ResizableBytes,
>(
output_len: usize,
salt: Option<&Salt>,
ikm: &Ikm,
context: &Context,
) -> Result<Output, Error> {
Self::extract(salt, ikm).expand_to_bytes(output_len, context)
}
}
impl<Variant, Prk, const PRK_LENGTH: usize> Hkdf<Variant, Prk, PRK_LENGTH>
where
Variant: HkdfVariant<PRK_LENGTH>,
Prk: ByteArray<PRK_LENGTH> + Zeroize + ZeroizeOnDrop,
{
pub fn from_prk(prk: Prk) -> Self {
Self {
prk,
_variant: PhantomData,
}
}
pub fn into_prk(self) -> Prk {
self.prk
}
pub fn expand<const OUTPUT_LENGTH: usize, Context: Bytes + ?Sized, Output>(
&self,
context: &Context,
) -> Result<Output, Error>
where
Output: NewByteArray<OUTPUT_LENGTH>,
{
Variant::validate_output_len(OUTPUT_LENGTH)?;
let mut output = Output::new_byte_array();
Variant::expand(
output.as_mut_slice(),
context.as_slice(),
self.prk.as_array(),
)?;
Ok(output)
}
pub fn expand_to_vec<Context: Bytes + ?Sized>(
&self,
output_len: usize,
context: &Context,
) -> Result<Vec<u8>, Error> {
self.expand_to_bytes(output_len, context)
}
pub fn expand_to_bytes<Context: Bytes + ?Sized, Output: NewBytes + ResizableBytes>(
&self,
output_len: usize,
context: &Context,
) -> Result<Output, Error> {
Variant::validate_output_len(output_len)?;
let mut output = Output::new_bytes();
output.resize(output_len, 0);
Variant::expand(
output.as_mut_slice(),
context.as_slice(),
self.prk.as_array(),
)?;
Ok(output)
}
}
impl<Variant, const PRK_LENGTH: usize> Hkdf<Variant, Variant::Prk, PRK_LENGTH>
where
Variant: HkdfVariant<PRK_LENGTH>,
{
pub fn generate_with_defaults() -> Self {
Self::generate()
}
#[deprecated(note = "use generate_with_defaults() instead")]
pub fn gen_with_defaults() -> Self {
Self::generate_with_defaults()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hkdf_sha256() {
let hkdf = HkdfSha256::extract(Some(b"salt"), b"input keying material");
let output: HkdfSha256Prk = hkdf.expand(b"context").expect("expand failed");
assert_eq!(output.len(), CRYPTO_KDF_HKDF_SHA256_KEYBYTES);
let output = hkdf.expand_to_vec(42, b"context").expect("expand failed");
assert_eq!(output.len(), 42);
}
#[test]
fn test_hkdf_sha512() {
let output: Vec<u8> =
HkdfSha512::extract_and_expand_to_vec(64, Some(b"salt"), b"ikm", b"context")
.expect("expand failed");
assert_eq!(output.len(), 64);
}
#[test]
fn test_hkdf_rejects_invalid_length() {
let hkdf = HkdfSha256::extract(None::<&[u8]>, b"ikm");
hkdf.expand_to_vec(
crate::constants::CRYPTO_KDF_HKDF_SHA256_BYTES_MAX + 1,
b"context",
)
.expect_err("oversized output should fail");
}
#[test]
fn test_hkdf_rejects_huge_length_before_allocation() {
let hkdf = HkdfSha256::extract(None::<&[u8]>, b"ikm");
hkdf.expand_to_vec(usize::MAX, b"context")
.expect_err("huge output should fail before allocation");
}
#[test]
fn test_hkdf_variant_generic_api() {
fn extract_and_expand_with_variant<Variant, const PRK_LENGTH: usize>(
salt: Option<&[u8]>,
ikm: &[u8],
context: &[u8],
) -> Vec<u8>
where
Variant: HkdfVariant<PRK_LENGTH>,
{
Hkdf::<Variant, Variant::Prk, PRK_LENGTH>::extract_and_expand_to_vec(
42, salt, ikm, context,
)
.expect("expand failed")
}
let generic_output = extract_and_expand_with_variant::<
HkdfSha256Variant,
CRYPTO_KDF_HKDF_SHA256_KEYBYTES,
>(Some(b"salt"), b"input keying material", b"context");
let concrete_output = HkdfSha256::extract_and_expand_to_vec(
42,
Some(b"salt"),
b"input keying material",
b"context",
)
.expect("expand failed");
assert_eq!(generic_output, concrete_output);
}
}