Skip to content

Commit

Permalink
Add SHA256 and SHA512 bindings to bssl-crypto
Browse files Browse the repository at this point in the history
Bug: 285222360
Change-Id: I7f35bcc734dd853e99cb691bdc681f75c9f137e4
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/60265
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
  • Loading branch information
Maurice Lam authored and Boringssl LUCI CQ committed Jun 5, 2023
1 parent b034104 commit 4a0393f
Showing 1 changed file with 131 additions and 0 deletions.
131 changes: 131 additions & 0 deletions rust/bssl-crypto/src/digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

use core::marker::PhantomData;

use crate::ForeignTypeRef;

/// The BoringSSL implemented SHA-256 digest algorithm.
Expand All @@ -26,6 +28,7 @@ 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]
pub struct MdRef;

unsafe impl ForeignTypeRef for MdRef {
Expand All @@ -51,6 +54,15 @@ impl Md for Sha256 {
}
}

impl Sha256 {
/// Create a new [digest] to compute the SHA256 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()
}
}

impl Md for Sha512 {
const OUTPUT_SIZE: usize = bssl_sys::SHA512_DIGEST_LENGTH as usize;

Expand All @@ -61,8 +73,98 @@ impl Md for Sha512 {
}
}

impl Sha512 {
/// Create a new [digest] to compute the SHA512 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()
}
}

/// A struct for computing the digest
pub struct Digest<M: Md, const OUTPUT_SIZE: usize>(bssl_sys::EVP_MD_CTX, PhantomData<M>);

impl<M: Md, const OUTPUT_SIZE: usize> Digest<M, OUTPUT_SIZE> {

/// Create 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)
}

/// Updates this digest computation using the given `data`.
pub fn update(&mut self, data: &[u8]) {
// Safety:
// - `data` is a slice from safe Rust.
let result = unsafe {
bssl_sys::EVP_DigestUpdate(&mut self.0, data.as_ptr() as *const _, data.len())
};
assert_eq!(result, 1, "bssl_sys::EVP_DigestUpdate failed");
}

/// Consumes this digest and returns the output digest value.
#[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")
}
}

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);
}
}
}

#[cfg(test)]
mod test {
use crate::test_helpers::decode_hex;

use super::*;

#[test]
Expand All @@ -84,4 +186,33 @@ mod test {
)
}
}

#[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());
}

#[test]
fn test_digest_sha512() {
let mut digest = Sha512::new_digest();
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());
}

#[test]
#[should_panic]
fn test_digest_wrong_size() {
// This should not happen since we don't externally expose Digest::new
Digest::<Sha256, 64>::new();
}
}

0 comments on commit 4a0393f

Please sign in to comment.