use super::headers::{Writable, Writer};
use crate::{Result, dkim::Canonicalization};
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
pub(crate) mod mldsa;
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
pub(crate) const ED25519_PUBLIC_KEY_LEN: usize = 32;
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
pub(crate) const MLDSA44_MESSAGE_CAPACITY: usize = 1024;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum CryptoError {
Library(String),
FailedVerification,
IncompatibleAlgorithms,
MalformedPublicKey,
}
impl std::fmt::Display for CryptoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CryptoError::Library(err) => write!(f, "Cryptography layer error: {err}"),
CryptoError::FailedVerification => write!(f, "Signature verification failed"),
CryptoError::IncompatibleAlgorithms => write!(
f,
"Incompatible algorithms used in signature and DKIM DNS record"
),
CryptoError::MalformedPublicKey => write!(f, "Malformed public key"),
}
}
}
#[cfg(feature = "aws-lc-rs")]
mod aws_lc;
#[cfg(feature = "aws-lc-rs")]
mod aws_lc_mldsa;
#[cfg(feature = "aws-lc-rs")]
pub use aws_lc::{Ed25519Key, MlDsa44Key, RsaKey};
#[cfg(feature = "aws-lc-rs")]
pub(crate) use aws_lc::{Ed25519PublicKey, MlDsa44PublicKey, RsaPublicKey};
#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
mod rust_crypto;
#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
pub use rust_crypto::{Ed25519Key, MlDsa44Key, RsaKey};
#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
pub(crate) use rust_crypto::{Ed25519PublicKey, MlDsa44PublicKey, RsaPublicKey};
pub trait SigningKey {
type Hasher: HashImpl;
fn sign(&self, input: impl Writable) -> Result<Vec<u8>>;
fn hash(&self, data: impl Writable) -> HashOutput {
let mut hasher = <Self::Hasher as HashImpl>::hasher();
data.write(&mut hasher);
hasher.complete()
}
fn algorithm(&self) -> Algorithm;
fn heap_size(&self) -> usize {
0
}
}
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
pub enum DkimKey {
Rsa(RsaKey<Sha256>),
Ed25519(Ed25519Key),
MlDsa44(Box<MlDsa44Key>),
}
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
impl SigningKey for DkimKey {
type Hasher = Sha256;
fn sign(&self, input: impl Writable) -> Result<Vec<u8>> {
match self {
DkimKey::Rsa(key) => key.sign(input),
DkimKey::Ed25519(key) => key.sign(input),
DkimKey::MlDsa44(key) => key.sign(input),
}
}
fn algorithm(&self) -> Algorithm {
match self {
DkimKey::Rsa(key) => key.algorithm(),
DkimKey::Ed25519(key) => key.algorithm(),
DkimKey::MlDsa44(key) => key.algorithm(),
}
}
fn heap_size(&self) -> usize {
match self {
DkimKey::Rsa(key) => key.heap_size(),
DkimKey::Ed25519(key) => key.heap_size(),
DkimKey::MlDsa44(key) => size_of::<MlDsa44Key>() + key.heap_size(),
}
}
}
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
pub(crate) const fn rsa_private_key_heap_size(modulus_len: usize) -> usize {
const BASE: usize = 768;
const PER_MODULUS_BYTE: usize = 8;
BASE + PER_MODULUS_BYTE * modulus_len
}
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
impl From<RsaKey<Sha256>> for DkimKey {
fn from(key: RsaKey<Sha256>) -> Self {
DkimKey::Rsa(key)
}
}
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
impl From<Ed25519Key> for DkimKey {
fn from(key: Ed25519Key) -> Self {
DkimKey::Ed25519(key)
}
}
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
impl From<MlDsa44Key> for DkimKey {
fn from(key: MlDsa44Key) -> Self {
DkimKey::MlDsa44(Box::new(key))
}
}
pub trait VerifyingKey {
fn verify<'a>(
&self,
headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>,
signature: &[u8],
canonicalization: Canonicalization,
algorithm: Algorithm,
) -> Result<()>;
fn verify_bytes(&self, input: &[u8], signature: &[u8], algorithm: Algorithm) -> Result<()>;
fn public_key_bits(&self) -> usize {
usize::MAX
}
fn heap_size(&self) -> usize {
0
}
}
pub(crate) enum VerifyingKeyType {
Rsa,
Ed25519,
MlDsa44,
}
impl VerifyingKeyType {
pub(crate) fn verifying_key(
&self,
bytes: &[u8],
) -> Result<Box<dyn VerifyingKey + Send + Sync>> {
match self {
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
Self::Rsa => RsaPublicKey::verifying_key_from_bytes(bytes),
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
Self::Ed25519 => Ed25519PublicKey::verifying_key_from_bytes(bytes),
#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
Self::MlDsa44 => MlDsa44PublicKey::verifying_key_from_bytes(bytes),
}
}
}
pub trait HashContext: Writer + Sized {
fn complete(self) -> HashOutput;
}
pub trait HashImpl {
type Context: HashContext;
fn hasher() -> Self::Context;
fn digest(data: impl Writable) -> HashOutput {
let mut hasher = Self::hasher();
data.write(&mut hasher);
hasher.complete()
}
}
#[derive(Clone, Copy)]
pub struct Sha1;
#[derive(Clone, Copy)]
pub struct Sha256;
#[derive(Clone, Copy)]
pub struct Sha512;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u64)]
pub enum HashAlgorithm {
Sha1 = R_HASH_SHA1,
Sha256 = R_HASH_SHA256,
Sha512 = R_HASH_SHA512,
}
#[cfg(feature = "aws-lc-rs")]
impl HashAlgorithm {
pub fn hash(&self, data: impl Writable) -> HashOutput {
let mut hasher = aws_lc_rs::digest::Context::new(match self {
Self::Sha1 => &aws_lc_rs::digest::SHA1_FOR_LEGACY_USE_ONLY,
Self::Sha256 => &aws_lc_rs::digest::SHA256,
Self::Sha512 => &aws_lc_rs::digest::SHA512,
});
data.write(&mut hasher);
HashOutput::Digest(hasher.finish())
}
}
#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
impl HashAlgorithm {
pub fn hash(&self, data: impl Writable) -> HashOutput {
match self {
Self::Sha1 => Sha1::digest(data),
Self::Sha256 => Sha256::digest(data),
Self::Sha512 => Sha512::digest(data),
}
}
}
impl HashAlgorithm {
pub fn parse(name: &str) -> Option<Self> {
if name.eq_ignore_ascii_case("sha256") {
Some(HashAlgorithm::Sha256)
} else if name.eq_ignore_ascii_case("sha1") {
Some(HashAlgorithm::Sha1)
} else {
None
}
}
pub fn name(&self) -> &'static str {
match self {
HashAlgorithm::Sha1 => "sha1",
HashAlgorithm::Sha256 => "sha256",
HashAlgorithm::Sha512 => "sha512",
}
}
}
#[derive(Clone)]
#[non_exhaustive]
pub enum HashOutput {
#[cfg(feature = "aws-lc-rs")]
Digest(aws_lc_rs::digest::Digest),
#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
RustCryptoSha1(sha1::digest::Output<sha1::Sha1>),
#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
RustCryptoSha256(sha2::digest::Output<sha2::Sha256>),
#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
RustCryptoSha512(sha2::digest::Output<sha2::Sha512>),
}
impl AsRef<[u8]> for HashOutput {
fn as_ref(&self) -> &[u8] {
match self {
#[cfg(feature = "aws-lc-rs")]
Self::Digest(output) => output.as_ref(),
#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
Self::RustCryptoSha1(output) => output.as_ref(),
#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
Self::RustCryptoSha256(output) => output.as_ref(),
#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
Self::RustCryptoSha512(output) => output.as_ref(),
}
}
}
impl PartialEq for HashOutput {
fn eq(&self, other: &Self) -> bool {
self.as_ref() == other.as_ref()
}
}
impl Eq for HashOutput {}
impl std::fmt::Debug for HashOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_ref().fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum Algorithm {
RsaSha1,
#[default]
RsaSha256,
Ed25519Sha256,
MlDsa44,
}
pub(crate) const R_HASH_SHA1: u64 = 0x01;
pub(crate) const R_HASH_SHA256: u64 = 0x02;
pub(crate) const R_HASH_SHA512: u64 = 0x40;
impl Algorithm {
pub fn parse(name: &[u8]) -> Option<Self> {
hashify::map_ignore_case!(name, Algorithm,
b"rsa-sha1" => Algorithm::RsaSha1,
b"rsa-sha256" => Algorithm::RsaSha256,
b"ed25519-sha256" => Algorithm::Ed25519Sha256,
)
.copied()
}
pub fn parse_dkim2(name: &[u8]) -> Option<Self> {
hashify::map!(name, Algorithm,
b"rsa-sha256" => Algorithm::RsaSha256,
b"ed25519-sha256" => Algorithm::Ed25519Sha256,
b"mldsa44" => Algorithm::MlDsa44,
)
.copied()
}
pub fn name(&self) -> &'static str {
match self {
Algorithm::RsaSha1 => "rsa-sha1",
Algorithm::RsaSha256 => "rsa-sha256",
Algorithm::Ed25519Sha256 => "ed25519-sha256",
Algorithm::MlDsa44 => "mldsa44",
}
}
pub(crate) fn is_dkim1(&self) -> bool {
!matches!(self, Algorithm::MlDsa44)
}
}