use crate::error::ErrorStack; use crate::md::MdRef; use crate::{cvt, cvt_p}; use ffi::HMAC_CTX; use foreign_types::ForeignTypeRef; use libc::{c_uint, c_void}; use openssl_macros::corresponds; use std::convert::TryFrom; use std::ptr; /// Computes the HMAC as a one-shot operation. /// /// Calculates the HMAC of data, using the given |key| /// and hash function |md|, and returns the result re-using the space from /// buffer |out|. On entry, |out| must contain at least |EVP_MD_size| bytes /// of space. The actual length of the result is used to resize the returned /// slice. An output size of |EVP_MAX_MD_SIZE| will always be large enough. /// It returns a resized |out| or ErrorStack on error. #[corresponds(HMAC)] #[inline] pub fn hmac<'a>( md: &MdRef, key: &[u8], data: &[u8], out: &'a mut [u8], ) -> Result<&'a [u8], ErrorStack> { assert!(out.len() >= md.size()); let mut out_len = c_uint::try_from(out.len()).unwrap(); unsafe { cvt_p(ffi::HMAC( md.as_ptr(), key.as_ptr() as *const c_void, key.len(), data.as_ptr(), data.len(), out.as_mut_ptr(), &mut out_len, ))?; } Ok(&out[..out_len as usize]) } /// A context object used to perform HMAC operations. /// /// HMAC is a MAC (message authentication code), i.e. a keyed hash function used for message /// authentication, which is based on a hash function. /// /// Note: Only available in boringssl. For openssl, use `PKey::hmac` instead. #[cfg(boringssl)] pub struct HmacCtx { ctx: *mut HMAC_CTX, output_size: usize, } #[cfg(boringssl)] impl HmacCtx { /// Creates a new [HmacCtx] to use the hash function `md` and key `key`. #[corresponds(HMAC_Init_ex)] pub fn new(key: &[u8], md: &MdRef) -> Result { unsafe { // Safety: If an error occurred, the resulting null from HMAC_CTX_new is converted into // ErrorStack in the returned result by `cvt_p`. let ctx = cvt_p(ffi::HMAC_CTX_new())?; // Safety: // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new, // which is the line above. // - HMAC_Init_ex may return an error if key is null but the md is different from // before. This is avoided here since key is guaranteed to be non-null. cvt(ffi::HMAC_Init_ex( ctx, key.as_ptr() as *const c_void, key.len(), md.as_ptr(), ptr::null_mut(), ))?; Ok(Self { ctx, output_size: md.size(), }) } } /// `update` can be called repeatedly with chunks of the message `data` to be authenticated. #[corresponds(HMAC_Update)] pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> { unsafe { // Safety: HMAC_Update returns 0 on error, and that is converted into ErrorStack in the // returned result by `cvt`. cvt(ffi::HMAC_Update(self.ctx, data.as_ptr(), data.len())).map(|_| ()) } } /// Finishes the HMAC process, and places the message authentication code in `output`. /// The number of bytes written to `output` is returned. /// /// # Panics /// /// Panics if the `output` is smaller than the required size. The output size is indicated by /// `md.size()` for the `Md` instance passed in [new]. An output size of |EVP_MAX_MD_SIZE| will /// always be large enough. #[corresponds(HMAC_Final)] pub fn finalize(&mut self, output: &mut [u8]) -> Result { assert!(output.len() >= self.output_size); unsafe { // Safety: The length assertion above makes sure that `HMAC_Final` will not write longer // than the length of `output`. let mut size: c_uint = 0; cvt(ffi::HMAC_Final( self.ctx, output.as_mut_ptr(), &mut size as *mut c_uint, )) .map(|_| size as usize) } } } impl Drop for HmacCtx { #[corresponds(HMAC_CTX_free)] fn drop(&mut self) { unsafe { ffi::HMAC_CTX_free(self.ctx); } } } #[cfg(test)] mod tests { use super::*; use crate::md::Md; const SHA_256_DIGEST_SIZE: usize = 32; #[test] fn hmac_sha256_test() { let expected_hmac = [ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7, ]; let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE]; let key: [u8; 20] = [0x0b; 20]; let data = b"Hi There"; let hmac_result = hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac"); assert_eq!(&hmac_result, &expected_hmac); } #[test] #[should_panic] fn hmac_sha256_output_too_short() { let mut out = vec![0_u8; 1]; let key: [u8; 20] = [0x0b; 20]; let data = b"Hi There"; hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac"); } #[test] fn hmac_sha256_test_big_buffer() { let expected_hmac = [ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7, ]; let mut out: [u8; 100] = [0; 100]; let key: [u8; 20] = [0x0b; 20]; let data = b"Hi There"; let hmac_result = hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac"); assert_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE); assert_eq!(&hmac_result, &expected_hmac); } #[test] fn hmac_sha256_update_test() { let expected_hmac = [ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7, ]; let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE]; let key: [u8; 20] = [0x0b; 20]; let data = b"Hi There"; let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap(); hmac_ctx.update(data).unwrap(); let size = hmac_ctx.finalize(&mut out).unwrap(); assert_eq!(&out, &expected_hmac); assert_eq!(size, SHA_256_DIGEST_SIZE); } #[test] fn hmac_sha256_update_chunks_test() { let expected_hmac = [ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7, ]; let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE]; let key: [u8; 20] = [0x0b; 20]; let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap(); hmac_ctx.update(b"Hi").unwrap(); hmac_ctx.update(b" There").unwrap(); let size = hmac_ctx.finalize(&mut out).unwrap(); assert_eq!(&out, &expected_hmac); assert_eq!(size, SHA_256_DIGEST_SIZE); } #[test] #[should_panic] fn hmac_sha256_update_output_too_short() { let mut out = vec![0_u8; 1]; let key: [u8; 20] = [0x0b; 20]; let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap(); hmac_ctx.update(b"Hi There").unwrap(); hmac_ctx.finalize(&mut out).unwrap(); } }