// Copyright 2023 The BoringSSL Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Hash-based message authentication from . //! //! HMAC-SHA256 and HMAC-SHA512 are supported. //! //! MACs can be computed in a single shot: //! //! ``` //! use bssl_crypto::hmac::HmacSha256; //! //! let mac: [u8; 32] = HmacSha256::mac(b"key", b"hello"); //! ``` //! //! Or they can be computed incrementally: //! //! ``` //! use bssl_crypto::hmac::HmacSha256; //! //! let key = bssl_crypto::rand_array(); //! let mut ctx = HmacSha256::new(&key); //! ctx.update(b"hel"); //! ctx.update(b"lo"); //! let mac: [u8; 32] = ctx.digest(); //! ``` //! //! **WARNING** comparing MACs using typical methods will often leak information //! about the size of the matching prefix. Use the `verify` method instead. //! //! If you need to compute many MACs with the same key, contexts can be //! cloned: //! //! ``` //! use bssl_crypto::hmac::HmacSha256; //! //! let key = bssl_crypto::rand_array(); //! let mut keyed_ctx = HmacSha256::new(&key); //! let mut ctx1 = keyed_ctx.clone(); //! ctx1.update(b"foo"); //! let mut ctx2 = keyed_ctx.clone(); //! ctx2.update(b"foo"); //! //! assert_eq!(ctx1.digest(), ctx2.digest()); //! ``` use crate::{ digest, digest::{Sha256, Sha512}, initialized_struct, sealed, FfiMutSlice, FfiSlice, ForeignTypeRef as _, InvalidSignatureError, }; use core::{ffi::c_uint, marker::PhantomData, ptr}; /// HMAC-SHA256. pub struct HmacSha256(Hmac<32, Sha256>); impl HmacSha256 { /// Computes the HMAC-SHA256 of `data` as a one-shot operation. pub fn mac(key: &[u8], data: &[u8]) -> [u8; 32] { hmac::<32, Sha256>(key, data) } /// Creates a new HMAC-SHA256 operation from a fixed-length key. pub fn new(key: &[u8; 32]) -> Self { Self(Hmac::new(key)) } /// Creates a new HMAC-SHA256 operation from a variable-length key. pub fn new_from_slice(key: &[u8]) -> Self { Self(Hmac::new_from_slice(key)) } /// Hashes the provided input into the HMAC operation. pub fn update(&mut self, data: &[u8]) { self.0.update(data) } /// Computes the final HMAC value, consuming the object. pub fn digest(self) -> [u8; 32] { self.0.digest() } /// Checks that the provided tag value matches the computed HMAC value. pub fn verify_slice(self, tag: &[u8]) -> Result<(), InvalidSignatureError> { self.0.verify_slice(tag) } /// Checks that the provided tag value matches the computed HMAC value. pub fn verify(self, tag: &[u8; 32]) -> Result<(), InvalidSignatureError> { self.0.verify(tag) } /// Checks that the provided tag value matches the computed HMAC, truncated to the input tag's /// length. /// /// Truncating an HMAC reduces the security of the construction. Callers must ensure `tag`'s /// length matches the desired HMAC length and security level. pub fn verify_truncated_left(self, tag: &[u8]) -> Result<(), InvalidSignatureError> { self.0.verify_truncated_left(tag) } } #[cfg(feature = "std")] impl std::io::Write for HmacSha256 { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.update(buf); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } impl Clone for HmacSha256 { fn clone(&self) -> Self { HmacSha256(self.0.clone()) } } /// HMAC-SHA512. pub struct HmacSha512(Hmac<64, Sha512>); impl HmacSha512 { /// Computes the HMAC-SHA512 of `data` as a one-shot operation. pub fn mac(key: &[u8], data: &[u8]) -> [u8; 64] { hmac::<64, Sha512>(key, data) } /// Creates a new HMAC-SHA512 operation from a fixed-size key. pub fn new(key: &[u8; 64]) -> Self { Self(Hmac::new(key)) } /// Creates a new HMAC-SHA512 operation from a variable-length key. pub fn new_from_slice(key: &[u8]) -> Self { Self(Hmac::new_from_slice(key)) } /// Hashes the provided input into the HMAC operation. pub fn update(&mut self, data: &[u8]) { self.0.update(data) } /// Computes the final HMAC value, consuming the object. pub fn digest(self) -> [u8; 64] { self.0.digest() } /// Checks that the provided tag value matches the computed HMAC value. pub fn verify_slice(self, tag: &[u8]) -> Result<(), InvalidSignatureError> { self.0.verify_slice(tag) } /// Checks that the provided tag value matches the computed HMAC value. pub fn verify(self, tag: &[u8; 64]) -> Result<(), InvalidSignatureError> { self.0.verify(tag) } /// Checks that the provided tag value matches the computed HMAC, truncated to the input tag's /// length. /// /// Truncating an HMAC reduces the security of the construction. Callers must ensure `tag`'s /// length matches the desired HMAC length and security level. pub fn verify_truncated_left(self, tag: &[u8]) -> Result<(), InvalidSignatureError> { self.0.verify_truncated_left(tag) } } #[cfg(feature = "std")] impl std::io::Write for HmacSha512 { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.update(buf); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } impl Clone for HmacSha512 { fn clone(&self) -> Self { HmacSha512(self.0.clone()) } } /// Private generically implemented function for computing HMAC as a oneshot operation. /// This should only be exposed publicly by types with the correct output size `N` which corresponds /// to the output size of the provided generic hash function. Ideally `N` would just come from `MD`, /// but this is not possible until the Rust language can support the `min_const_generics` feature. /// Until then we will have to pass both separately: https://github.com/rust-lang/rust/issues/60551 #[inline] fn hmac(key: &[u8], data: &[u8]) -> [u8; N] { let mut out = [0_u8; N]; let mut size: c_uint = 0; // Safety: // - buf always contains N bytes of space // - If NULL is returned on error we panic immediately let result = unsafe { bssl_sys::HMAC( MD::get_md(sealed::Sealed).as_ptr(), key.as_ffi_void_ptr(), key.len(), data.as_ffi_ptr(), data.len(), out.as_mut_ffi_ptr(), &mut size as *mut c_uint, ) }; assert_eq!(size as usize, N); assert!(!result.is_null(), "Result of bssl_sys::HMAC was null"); out } /// Private generically implemented HMAC instance given a generic hash function and a length `N`, /// where `N` is the output size of the hash function. This should only be exposed publicly by /// wrapper types with the correct output size `N` which corresponds to the output size of the /// provided generic hash function. Ideally `N` would just come from `MD`, but this is not possible /// until the Rust language can support the `min_const_generics` feature. Until then we will have to /// pass both separately: https://github.com/rust-lang/rust/issues/60551 struct Hmac { // Safety: this relies on HMAC_CTX being relocatable via `memcpy`, which is // not generally true of BoringSSL types. This is fine to rely on only // because we do not allow any version skew between bssl-crypto and // BoringSSL. It is *not* safe to copy this code in any other project. ctx: bssl_sys::HMAC_CTX, _marker: PhantomData, } impl Hmac { /// Creates a new HMAC operation from a fixed-length key. fn new(key: &[u8; N]) -> Self { Self::new_from_slice(key) } /// Creates a new HMAC operation from a variable-length key. fn new_from_slice(key: &[u8]) -> Self { let mut ret = Self { // Safety: type checking will ensure that |ctx| is the correct size // for `HMAC_CTX_init`. ctx: unsafe { initialized_struct(|ctx| bssl_sys::HMAC_CTX_init(ctx)) }, _marker: Default::default(), }; // Safety: // - HMAC_Init_ex must be called with an initialized context, which // `HMAC_CTX_init` provides. // - 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. // - HMAC_Init_ex returns 0 on allocation failure in which case we panic let result = unsafe { bssl_sys::HMAC_Init_ex( &mut ret.ctx, key.as_ffi_void_ptr(), key.len(), MD::get_md(sealed::Sealed).as_ptr(), ptr::null_mut(), ) }; assert!(result > 0, "Allocation failure in bssl_sys::HMAC_Init_ex"); ret } /// Hashes the provided input into the HMAC operation. fn update(&mut self, data: &[u8]) { // Safety: `HMAC_Update` needs an initialized context, but the only way // to create this object is via `new_from_slice`, which ensures that. let result = unsafe { bssl_sys::HMAC_Update(&mut self.ctx, data.as_ffi_ptr(), data.len()) }; // HMAC_Update always returns 1. assert_eq!(result, 1, "failure in bssl_sys::HMAC_Update"); } /// Computes the final HMAC value, consuming the object. fn digest(mut self) -> [u8; N] { let mut buf = [0_u8; N]; let mut size: c_uint = 0; // Safety: // - HMAC has a fixed size output of N which will never exceed the length of an N // length array // - `HMAC_Final` needs an initialized context, but the only way // to create this object is via `new_from_slice`, which ensures that. // - on allocation failure we panic let result = unsafe { bssl_sys::HMAC_Final(&mut self.ctx, buf.as_mut_ffi_ptr(), &mut size) }; assert!(result > 0, "Allocation failure in bssl_sys::HMAC_Final"); assert_eq!(size as usize, N); buf } /// Checks that the provided tag value matches the computed HMAC value. fn verify(self, tag: &[u8; N]) -> Result<(), InvalidSignatureError> { self.verify_slice(tag) } /// Checks that the provided tag value matches the computed HMAC value. /// /// Returns `Error` if `tag` is not valid or not equal in length /// to MAC's output. fn verify_slice(self, tag: &[u8]) -> Result<(), InvalidSignatureError> { if tag.len() == N { self.verify_truncated_left(tag) } else { Err(InvalidSignatureError) } } /// Checks that the provided tag value matches the computed HMAC, truncated to the input tag's /// length. /// /// Returns `Error` if `tag` is not valid or empty. /// /// Truncating an HMAC reduces the security of the construction. Callers must ensure `tag`'s /// length matches the desired HMAC length and security level. fn verify_truncated_left(self, tag: &[u8]) -> Result<(), InvalidSignatureError> { let len = tag.len(); if len == 0 || len > N { return Err(InvalidSignatureError); } let calculated = self.digest(); // Safety: both `calculated` and `tag` must be at least `len` bytes available. // This is true because `len` is the length of `tag` and `len` is <= N, // the length of `calculated`, which is checked above. let result = unsafe { bssl_sys::CRYPTO_memcmp(calculated.as_ffi_void_ptr(), tag.as_ffi_void_ptr(), len) }; if result == 0 { Ok(()) } else { Err(InvalidSignatureError) } } fn clone(&self) -> Self { let mut ret = Self { // Safety: type checking will ensure that |ctx| is the correct size // for `HMAC_CTX_init`. ctx: unsafe { initialized_struct(|ctx| bssl_sys::HMAC_CTX_init(ctx)) }, _marker: Default::default(), }; // Safety: `ret.ctx` is initialized and `self.ctx` is valid by // construction. let result = unsafe { bssl_sys::HMAC_CTX_copy(&mut ret.ctx, &self.ctx) }; assert_eq!(result, 1); ret } } impl Drop for Hmac { fn drop(&mut self) { unsafe { bssl_sys::HMAC_CTX_cleanup(&mut self.ctx) } } } #[cfg(test)] mod tests { use super::*; use alloc::boxed::Box; #[test] fn hmac_sha256() { let expected: [u8; 32] = [ 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 key: [u8; 20] = [0x0b; 20]; let data = b"Hi There"; let mut hmac = HmacSha256::new_from_slice(&key); hmac.update(data); assert_eq!(hmac.digest(), expected); let mut hmac = HmacSha256::new_from_slice(&key); hmac.update(&data[..1]); hmac.update(&data[1..]); assert_eq!(hmac.digest(), expected); let mut hmac = HmacSha256::new_from_slice(&key); hmac.update(data); assert!(hmac.verify(&expected).is_ok()); let mut hmac = HmacSha256::new_from_slice(&key); hmac.update(data); assert!(hmac.verify_truncated_left(&expected[..4]).is_ok()); let mut hmac = HmacSha256::new_from_slice(&key); hmac.update(data); assert!(hmac.verify_truncated_left(&expected[4..8]).is_err()); let mut hmac = HmacSha256::new_from_slice(&key); hmac.update(&data[..1]); let mut hmac2 = hmac.clone(); let mut hmac3 = Box::new(hmac2.clone()); hmac.update(&data[1..]); hmac2.update(&data[1..]); hmac3.update(&data[1..]); assert_eq!(hmac.digest(), expected); assert_eq!(hmac2.digest(), expected); assert_eq!(hmac3.digest(), expected); } #[test] fn hmac_sha256_fixed_size_key() { let expected_hmac = [ 0x19, 0x8a, 0x60, 0x7e, 0xb4, 0x4b, 0xfb, 0xc6, 0x99, 0x3, 0xa0, 0xf1, 0xcf, 0x2b, 0xbd, 0xc5, 0xba, 0xa, 0xa3, 0xf3, 0xd9, 0xae, 0x3c, 0x1c, 0x7a, 0x3b, 0x16, 0x96, 0xa0, 0xb6, 0x8c, 0xf7, ]; let key: [u8; 32] = [0x0b; 32]; let data = b"Hi There"; let mut hmac = HmacSha256::new(&key); hmac.update(data); let hmac_result: [u8; 32] = hmac.digest(); assert_eq!(&hmac_result, &expected_hmac); } #[test] fn hmac_sha512() { let expected: [u8; 64] = [ 135, 170, 124, 222, 165, 239, 97, 157, 79, 240, 180, 36, 26, 29, 108, 176, 35, 121, 244, 226, 206, 78, 194, 120, 122, 208, 179, 5, 69, 225, 124, 222, 218, 168, 51, 183, 214, 184, 167, 2, 3, 139, 39, 78, 174, 163, 244, 228, 190, 157, 145, 78, 235, 97, 241, 112, 46, 105, 108, 32, 58, 18, 104, 84, ]; let key: [u8; 20] = [0x0b; 20]; let data = b"Hi There"; let mut hmac = HmacSha512::new_from_slice(&key); hmac.update(data); assert_eq!(hmac.digest(), expected); let mut hmac = HmacSha512::new_from_slice(&key); hmac.update(&data[..1]); hmac.update(&data[1..]); assert_eq!(hmac.digest(), expected); let mut hmac = HmacSha512::new_from_slice(&key); hmac.update(data); assert!(hmac.verify(&expected).is_ok()); let mut hmac = HmacSha512::new_from_slice(&key); hmac.update(data); assert!(hmac.verify_truncated_left(&expected[..4]).is_ok()); let mut hmac = HmacSha512::new_from_slice(&key); hmac.update(data); assert!(hmac.verify_truncated_left(&expected[4..8]).is_err()); let mut hmac = HmacSha512::new_from_slice(&key); hmac.update(&data[..1]); let mut hmac2 = hmac.clone(); let mut hmac3 = Box::new(hmac.clone()); hmac.update(&data[1..]); hmac2.update(&data[1..]); hmac3.update(&data[1..]); assert_eq!(hmac.digest(), expected); assert_eq!(hmac2.digest(), expected); assert_eq!(hmac3.digest(), expected); } }