use crate::{error::*, hmac};
pub fn extract_and_expand(
salt: &hmac::SigningKey,
secret: &[u8],
info: &[u8],
out: &mut [u8],
) -> Result<()> {
let prk = extract(salt, secret)?;
expand(&prk, info, out)?;
Ok(())
}
pub fn extract(salt: &hmac::SigningKey, secret: &[u8]) -> Result<hmac::SigningKey> {
let prk = hmac::sign(salt, secret)?;
Ok(hmac::SigningKey::new(salt.digest_algorithm(), prk.as_ref()))
}
pub fn expand(prk: &hmac::SigningKey, info: &[u8], out: &mut [u8]) -> Result<()> {
let mut derived =
nss::pk11::sym_key::hkdf_expand(prk.digest_alg, &prk.key_value, info, out.len())?;
out.swap_with_slice(&mut derived[0..out.len()]);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::digest;
#[test]
fn hkdf_produces_correct_result() {
let secret = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
let salt = hex::decode("000102030405060708090a0b0c").unwrap();
let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap();
let expected_out = hex::decode(
"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
)
.unwrap();
let salt = hmac::SigningKey::new(&digest::SHA256, &salt);
let mut out = vec![0u8; expected_out.len()];
extract_and_expand(&salt, &secret, &info, &mut out).unwrap();
assert_eq!(out, expected_out);
}
#[test]
fn hkdf_rejects_gigantic_salt() {
if (u32::MAX as usize) < usize::MAX {
let salt_bytes = vec![0; (u32::MAX as usize) + 1];
let salt = hmac::SigningKey {
digest_alg: &digest::SHA256,
key_value: salt_bytes,
};
let mut out = vec![0u8; 8];
assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err());
}
}
#[test]
fn hkdf_rejects_gigantic_secret() {
if (u32::MAX as usize) < usize::MAX {
let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
let secret = vec![0; (u32::MAX as usize) + 1];
let mut out = vec![0u8; 8];
assert!(extract_and_expand(&salt, secret.as_slice(), b"info", &mut out).is_err());
}
}
#[test]
fn hkdf_rejects_gigantic_output_buffers() {
let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
let mut out = vec![0u8; 8160 + 1]; assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err());
}
#[test]
fn hkdf_rejects_zero_length_output_buffer() {
let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
let mut out = vec![0u8; 0];
assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err());
}
#[test]
fn hkdf_can_produce_small_output() {
let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
let mut out = vec![0u8; 1];
assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_ok());
}
#[test]
fn hkdf_accepts_zero_length_info() {
let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
let mut out = vec![0u8; 32];
assert!(extract_and_expand(&salt, b"secret", b"", &mut out).is_ok());
}
#[test]
fn hkdf_expand_rejects_short_prk() {
let prk = hmac::SigningKey::new(&digest::SHA256, b"too short"); let mut out = vec![0u8; 8];
assert!(expand(&prk, b"info", &mut out).is_ok());
}
}