use super::{noxtls_sha1, noxtls_sha256, noxtls_sha384, noxtls_sha512, Digest, Sha256};
use crate::internal_alloc::Vec;
#[derive(Clone)]
pub(crate) struct HmacSha256Context {
inner_template: Sha256,
outer_template: Sha256,
}
impl HmacSha256Context {
pub(crate) fn new(key: &[u8]) -> Self {
let mut key_block = [0_u8; 64];
if key.len() > key_block.len() {
key_block[..32].copy_from_slice(&noxtls_sha256(key));
} else {
key_block[..key.len()].copy_from_slice(key);
}
let mut ipad = [0x36_u8; 64];
let mut opad = [0x5c_u8; 64];
for i in 0..64 {
ipad[i] ^= key_block[i];
opad[i] ^= key_block[i];
}
let mut inner_template = Sha256::noxtls_new();
inner_template.noxtls_update(&ipad);
let mut outer_template = Sha256::noxtls_new();
outer_template.noxtls_update(&opad);
Self {
inner_template,
outer_template,
}
}
pub(crate) fn finalize_parts(&self, parts: &[&[u8]]) -> [u8; 32] {
let mut inner = self.inner_template.clone();
for part in parts {
inner.noxtls_update(part);
}
let inner_hash = inner.finalize_array();
let mut outer = self.outer_template.clone();
outer.noxtls_update(&inner_hash);
outer.finalize_array()
}
}
#[must_use]
pub fn noxtls_hmac_sha1(key: &[u8], data: &[u8]) -> [u8; 20] {
hmac_with_block(key, data, 64, HashVariant::Sha1)
.try_into()
.expect("hmac-sha1 output is always 20 bytes")
}
#[must_use]
pub fn noxtls_hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
noxtls_hmac_sha256_parts(key, &[data])
}
pub(crate) fn noxtls_hmac_sha256_parts(key: &[u8], parts: &[&[u8]]) -> [u8; 32] {
let mut key_block = [0_u8; 64];
if key.len() > key_block.len() {
key_block[..32].copy_from_slice(&noxtls_sha256(key));
} else {
key_block[..key.len()].copy_from_slice(key);
}
let mut ipad = [0x36_u8; 64];
let mut opad = [0x5c_u8; 64];
for i in 0..64 {
ipad[i] ^= key_block[i];
opad[i] ^= key_block[i];
}
let mut inner = Sha256::noxtls_new();
inner.noxtls_update(&ipad);
for part in parts {
inner.noxtls_update(part);
}
let inner_hash = inner.finalize_array();
let mut outer = Sha256::noxtls_new();
outer.noxtls_update(&opad);
outer.noxtls_update(&inner_hash);
outer.finalize_array()
}
#[must_use]
pub fn noxtls_hmac_sha512(key: &[u8], data: &[u8]) -> [u8; 64] {
hmac_with_block(key, data, 128, HashVariant::Sha512)
.try_into()
.expect("hmac-sha512 output is always 64 bytes")
}
#[must_use]
pub fn noxtls_hmac_sha384(key: &[u8], data: &[u8]) -> [u8; 48] {
hmac_with_block(key, data, 128, HashVariant::Sha384)
.try_into()
.expect("hmac-sha384 output is always 48 bytes")
}
#[derive(Copy, Clone)]
enum HashVariant {
Sha1,
Sha384,
Sha512,
}
fn hmac_with_block(key: &[u8], data: &[u8], block_size: usize, variant: HashVariant) -> Vec<u8> {
let mut k0 = vec![0_u8; block_size];
if key.len() > block_size {
let digest = match variant {
HashVariant::Sha1 => noxtls_sha1(key).to_vec(),
HashVariant::Sha384 => noxtls_sha384(key).to_vec(),
HashVariant::Sha512 => noxtls_sha512(key).to_vec(),
};
k0[..digest.len()].copy_from_slice(&digest);
} else {
k0[..key.len()].copy_from_slice(key);
}
let mut ipad = vec![0x36_u8; block_size];
let mut opad = vec![0x5c_u8; block_size];
for i in 0..block_size {
ipad[i] ^= k0[i];
opad[i] ^= k0[i];
}
let mut inner = ipad;
inner.extend_from_slice(data);
let inner_hash = match variant {
HashVariant::Sha1 => noxtls_sha1(&inner).to_vec(),
HashVariant::Sha384 => noxtls_sha384(&inner).to_vec(),
HashVariant::Sha512 => noxtls_sha512(&inner).to_vec(),
};
let mut outer = opad;
outer.extend_from_slice(&inner_hash);
match variant {
HashVariant::Sha1 => noxtls_sha1(&outer).to_vec(),
HashVariant::Sha384 => noxtls_sha384(&outer).to_vec(),
HashVariant::Sha512 => noxtls_sha512(&outer).to_vec(),
}
}
#[cfg(test)]
mod tests {
use super::{
noxtls_hmac_sha256, noxtls_hmac_sha256_parts, noxtls_hmac_sha384, HmacSha256Context,
};
use crate::internal_alloc::Vec;
fn decode_hex(hex: &str) -> Vec<u8> {
assert_eq!(hex.len() % 2, 0, "hex string must have even length");
(0..hex.len())
.step_by(2)
.map(|index| {
u8::from_str_radix(&hex[index..index + 2], 16)
.expect("hex test vector should be valid")
})
.collect()
}
#[test]
fn noxtls_hmac_sha256_matches_rfc4231_case_1() {
let key = vec![0x0b_u8; 20];
let data = b"Hi There";
let expected_full =
decode_hex("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
let actual = noxtls_hmac_sha256(&key, data);
assert_eq!(actual.as_slice(), expected_full.as_slice());
}
#[test]
fn noxtls_hmac_sha256_matches_tls13_empty_psk_extract_case() {
let key = [0_u8; 32];
let data: [u8; 0] = [];
let expected =
decode_hex("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad");
let actual = noxtls_hmac_sha256(&key, &data);
assert_eq!(actual.as_slice(), expected.as_slice());
}
#[test]
fn noxtls_hmac_sha384_matches_rfc4231_case_1() {
let key = vec![0x0b_u8; 20];
let data = b"Hi There";
let expected = decode_hex(
"afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6",
);
let actual = noxtls_hmac_sha384(&key, data);
assert_eq!(actual.as_slice(), expected.as_slice());
}
#[test]
fn hmac_sha256_context_matches_parts_helper() {
let key = b"hkdf reusable key";
let context = HmacSha256Context::new(key);
let parts: [&[u8]; 3] = [b"prefix", b"middle", b"suffix"];
let expected = noxtls_hmac_sha256_parts(key, &parts);
let actual = context.finalize_parts(&parts);
assert_eq!(actual, expected);
}
}