Reworking bssl_crypto: HMAC Change-Id: If67ca7892f418f1b80ba8e8425bdefa14921e8a2 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/65171 Commit-Queue: Adam Langley <agl@google.com> Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/rust/bssl-crypto/src/hmac.rs b/rust/bssl-crypto/src/hmac.rs index 56b06e6..2a07fbc 100644 --- a/rust/bssl-crypto/src/hmac.rs +++ b/rust/bssl-crypto/src/hmac.rs
@@ -12,43 +12,72 @@ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +//! Hash-based message authentication from <https://datatracker.ietf.org/doc/html/rfc2104>. +//! +//! 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 = [0u8; 32]; +//! 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 = [0u8; 32]; +//! 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}, - sealed, CSlice, ForeignTypeRef as _, + initialized_struct, sealed, FfiMutSlice, FfiSlice, ForeignTypeRef as _, InvalidSignatureError, }; -use core::{ - ffi::{c_uint, c_void}, - marker::PhantomData, - ptr, -}; +use core::{ffi::c_uint, marker::PhantomData, ptr}; -/// Computes the HMAC-SHA256 of `data` as a one-shot operation. -/// -/// Calculates the HMAC of data, using the given `key` and returns the result. -/// It returns the computed HMAC. -pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] { - hmac::<32, Sha256>(key, data) -} - -/// Computes the HMAC-SHA512 of `data` as a one-shot operation. -/// -/// Calculates the HMAC of data, using the given `key` and returns the result. -/// It returns the computed HMAC. -pub fn hmac_sha512(key: &[u8], data: &[u8]) -> [u8; 64] { - hmac::<64, Sha512>(key, data) -} - -/// An HMAC-SHA256 operation in progress. +/// HMAC-SHA256. pub struct HmacSha256(Hmac<32, Sha256>); impl HmacSha256 { - /// Creates a new HMAC operation from a fixed-length key. - pub fn new(key: [u8; 32]) -> Self { + /// 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 operation from a variable-length 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)) } @@ -59,17 +88,17 @@ } /// Computes the final HMAC value, consuming the object. - pub fn finalize(self) -> [u8; 32] { - self.0.finalize() + 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<(), MacError> { + 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<(), MacError> { + pub fn verify(self, tag: &[u8; 32]) -> Result<(), InvalidSignatureError> { self.0.verify(tag) } @@ -78,32 +107,44 @@ /// /// 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<(), MacError> { + pub fn verify_truncated_left(self, tag: &[u8]) -> Result<(), InvalidSignatureError> { self.0.verify_truncated_left(tag) } - - /// Resets the object to its initial state. The key is retained, but input is discarded. - pub fn reset(&mut self) { - // TODO(davidben): This method is a little odd. The main use case I can imagine is - // computing multiple HMACs with the same key, while reusing the input-independent key - // setup. However, finalize consumes the object, so you cannot actually reuse the - // context afterwards. Moreover, even if you could, that mutates the context, so a - // better pattern would be to copy the initial context, or to have an HmacKey type - // that one could create contexts out of. - self.0.reset() - } } -/// An HMAC-SHA512 operation in progress. +#[cfg(feature = "std")] +impl std::io::Write for HmacSha256 { + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + 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 { - /// Creates a new HMAC operation from a fixed-size key. - pub fn new(key: [u8; 64]) -> Self { + /// 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 operation from a variable-length 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)) } @@ -114,17 +155,17 @@ } /// Computes the final HMAC value, consuming the object. - pub fn finalize(self) -> [u8; 64] { - self.0.finalize() + 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<(), MacError> { + 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<(), MacError> { + pub fn verify(self, tag: &[u8; 64]) -> Result<(), InvalidSignatureError> { self.0.verify(tag) } @@ -133,27 +174,30 @@ /// /// 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<(), MacError> { + pub fn verify_truncated_left(self, tag: &[u8]) -> Result<(), InvalidSignatureError> { self.0.verify_truncated_left(tag) } - - /// Resets the object to its initial state. The key is retained, but input is discarded. - pub fn reset(&mut self) { - // TODO(davidben): This method is a little odd. The main use case I can imagine is - // computing multiple HMACs with the same key, while reusing the input-independent key - // setup. However, finalize consumes the object, so you cannot actually reuse the - // context afterwards. Moreover, even if you could, that mutates the context, so a - // better pattern would be to copy the initial context, or to have an HmacKey type - // that one could create contexts out of. - self.0.reset() - } } -/// Error type for when the output of the hmac operation is not equal to the expected value. -#[derive(Debug)] -pub struct MacError; +#[cfg(feature = "std")] +impl std::io::Write for HmacSha512 { + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + self.update(buf); + Ok(buf.len()) + } -/// Private generically implemented function for computing hmac as a oneshot operation. + 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. @@ -169,104 +213,106 @@ let result = unsafe { bssl_sys::HMAC( MD::get_md(sealed::Sealed).as_ptr(), - CSlice::from(key).as_ptr(), + key.as_ffi_void_ptr(), key.len(), - CSlice::from(data).as_ptr(), + data.as_ffi_ptr(), data.len(), - out.as_mut_ptr(), + 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`, +/// 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<const N: usize, MD: digest::Algorithm> { - ctx: *mut bssl_sys::HMAC_CTX, + ctx: bssl_sys::HMAC_CTX, _marker: PhantomData<MD>, } impl<const N: usize, MD: digest::Algorithm> Hmac<N, MD> { /// Creates a new HMAC operation from a fixed-length key. - fn new(key: [u8; N]) -> Self { - Self::new_from_slice(&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 { - // Safety: - // - HMAC_CTX_new panics if allocation fails - let ctx = unsafe { bssl_sys::HMAC_CTX_new() }; - assert!( - !ctx.is_null(), - "result of bssl_sys::HMAC_CTX_new() was null" - ); + 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 a context previously created with HMAC_CTX_new, - // which is the line above. + // - 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( - ctx, - CSlice::from(key).as_ptr() as *const c_void, + &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"); - - Self { - ctx, - _marker: Default::default(), - } + ret } /// Hashes the provided input into the HMAC operation. fn update(&mut self, data: &[u8]) { - let result = unsafe { - // Safety: HMAC_Update will always return 1, in case it doesnt we panic - bssl_sys::HMAC_Update(self.ctx, data.as_ptr(), data.len()) - }; + // 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 finalize(self) -> [u8; N] { + 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 + // - 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(self.ctx, buf.as_mut_ptr(), &mut size as *mut c_uint) }; + 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<(), MacError> { - self.verify_slice(&tag) + 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<(), MacError> { - tag.len().eq(&N).then_some(()).ok_or(MacError)?; - self.verify_truncated_left(tag) + 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 @@ -276,53 +322,44 @@ /// /// 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<(), MacError> { + fn verify_truncated_left(self, tag: &[u8]) -> Result<(), InvalidSignatureError> { let len = tag.len(); if len == 0 || len > N { - return Err(MacError); + return Err(InvalidSignatureError); } + let calculated = self.digest(); - let result = &self.finalize()[..len]; - - // Safety: - // - if a != b is undefined, it simply returns a non-zero result - unsafe { - bssl_sys::CRYPTO_memcmp( - CSlice::from(result).as_ptr() as *const c_void, - CSlice::from(tag).as_ptr() as *const c_void, - result.len(), - ) + // 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) } - .eq(&0) - .then_some(()) - .ok_or(MacError) } - /// Resets the hmac instance to its original state - fn reset(&mut self) { - // Passing a null ptr for the key will re-use the existing key - // Safety: - // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new, - // which will always be the case if it is coming from self - // - HMAC_Init_ex may return an error if key is null but the md is different from - // before. The MD is guaranteed to be the same because it comes from the same generic param - // - HMAC_Init_ex returns 0 on allocation failure in which case we panic - let result = unsafe { - bssl_sys::HMAC_Init_ex( - self.ctx, - ptr::null_mut(), - 0, - MD::get_md(sealed::Sealed).as_ptr(), - ptr::null_mut(), - ) + 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(), }; - assert!(result > 0, "Allocation failure in bssl_sys::HMAC_Init_ex"); + // 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<const N: usize, MD: digest::Algorithm> Drop for Hmac<N, MD> { fn drop(&mut self) { - unsafe { bssl_sys::HMAC_CTX_free(self.ctx) } + unsafe { bssl_sys::HMAC_CTX_cleanup(&mut self.ctx) } } } @@ -331,118 +368,99 @@ use super::*; #[test] - fn hmac_sha256_test() { - let expected_hmac = [ + 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); - let hmac_result: [u8; 32] = hmac.finalize(); - - assert_eq!(&hmac_result, &expected_hmac); - } - - #[test] - fn hmac_sha256_reset_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 key: [u8; 20] = [0x0b; 20]; - let data = b"Hi There"; - let incorrect_data = b"This data does not match the expected mac"; + assert_eq!(hmac.digest(), expected); let mut hmac = HmacSha256::new_from_slice(&key); - hmac.update(incorrect_data); - hmac.reset(); + hmac.update(&data[..1]); + hmac.update(&data[1..]); + assert_eq!(hmac.digest(), expected); - // hmac should be back to original state, so now when we update with the correct data it - // should work + let mut hmac = HmacSha256::new_from_slice(&key); hmac.update(data); - let hmac_result: [u8; 32] = hmac.finalize(); - assert_eq!(&hmac_result, &expected_hmac); + 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(); + hmac.update(&data[1..]); + hmac2.update(&data[1..]); + assert_eq!(hmac.digest(), expected); + assert_eq!(hmac2.digest(), expected); } #[test] - fn hmac_sha256_fixed_size_key_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); + let mut hmac = HmacSha256::new(&key); hmac.update(data); - let hmac_result: [u8; 32] = hmac.finalize(); + let hmac_result: [u8; 32] = hmac.digest(); 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, + 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: HmacSha256 = HmacSha256::new_from_slice(&key); + + let mut hmac = HmacSha512::new_from_slice(&key); hmac.update(data); - let result = hmac.finalize(); - assert_eq!(&result, &expected_hmac); - assert_eq!(result.len(), 32); - } + assert_eq!(hmac.digest(), expected); - #[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 key: [u8; 20] = [0x0b; 20]; - let data = b"Hi There"; - let hmac_result = hmac_sha256(&key, data); - assert_eq!(&hmac_result, &expected_hmac); - } + let mut hmac = HmacSha512::new_from_slice(&key); + hmac.update(&data[..1]); + hmac.update(&data[1..]); + assert_eq!(hmac.digest(), expected); - #[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 key: [u8; 20] = [0x0b; 20]; - let mut hmac = HmacSha256::new_from_slice(&key); - hmac.update(b"Hi"); - hmac.update(b" There"); - let result = hmac.finalize(); - assert_eq!(&result, &expected_hmac); - } - - #[test] - fn hmac_sha256_verify_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 key: [u8; 20] = [0x0b; 20]; - let data = b"Hi There"; - let mut hmac: HmacSha256 = HmacSha256::new_from_slice(&key); + let mut hmac = HmacSha512::new_from_slice(&key); hmac.update(data); - assert!(hmac.verify(expected_hmac).is_ok()) + 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(); + hmac.update(&data[1..]); + hmac2.update(&data[1..]); + assert_eq!(hmac.digest(), expected); + assert_eq!(hmac2.digest(), expected); } }
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs index c16a68b..8853238 100644 --- a/rust/bssl-crypto/src/lib.rs +++ b/rust/bssl-crypto/src/lib.rs
@@ -49,7 +49,6 @@ pub mod hkdf; -/// HMAC, a hash-based message authentication code. pub mod hmac; /// Random number generation. @@ -70,6 +69,11 @@ #[cfg(test)] mod test_helpers; +/// Error type for when a "signature" (either a public-key signature or a MAC) +/// is incorrect. +#[derive(Debug)] +pub struct InvalidSignatureError; + /// FfiSlice exists to provide `as_ffi_ptr` on slices. Calling `as_ptr` on an /// empty Rust slice may return the alignment of the type, rather than NULL, as /// the pointer. When passing pointers into C/C++ code, that is not a valid