use crate::core::{AuthParams, AuthProvider, AuthSpec, VerifyMacResult};
use core::{convert::Infallible, marker::PhantomData};
use ctutils::CtEq;
use hmac::{EagerHash, Hmac, KeyInit, Mac, digest::OutputSizeUser};
#[derive(Debug, Clone, Copy, Default)]
pub struct HmacShaSpec<Sha>(PhantomData<Sha>);
impl<Sha> AuthSpec for HmacShaSpec<Sha>
where Sha: EagerHash
{
type KeySize = Sha::BlockSize;
type MacSize = <Sha::Core as OutputSizeUser>::OutputSize;
}
#[derive(Debug, Clone, Copy)]
pub struct HmacSha<Sha>(PhantomData<Sha>);
impl<Sha> Default for HmacSha<Sha> {
fn default() -> Self { Self(PhantomData) }
}
impl<Sha> AuthProvider for HmacSha<Sha>
where Sha: EagerHash
{
type Spec = HmacShaSpec<Sha>;
type SignError = Infallible;
type VerifyError = Infallible;
fn sign(
&self,
p: &AuthParams<Self::Spec>,
data: &[&[u8]],
) -> Result<crate::core::Mac<<Self::Spec as AuthSpec>::MacSize>, Self::SignError> {
let mut mac = <Hmac<Sha> as KeyInit>::new(&p.key);
for chunk in data {
mac.update(chunk);
}
Ok(mac.finalize().into_bytes().into())
}
fn verify(
&self,
p: &AuthParams<Self::Spec>,
data: &[&[u8]],
mac: &[u8],
) -> Result<VerifyMacResult, Infallible> {
let n = mac.len();
let mut verifier = <Hmac<Sha> as KeyInit>::new(&p.key);
for chunk in data {
verifier.update(chunk);
}
let actual_mac = verifier.finalize().into_bytes();
if n == 0 || n > actual_mac.len() {
return Ok(VerifyMacResult::InvalidMac);
}
if mac.ct_eq(&actual_mac[0..mac.len()]).into() {
if n < actual_mac.len() {
Ok(VerifyMacResult::OkButTruncated)
} else {
Ok(VerifyMacResult::Ok)
}
} else {
Ok(VerifyMacResult::InvalidMac)
}
}
}
#[cfg(test)]
mod tests {
use super::HmacSha;
use crate::core::{AuthParams, AuthProvider, VerifyMacResult};
use assert_matches::assert_matches;
use sha2::Sha256;
use std::vec;
type Provider = HmacSha<Sha256>;
#[test]
fn computes_and_accepts_valid_mac() {
let provider = Provider::default();
let params = AuthParams { key: [0x11_u8; 64].into() };
let data = b"ccsds-auth-payload";
let mac = provider.sign(¶ms, &[data]).unwrap();
assert_matches!(
provider.verify(¶ms, &[data], mac.as_slice()).unwrap(),
VerifyMacResult::Ok
);
}
#[test]
fn rejects_tampered_mac() {
let provider = Provider::default();
let params = AuthParams { key: [0x22_u8; 64].into() };
let data = b"ccsds-auth-payload";
let mut mac = provider.sign(¶ms, &[data]).unwrap();
mac[0] ^= 0x01;
let result = provider.verify(¶ms, &[data], mac.as_slice());
assert_matches!(result.unwrap(), VerifyMacResult::InvalidMac);
}
#[test]
fn accepts_truncated_mac() {
let provider = Provider::default();
let params = AuthParams { key: [0x22_u8; 64].into() };
let data = b"ccsds-auth-payload";
let mac = provider.sign(¶ms, &[data]).unwrap();
let result = provider.verify(¶ms, &[data], &mac.as_slice()[0..15]);
assert_matches!(result.unwrap(), VerifyMacResult::OkButTruncated);
}
#[test]
fn rejects_empty_mac() {
let provider = Provider::default();
let params = AuthParams { key: [0x22_u8; 64].into() };
let data = b"ccsds-auth-payload";
let result = provider.verify(¶ms, &[data], &[]);
assert_matches!(result.unwrap(), VerifyMacResult::InvalidMac);
}
#[test]
fn rejects_oversized_mac() {
let provider = Provider::default();
let params = AuthParams { key: [0x22_u8; 64].into() };
let data = b"ccsds-auth-payload";
let bad = vec![0_u8; 33];
let result = provider.verify(¶ms, &[data], &bad);
assert_matches!(result.unwrap(), VerifyMacResult::InvalidMac);
}
}