#![allow(non_snake_case)]
use crate::fips::indicator_check;
use crate::{debug, derive_debug_via_id};
pub(crate) mod digest_ctx;
mod sha;
use crate::error::Unspecified;
use crate::ptr::ConstPointer;
use crate::wolfcrypt_rs::{
EVP_DigestFinal, EVP_DigestUpdate, EVP_sha1, EVP_sha224, EVP_sha256, EVP_sha384, EVP_sha3_256,
EVP_sha3_384, EVP_sha3_512, EVP_sha512, EVP_sha512_256, EVP_MD,
};
use core::ffi::c_uint;
use core::mem::MaybeUninit;
use digest_ctx::DigestContext;
pub use sha::{
SHA1_FOR_LEGACY_USE_ONLY, SHA1_OUTPUT_LEN, SHA224, SHA224_OUTPUT_LEN, SHA256,
SHA256_OUTPUT_LEN, SHA384, SHA384_OUTPUT_LEN, SHA3_256, SHA3_384, SHA3_512, SHA512, SHA512_256,
SHA512_256_OUTPUT_LEN, SHA512_OUTPUT_LEN,
};
#[derive(Clone)]
pub struct Context {
pub(crate) algorithm: &'static Algorithm,
digest_ctx: DigestContext,
msg_len: u64,
max_input_reached: bool,
}
impl Context {
#[must_use]
pub fn new(algorithm: &'static Algorithm) -> Self {
Self {
algorithm,
digest_ctx: DigestContext::new(algorithm).unwrap(),
msg_len: 0u64,
max_input_reached: false,
}
}
#[inline]
pub fn update(&mut self, data: &[u8]) {
Self::try_update(self, data).expect("digest update failed");
}
#[inline]
fn try_update(&mut self, data: &[u8]) -> Result<(), Unspecified> {
unsafe {
let (msg_len, overflowed) = self.msg_len.overflowing_add(data.len() as u64);
if overflowed || msg_len > self.algorithm.max_input_len {
return Err(Unspecified);
}
self.msg_len = msg_len;
self.max_input_reached = self.msg_len == self.algorithm.max_input_len;
if 1 != EVP_DigestUpdate(
self.digest_ctx.as_mut_ptr(),
data.as_ptr().cast(),
data.len(),
) {
return Err(Unspecified);
}
Ok(())
}
}
#[inline]
#[must_use]
pub fn finish(self) -> Digest {
Self::try_finish(self).expect("EVP_DigestFinal failed")
}
#[inline]
fn try_finish(mut self) -> Result<Digest, Unspecified> {
let mut output = [0u8; MAX_OUTPUT_LEN];
let mut out_len = MaybeUninit::<c_uint>::uninit();
if 1 != indicator_check!(unsafe {
EVP_DigestFinal(
self.digest_ctx.as_mut_ptr(),
output.as_mut_ptr(),
out_len.as_mut_ptr(),
)
}) {
return Err(Unspecified);
}
Ok(Digest {
algorithm: self.algorithm,
message: output,
len: self.algorithm.output_len,
})
}
#[inline]
#[must_use]
pub fn algorithm(&self) -> &'static Algorithm {
self.algorithm
}
}
#[inline]
#[must_use]
pub fn digest(algorithm: &'static Algorithm, data: &[u8]) -> Digest {
let mut output = [0u8; MAX_OUTPUT_LEN];
(algorithm.one_shot_hash)(data, &mut output);
Digest {
algorithm,
message: output,
len: algorithm.output_len,
}
}
#[derive(Clone, Copy)]
pub struct Digest {
message: [u8; MAX_OUTPUT_LEN],
len: usize,
algorithm: &'static Algorithm,
}
impl Digest {
pub fn import_less_safe(
digest: &[u8],
algorithm: &'static Algorithm,
) -> Result<Self, Unspecified> {
if digest.len() != algorithm.output_len {
return Err(Unspecified);
}
let mut my_digest = [0u8; MAX_OUTPUT_LEN];
my_digest[0..digest.len()].copy_from_slice(&digest[0..digest.len()]);
Ok(Digest {
message: my_digest,
len: digest.len(),
algorithm,
})
}
#[inline]
#[must_use]
pub fn algorithm(&self) -> &'static Algorithm {
self.algorithm
}
}
impl AsRef<[u8]> for Digest {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.message[..self.len]
}
}
impl core::fmt::Debug for Digest {
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(fmt, "{:?}:", self.algorithm)?;
debug::write_hex_bytes(fmt, self.as_ref())
}
}
pub struct Algorithm {
pub output_len: usize,
#[deprecated]
pub chaining_len: usize,
pub block_len: usize,
max_input_len: u64,
one_shot_hash: fn(msg: &[u8], output: &mut [u8]),
pub(crate) id: AlgorithmID,
}
unsafe impl Send for Algorithm {}
impl Algorithm {
#[inline]
#[must_use]
pub fn output_len(&self) -> usize {
self.output_len
}
#[deprecated]
#[inline]
#[must_use]
pub fn chaining_len(&self) -> usize {
#![allow(deprecated)]
self.chaining_len
}
#[inline]
#[must_use]
pub fn block_len(&self) -> usize {
self.block_len
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum AlgorithmID {
SHA1,
SHA224,
SHA256,
SHA384,
SHA512,
SHA512_256,
SHA3_256,
SHA3_384,
SHA3_512,
}
impl PartialEq for Algorithm {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Algorithm {}
derive_debug_via_id!(Algorithm);
pub const MAX_BLOCK_LEN: usize = 1024 / 8;
pub const MAX_OUTPUT_LEN: usize = 512 / 8;
pub const MAX_CHAINING_LEN: usize = MAX_OUTPUT_LEN;
pub(crate) fn match_digest_type(algorithm_id: &AlgorithmID) -> ConstPointer<'_, EVP_MD> {
unsafe {
ConstPointer::new_static(match algorithm_id {
AlgorithmID::SHA1 => EVP_sha1(),
AlgorithmID::SHA224 => EVP_sha224(),
AlgorithmID::SHA256 => EVP_sha256(),
AlgorithmID::SHA384 => EVP_sha384(),
AlgorithmID::SHA512 => EVP_sha512(),
AlgorithmID::SHA512_256 => EVP_sha512_256(),
AlgorithmID::SHA3_256 => EVP_sha3_256(),
AlgorithmID::SHA3_384 => EVP_sha3_384(),
AlgorithmID::SHA3_512 => EVP_sha3_512(),
})
.unwrap_or_else(|()| panic!("Digest algorithm not found: {algorithm_id:?}"))
}
}
pub(crate) fn match_wc_hash_type(algorithm_id: &AlgorithmID) -> core::ffi::c_int {
use crate::wolfcrypt_rs::*;
match algorithm_id {
AlgorithmID::SHA1 => WC_HASH_TYPE_SHA,
AlgorithmID::SHA256 => WC_HASH_TYPE_SHA256,
AlgorithmID::SHA384 => WC_HASH_TYPE_SHA384,
AlgorithmID::SHA512 => WC_HASH_TYPE_SHA512,
_ => panic!("Unsupported hash type for wolfCrypt KDF: {algorithm_id:?}"),
}
}
#[cfg(test)]
mod tests {
use crate::digest;
#[cfg(feature = "fips")]
mod fips;
mod max_input {
extern crate alloc;
use super::super::super::digest;
use crate::digest::digest_ctx::DigestContext;
use crate::digest::Digest;
use alloc::vec;
macro_rules! max_input_tests {
( $algorithm_name:ident ) => {
mod $algorithm_name {
use super::super::super::super::digest;
#[test]
fn max_input_test() {
super::max_input_test(&digest::$algorithm_name);
}
#[test]
#[should_panic(expected = "digest update failed")]
fn too_long_input_test_block() {
super::too_long_input_test_block(&digest::$algorithm_name);
}
#[test]
#[should_panic(expected = "digest update failed")]
fn too_long_input_test_byte() {
super::too_long_input_test_byte(&digest::$algorithm_name);
}
}
};
}
fn max_input_test(alg: &'static digest::Algorithm) {
let mut context = nearly_full_context(alg);
let next_input = vec![0u8; alg.block_len - 1];
context.update(&next_input);
let _: Digest = context.finish(); }
fn too_long_input_test_block(alg: &'static digest::Algorithm) {
let mut context = nearly_full_context(alg);
let next_input = vec![0u8; alg.block_len];
context.update(&next_input);
let _: Digest = context.finish(); }
fn too_long_input_test_byte(alg: &'static digest::Algorithm) {
let mut context = nearly_full_context(alg);
let next_input = vec![0u8; alg.block_len - 1];
context.update(&next_input); context.update(&[0]);
let _: Digest = context.finish(); }
fn nearly_full_context(alg: &'static digest::Algorithm) -> digest::Context {
let block_len = alg.block_len as u64;
digest::Context {
algorithm: alg,
digest_ctx: DigestContext::new(alg).unwrap(),
msg_len: alg.max_input_len - block_len + 1,
max_input_reached: false,
}
}
max_input_tests!(SHA1_FOR_LEGACY_USE_ONLY);
max_input_tests!(SHA224);
max_input_tests!(SHA256);
max_input_tests!(SHA384);
max_input_tests!(SHA512);
max_input_tests!(SHA3_384);
max_input_tests!(SHA3_512);
}
#[test]
fn digest_coverage() {
for alg in [
&digest::SHA1_FOR_LEGACY_USE_ONLY,
&digest::SHA224,
&digest::SHA256,
&digest::SHA384,
&digest::SHA512,
&digest::SHA3_384,
&digest::SHA3_512,
] {
let mut ctx = digest::Context::new(alg);
ctx.update(b"hello, world");
let ctx_clone = ctx.clone();
assert_eq!(ctx_clone.algorithm(), ctx.algorithm());
let orig_digest = ctx.finish();
let clone_digest = ctx_clone.finish();
assert_eq!(orig_digest.algorithm(), clone_digest.algorithm());
assert_eq!(orig_digest.as_ref(), clone_digest.as_ref());
assert_eq!(orig_digest.clone().as_ref(), clone_digest.as_ref());
}
}
#[test]
fn test_import_less_safe() {
let digest = digest::digest(&digest::SHA256, b"hello, world");
let digest_copy =
digest::Digest::import_less_safe(digest.as_ref(), &digest::SHA256).unwrap();
assert_eq!(digest.as_ref(), digest_copy.as_ref());
assert_eq!(digest.algorithm, digest_copy.algorithm);
}
}