use crate::error::ErrorStack;
use native_ossl_sys as sys;
use std::ffi::CStr;
use std::sync::Arc;
pub struct MacAlg {
ptr: *mut sys::EVP_MAC,
lib_ctx: Option<Arc<crate::lib_ctx::LibCtx>>,
}
impl MacAlg {
pub fn fetch(name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack> {
let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
let ptr = unsafe { sys::EVP_MAC_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(MacAlg { ptr, lib_ctx: None })
}
pub fn fetch_in(
ctx: &Arc<crate::lib_ctx::LibCtx>,
name: &CStr,
props: Option<&CStr>,
) -> Result<Self, ErrorStack> {
let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
let ptr = unsafe { sys::EVP_MAC_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(MacAlg {
ptr,
lib_ctx: Some(Arc::clone(ctx)),
})
}
#[must_use]
pub fn as_ptr(&self) -> *const sys::EVP_MAC {
self.ptr
}
#[must_use]
pub fn name(&self) -> &CStr {
unsafe { CStr::from_ptr(sys::EVP_MAC_get0_name(self.ptr)) }
}
}
impl Clone for MacAlg {
fn clone(&self) -> Self {
unsafe { sys::EVP_MAC_up_ref(self.ptr) };
MacAlg {
ptr: self.ptr,
lib_ctx: self.lib_ctx.clone(),
}
}
}
impl Drop for MacAlg {
fn drop(&mut self) {
unsafe { sys::EVP_MAC_free(self.ptr) };
}
}
unsafe impl Send for MacAlg {}
unsafe impl Sync for MacAlg {}
pub struct MacCtx {
ptr: *mut sys::EVP_MAC_CTX,
}
impl MacCtx {
pub fn new(alg: &MacAlg) -> Result<Self, ErrorStack> {
let ptr = unsafe { sys::EVP_MAC_CTX_new(alg.ptr) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(MacCtx { ptr })
}
pub fn init(
&mut self,
key: &[u8],
params: Option<&crate::params::Params<'_>>,
) -> Result<(), ErrorStack> {
let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
crate::ossl_call!(sys::EVP_MAC_init(
self.ptr,
key.as_ptr(),
key.len(),
params_ptr
))
}
pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
crate::ossl_call!(sys::EVP_MAC_update(self.ptr, data.as_ptr(), data.len()))
}
pub fn finish(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack> {
let mut outl: usize = 0;
crate::ossl_call!(sys::EVP_MAC_final(
self.ptr,
out.as_mut_ptr(),
std::ptr::addr_of_mut!(outl),
out.len()
))?;
Ok(outl)
}
pub fn finish_xof(&mut self, out: &mut [u8]) -> Result<(), ErrorStack> {
crate::ossl_call!(sys::EVP_MAC_finalXOF(self.ptr, out.as_mut_ptr(), out.len()))
}
#[must_use]
pub fn mac_size(&self) -> usize {
unsafe { sys::EVP_MAC_CTX_get_mac_size(self.ptr) }
}
pub fn fork(&self) -> Result<MacCtx, ErrorStack> {
let ptr = unsafe { sys::EVP_MAC_CTX_dup(self.ptr) };
if ptr.is_null() {
return Err(ErrorStack::drain());
}
Ok(MacCtx { ptr })
}
}
impl Drop for MacCtx {
fn drop(&mut self) {
unsafe { sys::EVP_MAC_CTX_free(self.ptr) };
}
}
unsafe impl Send for MacCtx {}
pub struct HmacCtx(MacCtx);
impl HmacCtx {
pub fn new(digest: &crate::digest::DigestAlg, key: &[u8]) -> Result<Self, ErrorStack> {
let alg = MacAlg::fetch(c"HMAC", None)?;
let mut ctx = MacCtx::new(&alg)?;
let nid = digest.nid();
let name_cstr: std::ffi::CString = {
let name_ptr = unsafe { native_ossl_sys::OBJ_nid2sn(nid) };
if name_ptr.is_null() {
return Err(crate::error::ErrorStack::drain());
}
unsafe { std::ffi::CStr::from_ptr(name_ptr) }.to_owned()
};
let params = crate::params::ParamBuilder::new()?
.push_utf8_string(c"digest", &name_cstr)?
.build()?;
ctx.init(key, Some(¶ms))?;
Ok(HmacCtx(ctx))
}
pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
self.0.update(data)
}
pub fn finish(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack> {
self.0.finish(out)
}
pub fn finish_to_vec(&mut self) -> Result<Vec<u8>, ErrorStack> {
let size = self.0.mac_size();
let mut out = vec![0u8; size];
let n = self.0.finish(&mut out)?;
out.truncate(n);
Ok(out)
}
#[must_use]
pub fn mac_size(&self) -> usize {
self.0.mac_size()
}
pub fn oneshot(
digest: &crate::digest::DigestAlg,
key: &[u8],
data: &[u8],
out: &mut [u8],
) -> Result<usize, ErrorStack> {
let mut ctx = HmacCtx::new(digest, key)?;
ctx.update(data)?;
ctx.finish(out)
}
}
pub struct CmacCtx(MacCtx);
impl CmacCtx {
pub fn new(cipher: &crate::cipher::CipherAlg, key: &[u8]) -> Result<Self, ErrorStack> {
let alg = MacAlg::fetch(c"CMAC", None)?;
let mut ctx = MacCtx::new(&alg)?;
let key_len = cipher.key_len();
let cipher_name: &std::ffi::CStr = match key_len {
16 => c"AES-128-CBC",
24 => c"AES-192-CBC",
32 => c"AES-256-CBC",
_ => return Err(ErrorStack::drain()),
};
let params = crate::params::ParamBuilder::new()?
.push_utf8_string(c"cipher", cipher_name)?
.build()?;
ctx.init(key, Some(¶ms))?;
Ok(CmacCtx(ctx))
}
pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
self.0.update(data)
}
pub fn finish(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack> {
self.0.finish(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::digest::DigestAlg;
#[test]
fn fetch_hmac_succeeds() {
let alg = MacAlg::fetch(c"HMAC", None).unwrap();
drop(alg);
}
#[test]
fn fetch_nonexistent_fails() {
assert!(MacAlg::fetch(c"NONEXISTENT_MAC_XYZ", None).is_err());
}
#[test]
fn clone_then_drop_both() {
let alg = MacAlg::fetch(c"HMAC", None).unwrap();
let alg2 = alg.clone();
drop(alg);
drop(alg2);
}
#[test]
fn hmac_sha256_rfc4231_tv1() {
let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
let key = [0x0b_u8; 20];
let data = b"Hi There";
let mut ctx = HmacCtx::new(&digest, &key).unwrap();
ctx.update(data).unwrap();
let mac = ctx.finish_to_vec().unwrap();
assert_eq!(
hex::encode(&mac),
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
);
}
#[test]
fn hmac_sha256_oneshot() {
let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
let key = [0x0b_u8; 20];
let mut out = [0u8; 32];
let n = HmacCtx::oneshot(&digest, &key, b"Hi There", &mut out).unwrap();
assert_eq!(n, 32);
assert_eq!(
hex::encode(out),
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
);
}
#[test]
fn mac_ctx_fork_mid_stream() {
let alg = MacAlg::fetch(c"HMAC", None).unwrap();
let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
let nid = digest.nid();
let name_ptr = unsafe { native_ossl_sys::OBJ_nid2sn(nid) };
let name = unsafe { std::ffi::CStr::from_ptr(name_ptr) };
let params = crate::params::ParamBuilder::new()
.unwrap()
.push_utf8_string(c"digest", name)
.unwrap()
.build()
.unwrap();
let mut ctx = MacCtx::new(&alg).unwrap();
ctx.init(&[0u8; 32], Some(¶ms)).unwrap();
ctx.update(b"common").unwrap();
let mut fork = ctx.fork().unwrap();
ctx.update(b" A").unwrap();
fork.update(b" B").unwrap();
let mut out_a = [0u8; 32];
let mut out_b = [0u8; 32];
ctx.finish(&mut out_a).unwrap();
fork.finish(&mut out_b).unwrap();
assert_ne!(out_a, out_b);
}
}