Implement std::hash::Hasher + Clone for digests
Closes #20
Change-Id: Ib150970ce3793a6fbeb8d26bc98e7ef013dcec00
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93f926e..ea9a435 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
`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`.
+- `hash::Hasher` now similarly implies `Clone` and `std::hash::Hasher`.
### Changed
- `build.rs` implements symbol name scraping natively, and no longer relies on
diff --git a/src/boringssl/mod.rs b/src/boringssl/mod.rs
index 84f99be..29a1b0e 100644
--- a/src/boringssl/mod.rs
+++ b/src/boringssl/mod.rs
@@ -792,6 +792,20 @@
(@doc_string $s:expr) => (#[doc="The `"] #[doc=$s] #[doc="` function."]);
}
+/// Implements `Clone` for a `CStackWrapper<T>`.
+///
+/// Unsound for types without no-op `CDestruct` impls, or which
+/// capture `!Sync` shared state.
+macro_rules! impl_clone {
+ ($ty: ty) => {
+ impl Clone for CStackWrapper<$ty> {
+ fn clone(&self) -> Self {
+ unsafe { CStackWrapper::new(*self.as_const()) }
+ }
+ }
+ };
+}
+
impl_hash!(
SHA_CTX,
SHA_DIGEST_LENGTH,
@@ -802,6 +816,7 @@
sha1_final,
SHA1_Final
);
+impl_clone!(SHA_CTX);
impl_hash!(
SHA256_CTX,
SHA256_DIGEST_LENGTH,
@@ -812,6 +827,7 @@
sha256_final,
SHA256_Final
);
+impl_clone!(SHA256_CTX);
impl_hash!(
SHA512_CTX,
SHA384_DIGEST_LENGTH,
@@ -832,6 +848,7 @@
sha512_final,
SHA512_Final
);
+impl_clone!(SHA512_CTX);
/// The `CRYPTO_memcmp` function.
///
diff --git a/src/hash.rs b/src/hash.rs
index 4f2ca68..664821d 100644
--- a/src/hash.rs
+++ b/src/hash.rs
@@ -49,8 +49,13 @@
}
/// A cryptographic hash function.
+// 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.
#[must_use]
-pub trait Hasher: Default + self::inner::Hasher {
+pub trait Hasher: Default + Clone + std::hash::Hasher + self::inner::Hasher {
/// The output digest.
#[must_use]
type Digest: Digest;
@@ -99,7 +104,7 @@
// NOTE: InsecureSha1 is not publicly available; it is only used in HMAC-SHA1.
#[cfg(feature = "insecure")]
#[deprecated(note = "SHA-1 is considered insecure")]
-#[derive(Default)]
+#[derive(Clone, Default)]
pub(crate) struct InsecureSha1 {
ctx: CStackWrapper<boringssl::SHA_CTX>,
}
@@ -119,7 +124,7 @@
}
/// The SHA-256 hash function.
-#[derive(Default)]
+#[derive(Clone, Default)]
#[must_use]
pub struct Sha256 {
ctx: CStackWrapper<boringssl::SHA256_CTX>,
@@ -132,6 +137,7 @@
pub struct Sha256Digest(pub(crate) [u8; boringssl::SHA256_DIGEST_LENGTH as usize]);
/// The SHA-384 hash function.
+#[derive(Clone)]
#[must_use]
pub struct Sha384 {
ctx: CStackWrapper<boringssl::SHA512_CTX>,
@@ -153,7 +159,7 @@
pub struct Sha384Digest(pub(crate) [u8; boringssl::SHA384_DIGEST_LENGTH as usize]);
/// The SHA-512 hash function.
-#[derive(Default)]
+#[derive(Clone, Default)]
#[must_use]
pub struct Sha512 {
ctx: CStackWrapper<boringssl::SHA512_CTX>,
@@ -203,6 +209,23 @@
}
}
#[allow(deprecated)]
+ impl std::hash::Hasher for $name {
+ fn write(&mut self, bytes: &[u8]) {
+ self.update(bytes);
+ }
+
+ fn finish(&self) -> u64 {
+ use self::inner::Digest;
+ 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)
+ }
+ }
+ #[allow(deprecated)]
impl self::inner::Digest for $digest_name {
fn zero() -> Self {
$digest_name([0; $digest_len as usize])
@@ -295,6 +318,7 @@
#[cfg(feature = "insecure")]
use super::insecure_sha1_digest::*;
use super::*;
+ use std::convert::TryInto;
#[test]
fn test_constants() {
@@ -330,10 +354,21 @@
sha512: <Sha512 as Hasher>::Digest,
}
+ fn std_hash<H: Hasher>(x: &[u8]) -> u64 {
+ let mut hasher = H::default();
+ hasher.write(x);
+ <H as std::hash::Hasher>::finish(&hasher)
+ }
+
for case in TEST_CASES.iter() {
fn test<H: Hasher>(input: &'static [u8], digest: &H::Digest) {
+ use self::inner::Digest;
let got = H::hash(input);
assert_eq!(&got, digest, "input: {:?}", input);
+ assert_eq!(
+ std_hash::<H>(input),
+ u64::from_le_bytes(digest.as_ref()[0..8].try_into().unwrap())
+ );
// Also use this as an opportunity to test Digest::from_bytes
// and Digest::as_ref.
assert_eq!(