#![forbid(unsafe_code)]
use core::{array::TryFromSliceError, cmp, fmt};
use ctutils::{Choice, CtEq};
use hybrid_array::{Array, ArraySize};
use zeroize::{ZeroizeOnDrop, Zeroizing};
use crate::{
block::{Block, BlockSize},
csprng::{Csprng, Random},
hash::{Digest, Hash},
import::{ExportError, Import, ImportError},
keys::{SecretKey, SecretKeyBytes},
};
#[derive(Clone, Debug)]
pub struct Hmac<H> {
ipad: H,
opad: H,
}
impl<H: Hash + BlockSize> Hmac<H> {
pub fn new(key: &HmacKey<H>) -> Self {
let mut key = Zeroizing::new(key.0.clone());
for v in key.iter_mut() {
*v ^= 0x36;
}
let mut ipad = H::new();
ipad.update(key.as_slice());
for v in key.iter_mut() {
*v ^= 0x36 ^ 0x5c;
}
let mut opad = H::new();
opad.update(key.as_slice());
Self { ipad, opad }
}
#[inline]
pub fn update(&mut self, data: &[u8]) {
self.ipad.update(data)
}
#[inline]
pub fn tag(mut self) -> Tag<H::DigestSize> {
let d = self.ipad.digest();
self.opad.update(&d);
Tag(self.opad.digest())
}
pub fn mac_multi<I>(key: &HmacKey<H>, data: I) -> Tag<H::DigestSize>
where
I: IntoIterator<Item: AsRef<[u8]>>,
{
let mut h = Self::new(key);
data.into_iter().for_each(|s| {
h.update(s.as_ref());
});
h.tag()
}
}
#[derive(Clone, Debug)]
#[repr(transparent)]
pub struct Tag<N: ArraySize>(Digest<N>);
impl<N: ArraySize> Tag<N> {
#[cfg(feature = "committing-aead")]
#[cfg_attr(docsrs, doc(cfg(feature = "committing-aead")))]
#[allow(clippy::len_without_is_empty)]
pub const fn len(&self) -> usize {
self.0.len()
}
#[doc(hidden)]
pub fn into_array(self) -> Array<u8, N> {
self.0.into_array()
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "hazmat")] {
impl<N: ArraySize> Tag<N> {
#[cfg_attr(docsrs, doc(cfg(feature = "hazmat")))]
pub const fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
} else {
impl<N: ArraySize> Tag<N> {
pub(crate) const fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
}
}
impl<N: ArraySize> CtEq for Tag<N> {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl<'a, N: ArraySize> TryFrom<&'a [u8]> for Tag<N> {
type Error = TryFromSliceError;
fn try_from(tag: &'a [u8]) -> Result<Self, Self::Error> {
let digest = Array::try_from(tag)?;
Ok(Self(Digest::new(digest)))
}
}
#[repr(transparent)]
#[derive(ZeroizeOnDrop)]
pub struct HmacKey<H: Hash + BlockSize>(Block<H>);
impl<H: Hash + BlockSize> HmacKey<H> {
pub fn new(key: &[u8]) -> Self {
let mut out = Block::<H>::default();
if key.len() <= out.len() {
out[..key.len()].copy_from_slice(key);
} else {
let d = H::hash(key);
let n = cmp::min(d.len(), out.len());
out[..n].copy_from_slice(&d[..n]);
};
Self(out)
}
}
impl<H: Hash + BlockSize> Clone for HmacKey<H> {
#[inline]
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<H: Hash + BlockSize> SecretKey for HmacKey<H> {
type Size = H::BlockSize;
#[inline]
fn try_export_secret(&self) -> Result<SecretKeyBytes<Self::Size>, ExportError> {
Ok(SecretKeyBytes::new(self.0.clone()))
}
}
impl<H: Hash + BlockSize> Random for HmacKey<H> {
fn random<R: Csprng>(rng: R) -> Self {
Self(Block::<H>::random(rng))
}
}
impl<H: Hash + BlockSize> Import<&[u8]> for HmacKey<H> {
#[inline]
fn import(data: &[u8]) -> Result<Self, ImportError> {
Ok(Self::new(data))
}
}
impl<H: Hash + BlockSize> CtEq for HmacKey<H> {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl<H: Hash + BlockSize> fmt::Debug for HmacKey<H> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HmacKey").finish_non_exhaustive()
}
}
#[macro_export]
macro_rules! hmac_impl {
($name:ident, $doc:expr, $hash:ident $(, $oid:ident)? $(,)?) => {
#[doc = concat!($doc, ".")]
#[derive(Clone, Debug)]
pub struct $name($crate::hmac::Hmac<$hash>);
impl $crate::mac::Mac for $name {
type Tag = $crate::hmac::Tag<Self::TagSize>;
type TagSize = <$hash as $crate::hash::Hash>::DigestSize;
type Key = $crate::hmac::HmacKey<$hash>;
type KeySize = <$hash as $crate::block::BlockSize>::BlockSize;
type MinKeySize = <$hash as $crate::hash::Hash>::DigestSize;
#[inline]
fn new(key: &Self::Key) -> Self {
Self($crate::hmac::Hmac::new(key))
}
#[inline]
fn try_new(key: &[u8]) -> ::core::result::Result<Self, $crate::keys::InvalidKey> {
use $crate::typenum::Unsigned;
if key.len() < Self::MinKeySize::USIZE {
::core::result::Result::Err($crate::keys::InvalidKey)
} else {
let key = $crate::hmac::HmacKey::<$hash>::new(key);
::core::result::Result::Ok(Self::new(&key))
}
}
#[inline]
fn update(&mut self, data: &[u8]) {
self.0.update(data)
}
#[inline]
fn tag(self) -> Self::Tag {
self.0.tag()
}
}
$(impl $crate::oid::Identified for $name {
const OID: &'static $crate::oid::Oid = $oid;
})?
};
}
pub(crate) use hmac_impl;
#[cfg(test)]
#[allow(clippy::wildcard_imports)]
mod tests {
macro_rules! hmac_tests {
() => {
use crate::{
oid::consts::{HMAC_WITH_SHA2_256, HMAC_WITH_SHA2_384, HMAC_WITH_SHA2_512},
test_util::test_mac,
};
hmac_impl!(HmacSha2_256, "HMAC-SHA256", Sha256, HMAC_WITH_SHA2_256);
hmac_impl!(HmacSha2_384, "HMAC-SHA384", Sha384, HMAC_WITH_SHA2_384);
hmac_impl!(HmacSha2_512, "HMAC-SHA512", Sha512, HMAC_WITH_SHA2_512);
test_mac!(hmac_sha256, HmacSha2_256, MacTest::HmacSha256);
test_mac!(hmac_sha384, HmacSha2_384, MacTest::HmacSha384);
test_mac!(hmac_sha512, HmacSha2_512, MacTest::HmacSha512);
};
}
#[cfg(feature = "bearssl")]
mod bearssl {
use crate::bearssl::{Sha256, Sha384, Sha512};
hmac_tests!();
}
mod rust {
use crate::rust::{Sha256, Sha384, Sha512};
hmac_tests!();
}
}