use crate::{Result, SodiumError};
use std::convert::TryFrom;
pub const BYTES: usize = libsodium_sys::crypto_auth_BYTES as usize;
pub const KEYBYTES: usize = libsodium_sys::crypto_auth_KEYBYTES as usize;
#[derive(Debug, zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
pub struct Key([u8; KEYBYTES]);
impl Key {
pub fn generate() -> Result<Self> {
let mut key = [0u8; KEYBYTES];
unsafe {
libsodium_sys::crypto_auth_keygen(key.as_mut_ptr());
}
Ok(Key(key))
}
pub fn from_slice(slice: &[u8]) -> Result<Self> {
if slice.len() != KEYBYTES {
return Err(SodiumError::InvalidInput(format!(
"key must be exactly {KEYBYTES} bytes"
)));
}
let mut key = [0u8; KEYBYTES];
key.copy_from_slice(slice);
Ok(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(slice: &[u8]) -> std::result::Result<Self, Self::Error> {
Self::from_slice(slice)
}
}
impl From<[u8; KEYBYTES]> for Key {
fn from(bytes: [u8; KEYBYTES]) -> Self {
Self(bytes)
}
}
impl From<Key> for [u8; KEYBYTES] {
fn from(key: Key) -> [u8; KEYBYTES] {
key.0
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Tag([u8; BYTES]);
impl Tag {
pub fn from_slice(slice: &[u8]) -> Result<Self> {
if slice.len() != BYTES {
return Err(SodiumError::InvalidInput(format!(
"tag must be exactly {BYTES} bytes"
)));
}
let mut tag = [0u8; BYTES];
tag.copy_from_slice(slice);
Ok(Tag(tag))
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl AsRef<[u8]> for Tag {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for Tag {
type Error = SodiumError;
fn try_from(slice: &[u8]) -> std::result::Result<Self, Self::Error> {
Self::from_slice(slice)
}
}
impl From<[u8; BYTES]> for Tag {
fn from(bytes: [u8; BYTES]) -> Self {
Self(bytes)
}
}
impl From<Tag> for [u8; BYTES] {
fn from(tag: Tag) -> [u8; BYTES] {
tag.0
}
}
pub fn auth(message: &[u8], key: &Key) -> Result<Tag> {
let mut tag = [0u8; BYTES];
let result = unsafe {
libsodium_sys::crypto_auth(
tag.as_mut_ptr(),
message.as_ptr(),
message.len() as u64,
key.as_bytes().as_ptr(),
)
};
if result != 0 {
return Err(SodiumError::OperationError("authentication failed".into()));
}
Ok(Tag(tag))
}
pub fn verify(tag: &Tag, message: &[u8], key: &Key) -> bool {
let result = unsafe {
libsodium_sys::crypto_auth_verify(
tag.as_bytes().as_ptr(),
message.as_ptr(),
message.len() as u64,
key.as_bytes().as_ptr(),
)
};
result == 0
}
pub mod hmacsha256;
pub mod hmacsha512;
pub mod hmacsha512256;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_generation() {
let key = Key::generate().unwrap();
assert_eq!(key.as_bytes().len(), KEYBYTES);
}
#[test]
fn test_key_from_slice() {
let bytes = vec![0u8; KEYBYTES];
let key = Key::from_slice(&bytes).unwrap();
assert_eq!(key.as_bytes(), bytes.as_slice());
assert!(Key::from_slice(&[0u8; KEYBYTES + 1]).is_err());
}
#[test]
fn test_tag_from_slice() {
let bytes = vec![0u8; BYTES];
let tag = Tag::from_slice(&bytes).unwrap();
assert_eq!(tag.as_bytes(), bytes.as_slice());
assert!(Tag::from_slice(&[0u8; BYTES + 1]).is_err());
}
#[test]
fn test_auth_and_verify() {
let key = Key::generate().unwrap();
let message = b"test message";
let tag = auth(message, &key).unwrap();
assert!(verify(&tag, message, &key));
assert!(!verify(&tag, b"wrong message", &key));
let wrong_key = Key::generate().unwrap();
assert!(!verify(&tag, message, &wrong_key));
}
#[test]
fn test_key_as_ref() {
let key = Key::generate().unwrap();
let key_ref: &[u8] = key.as_ref();
assert_eq!(key_ref.len(), KEYBYTES);
assert_eq!(key_ref, key.as_bytes());
}
#[test]
fn test_key_try_from_slice() {
let bytes = vec![0x42; KEYBYTES];
let key = Key::try_from(bytes.as_slice()).unwrap();
assert_eq!(key.as_bytes(), bytes.as_slice());
let short_bytes = vec![0x42; KEYBYTES - 1];
assert!(Key::try_from(short_bytes.as_slice()).is_err());
let long_bytes = vec![0x42; KEYBYTES + 1];
assert!(Key::try_from(long_bytes.as_slice()).is_err());
}
#[test]
fn test_key_from_bytes() {
let bytes = [0x42; KEYBYTES];
let key = Key::from(bytes);
assert_eq!(key.as_bytes(), &bytes);
}
#[test]
fn test_key_into_bytes() {
let original_bytes = [0x42; KEYBYTES];
let key = Key::from(original_bytes);
let bytes: [u8; KEYBYTES] = key.into();
assert_eq!(bytes, original_bytes);
}
#[test]
fn test_tag_as_ref() {
let key = Key::generate().unwrap();
let message = b"test message";
let tag = auth(message, &key).unwrap();
let tag_ref: &[u8] = tag.as_ref();
assert_eq!(tag_ref.len(), BYTES);
assert_eq!(tag_ref, tag.as_bytes());
}
#[test]
fn test_tag_try_from_slice() {
let bytes = vec![0x42; BYTES];
let tag = Tag::try_from(bytes.as_slice()).unwrap();
assert_eq!(tag.as_bytes(), bytes.as_slice());
let short_bytes = vec![0x42; BYTES - 1];
assert!(Tag::try_from(short_bytes.as_slice()).is_err());
let long_bytes = vec![0x42; BYTES + 1];
assert!(Tag::try_from(long_bytes.as_slice()).is_err());
}
#[test]
fn test_tag_from_bytes() {
let bytes = [0x42; BYTES];
let tag = Tag::from(bytes);
assert_eq!(tag.as_bytes(), &bytes);
}
#[test]
fn test_tag_into_bytes() {
let original_bytes = [0x42; BYTES];
let tag = Tag::from(original_bytes);
let bytes: [u8; BYTES] = tag.into();
assert_eq!(bytes, original_bytes);
}
#[test]
fn test_key_tag_roundtrip() {
let key_bytes = [0x42; KEYBYTES];
let key = Key::from(key_bytes);
let key_bytes_out: [u8; KEYBYTES] = key.into();
assert_eq!(key_bytes, key_bytes_out);
let key_from_bytes = Key::from(key_bytes_out);
assert_eq!(key_from_bytes.as_bytes(), &key_bytes);
let key = Key::generate().unwrap();
let message = b"test message";
let tag = auth(message, &key).unwrap();
let tag_bytes: [u8; BYTES] = tag.clone().into();
let tag_from_bytes = Tag::from(tag_bytes);
assert_eq!(tag.as_bytes(), tag_from_bytes.as_bytes());
assert!(verify(&tag_from_bytes, message, &key));
}
}