Reworking bssl_crypto: digest

Change-Id: I43de22995908ea39b19aa03d167c62a9580ba7b1
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/65168
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: Maurice Lam <yukl@google.com>
diff --git a/rust/bssl-crypto/src/digest.rs b/rust/bssl-crypto/src/digest.rs
index 7240297..a10b5ab 100644
--- a/rust/bssl-crypto/src/digest.rs
+++ b/rust/bssl-crypto/src/digest.rs
@@ -13,162 +13,135 @@
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-use core::marker::PhantomData;
+//! Hash functions.
+//!
+//! ```
+//! use bssl_crypto::digest;
+//!
+//! // One-shot hashing.
+//! let digest: [u8; 32] = digest::Sha256::hash(b"hello");
+//!
+//! // Incremental hashing.
+//! let mut ctx = digest::Sha256::new();
+//! ctx.update(b"hel");
+//! ctx.update(b"lo");
+//! let digest2: [u8; 32] = ctx.digest();
+//!
+//! assert_eq!(digest, digest2);
+//! ```
 
-use crate::{CSlice, ForeignTypeRef};
+use crate::{sealed, FfiSlice, ForeignTypeRef};
+use bssl_sys;
 
-/// The SHA-256 digest algorithm.
-#[derive(Clone)]
-pub struct Sha256 {}
-
-/// The SHA-512 digest algorithm.
-#[derive(Clone)]
-pub struct Sha512 {}
-
-/// A reference to an [`Md`], which abstracts the details of a specific hash function allowing code
-/// to deal with the concept of a "hash function" without needing to know exactly which hash function
-/// it is.
 #[non_exhaustive]
+#[doc(hidden)]
 pub struct MdRef;
 
 unsafe impl ForeignTypeRef for MdRef {
     type CType = bssl_sys::EVP_MD;
 }
 
