1use super::headers::{Writable, Writer};
21use crate::{Result, dkim::Canonicalization};
22
23#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
24pub(crate) mod mldsa;
25
26#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
27pub(crate) const ED25519_PUBLIC_KEY_LEN: usize = 32;
28#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
29pub(crate) const MLDSA44_MESSAGE_CAPACITY: usize = 1024;
30
31#[derive(Debug, PartialEq, Eq, Clone)]
34pub enum CryptoError {
35 Library(String),
38 FailedVerification,
40 IncompatibleAlgorithms,
44 MalformedPublicKey,
49}
50
51impl std::fmt::Display for CryptoError {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 CryptoError::Library(err) => write!(f, "Cryptography layer error: {err}"),
55 CryptoError::FailedVerification => write!(f, "Signature verification failed"),
56 CryptoError::IncompatibleAlgorithms => write!(
57 f,
58 "Incompatible algorithms used in signature and DKIM DNS record"
59 ),
60 CryptoError::MalformedPublicKey => write!(f, "Malformed public key"),
61 }
62 }
63}
64
65#[cfg(feature = "aws-lc-rs")]
66mod aws_lc;
67#[cfg(feature = "aws-lc-rs")]
68mod aws_lc_mldsa;
69#[cfg(feature = "aws-lc-rs")]
70pub use aws_lc::{Ed25519Key, MlDsa44Key, RsaKey};
71#[cfg(feature = "aws-lc-rs")]
72pub(crate) use aws_lc::{Ed25519PublicKey, MlDsa44PublicKey, RsaPublicKey};
73
74#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
75mod rust_crypto;
76#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
77pub use rust_crypto::{Ed25519Key, MlDsa44Key, RsaKey};
78#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
79pub(crate) use rust_crypto::{Ed25519PublicKey, MlDsa44PublicKey, RsaPublicKey};
80
81pub trait SigningKey {
87 type Hasher: HashImpl;
89
90 fn sign(&self, input: impl Writable) -> Result<Vec<u8>>;
97
98 fn hash(&self, data: impl Writable) -> HashOutput {
100 let mut hasher = <Self::Hasher as HashImpl>::hasher();
101 data.write(&mut hasher);
102 hasher.complete()
103 }
104
105 fn algorithm(&self) -> Algorithm;
107
108 fn heap_size(&self) -> usize {
111 0
112 }
113}
114
115#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
123pub enum DkimKey {
124 Rsa(RsaKey<Sha256>),
126 Ed25519(Ed25519Key),
128 MlDsa44(Box<MlDsa44Key>),
130}
131
132#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
133impl SigningKey for DkimKey {
134 type Hasher = Sha256;
135
136 fn sign(&self, input: impl Writable) -> Result<Vec<u8>> {
137 match self {
138 DkimKey::Rsa(key) => key.sign(input),
139 DkimKey::Ed25519(key) => key.sign(input),
140 DkimKey::MlDsa44(key) => key.sign(input),
141 }
142 }
143
144 fn algorithm(&self) -> Algorithm {
145 match self {
146 DkimKey::Rsa(key) => key.algorithm(),
147 DkimKey::Ed25519(key) => key.algorithm(),
148 DkimKey::MlDsa44(key) => key.algorithm(),
149 }
150 }
151
152 fn heap_size(&self) -> usize {
153 match self {
154 DkimKey::Rsa(key) => key.heap_size(),
155 DkimKey::Ed25519(key) => key.heap_size(),
156 DkimKey::MlDsa44(key) => size_of::<MlDsa44Key>() + key.heap_size(),
157 }
158 }
159}
160
161#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
162pub(crate) const fn rsa_private_key_heap_size(modulus_len: usize) -> usize {
163 const BASE: usize = 768;
164 const PER_MODULUS_BYTE: usize = 8;
165 BASE + PER_MODULUS_BYTE * modulus_len
166}
167
168#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
169impl From<RsaKey<Sha256>> for DkimKey {
170 fn from(key: RsaKey<Sha256>) -> Self {
171 DkimKey::Rsa(key)
172 }
173}
174
175#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
176impl From<Ed25519Key> for DkimKey {
177 fn from(key: Ed25519Key) -> Self {
178 DkimKey::Ed25519(key)
179 }
180}
181
182#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
183impl From<MlDsa44Key> for DkimKey {
184 fn from(key: MlDsa44Key) -> Self {
185 DkimKey::MlDsa44(Box::new(key))
186 }
187}
188
189pub trait VerifyingKey {
192 fn verify<'a>(
202 &self,
203 headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>,
204 signature: &[u8],
205 canonicalization: Canonicalization,
206 algorithm: Algorithm,
207 ) -> Result<()>;
208
209 fn verify_bytes(&self, input: &[u8], signature: &[u8], algorithm: Algorithm) -> Result<()>;
219
220 fn public_key_bits(&self) -> usize {
224 usize::MAX
225 }
226
227 fn heap_size(&self) -> usize {
230 0
231 }
232}
233
234pub(crate) enum VerifyingKeyType {
235 Rsa,
236 Ed25519,
237 MlDsa44,
238}
239
240impl VerifyingKeyType {
241 pub(crate) fn verifying_key(
242 &self,
243 bytes: &[u8],
244 ) -> Result<Box<dyn VerifyingKey + Send + Sync>> {
245 match self {
246 #[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
247 Self::Rsa => RsaPublicKey::verifying_key_from_bytes(bytes),
248 #[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
249 Self::Ed25519 => Ed25519PublicKey::verifying_key_from_bytes(bytes),
250 #[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
251 Self::MlDsa44 => MlDsa44PublicKey::verifying_key_from_bytes(bytes),
252 }
253 }
254}
255
256pub trait HashContext: Writer + Sized {
258 fn complete(self) -> HashOutput;
260}
261
262pub trait HashImpl {
264 type Context: HashContext;
266
267 fn hasher() -> Self::Context;
269
270 fn digest(data: impl Writable) -> HashOutput {
272 let mut hasher = Self::hasher();
273 data.write(&mut hasher);
274 hasher.complete()
275 }
276}
277
278#[derive(Clone, Copy)]
282pub struct Sha1;
283
284#[derive(Clone, Copy)]
286pub struct Sha256;
287
288#[derive(Clone, Copy)]
290pub struct Sha512;
291
292#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296#[repr(u64)]
297pub enum HashAlgorithm {
298 Sha1 = R_HASH_SHA1,
300 Sha256 = R_HASH_SHA256,
302 Sha512 = R_HASH_SHA512,
304}
305
306#[cfg(feature = "aws-lc-rs")]
307impl HashAlgorithm {
308 pub fn hash(&self, data: impl Writable) -> HashOutput {
310 let mut hasher = aws_lc_rs::digest::Context::new(match self {
311 Self::Sha1 => &aws_lc_rs::digest::SHA1_FOR_LEGACY_USE_ONLY,
312 Self::Sha256 => &aws_lc_rs::digest::SHA256,
313 Self::Sha512 => &aws_lc_rs::digest::SHA512,
314 });
315 data.write(&mut hasher);
316 HashOutput::Digest(hasher.finish())
317 }
318}
319
320#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
321impl HashAlgorithm {
322 pub fn hash(&self, data: impl Writable) -> HashOutput {
324 match self {
325 Self::Sha1 => Sha1::digest(data),
326 Self::Sha256 => Sha256::digest(data),
327 Self::Sha512 => Sha512::digest(data),
328 }
329 }
330}
331
332impl HashAlgorithm {
333 pub fn parse(name: &str) -> Option<Self> {
337 if name.eq_ignore_ascii_case("sha256") {
338 Some(HashAlgorithm::Sha256)
339 } else if name.eq_ignore_ascii_case("sha1") {
340 Some(HashAlgorithm::Sha1)
341 } else {
342 None
343 }
344 }
345
346 pub fn name(&self) -> &'static str {
348 match self {
349 HashAlgorithm::Sha1 => "sha1",
350 HashAlgorithm::Sha256 => "sha256",
351 HashAlgorithm::Sha512 => "sha512",
352 }
353 }
354}
355
356#[derive(Clone)]
361#[non_exhaustive]
362pub enum HashOutput {
363 #[cfg(feature = "aws-lc-rs")]
365 Digest(aws_lc_rs::digest::Digest),
366 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
368 RustCryptoSha1(sha1::digest::Output<sha1::Sha1>),
369 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
371 RustCryptoSha256(sha2::digest::Output<sha2::Sha256>),
372 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
374 RustCryptoSha512(sha2::digest::Output<sha2::Sha512>),
375}
376
377impl AsRef<[u8]> for HashOutput {
378 fn as_ref(&self) -> &[u8] {
379 match self {
380 #[cfg(feature = "aws-lc-rs")]
381 Self::Digest(output) => output.as_ref(),
382 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
383 Self::RustCryptoSha1(output) => output.as_ref(),
384 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
385 Self::RustCryptoSha256(output) => output.as_ref(),
386 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
387 Self::RustCryptoSha512(output) => output.as_ref(),
388 }
389 }
390}
391
392impl PartialEq for HashOutput {
393 fn eq(&self, other: &Self) -> bool {
394 self.as_ref() == other.as_ref()
395 }
396}
397
398impl Eq for HashOutput {}
399
400impl std::fmt::Debug for HashOutput {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 self.as_ref().fmt(f)
403 }
404}
405
406#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
409#[repr(u8)]
410pub enum Algorithm {
411 RsaSha1,
414 #[default]
416 RsaSha256,
417 Ed25519Sha256,
419 MlDsa44,
424}
425
426pub(crate) const R_HASH_SHA1: u64 = 0x01;
427pub(crate) const R_HASH_SHA256: u64 = 0x02;
428pub(crate) const R_HASH_SHA512: u64 = 0x40;
429
430impl Algorithm {
431 pub fn parse(name: &[u8]) -> Option<Self> {
435 hashify::map_ignore_case!(name, Algorithm,
436 b"rsa-sha1" => Algorithm::RsaSha1,
437 b"rsa-sha256" => Algorithm::RsaSha256,
438 b"ed25519-sha256" => Algorithm::Ed25519Sha256,
439 )
440 .copied()
441 }
442
443 pub fn parse_dkim2(name: &[u8]) -> Option<Self> {
448 hashify::map!(name, Algorithm,
449 b"rsa-sha256" => Algorithm::RsaSha256,
450 b"ed25519-sha256" => Algorithm::Ed25519Sha256,
451 b"mldsa44" => Algorithm::MlDsa44,
452 )
453 .copied()
454 }
455
456 pub fn name(&self) -> &'static str {
458 match self {
459 Algorithm::RsaSha1 => "rsa-sha1",
460 Algorithm::RsaSha256 => "rsa-sha256",
461 Algorithm::Ed25519Sha256 => "ed25519-sha256",
462 Algorithm::MlDsa44 => "mldsa44",
463 }
464 }
465
466 pub(crate) fn is_dkim1(&self) -> bool {
467 !matches!(self, Algorithm::MlDsa44)
468 }
469}