use super::digest::MAX_OUTPUT_LEN;
use super::hash::Hash;
use super::hmac::{self, Hmac, Keyed};
use crate::{Error, aead};
#[derive(Debug)]
pub struct Algorithm(&'static hmac::Algorithm);
pub static HKDF_SHA1: Algorithm = Algorithm(&hmac::HMAC_SHA1);
pub static HKDF_SHA256: Algorithm = Algorithm(&hmac::HMAC_SHA256);
pub static HKDF_SHA384: Algorithm = Algorithm(&hmac::HMAC_SHA384);
pub static HKDF_SHA512: Algorithm = Algorithm(&hmac::HMAC_SHA512);
pub struct Salt(hmac::Key);
impl Salt {
pub fn new(algorithm: &'static Algorithm, value: &[u8]) -> Self {
Self(hmac::Key::new(algorithm.0, value))
}
pub fn extract(&self, secret: &[u8]) -> Prk {
let prk = hmac::sign(&self.0, secret);
Prk(hmac::Key::new(self.0.algorithm(), prk.as_ref()))
}
}
pub struct Prk(hmac::Key);
impl Prk {
pub fn expand<'a, L: KeyType>(&'a self, info: &'a [&'a [u8]], len: L) -> Okm<'a, L> {
Okm {
prk: self,
info,
len,
}
}
fn expand_into(&self, info: &[&[u8]], out: &mut [u8]) {
fn expand<H: Hash>(hmac: &Hmac<H>, info: &[&[u8]], out: &mut [u8]) {
let mut block = H::zeroed_output();
let hash_len = block.as_ref().len();
for (counter, chunk) in (1..=255u8).zip(out.chunks_mut(hash_len)) {
let mut ctx = hmac.clone();
if counter > 1 {
ctx.update(&block);
}
for part in info {
ctx.update(part);
}
ctx.update([counter]);
block = ctx.finish();
chunk.copy_from_slice(&block.as_ref()[..chunk.len()]);
}
}
match &self.0.0 {
Keyed::Sha1(hmac) => expand(hmac, info, out),
Keyed::Sha256(hmac) => expand(hmac, info, out),
Keyed::Sha384(hmac) => expand(hmac, info, out),
Keyed::Sha512(hmac) => expand(hmac, info, out),
}
}
}
mod sealed {
#[allow(clippy::len_without_is_empty)]
pub trait Len {
fn len(&self) -> usize;
}
}
pub trait KeyType: sealed::Len {}
impl KeyType for usize {}
impl sealed::Len for usize {
fn len(&self) -> usize {
*self
}
}
impl KeyType for &'static aead::Algorithm {}
impl sealed::Len for &'static aead::Algorithm {
fn len(&self) -> usize {
self.key_len()
}
}
impl KeyType for &'static aead::quic::Algorithm {}
impl sealed::Len for &'static aead::quic::Algorithm {
fn len(&self) -> usize {
self.key_len()
}
}
impl KeyType for &'static hmac::Algorithm {}
impl sealed::Len for &'static hmac::Algorithm {
fn len(&self) -> usize {
self.tag_len()
}
}
pub struct Okm<'a, L: KeyType> {
prk: &'a Prk,
info: &'a [&'a [u8]],
len: L,
}
impl Okm<'_, usize> {
pub fn fill(self, out: &mut [u8]) -> Result<(), Error> {
if out.len() != self.len || out.len() > 255 * self.prk.0.algorithm().tag_len() {
return Err(Error::WrongLength);
}
self.prk.expand_into(self.info, out);
Ok(())
}
}
impl<L: KeyType> Okm<'_, L> {
fn into_key<K>(self, key: impl FnOnce(&L, &[u8]) -> K) -> K {
let mut buf = [0u8; MAX_OUTPUT_LEN];
let bytes = &mut buf[..self.len.len()];
self.prk.expand_into(self.info, bytes);
key(&self.len, bytes)
}
}
impl From<Okm<'_, &'static aead::Algorithm>> for aead::Key {
fn from(okm: Okm<'_, &'static aead::Algorithm>) -> Self {
okm.into_key(|algorithm, bytes| Self::new(algorithm, bytes))
.expect("HKDF output is the algorithm's key length")
}
}
impl From<Okm<'_, &'static aead::quic::Algorithm>> for aead::quic::HeaderProtectionKey {
fn from(okm: Okm<'_, &'static aead::quic::Algorithm>) -> Self {
okm.into_key(|algorithm, bytes| Self::new(algorithm, bytes))
.expect("HKDF output is the algorithm's key length")
}
}
impl From<Okm<'_, &'static hmac::Algorithm>> for hmac::Key {
fn from(okm: Okm<'_, &'static hmac::Algorithm>) -> Self {
okm.into_key(|algorithm, bytes| Self::new(algorithm, bytes))
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use crate::aead::{AES_128_GCM, AES_256_GCM, CHACHA20_POLY1305, Nonce, quic};
#[test]
fn rfc5869() {
let cases = [
(
&HKDF_SHA256,
"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
"000102030405060708090a0b0c",
"f0f1f2f3f4f5f6f7f8f9",
"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5",
"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf\
34007208d5b887185865",
),
(
&HKDF_SHA256,
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\
202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f\
404142434445464748494a4b4c4d4e4f",
"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f\
808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f\
a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf\
d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef\
f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
"06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244",
"b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c\
59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71\
cc30c58179ec3e87c14c01d5c1f3434f1d87",
),
(
&HKDF_SHA256,
"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
"",
"",
"19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04",
"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d\
9d201395faa4b61a96c8",
),
(
&HKDF_SHA1,
"0b0b0b0b0b0b0b0b0b0b0b",
"000102030405060708090a0b0c",
"f0f1f2f3f4f5f6f7f8f9",
"9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243",
"085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2\
c22e422478d305f3f896",
),
(
&HKDF_SHA1,
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\
202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f\
404142434445464748494a4b4c4d4e4f",
"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f\
808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f\
a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf\
d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef\
f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
"8adae09a2a307059478d309b26c4115a224cfaf6",
"0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe\
8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e\
927336d0441f4c4300e2cff0d0900b52d3b4",
),
(
&HKDF_SHA1,
"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
"",
"",
"da8c8a73c7fa77288ec6f5e7c297786aa0d32d01",
"0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0\
ea00033de03984d34918",
),
(
&HKDF_SHA1,
"0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
"",
"",
"2adccada18779e7c2077ad2eb19d3f3e731385dd",
"2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5\
673a081d70cce7acfc48",
),
];
for (algorithm, ikm, salt, info, prk, okm) in cases {
let got = Salt::new(algorithm, &hex::decode(salt).unwrap())
.extract(&hex::decode(ikm).unwrap());
let wanted = hmac::Key::new(algorithm.0, &hex::decode(prk).unwrap());
assert_eq!(
hmac::sign(&got.0, b"").as_ref(),
hmac::sign(&wanted, b"").as_ref()
);
let info = hex::decode(info).unwrap();
let mut out = vec![0u8; okm.len() / 2];
got.expand(&[&info], out.len()).fill(&mut out).unwrap();
assert_eq!(hex::encode(out), okm);
}
}
static ALGORITHMS: [&Algorithm; 4] = [&HKDF_SHA1, &HKDF_SHA256, &HKDF_SHA384, &HKDF_SHA512];
#[test]
fn lengths() {
for algorithm in ALGORITHMS {
let prk = Salt::new(algorithm, b"salt").extract(b"secret");
let max = 255 * algorithm.0.tag_len();
let mut out = vec![0u8; max];
prk.expand(&[], max).fill(&mut out).unwrap();
let mut long = vec![0u8; max + 1];
assert_eq!(
prk.expand(&[], max + 1).fill(&mut long),
Err(Error::WrongLength)
);
assert!(long.iter().all(|b| *b == 0));
for len in [0, max - 1, max + 1] {
let mut wrong = vec![0u8; len];
assert_eq!(
prk.expand(&[], max).fill(&mut wrong),
Err(Error::WrongLength)
);
assert!(wrong.iter().all(|b| *b == 0));
}
}
}
#[test]
fn keys_match_filled_bytes() {
let prk = Salt::new(&HKDF_SHA256, b"salt").extract(b"secret");
let info: [&[u8]; 2] = [b"label", b"context"];
for algorithm in [&AES_128_GCM, &AES_256_GCM, &CHACHA20_POLY1305] {
let mut bytes = vec![0u8; algorithm.key_len()];
prk.expand(&info, bytes.len()).fill(&mut bytes).unwrap();
let seal = |key: aead::Key| {
key.seal_in_place_separate_tag(
Nonce::assume_unique_for_key([0; 12]),
&[],
&mut [0u8; 16],
)
.unwrap()
};
assert_eq!(
seal(aead::Key::from(prk.expand(&info, algorithm))).as_ref(),
seal(aead::Key::new(algorithm, &bytes).unwrap()).as_ref(),
);
}
for algorithm in [&quic::AES_128, &quic::AES_256, &quic::CHACHA20] {
let mut bytes = vec![0u8; algorithm.key_len()];
prk.expand(&info, bytes.len()).fill(&mut bytes).unwrap();
let key = quic::HeaderProtectionKey::from(prk.expand(&info, algorithm));
assert_eq!(
key.mask(&[7; 16]),
quic::HeaderProtectionKey::new(algorithm, &bytes)
.unwrap()
.mask(&[7; 16]),
);
}
for algorithm in [
&hmac::HMAC_SHA1,
&hmac::HMAC_SHA256,
&hmac::HMAC_SHA384,
&hmac::HMAC_SHA512,
] {
let mut bytes = vec![0u8; algorithm.tag_len()];
prk.expand(&info, bytes.len()).fill(&mut bytes).unwrap();
let key = hmac::Key::from(prk.expand(&info, algorithm));
assert_eq!(key.algorithm().tag_len(), algorithm.tag_len());
assert_eq!(
hmac::sign(&key, b"message").as_ref(),
hmac::sign(&hmac::Key::new(algorithm, &bytes), b"message").as_ref()
);
}
}
#[test]
fn secret_ikm() {
let info: [&[u8]; 1] = [b"info"];
let derive = |algorithm: &'static Algorithm, ikm: &[u8]| {
let prk = Salt::new(algorithm, b"salt").extract(ikm);
let mut bytes = [0u8; 100];
prk.expand(&info, bytes.len()).fill(&mut bytes).unwrap();
let aead_key = aead::Key::from(prk.expand(&info, &AES_256_GCM));
let aead_tag = aead_key
.seal_in_place_separate_tag(Nonce::assume_unique_for_key([0; 12]), &[], &mut [])
.unwrap();
let hmac_key = hmac::Key::from(prk.expand(&info, &hmac::HMAC_SHA256));
let hmac_tag = hmac::sign(&hmac_key, b"message");
(
bytes,
aead_tag.as_ref().to_vec(),
hmac_tag.as_ref().to_vec(),
)
};
for algorithm in ALGORITHMS {
let ikm = [0x5a; 48];
let wanted = derive(algorithm, &ikm);
crate::low::ct::secret_slice(&ikm);
let (bytes, aead_tag, hmac_tag) = derive(algorithm, &ikm);
crate::low::ct::public_slice(&bytes);
crate::low::ct::public_slice(&aead_tag);
crate::low::ct::public_slice(&hmac_tag);
assert_eq!((bytes, aead_tag, hmac_tag), wanted);
}
}
}