-/// Used internally to get a BoringSSL internal MD
-pub trait Md {
-    /// The output size of the hash operation.
-    const OUTPUT_SIZE: usize;
+/// Used internally to parameterize other primitives.
+pub trait Algorithm {
+    /// The size of the resulting digest.
+    const OUTPUT_LEN: usize;
 
     /// Gets a reference to a message digest algorithm to be used by the HKDF implementation.
-    fn get_md() -> &'static MdRef;
+    #[doc(hidden)]
+    fn get_md(_: sealed::Sealed) -> &'static MdRef;
 }
 
-impl Md for Sha256 {
-    const OUTPUT_SIZE: usize = bssl_sys::SHA256_DIGEST_LENGTH as usize;
-
-    fn get_md() -> &'static MdRef {
-        // Safety:
-        // - this always returns a valid pointer to an EVP_MD
-        unsafe { MdRef::from_ptr(bssl_sys::EVP_sha256() as *mut _) }
-    }
+/// The insecure SHA-1 hash algorithm.
+///
+/// Some existing protocols depend on SHA-1 and so it is provided here, but it
+/// does not provide collision resistance and should not be used if at all
+/// avoidable. Use SHA-256 instead.
+#[derive(Clone)]
+pub struct InsecureSha1 {
+    ctx: bssl_sys::SHA_CTX,
 }
 
-impl Sha256 {
-    /// Creates a new [Digest] to compute a SHA-256 hash.
-    pub fn new_digest() -> Digest<Self, { Self::OUTPUT_SIZE }> {
-        // Note: This cannot be in the trait because using associated constants exprs there
-        // requires nightly.
-        Digest::<Self, { Self::OUTPUT_SIZE }>::new()
-    }
+unsafe_iuf_algo!(
+    InsecureSha1,
+    20,
+    EVP_sha1,
+    SHA1,
+    SHA1_Init,
+    SHA1_Update,
+    SHA1_Final
+);
+
+/// The SHA-256 hash algorithm.
+#[derive(Clone)]
+pub struct Sha256 {
+    ctx: bssl_sys::SHA256_CTX,
 }
 
-impl Md for Sha512 {
-    const OUTPUT_SIZE: usize = bssl_sys::SHA512_DIGEST_LENGTH as usize;
+unsafe_iuf_algo!(
+    Sha256,
+    32,
+    EVP_sha256,
+    SHA256,
+    SHA256_Init,
+    SHA256_Update,
+    SHA256_Final
+);
 
-    fn get_md() -> &'static MdRef {
-        // Safety:
-        // - this always returns a valid pointer to an EVP_MD
-        unsafe { MdRef::from_ptr(bssl_sys::EVP_sha512() as *mut _) }
-    }
+/// The SHA-384 hash algorithm.
+#[derive(Clone)]
+pub struct Sha384 {
+    ctx: bssl_sys::SHA512_CTX,
 }
 
-impl Sha512 {
-    /// Create a new [Digest] to compute a SHA-512 hash.
-    pub fn new_digest() -> Digest<Self, { Self::OUTPUT_SIZE }> {
-        // Note: This cannot be in the trait because using associated constants exprs there
-        // requires nightly.
-        Digest::<Self, { Self::OUTPUT_SIZE }>::new()
-    }
+unsafe_iuf_algo!(
+    Sha384,
+    48,
+    EVP_sha384,
+    SHA384,
+    SHA384_Init,
+    SHA384_Update,
+    SHA384_Final
+);
+
+/// The SHA-512 hash algorithm.
+#[derive(Clone)]
+pub struct Sha512 {
+    ctx: bssl_sys::SHA512_CTX,
 }
 
-/// A pending digest operation.
-pub struct Digest<M: Md, const OUTPUT_SIZE: usize>(bssl_sys::EVP_MD_CTX, PhantomData<M>);
+unsafe_iuf_algo!(
+    Sha512,
+    64,
+    EVP_sha512,
+    SHA512,
+    SHA512_Init,
+    SHA512_Update,
+    SHA512_Final
+);
 
-impl<M: Md, const OUTPUT_SIZE: usize> Digest<M, OUTPUT_SIZE> {
-    /// Creates a new Digest from the given `Md` type parameter.
-    ///
-    /// Panics:
-    /// - If `Md::OUTPUT_SIZE` is not the same as `OUTPUT_SIZE`.
-    fn new() -> Self {
-        // Note: runtime assertion needed here since using {M::OUTPUT_SIZE} in return type requires
-        // unstable Rust feature.
-        assert_eq!(M::OUTPUT_SIZE, OUTPUT_SIZE);
-        let mut md_ctx_uninit = core::mem::MaybeUninit::<bssl_sys::EVP_MD_CTX>::uninit();
-        // Safety:
-        // - `EVP_DigestInit` initializes `md_ctx_uninit`
-        // - `MdRef` ensures the validity of `md.as_ptr`
-        let result =
-            unsafe { bssl_sys::EVP_DigestInit(md_ctx_uninit.as_mut_ptr(), M::get_md().as_ptr()) };
-        assert_eq!(result, 1, "bssl_sys::EVP_DigestInit failed");
-        // Safety:
-        // - md_ctx_uninit initialized with EVP_DigestInit, and the function returned 1 (success)
-        let md_ctx = unsafe { md_ctx_uninit.assume_init() };
-        Self(md_ctx, PhantomData)
-    }
-
-    /// Hashes the provided input into the current digest operation.
-    pub fn update(&mut self, data: &[u8]) {
-        let data_ffi = CSlice(data);
-        // Safety:
-        // - `data` is a CSlice from safe Rust.
-        let result = unsafe {
-            bssl_sys::EVP_DigestUpdate(&mut self.0, data_ffi.as_ptr() as *const _, data_ffi.len())
-        };
-        assert_eq!(result, 1, "bssl_sys::EVP_DigestUpdate failed");
-    }
-
-    /// Computes the final digest value, consuming the object.
-    #[allow(clippy::expect_used)]
-    pub fn finalize(mut self) -> [u8; OUTPUT_SIZE] {
-        let mut digest_uninit =
-            core::mem::MaybeUninit::<[u8; bssl_sys::EVP_MAX_MD_SIZE as usize]>::uninit();
-        let mut len_uninit = core::mem::MaybeUninit::<u32>::uninit();
-        // Safety:
-        // - `digest_uninit` is allocated to `EVP_MAX_MD_SIZE` bytes long, as required by
-        //   EVP_DigestFinal_ex
-        // - `self.0` is owned by `self`, and is going to be cleaned up on drop.
-        let result = unsafe {
-            bssl_sys::EVP_DigestFinal_ex(
-                &mut self.0,
-                digest_uninit.as_mut_ptr() as *mut _,
-                len_uninit.as_mut_ptr(),
-            )
-        };
-        assert_eq!(result, 1, "bssl_sys::EVP_DigestFinal_ex failed");
-        // Safety:
-        // - `len_uninit` is initialized by `EVP_DigestFinal_ex`, and we checked the result above
-        let len = unsafe { len_uninit.assume_init() };
-        assert_eq!(
-            OUTPUT_SIZE, len as usize,
-            "bssl_sys::EVP_DigestFinal_ex failed"
-        );
-        // Safety: Result of DigestFinal_ex was checked above
-        let digest = unsafe { digest_uninit.assume_init() };
-        digest
-            .get(..OUTPUT_SIZE)
-            .and_then(|digest| digest.try_into().ok())
-            .expect("The length of `digest` was checked above")
-    }
+/// The SHA-512/256 hash algorithm.
+#[derive(Clone)]
+pub struct Sha512_256 {
+    ctx: bssl_sys::SHA512_CTX,
 }
 
-impl<M: Md, const OUTPUT_SIZE: usize> Drop for Digest<M, OUTPUT_SIZE> {
-    fn drop(&mut self) {
-        // Safety: `self.0` is owned by `self`, and is invalidated after `drop`.
-        unsafe {
-            bssl_sys::EVP_MD_CTX_cleanup(&mut self.0);
-        }
-    }
-}
+unsafe_iuf_algo!(
+    Sha512_256,
+    32,
+    EVP_sha512_256,
+    SHA512_256,
+    SHA512_256_Init,
+    SHA512_256_Update,
+    SHA512_256_Final
+);
 
 #[cfg(test)]
 mod test {
+    use super::*;
     use crate::test_helpers::decode_hex;
 
-    use super::*;
-
     #[test]
-    fn test_sha256_c_type() {
+    fn sha256_c_type() {
         unsafe {
             assert_eq!(
                 MdRef::from_ptr(bssl_sys::EVP_sha256() as *mut _).as_ptr(),
@@ -178,7 +151,7 @@
     }
 
     #[test]
-    fn test_sha512_c_type() {
+    fn sha512_c_type() {
         unsafe {
             assert_eq!(
                 MdRef::from_ptr(bssl_sys::EVP_sha512() as *mut _).as_ptr(),
@@ -188,31 +161,60 @@
     }
 
     #[test]
-    fn test_digest_sha256() {
-        let mut digest = Sha256::new_digest();
-        let msg: [u8; 4] = decode_hex("74ba2521");
-        digest.update(&msg);
-        let expected_digest: [u8; 32] =
-            decode_hex("b16aa56be3880d18cd41e68384cf1ec8c17680c45a02b1575dc1518923ae8b0e");
-        assert_eq!(expected_digest, digest.finalize());
+    fn sha1() {
+        assert_eq!(
+            decode_hex("a9993e364706816aba3e25717850c26c9cd0d89d"),
+            InsecureSha1::hash(b"abc")
+        );
     }
 
     #[test]
-    fn test_digest_sha512() {
-        let mut digest = Sha512::new_digest();
+    fn sha256() {
+        let msg: [u8; 4] = decode_hex("74ba2521");
+        let expected_digest: [u8; 32] =
+            decode_hex("b16aa56be3880d18cd41e68384cf1ec8c17680c45a02b1575dc1518923ae8b0e");
+
+        assert_eq!(Sha256::hash(&msg), expected_digest);
+
+        let mut ctx = Sha256::new();
+        ctx.update(&msg);
+        assert_eq!(expected_digest, ctx.digest());
+
+        let mut ctx = Sha256::new();
+        ctx.update(&msg[0..1]);
+        let mut ctx2 = ctx.clone();
+        ctx2.update(&msg[1..]);
+        assert_eq!(expected_digest, ctx2.digest());
+    }
+
+    #[test]
+    fn sha384() {
+        assert_eq!(
+            decode_hex("cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7"),
+            Sha384::hash(b"abc")
+        );
+    }
+
+    #[test]
+    fn sha512() {
         let msg: [u8; 4] = decode_hex("23be86d5");
-        digest.update(&msg);
         let expected_digest: [u8; 64] = decode_hex(concat!(
             "76d42c8eadea35a69990c63a762f330614a4699977f058adb988f406fb0be8f2",
             "ea3dce3a2bbd1d827b70b9b299ae6f9e5058ee97b50bd4922d6d37ddc761f8eb"
         ));
-        assert_eq!(expected_digest, digest.finalize());
+
+        assert_eq!(Sha512::hash(&msg), expected_digest);
+
+        let mut ctx = Sha512::new();
+        ctx.update(&msg);
+        assert_eq!(expected_digest, ctx.digest());
     }
 
     #[test]
-    #[should_panic]
-    fn test_digest_wrong_size() {
-        // This should not happen since we don't externally expose Digest::new
-        Digest::<Sha256, 64>::new();
+    fn sha512_256() {
+        assert_eq!(
+            decode_hex("53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23"),
+            Sha512_256::hash(b"abc")
+        );
     }
 }
diff --git a/rust/bssl-crypto/src/hkdf.rs b/rust/bssl-crypto/src/hkdf.rs
index e4e9c01..f3a68ee 100644
--- a/rust/bssl-crypto/src/hkdf.rs
+++ b/rust/bssl-crypto/src/hkdf.rs
@@ -12,8 +12,9 @@
  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-use crate::digest::Md;
+use crate::digest;
 use crate::digest::{Sha256, Sha512};
+use crate::sealed;
 use crate::{CSlice, CSliceMut, ForeignTypeRef};
 use alloc::vec::Vec;
 use core::marker::PhantomData;
@@ -31,15 +32,15 @@
 
 /// Implementation of HKDF operations which are generic over a provided hashing functions. Type
 /// aliases are provided above for convenience of commonly used hashes
-pub struct Hkdf<M: Md> {
+pub struct Hkdf<MD: digest::Algorithm> {
     salt: Option<Vec<u8>>,
     ikm: Vec<u8>,
-    _marker: PhantomData<M>,
+    _marker: PhantomData<MD>,
 }
 
-impl<M: Md> Hkdf<M> {
+impl<MD: digest::Algorithm> Hkdf<MD> {
     /// The max length of the output key material used for expanding
-    pub const MAX_OUTPUT_LENGTH: usize = M::OUTPUT_SIZE * 255;
+    pub const MAX_OUTPUT_LENGTH: usize = MD::OUTPUT_LEN * 255;
 
     /// Creates a new instance of HKDF from a salt and key material
     pub fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self {
@@ -79,7 +80,7 @@
                     bssl_sys::HKDF(
                         okm_cslice.as_mut_ptr(),
                         okm_cslice.len(),
-                        M::get_md().as_ptr(),
+                        MD::get_md(sealed::Sealed).as_ptr(),
                         CSlice::from(self.ikm.as_slice()).as_ptr(),
                         self.ikm.as_slice().len(),
                         CSlice::from(salt).as_ptr(),
diff --git a/rust/bssl-crypto/src/hmac.rs b/rust/bssl-crypto/src/hmac.rs
index 167e92e..56b06e6 100644
--- a/rust/bssl-crypto/src/hmac.rs
+++ b/rust/bssl-crypto/src/hmac.rs
@@ -13,8 +13,9 @@
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 use crate::{
-    digest::{Md, Sha256, Sha512},
-    CSlice, ForeignTypeRef as _,
+    digest,
+    digest::{Sha256, Sha512},
+    sealed, CSlice, ForeignTypeRef as _,
 };
 use core::{
     ffi::{c_uint, c_void},
@@ -154,11 +155,11 @@
 
 /// 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 `M`,
+/// 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<const N: usize, M: Md>(key: &[u8], data: &[u8]) -> [u8; N] {
+fn hmac<const N: usize, MD: digest::Algorithm>(key: &[u8], data: &[u8]) -> [u8; N] {
     let mut out = [0_u8; N];
     let mut size: c_uint = 0;
 
@@ -167,7 +168,7 @@
     // - If NULL is returned on error we panic immediately
     let result = unsafe {
         bssl_sys::HMAC(
-            M::get_md().as_ptr(),
+            MD::get_md(sealed::Sealed).as_ptr(),
             CSlice::from(key).as_ptr(),
             key.len(),
             CSlice::from(data).as_ptr(),
@@ -184,15 +185,15 @@
 /// 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 `M`, but this is not possible
+/// 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, M: Md> {
+struct Hmac<const N: usize, MD: digest::Algorithm> {
     ctx: *mut bssl_sys::HMAC_CTX,
-    _marker: PhantomData<M>,
+    _marker: PhantomData<MD>,
 }
 
-impl<const N: usize, M: Md> Hmac<N, M> {
+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)
@@ -219,7 +220,7 @@
                 ctx,
                 CSlice::from(key).as_ptr() as *const c_void,
                 key.len(),
-                M::get_md().as_ptr(),
+                MD::get_md(sealed::Sealed).as_ptr(),
                 ptr::null_mut(),
             )
         };
@@ -311,7 +312,7 @@
                 self.ctx,
                 ptr::null_mut(),
                 0,
-                M::get_md().as_ptr(),
+                MD::get_md(sealed::Sealed).as_ptr(),
                 ptr::null_mut(),
             )
         };
@@ -319,7 +320,7 @@
     }
 }
 
-impl<const N: usize, M: Md> Drop for Hmac<N, M> {
+impl<const N: usize, MD: digest::Algorithm> Drop for Hmac<N, MD> {
     fn drop(&mut self) {
         unsafe { bssl_sys::HMAC_CTX_free(self.ctx) }
     }
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs
index 022f5a3..dea522f 100644
--- a/rust/bssl-crypto/src/lib.rs
+++ b/rust/bssl-crypto/src/lib.rs
@@ -30,6 +30,9 @@
 
 use core::ffi::c_void;
 
+#[macro_use]
+mod macros;
+
 /// Authenticated Encryption with Additional Data algorithms.
 pub mod aead;
 
@@ -39,7 +42,6 @@
 /// Ciphers.
 pub mod cipher;
 
-/// Hash functions.
 pub mod digest;
 
 /// Ed25519, a signature scheme.
@@ -247,6 +249,20 @@
     fn as_ptr(&self) -> *mut Self::CType;
 }
 
+/// Returns a BoringSSL structure that is initialized by some function.
+/// Requires that the given function completely initializes the value.
+///
+/// (Tagged `unsafe` because a no-op argument would otherwise expose
+/// uninitialized memory.)
+unsafe fn initialized_struct<T, F>(init: F) -> T
+where
+    F: FnOnce(*mut T),
+{
+    let mut out_uninit = core::mem::MaybeUninit::<T>::uninit();
+    init(out_uninit.as_mut_ptr());
+    unsafe { out_uninit.assume_init() }
+}
+
 /// Wrap a closure that initializes an output buffer and return that buffer as
 /// an array. Requires that the closure fully initialize the given buffer.
 ///
@@ -290,3 +306,8 @@
         None
     }
 }
+
+/// Used to prevent external implementations of internal traits.
+mod sealed {
+    pub struct Sealed;
+}
diff --git a/rust/bssl-crypto/src/macros.rs b/rust/bssl-crypto/src/macros.rs
new file mode 100644
index 0000000..79049d4
--- /dev/null
+++ b/rust/bssl-crypto/src/macros.rs
@@ -0,0 +1,105 @@
+/* Copyright (c) 2024, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+// Generates a hash function from init/update/final-style FFI functions. Rust
+// doesn't accept function pointers as a generic arguments so this is the only
+// mechanism to avoid duplicating the code.
+//
+// The name is prefixed with "unsafe_" because it contains unsafe blocks.
+//
+// Safety: see the "Safety" sections within about the requirements for the
+// functions named in the macro parameters.
+macro_rules! unsafe_iuf_algo {
+    ($name:ident, $output_len:expr, $evp_md:ident, $one_shot:ident, $init:ident, $update:ident, $final_func:ident) => {
+        impl Algorithm for $name {
+            const OUTPUT_LEN: usize = $output_len as usize;
+
+            fn get_md(_: sealed::Sealed) -> &'static MdRef {
+                // Safety:
+                // - this always returns a valid pointer to an EVP_MD.
+                unsafe { MdRef::from_ptr(bssl_sys::$evp_md() as *mut _) }
+            }
+        }
+
+        impl $name {
+            /// Digest `input` in a single operation.
+            pub fn hash(input: &[u8]) -> [u8; $output_len] {
+                // Safety: it is assumed that `$one_shot` indeed writes
+                // `$output_len` bytes.
+                unsafe {
+                    crate::with_output_array(|out, _| {
+                        bssl_sys::$one_shot(input.as_ffi_ptr(), input.len(), out);
+                    })
+                }
+            }
+
+            /// Create a new context for incremental hashing.
+            pub fn new() -> Self {
+                unsafe {
+                    Self {
+                        ctx: crate::initialized_struct(|ctx| {
+                            // Safety: type checking will ensure that `ctx` is the
+                            // correct type for `$init` to write into.
+                            bssl_sys::$init(ctx);
+                        }),
+                    }
+                }
+            }
+
+            /// Hash the contents of `input`.
+            pub fn update(&mut self, input: &[u8]) {
+                // Safety: arguments point to a valid buffer.
+                unsafe {
+                    bssl_sys::$update(&mut self.ctx, input.as_ffi_void_ptr(), input.len());
+                }
+            }
+
+            /// Finish the hashing and return the digest.
+            pub fn digest(mut self) -> [u8; $output_len] {
+                // Safety: it is assumed that `$final_func` indeed writes
+                // `$output_len` bytes.
+                unsafe {
+                    crate::with_output_array(|out, _| {
+                        bssl_sys::$final_func(out, &mut self.ctx);
+                    })
+                }
+            }
+        }
+
+        impl From<$name> for [u8; $output_len] {
+            fn from(ctx: $name) -> [u8; $output_len] {
+                ctx.digest()
+            }
+        }
+
+        impl From<$name> for alloc::vec::Vec<u8> {
+            fn from(ctx: $name) -> alloc::vec::Vec<u8> {
+                ctx.digest().into()
+            }
+        }
+
+        #[cfg(feature = "std")]
+        impl std::io::Write for $name {
+            fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+                self.update(buf);
+                Ok(buf.len())
+            }
+
+            fn flush(&mut self) -> std::io::Result<()> {
+                Ok(())
+            }
+        }
+    };
+}