use crate::{Result, SodiumError};
use libc;
pub const KEYBYTES: usize = libsodium_sys::crypto_auth_hmacsha512256_KEYBYTES as usize;
pub const BYTES: usize = libsodium_sys::crypto_auth_hmacsha512256_BYTES as usize;
#[derive(Debug, Clone, Eq, PartialEq, zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
pub struct Key([u8; KEYBYTES]);
impl Key {
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != KEYBYTES {
return Err(SodiumError::InvalidInput(format!(
"key must be exactly {KEYBYTES} bytes"
)));
}
let mut key = [0u8; KEYBYTES];
key.copy_from_slice(bytes);
Ok(Key(key))
}
pub fn generate() -> Self {
let mut key = [0u8; KEYBYTES];
unsafe {
libsodium_sys::crypto_auth_hmacsha512256_keygen(key.as_mut_ptr());
}
Key(key)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl AsRef<[u8]> for Key {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for Key {
type Error = SodiumError;
fn try_from(bytes: &[u8]) -> std::result::Result<Self, Self::Error> {
Self::from_bytes(bytes)
}
}
impl From<[u8; KEYBYTES]> for Key {
fn from(bytes: [u8; KEYBYTES]) -> Self {
Key(bytes)
}
}
impl From<Key> for [u8; KEYBYTES] {
fn from(key: Key) -> Self {
key.0
}
}
pub struct State {
state: Box<libsodium_sys::crypto_auth_hmacsha512256_state>,
}
impl State {
pub fn new(key: &Key) -> Result<Self> {
let mut state: Box<libsodium_sys::crypto_auth_hmacsha512256_state> =
Box::new(unsafe { std::mem::zeroed() });
let result = unsafe {
libsodium_sys::crypto_auth_hmacsha512256_init(
state.as_mut(),
key.as_bytes().as_ptr(),
key.as_bytes().len() as libc::size_t,
)
};
if result != 0 {
return Err(SodiumError::OperationError(
"failed to initialize state".into(),
));
}
Ok(State { state })
}
pub fn update(&mut self, input: &[u8]) -> Result<()> {
let result = unsafe {
libsodium_sys::crypto_auth_hmacsha512256_update(
self.state.as_mut(),
input.as_ptr(),
input.len() as u64,
)
};
if result != 0 {
return Err(SodiumError::OperationError("failed to update state".into()));
}
Ok(())
}
pub fn finalize(&mut self) -> Result<[u8; BYTES]> {
let mut mac = [0u8; BYTES];
let result = unsafe {
libsodium_sys::crypto_auth_hmacsha512256_final(self.state.as_mut(), mac.as_mut_ptr())
};
if result != 0 {
return Err(SodiumError::OperationError(
"failed to finalize authentication".into(),
));
}
Ok(mac)
}
}
pub fn auth(input: &[u8], key: &Key) -> Result<[u8; BYTES]> {
let mut mac = [0u8; BYTES];
let result = unsafe {
libsodium_sys::crypto_auth_hmacsha512256(
mac.as_mut_ptr(),
input.as_ptr(),
input.len() as u64,
key.as_bytes().as_ptr(),
)
};
if result != 0 {
return Err(SodiumError::OperationError("authentication failed".into()));
}
Ok(mac)
}
pub fn verify(mac: &[u8; BYTES], input: &[u8], key: &Key) -> Result<()> {
let result = unsafe {
libsodium_sys::crypto_auth_hmacsha512256_verify(
mac.as_ptr(),
input.as_ptr(),
input.len() as u64,
key.as_bytes().as_ptr(),
)
};
if result != 0 {
return Err(SodiumError::OperationError("verification failed".into()));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use ct_codecs::{Decoder, Encoder, Hex};
#[test]
fn test_auth() {
let key = Key::generate();
let message = b"Hello, World!";
let mac = auth(message, &key).unwrap();
assert_eq!(mac.len(), BYTES);
assert!(verify(&mac, message, &key).is_ok());
let wrong_message = b"Wrong message";
assert!(verify(&mac, wrong_message, &key).is_err());
let wrong_key = Key::generate();
assert!(verify(&mac, message, &wrong_key).is_err());
}
#[test]
fn test_auth_incremental() {
let key = Key::generate();
let message1 = b"Hello, ";
let message2 = b"World!";
let full_message = b"Hello, World!";
let expected_mac = auth(full_message, &key).unwrap();
let mut state = State::new(&key).unwrap();
state.update(message1).unwrap();
state.update(message2).unwrap();
let incremental_mac = state.finalize().unwrap();
assert_eq!(expected_mac, incremental_mac);
}
#[test]
fn test_known_vector() {
let mut key_bytes = vec![0u8; 20]; Hex::decode(
&mut key_bytes,
"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
None,
)
.unwrap();
let mut padded_key = [0u8; KEYBYTES];
padded_key[..key_bytes.len()].copy_from_slice(&key_bytes);
let key = Key::from_bytes(&padded_key).unwrap();
let message = b"Hi There";
let mac = auth(message, &key).unwrap();
let expected = "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde";
let mut encoded = vec![0u8; mac.len() * 2]; let encoded = Hex::encode(&mut encoded, mac).unwrap();
assert_eq!(std::str::from_utf8(encoded).unwrap(), expected);
}
#[test]
fn test_key_asref() {
let key = Key::generate();
let bytes: &[u8] = key.as_ref();
assert_eq!(bytes.len(), KEYBYTES);
assert_eq!(bytes, key.as_bytes());
}
#[test]
fn test_key_try_from_slice() {
use std::convert::TryFrom;
let bytes = [42u8; KEYBYTES];
let key = Key::try_from(&bytes[..]).unwrap();
assert_eq!(key.as_bytes(), &bytes);
let short_bytes = [0u8; KEYBYTES - 1];
assert!(Key::try_from(&short_bytes[..]).is_err());
let long_bytes = [0u8; KEYBYTES + 1];
assert!(Key::try_from(&long_bytes[..]).is_err());
}
#[test]
fn test_key_from_bytes() {
let bytes = [42u8; KEYBYTES];
let key = Key::from(bytes);
assert_eq!(key.as_bytes(), &bytes);
}
#[test]
fn test_key_into_bytes() {
let original_bytes = [42u8; KEYBYTES];
let key = Key::from(original_bytes);
let bytes: [u8; KEYBYTES] = key.into();
assert_eq!(bytes, original_bytes);
}
#[test]
fn test_key_roundtrip() {
let original = [99u8; KEYBYTES];
let key = Key::from(original);
let recovered: [u8; KEYBYTES] = key.into();
assert_eq!(recovered, original);
}
}