Implement std::hash::Hasher + Clone for Hmac
Change-Id: I408c3b8d60ebe18664ed14d62eaf08e2ca699257
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf1b967..93f926e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,8 @@
feature flag).
- Added `bytes` module guarded by the `bytes` feature, containing
`constant_time_eq`.
+- `hmac::Hmac` now implements `Clone` and `std::hash::Hasher`, allowing it to be
+ used with any type that implements `std::hash::Hash`.
### Changed
- `build.rs` implements symbol name scraping natively, and no longer relies on
diff --git a/boringssl/bindgen.sh b/boringssl/bindgen.sh
index a8262b8..1ff819e 100755
--- a/boringssl/bindgen.sh
+++ b/boringssl/bindgen.sh
@@ -86,6 +86,7 @@
EVP_PBE_scrypt|\
HMAC_CTX_init|\
HMAC_CTX_cleanup|\
+HMAC_CTX_copy|\
HMAC_Init_ex|\
HMAC_Update|\
HMAC_Final|\
diff --git a/boringssl/boringssl.rs b/boringssl/boringssl.rs
index 5c371a1..30f904f 100644
--- a/boringssl/boringssl.rs
+++ b/boringssl/boringssl.rs
@@ -913,6 +913,10 @@
#[link_name = "__RUST_MUNDANE_0_4_0_HMAC_size"]
pub fn HMAC_size(ctx: *const HMAC_CTX) -> usize;
}
+extern "C" {
+ #[link_name = "__RUST_MUNDANE_0_4_0_HMAC_CTX_copy"]
+ pub fn HMAC_CTX_copy(dest: *mut HMAC_CTX, src: *const HMAC_CTX) -> ::std::os::raw::c_int;
+}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct hmac_ctx_st {
diff --git a/src/boringssl/mod.rs b/src/boringssl/mod.rs
index b51a979..84f99be 100644
--- a/src/boringssl/mod.rs
+++ b/src/boringssl/mod.rs
@@ -109,9 +109,9 @@
EC_KEY_set_group, EC_curve_nid2nist, ED25519_keypair, ED25519_keypair_from_seed, ED25519_sign,
ED25519_verify, ERR_print_errors_cb, EVP_PBE_scrypt, EVP_PKEY_assign_EC_KEY,
EVP_PKEY_assign_RSA, EVP_PKEY_get1_EC_KEY, EVP_PKEY_get1_RSA, EVP_marshal_public_key,
- EVP_parse_public_key, HMAC_CTX_init, HMAC_Final, HMAC_Init_ex, HMAC_Update, HMAC_size,
- RAND_bytes, RSA_bits, RSA_generate_key_ex, RSA_marshal_private_key, RSA_parse_private_key,
- RSA_sign_pss_mgf1, RSA_size, RSA_verify_pss_mgf1, SHA384_Init,
+ EVP_parse_public_key, HMAC_CTX_copy, HMAC_CTX_init, HMAC_Final, HMAC_Init_ex, HMAC_Update,
+ HMAC_size, RAND_bytes, RSA_bits, RSA_generate_key_ex, RSA_marshal_private_key,
+ RSA_parse_private_key, RSA_sign_pss_mgf1, RSA_size, RSA_verify_pss_mgf1, SHA384_Init,
};
#[cfg(feature = "rsa-pkcs1v15")]
use boringssl::raw::{RSA_sign, RSA_verify};
@@ -567,6 +567,15 @@
assert_abort_eq!(out.len(), size as usize);
}
}
+
+ /// The `HMAC_CTX_copy` function.
+ pub fn hmac_ctx_copy(&self) -> Result<Self, BoringError> {
+ unsafe {
+ let mut ctx = MaybeUninit::uninit();
+ HMAC_CTX_copy(ctx.as_mut_ptr(), self.as_const())?;
+ Ok(CStackWrapper::new(ctx.assume_init()))
+ }
+ }
}
impl CHeapWrapper<RSA> {
diff --git a/src/boringssl/raw.rs b/src/boringssl/raw.rs
index 9ac8fd7..f98fc29 100644
--- a/src/boringssl/raw.rs
+++ b/src/boringssl/raw.rs
@@ -357,6 +357,11 @@
one_or_err("HMAC_Final", ffi::HMAC_Final(ctx, out, out_len))
}
+#[allow(non_snake_case)]
+pub unsafe fn HMAC_CTX_copy(dest: *mut HMAC_CTX, src: *const HMAC_CTX) -> Result<(), BoringError> {
+ one_or_err("HMAC_CTX_copy", ffi::HMAC_CTX_copy(dest, src))
+}
+
// rand.h
#[allow(non_snake_case)]
diff --git a/src/hmac.rs b/src/hmac.rs
index 7db7949..bd68eb0 100644
--- a/src/hmac.rs
+++ b/src/hmac.rs
@@ -48,12 +48,42 @@
}
}
+// We expose `Clone` because implementing `std::hash::Hasher` is
+// useful, and forces us to expose a `fn finish(&self)`. That exposes
+// the capacity to compute a digest based on the current state and
+// keep going, so providing no way to do that using the native API
+// serves only to force users to jump through hoops.
+impl<H: Hasher> Clone for Hmac<H> {
+ fn clone(&self) -> Self {
+ Self {
+ // Can only fail due to OOM if `self` is properly initialized
+ ctx: self.ctx.hmac_ctx_copy().unwrap(),
+ _marker: PhantomData,
+ }
+ }
+}
+
impl<H: Hasher> Debug for Hmac<H> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "Hmac")
}
}
+impl<H: Hasher> std::hash::Hasher for Hmac<H> {
+ fn write(&mut self, bytes: &[u8]) {
+ self.update(bytes);
+ }
+
+ fn finish(&self) -> u64 {
+ let digest = self.clone().finish();
+ // Translate a digest to a u64 in an arbitrary reasonable way
+ let mut buf = [0; 8];
+ let len = digest.as_ref().len().min(8);
+ buf[0..len].copy_from_slice(&digest.as_ref()[0..len]);
+ u64::from_le_bytes(buf)
+ }
+}
+
/// HMAC-SHA256.
pub type HmacSha256 = Hmac<Sha256>;
/// HMAC-SHA384.
@@ -145,6 +175,7 @@
#[allow(deprecated)]
use hash::insecure_sha1_digest::InsecureSha1Digest;
use hash::*;
+ use std::convert::TryInto;
#[test]
fn test_hmac() {
@@ -158,9 +189,20 @@
sha512: <Sha512 as Hasher>::Digest,
}
+ fn std_hash<H: Hasher>(x: &[u8]) -> u64 {
+ use std::hash::Hasher;
+ let mut hmac = Hmac::<H>::new(TEST_KEY);
+ hmac.write(x);
+ <Hmac<H> as std::hash::Hasher>::finish(&hmac)
+ }
+
for case in TEST_CASES.iter() {
fn test<H: Hasher>(input: &'static [u8], digest: &H::Digest) {
assert_eq!(&hmac::<H>(TEST_KEY, input), digest, "input: {:?}", input);
+ assert_eq!(
+ std_hash::<H>(input),
+ u64::from_le_bytes(digest.as_ref()[0..8].try_into().unwrap())
+ );
// Test that adding bytes incrementally works too.
let mut hmac = Hmac::<H>::new(TEST_KEY);
for b in input {