use crate::algorithm::HashAlgorithm;
use crate::backend::{require_supported, Operation};
use crate::error::{Error, Result};
use digest::Digest;
pub trait DigestStream: Send {
fn update(&mut self, data: &[u8]);
fn finalize(self: Box<Self>) -> Result<Vec<u8>>;
fn algorithm(&self) -> HashAlgorithm;
}
pub fn new_digest(algorithm: HashAlgorithm) -> Result<Box<dyn DigestStream>> {
require_supported(Operation::Digest(algorithm))?;
match algorithm {
HashAlgorithm::Sha1 => Ok(Box::new(DigestImpl::<sha1::Sha1>::new(algorithm))),
HashAlgorithm::Sha224 => Ok(Box::new(DigestImpl::<sha2::Sha224>::new(algorithm))),
HashAlgorithm::Sha256 => Ok(Box::new(DigestImpl::<sha2::Sha256>::new(algorithm))),
HashAlgorithm::Sha384 => Ok(Box::new(DigestImpl::<sha2::Sha384>::new(algorithm))),
HashAlgorithm::Sha512 => Ok(Box::new(DigestImpl::<sha2::Sha512>::new(algorithm))),
HashAlgorithm::Sha3_224 => Ok(Box::new(DigestImpl::<sha3::Sha3_224>::new(algorithm))),
HashAlgorithm::Sha3_256 => Ok(Box::new(DigestImpl::<sha3::Sha3_256>::new(algorithm))),
HashAlgorithm::Sha3_384 => Ok(Box::new(DigestImpl::<sha3::Sha3_384>::new(algorithm))),
HashAlgorithm::Sha3_512 => Ok(Box::new(DigestImpl::<sha3::Sha3_512>::new(algorithm))),
#[cfg(feature = "legacy")]
HashAlgorithm::Md5 => Ok(Box::new(DigestImpl::<md5::Md5>::new(algorithm))),
#[cfg(feature = "legacy")]
HashAlgorithm::Ripemd160 => Ok(Box::new(DigestImpl::<ripemd::Ripemd160>::new(algorithm))),
}
}
pub fn digest(algorithm: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>> {
require_supported(Operation::Digest(algorithm))?;
Ok(match algorithm {
HashAlgorithm::Sha1 => sha1::Sha1::digest(data).to_vec(),
HashAlgorithm::Sha224 => sha2::Sha224::digest(data).to_vec(),
HashAlgorithm::Sha256 => sha2::Sha256::digest(data).to_vec(),
HashAlgorithm::Sha384 => sha2::Sha384::digest(data).to_vec(),
HashAlgorithm::Sha512 => sha2::Sha512::digest(data).to_vec(),
HashAlgorithm::Sha3_224 => sha3::Sha3_224::digest(data).to_vec(),
HashAlgorithm::Sha3_256 => sha3::Sha3_256::digest(data).to_vec(),
HashAlgorithm::Sha3_384 => sha3::Sha3_384::digest(data).to_vec(),
HashAlgorithm::Sha3_512 => sha3::Sha3_512::digest(data).to_vec(),
#[cfg(feature = "legacy")]
HashAlgorithm::Md5 => md5::Md5::digest(data).to_vec(),
#[cfg(feature = "legacy")]
HashAlgorithm::Ripemd160 => ripemd::Ripemd160::digest(data).to_vec(),
})
}
pub fn compute_hmac(hash: HashAlgorithm, key: &[u8], data: &[u8]) -> Result<Vec<u8>> {
require_supported(Operation::Hmac(hash))?;
use hmac::{Hmac, Mac};
macro_rules! hmac_compute {
($hasher:ty) => {{
let mut mac = <Hmac<$hasher>>::new_from_slice(key).expect("HMAC key");
mac.update(data);
mac.finalize().into_bytes().to_vec()
}};
}
Ok(match hash {
HashAlgorithm::Sha1 => hmac_compute!(sha1::Sha1),
HashAlgorithm::Sha224 => hmac_compute!(sha2::Sha224),
HashAlgorithm::Sha256 => hmac_compute!(sha2::Sha256),
HashAlgorithm::Sha384 => hmac_compute!(sha2::Sha384),
HashAlgorithm::Sha512 => hmac_compute!(sha2::Sha512),
HashAlgorithm::Sha3_224 => hmac_compute!(sha3::Sha3_224),
HashAlgorithm::Sha3_256 => hmac_compute!(sha3::Sha3_256),
HashAlgorithm::Sha3_384 => hmac_compute!(sha3::Sha3_384),
HashAlgorithm::Sha3_512 => hmac_compute!(sha3::Sha3_512),
#[cfg(feature = "legacy")]
HashAlgorithm::Md5 => hmac_compute!(md5::Md5),
#[cfg(feature = "legacy")]
HashAlgorithm::Ripemd160 => hmac_compute!(ripemd::Ripemd160),
})
}
pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() || a.is_empty() {
return false;
}
a.iter()
.zip(b.iter())
.fold(0u8, |acc, (x, y)| acc | (x ^ y))
== 0
}
pub fn hmac_verify_truncated(
hash: HashAlgorithm,
key: &[u8],
data: &[u8],
sig: &[u8],
expected_len_bytes: usize,
) -> Result<bool> {
if expected_len_bytes == 0 || sig.len() != expected_len_bytes {
return Ok(false);
}
let full = compute_hmac(hash, key, data)?;
if expected_len_bytes > full.len() {
return Ok(false);
}
Ok(constant_time_eq(&full[..expected_len_bytes], sig))
}
struct DigestImpl<H: Digest + Send> {
inner: H,
algo: HashAlgorithm,
}
impl<H: Digest + Send> DigestImpl<H> {
fn new(algo: HashAlgorithm) -> Self {
Self {
inner: H::new(),
algo,
}
}
}
impl<H: Digest + Send + 'static> DigestStream for DigestImpl<H> {
fn update(&mut self, data: &[u8]) {
Digest::update(&mut self.inner, data);
}
fn finalize(self: Box<Self>) -> Result<Vec<u8>> {
Ok(Digest::finalize(self.inner).to_vec())
}
fn algorithm(&self) -> HashAlgorithm {
self.algo
}
}
pub fn ecdsa_raw_to_der(curve: crate::algorithm::EcCurve, raw: &[u8]) -> Result<Vec<u8>> {
match curve {
crate::algorithm::EcCurve::P256 => {
let sig = raw_to_p256_sig(raw)?;
Ok(sig.to_der().as_bytes().to_vec())
}
crate::algorithm::EcCurve::P384 => {
let sig = raw_to_p384_sig(raw)?;
Ok(sig.to_der().as_bytes().to_vec())
}
crate::algorithm::EcCurve::P521 => {
let sig = raw_to_p521_sig(raw)?;
Ok(sig.to_der().as_bytes().to_vec())
}
}
}
pub fn ecdsa_der_to_raw(curve: crate::algorithm::EcCurve, der: &[u8]) -> Result<Vec<u8>> {
match curve {
crate::algorithm::EcCurve::P256 => {
let sig = p256::ecdsa::Signature::from_der(der)
.map_err(|e| Error::Crypto(format!("invalid P-256 DER signature: {e}")))?;
Ok(p256_sig_to_raw(&sig))
}
crate::algorithm::EcCurve::P384 => {
let sig = p384::ecdsa::Signature::from_der(der)
.map_err(|e| Error::Crypto(format!("invalid P-384 DER signature: {e}")))?;
Ok(p384_sig_to_raw(&sig))
}
crate::algorithm::EcCurve::P521 => {
let sig = p521::ecdsa::Signature::from_der(der)
.map_err(|e| Error::Crypto(format!("invalid P-521 DER signature: {e}")))?;
Ok(p521_sig_to_raw(&sig))
}
}
}
pub(crate) fn normalize_raw_ecdsa(sig_bytes: &[u8], field_size: usize) -> Result<Vec<u8>> {
if !sig_bytes.len().is_multiple_of(2) {
return Err(Error::Crypto(format!(
"ECDSA signature has odd length {}, cannot split into r||s",
sig_bytes.len()
)));
}
let half = sig_bytes.len() / 2;
let mut out = vec![0u8; field_size * 2];
for (i, component) in [&sig_bytes[..half], &sig_bytes[half..]].iter().enumerate() {
let trimmed = match component.iter().position(|&b| b != 0) {
Some(pos) => &component[pos..],
None => &component[component.len().saturating_sub(1)..],
};
if trimmed.len() > field_size {
return Err(Error::Crypto(format!(
"ECDSA component {} too large: {} bytes (field size {})",
if i == 0 { "r" } else { "s" },
trimmed.len(),
field_size
)));
}
let offset = i * field_size + field_size - trimmed.len();
out[offset..offset + trimmed.len()].copy_from_slice(trimmed);
}
Ok(out)
}
pub(crate) fn raw_to_p256_sig(sig_bytes: &[u8]) -> Result<p256::ecdsa::Signature> {
const FIELD: usize = 32;
if sig_bytes.len() == FIELD * 2 {
let r = p256::FieldBytes::from_slice(&sig_bytes[..FIELD]);
let s = p256::FieldBytes::from_slice(&sig_bytes[FIELD..]);
return p256::ecdsa::Signature::from_scalars(*r, *s)
.map_err(|e| Error::Crypto(format!("invalid P-256 signature: {e}")));
}
if sig_bytes.first() == Some(&0x30) && sig_bytes.len() > FIELD * 2 {
if let Ok(sig) = p256::ecdsa::Signature::from_der(sig_bytes) {
return Ok(sig);
}
}
let normalized = normalize_raw_ecdsa(sig_bytes, FIELD)?;
let r = p256::FieldBytes::from_slice(&normalized[..FIELD]);
let s = p256::FieldBytes::from_slice(&normalized[FIELD..]);
p256::ecdsa::Signature::from_scalars(*r, *s)
.map_err(|e| Error::Crypto(format!("invalid P-256 signature: {e}")))
}
pub(crate) fn p256_sig_to_raw(sig: &p256::ecdsa::Signature) -> Vec<u8> {
let (r, s) = sig.split_bytes();
let mut out = Vec::with_capacity(64);
out.extend_from_slice(&r);
out.extend_from_slice(&s);
out
}
pub(crate) fn raw_to_p384_sig(sig_bytes: &[u8]) -> Result<p384::ecdsa::Signature> {
const FIELD: usize = 48;
if sig_bytes.len() == FIELD * 2 {
let r = p384::FieldBytes::from_slice(&sig_bytes[..FIELD]);
let s = p384::FieldBytes::from_slice(&sig_bytes[FIELD..]);
return p384::ecdsa::Signature::from_scalars(*r, *s)
.map_err(|e| Error::Crypto(format!("invalid P-384 signature: {e}")));
}
if sig_bytes.first() == Some(&0x30) && sig_bytes.len() > FIELD * 2 {
if let Ok(sig) = p384::ecdsa::Signature::from_der(sig_bytes) {
return Ok(sig);
}
}
let normalized = normalize_raw_ecdsa(sig_bytes, FIELD)?;
let r = p384::FieldBytes::from_slice(&normalized[..FIELD]);
let s = p384::FieldBytes::from_slice(&normalized[FIELD..]);
p384::ecdsa::Signature::from_scalars(*r, *s)
.map_err(|e| Error::Crypto(format!("invalid P-384 signature: {e}")))
}
pub(crate) fn p384_sig_to_raw(sig: &p384::ecdsa::Signature) -> Vec<u8> {
let (r, s) = sig.split_bytes();
let mut out = Vec::with_capacity(96);
out.extend_from_slice(&r);
out.extend_from_slice(&s);
out
}
pub(crate) fn raw_to_p521_sig(sig_bytes: &[u8]) -> Result<p521::ecdsa::Signature> {
const FIELD: usize = 66;
if sig_bytes.len() == FIELD * 2 {
let r = p521::FieldBytes::from_slice(&sig_bytes[..FIELD]);
let s = p521::FieldBytes::from_slice(&sig_bytes[FIELD..]);
return p521::ecdsa::Signature::from_scalars(*r, *s)
.map_err(|e| Error::Crypto(format!("invalid P-521 signature: {e}")));
}
if sig_bytes.first() == Some(&0x30) && sig_bytes.len() > FIELD * 2 {
if let Ok(sig) = p521::ecdsa::Signature::from_der(sig_bytes) {
return Ok(sig);
}
}
let normalized = normalize_raw_ecdsa(sig_bytes, FIELD)?;
let r = p521::FieldBytes::from_slice(&normalized[..FIELD]);
let s = p521::FieldBytes::from_slice(&normalized[FIELD..]);
p521::ecdsa::Signature::from_scalars(*r, *s)
.map_err(|e| Error::Crypto(format!("invalid P-521 signature: {e}")))
}
pub(crate) fn p521_sig_to_raw(sig: &p521::ecdsa::Signature) -> Vec<u8> {
let (r, s) = sig.split_bytes();
let mut out = Vec::with_capacity(132);
out.extend_from_slice(&r);
out.extend_from_slice(&s);
out
}
pub(crate) fn pad_prehash(prehash: &[u8], field_size: usize) -> Vec<u8> {
if prehash.len() >= field_size {
return prehash.to_vec();
}
let mut padded = vec![0u8; field_size];
padded[field_size - prehash.len()..].copy_from_slice(prehash);
padded
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sha256() {
let result = digest(HashAlgorithm::Sha256, b"hello").unwrap();
assert_eq!(result.len(), 32);
let expected = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
let hex: String = result.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(hex, expected);
}
#[test]
fn test_sha1() {
let result = digest(HashAlgorithm::Sha1, b"hello").unwrap();
assert_eq!(result.len(), 20);
}
#[test]
fn test_sha512() {
let result = digest(HashAlgorithm::Sha512, b"hello").unwrap();
assert_eq!(result.len(), 64);
}
#[test]
fn test_streaming_digest() {
let mut hasher = new_digest(HashAlgorithm::Sha256).unwrap();
hasher.update(b"hel");
hasher.update(b"lo");
let result = hasher.finalize().unwrap();
let expected = digest(HashAlgorithm::Sha256, b"hello").unwrap();
assert_eq!(result, expected);
}
#[test]
fn constant_time_eq_rejects_length_mismatch() {
let expected = [0xABu8; 32];
assert!(!constant_time_eq(&expected, &[0xAB]));
assert!(!constant_time_eq(&expected, &expected[..16]));
assert!(!constant_time_eq(&expected, &[]));
let mut longer = expected.to_vec();
longer.push(0);
assert!(!constant_time_eq(&expected, &longer));
}
#[test]
fn constant_time_eq_equal_length() {
let a = [0xABu8; 32];
assert!(constant_time_eq(&a, &a));
let mut b = a;
b[31] ^= 1;
assert!(!constant_time_eq(&a, &b));
}
fn full_hmac_for_test() -> (HashAlgorithm, Vec<u8>, Vec<u8>, Vec<u8>) {
let hash = HashAlgorithm::Sha256;
let key = b"hmac-verify-truncated-test-key".to_vec();
let data = b"message to authenticate".to_vec();
let mac = compute_hmac(hash, &key, &data).unwrap();
(hash, key, data, mac)
}
#[test]
fn hmac_verify_truncated_full_length() {
let (hash, key, data, mac) = full_hmac_for_test();
assert!(hmac_verify_truncated(hash, &key, &data, &mac, mac.len()).unwrap());
}
#[test]
fn hmac_verify_truncated_accepts_verifier_declared_prefix() {
let (hash, key, data, mac) = full_hmac_for_test();
let sig = &mac[..10];
assert!(hmac_verify_truncated(hash, &key, &data, sig, 10).unwrap());
}
#[test]
fn hmac_verify_truncated_rejects_wrong_prefix() {
let (hash, key, data, mac) = full_hmac_for_test();
let mut sig = mac[..10].to_vec();
sig[0] ^= 0x01;
assert!(!hmac_verify_truncated(hash, &key, &data, &sig, 10).unwrap());
}
#[test]
fn hmac_verify_truncated_rejects_empty_expected() {
let (hash, key, data, _mac) = full_hmac_for_test();
assert!(!hmac_verify_truncated(hash, &key, &data, &[], 0).unwrap());
}
#[test]
fn hmac_verify_truncated_rejects_length_mismatch() {
let (hash, key, data, mac) = full_hmac_for_test();
assert!(!hmac_verify_truncated(hash, &key, &data, &mac[..5], 10).unwrap());
assert!(!hmac_verify_truncated(hash, &key, &data, &mac[..15], 10).unwrap());
}
#[test]
fn hmac_verify_truncated_rejects_over_hash_size() {
let (hash, key, data, _mac) = full_hmac_for_test();
let too_long = vec![0u8; 33];
assert!(!hmac_verify_truncated(hash, &key, &data, &too_long, 33).unwrap());
}
#[test]
fn hmac_verify_truncated_forty_bit_xmldsig_shape() {
let (hash, key, data, mac) = full_hmac_for_test();
let submitted = &mac[..5];
assert!(hmac_verify_truncated(hash, &key, &data, submitted, 5).unwrap());
let one_byte_forgery = &mac[..1];
assert!(!hmac_verify_truncated(hash, &key, &data, one_byte_forgery, 5).unwrap());
}
#[test]
fn p256_raw_starting_with_0x30_not_mistaken_for_der() {
use p256::ecdsa::{signature::Signer, SigningKey};
let msg = b"format-disambiguation-vector";
let mut sk;
let mut raw;
let mut tries = 0;
loop {
sk = SigningKey::random(&mut rand::rngs::OsRng);
let sig: p256::ecdsa::Signature = sk.sign(msg);
raw = p256_sig_to_raw(&sig);
if raw.first() == Some(&0x30) {
break;
}
tries += 1;
assert!(
tries < 5000,
"failed to generate 0x30-prefix raw sig in 5000 tries"
);
}
assert_eq!(raw.len(), 64);
let parsed = raw_to_p256_sig(&raw).expect("64-byte raw sig must parse");
assert_eq!(p256_sig_to_raw(&parsed), raw);
}
}