use crate::algorithm::{EcCurve, HashAlgorithm};
use crate::backend::{require_supported, Operation};
use crate::error::{Error, Result};
pub trait DigestStream: Send {
fn update(&mut self, data: &[u8]);
fn finalize(self: Box<Self>) -> Result<Vec<u8>>;
fn algorithm(&self) -> HashAlgorithm;
}
struct AwsLcDigest {
algorithm: HashAlgorithm,
context: aws_lc_rs::digest::Context,
}
impl DigestStream for AwsLcDigest {
fn update(&mut self, data: &[u8]) {
self.context.update(data);
}
fn finalize(self: Box<Self>) -> Result<Vec<u8>> {
Ok(self.context.finish().as_ref().to_vec())
}
fn algorithm(&self) -> HashAlgorithm {
self.algorithm
}
}
pub fn new_digest(algorithm: HashAlgorithm) -> Result<Box<dyn DigestStream>> {
require_supported(Operation::Digest(algorithm))?;
let aws_algorithm = aws_digest(algorithm).ok_or_else(|| {
Error::unsupported(Operation::Digest(algorithm), format!("{algorithm:?}"))
})?;
Ok(Box::new(AwsLcDigest {
algorithm,
context: aws_lc_rs::digest::Context::new(aws_algorithm),
}))
}
pub fn digest(algorithm: HashAlgorithm, data: &[u8]) -> Result<Vec<u8>> {
require_supported(Operation::Digest(algorithm))?;
let algorithm = aws_digest(algorithm).ok_or_else(|| {
Error::unsupported(Operation::Digest(algorithm), format!("{algorithm:?}"))
})?;
Ok(aws_lc_rs::digest::digest(algorithm, data).as_ref().to_vec())
}
pub fn compute_hmac(hash: HashAlgorithm, key: &[u8], data: &[u8]) -> Result<Vec<u8>> {
require_supported(Operation::Hmac(hash))?;
let algorithm = aws_hmac(hash)
.ok_or_else(|| Error::unsupported(Operation::Hmac(hash), format!("{hash:?}")))?;
let key = aws_lc_rs::hmac::Key::new(algorithm, key);
Ok(aws_lc_rs::hmac::sign(&key, data).as_ref().to_vec())
}
fn aws_digest(hash: HashAlgorithm) -> Option<&'static aws_lc_rs::digest::Algorithm> {
Some(match hash {
HashAlgorithm::Sha1 => &aws_lc_rs::digest::SHA1_FOR_LEGACY_USE_ONLY,
HashAlgorithm::Sha224 => &aws_lc_rs::digest::SHA224,
HashAlgorithm::Sha256 => &aws_lc_rs::digest::SHA256,
HashAlgorithm::Sha384 => &aws_lc_rs::digest::SHA384,
HashAlgorithm::Sha512 => &aws_lc_rs::digest::SHA512,
HashAlgorithm::Sha3_256 => &aws_lc_rs::digest::SHA3_256,
HashAlgorithm::Sha3_384 => &aws_lc_rs::digest::SHA3_384,
HashAlgorithm::Sha3_512 => &aws_lc_rs::digest::SHA3_512,
_ => return None,
})
}
fn aws_hmac(hash: HashAlgorithm) -> Option<aws_lc_rs::hmac::Algorithm> {
Some(match hash {
HashAlgorithm::Sha1 => aws_lc_rs::hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
HashAlgorithm::Sha224 => aws_lc_rs::hmac::HMAC_SHA224,
HashAlgorithm::Sha256 => aws_lc_rs::hmac::HMAC_SHA256,
HashAlgorithm::Sha384 => aws_lc_rs::hmac::HMAC_SHA384,
HashAlgorithm::Sha512 => aws_lc_rs::hmac::HMAC_SHA512,
_ => return None,
})
}
pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() || a.is_empty() {
return false;
}
a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
}
pub fn hmac_verify_truncated(
hash: HashAlgorithm,
key: &[u8],
data: &[u8],
signature: &[u8],
expected_len_bytes: usize,
) -> Result<bool> {
if expected_len_bytes == 0 || signature.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], signature))
}
pub fn ecdsa_raw_to_der(curve: EcCurve, raw: &[u8]) -> Result<Vec<u8>> {
let field = curve_field_len(curve);
raw_signature_to_der(field, raw, "ECDSA")
}
fn raw_signature_to_der(field: usize, raw: &[u8], name: &str) -> Result<Vec<u8>> {
if raw.first() == Some(&0x30) && raw.len() != field * 2 {
if let Ok(normalized) = der_signature_to_raw(field, raw, name) {
return raw_signature_to_der(field, &normalized, name);
}
}
if raw.len() < 2 || !raw.len().is_multiple_of(2) {
return Err(Error::Crypto(format!(
"invalid {name} raw signature length {}",
raw.len()
)));
}
let half = raw.len() / 2;
let mut normalized = vec![0u8; field * 2];
copy_raw_component(&raw[..half], &mut normalized[..field], name)?;
copy_raw_component(&raw[half..], &mut normalized[field..], name)?;
let r = der_integer(&normalized[..field]);
let s = der_integer(&normalized[field..]);
let mut content = Vec::with_capacity(r.len() + s.len());
content.extend_from_slice(&r);
content.extend_from_slice(&s);
let mut output = vec![0x30];
output.extend_from_slice(&der_length(content.len()));
output.extend_from_slice(&content);
Ok(output)
}
pub fn ecdsa_der_to_raw(curve: EcCurve, der: &[u8]) -> Result<Vec<u8>> {
der_signature_to_raw(curve_field_len(curve), der, "ECDSA")
}
fn der_signature_to_raw(field: usize, der: &[u8], name: &str) -> Result<Vec<u8>> {
let mut cursor = 0;
expect_tag(der, &mut cursor, 0x30)?;
let sequence_len = read_length(der, &mut cursor)?;
if cursor + sequence_len != der.len() {
return Err(Error::Crypto(format!("invalid {name} DER sequence length")));
}
let r = read_integer(der, &mut cursor)?;
let s = read_integer(der, &mut cursor)?;
if cursor != der.len() {
return Err(Error::Crypto("trailing ECDSA DER data".into()));
}
let mut raw = vec![0u8; field * 2];
copy_integer(r, &mut raw[..field])?;
copy_integer(s, &mut raw[field..])?;
Ok(raw)
}
fn curve_field_len(curve: EcCurve) -> usize {
match curve {
EcCurve::P256 => 32,
EcCurve::P384 => 48,
EcCurve::P521 => 66,
}
}
fn der_integer(value: &[u8]) -> Vec<u8> {
let value = value
.iter()
.position(|byte| *byte != 0)
.map_or(&value[value.len() - 1..], |index| &value[index..]);
let leading_zero = value[0] & 0x80 != 0;
let mut output = vec![0x02];
output.extend_from_slice(&der_length(value.len() + usize::from(leading_zero)));
if leading_zero {
output.push(0);
}
output.extend_from_slice(value);
output
}
fn der_length(length: usize) -> Vec<u8> {
if length < 128 {
return vec![length as u8];
}
let bytes = length.to_be_bytes();
let start = bytes
.iter()
.position(|byte| *byte != 0)
.unwrap_or(bytes.len() - 1);
let mut output = vec![0x80 | (bytes.len() - start) as u8];
output.extend_from_slice(&bytes[start..]);
output
}
fn expect_tag(input: &[u8], cursor: &mut usize, expected: u8) -> Result<()> {
if input.get(*cursor) != Some(&expected) {
return Err(Error::Crypto("invalid ECDSA DER tag".into()));
}
*cursor += 1;
Ok(())
}
fn read_length(input: &[u8], cursor: &mut usize) -> Result<usize> {
let first = *input
.get(*cursor)
.ok_or_else(|| Error::Crypto("truncated ECDSA DER length".into()))?;
*cursor += 1;
if first & 0x80 == 0 {
return Ok(first as usize);
}
let count = (first & 0x7f) as usize;
if count == 0 || count > std::mem::size_of::<usize>() || *cursor + count > input.len() {
return Err(Error::Crypto("invalid ECDSA DER length".into()));
}
let mut length = 0usize;
for byte in &input[*cursor..*cursor + count] {
length = (length << 8) | *byte as usize;
}
if count == 1 && length < 128 {
return Err(Error::Crypto("non-minimal ECDSA DER length".into()));
}
if count > 1 && input[*cursor] == 0 {
return Err(Error::Crypto("non-minimal ECDSA DER length".into()));
}
*cursor += count;
Ok(length)
}
fn read_integer<'a>(input: &'a [u8], cursor: &mut usize) -> Result<&'a [u8]> {
expect_tag(input, cursor, 0x02)?;
let length = read_length(input, cursor)?;
let value = input
.get(*cursor..*cursor + length)
.ok_or_else(|| Error::Crypto("truncated ECDSA DER integer".into()))?;
*cursor += length;
if value.is_empty() || value[0] & 0x80 != 0 {
return Err(Error::Crypto(
"invalid negative or empty ECDSA integer".into(),
));
}
if value.len() > 1 && value[0] == 0 && value[1] & 0x80 == 0 {
return Err(Error::Crypto("non-minimal ECDSA DER integer".into()));
}
Ok(value)
}
fn copy_integer(value: &[u8], output: &mut [u8]) -> Result<()> {
let value = if value.len() > 1 && value[0] == 0 {
&value[1..]
} else {
value
};
if value.len() > output.len() {
return Err(Error::Crypto(
"ECDSA DER integer exceeds curve width".into(),
));
}
let offset = output.len() - value.len();
output[offset..].copy_from_slice(value);
Ok(())
}
fn copy_raw_component(value: &[u8], output: &mut [u8], name: &str) -> Result<()> {
let value = value
.iter()
.position(|byte| *byte != 0)
.map_or(&value[value.len().saturating_sub(1)..], |index| {
&value[index..]
});
if value.len() > output.len() {
return Err(Error::Crypto(format!(
"{name} signature component exceeds field width"
)));
}
let offset = output.len() - value.len();
output[offset..].copy_from_slice(value);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sha256_known_answer() {
crate::backend::initialize_backend().expect("backend initialization");
let output = digest(HashAlgorithm::Sha256, b"abc").unwrap();
assert_eq!(
hex::encode(output),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
#[test]
fn ecdsa_der_round_trip() {
let mut raw = vec![0u8; 64];
raw[0] = 0x80;
raw[63] = 7;
let der = ecdsa_raw_to_der(EcCurve::P256, &raw).unwrap();
assert_eq!(ecdsa_der_to_raw(EcCurve::P256, &der).unwrap(), raw);
}
#[test]
fn ecdsa_accepts_short_and_zero_padded_raw_components() {
let short = vec![1u8; 62];
assert!(ecdsa_raw_to_der(EcCurve::P256, &short).is_ok());
let mut padded = vec![0u8; 66];
padded[1] = 1;
padded[34] = 2;
assert!(ecdsa_raw_to_der(EcCurve::P256, &padded).is_ok());
}
#[test]
fn ecdsa_rejects_non_minimal_long_form_length() {
let non_minimal = [0x30, 0x81, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01];
assert!(ecdsa_der_to_raw(EcCurve::P256, &non_minimal).is_err());
}
#[test]
fn ecdsa_rejects_non_minimal_multi_octet_length() {
let mut non_minimal = vec![0x30, 0x82, 0x00, 0x80];
non_minimal.push(0x02);
non_minimal.push(0x21);
non_minimal.push(0x00);
non_minimal.extend([0x80; 32]);
non_minimal.push(0x02);
non_minimal.push(0x21);
non_minimal.push(0x00);
non_minimal.extend([0x80; 32]);
assert!(ecdsa_der_to_raw(EcCurve::P256, &non_minimal).is_err());
}
#[test]
fn ecdsa_rejects_unnecessary_leading_zero_in_integer() {
let non_minimal = [0x30, 0x07, 0x02, 0x02, 0x00, 0x01, 0x02, 0x01, 0x01];
assert!(ecdsa_der_to_raw(EcCurve::P256, &non_minimal).is_err());
}
#[test]
fn ecdsa_accepts_required_leading_zero_in_integer() {
let mut der = vec![0x30, 0x08, 0x02, 0x02, 0x00, 0x80, 0x02, 0x02, 0x00, 0x80];
let _ = &mut der;
let raw =
ecdsa_der_to_raw(EcCurve::P256, &der).expect("required leading zero is canonical");
assert_eq!(raw.len(), 64);
}
}