1use super::headers::{Writable, Writer};
22use crate::{Result, dkim::Canonicalization};
23
24#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
25pub(crate) mod composite;
26
27#[derive(Debug, PartialEq, Eq, Clone)]
30pub enum CryptoError {
31 Library(String),
34 FailedVerification,
36 IncompatibleAlgorithms,
40}
41
42impl std::fmt::Display for CryptoError {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 CryptoError::Library(err) => write!(f, "Cryptography layer error: {err}"),
46 CryptoError::FailedVerification => write!(f, "Signature verification failed"),
47 CryptoError::IncompatibleAlgorithms => write!(
48 f,
49 "Incompatible algorithms used in signature and DKIM DNS record"
50 ),
51 }
52 }
53}
54
55#[cfg(feature = "aws-lc-rs")]
56mod aws_lc;
57#[cfg(feature = "aws-lc-rs")]
58mod aws_lc_mldsa;
59#[cfg(feature = "aws-lc-rs")]
60pub use aws_lc::{Ed25519Key, MlDsa44Ed25519Key, RsaKey};
61#[cfg(feature = "aws-lc-rs")]
62pub(crate) use aws_lc::{Ed25519PublicKey, MlDsa44Ed25519PublicKey, RsaPublicKey};
63
64#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
65mod rust_crypto;
66#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
67pub use rust_crypto::{Ed25519Key, MlDsa44Ed25519Key, RsaKey};
68#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
69pub(crate) use rust_crypto::{Ed25519PublicKey, MlDsa44Ed25519PublicKey, RsaPublicKey};
70
71pub trait SigningKey {
77 type Hasher: HashImpl;
79
80 fn sign(&self, input: impl Writable) -> Result<Vec<u8>>;
87
88 fn hash(&self, data: impl Writable) -> HashOutput {
90 let mut hasher = <Self::Hasher as HashImpl>::hasher();
91 data.write(&mut hasher);
92 hasher.complete()
93 }
94
95 fn algorithm(&self) -> Algorithm;
97
98 fn heap_size(&self) -> usize {
101 0
102 }
103}
104
105#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
113pub enum DkimKey {
114 Rsa(RsaKey<Sha256>),
116 Ed25519(Ed25519Key),
118 MlDsa44Ed25519(Box<MlDsa44Ed25519Key>),
121}
122
123#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
124impl SigningKey for DkimKey {
125 type Hasher = Sha256;
126
127 fn sign(&self, input: impl Writable) -> Result<Vec<u8>> {
128 match self {
129 DkimKey::Rsa(key) => key.sign(input),
130 DkimKey::Ed25519(key) => key.sign(input),
131 DkimKey::MlDsa44Ed25519(key) => key.sign(input),
132 }
133 }
134
135 fn algorithm(&self) -> Algorithm {
136 match self {
137 DkimKey::Rsa(key) => key.algorithm(),
138 DkimKey::Ed25519(key) => key.algorithm(),
139 DkimKey::MlDsa44Ed25519(key) => key.algorithm(),
140 }
141 }
142
143 fn heap_size(&self) -> usize {
144 match self {
145 DkimKey::Rsa(key) => key.heap_size(),
146 DkimKey::Ed25519(key) => key.heap_size(),
147 DkimKey::MlDsa44Ed25519(key) => size_of::<MlDsa44Ed25519Key>() + key.heap_size(),
148 }
149 }
150}
151
152#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
153pub(crate) const fn rsa_private_key_heap_size(modulus_len: usize) -> usize {
154 const BASE: usize = 768;
155 const PER_MODULUS_BYTE: usize = 8;
156 BASE + PER_MODULUS_BYTE * modulus_len
157}
158
159#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
160impl From<RsaKey<Sha256>> for DkimKey {
161 fn from(key: RsaKey<Sha256>) -> Self {
162 DkimKey::Rsa(key)
163 }
164}
165
166#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
167impl From<Ed25519Key> for DkimKey {
168 fn from(key: Ed25519Key) -> Self {
169 DkimKey::Ed25519(key)
170 }
171}
172
173#[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
174impl From<MlDsa44Ed25519Key> for DkimKey {
175 fn from(key: MlDsa44Ed25519Key) -> Self {
176 DkimKey::MlDsa44Ed25519(Box::new(key))
177 }
178}
179
180pub trait VerifyingKey {
183 fn verify<'a>(
193 &self,
194 headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>,
195 signature: &[u8],
196 canonicalization: Canonicalization,
197 algorithm: Algorithm,
198 ) -> Result<()>;
199
200 fn verify_bytes(&self, input: &[u8], signature: &[u8], algorithm: Algorithm) -> Result<()>;
210
211 fn public_key_bits(&self) -> usize {
215 usize::MAX
216 }
217
218 fn heap_size(&self) -> usize {
221 0
222 }
223}
224
225pub(crate) enum VerifyingKeyType {
226 Rsa,
227 Ed25519,
228 MlDsa44Ed25519,
229}
230
231impl VerifyingKeyType {
232 pub(crate) fn verifying_key(
233 &self,
234 bytes: &[u8],
235 ) -> Result<Box<dyn VerifyingKey + Send + Sync>> {
236 match self {
237 #[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
238 Self::Rsa => RsaPublicKey::verifying_key_from_bytes(bytes),
239 #[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
240 Self::Ed25519 => Ed25519PublicKey::verifying_key_from_bytes(bytes),
241 #[cfg(any(feature = "aws-lc-rs", feature = "rust-crypto"))]
242 Self::MlDsa44Ed25519 => MlDsa44Ed25519PublicKey::verifying_key_from_bytes(bytes),
243 }
244 }
245}
246
247pub trait HashContext: Writer + Sized {
249 fn complete(self) -> HashOutput;
251}
252
253pub trait HashImpl {
255 type Context: HashContext;
257
258 fn hasher() -> Self::Context;
260
261 fn digest(data: impl Writable) -> HashOutput {
263 let mut hasher = Self::hasher();
264 data.write(&mut hasher);
265 hasher.complete()
266 }
267}
268
269#[derive(Clone, Copy)]
273pub struct Sha1;
274
275#[derive(Clone, Copy)]
277pub struct Sha256;
278
279#[derive(Clone, Copy)]
282pub struct Sha512;
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288#[repr(u64)]
289pub enum HashAlgorithm {
290 Sha1 = R_HASH_SHA1,
292 Sha256 = R_HASH_SHA256,
294 Sha512 = R_HASH_SHA512,
296}
297
298#[cfg(feature = "aws-lc-rs")]
299impl HashAlgorithm {
300 pub fn hash(&self, data: impl Writable) -> HashOutput {
302 let mut hasher = aws_lc_rs::digest::Context::new(match self {
303 Self::Sha1 => &aws_lc_rs::digest::SHA1_FOR_LEGACY_USE_ONLY,
304 Self::Sha256 => &aws_lc_rs::digest::SHA256,
305 Self::Sha512 => &aws_lc_rs::digest::SHA512,
306 });
307 data.write(&mut hasher);
308 HashOutput::Digest(hasher.finish())
309 }
310}
311
312#[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
313impl HashAlgorithm {
314 pub fn hash(&self, data: impl Writable) -> HashOutput {
316 match self {
317 Self::Sha1 => Sha1::digest(data),
318 Self::Sha256 => Sha256::digest(data),
319 Self::Sha512 => Sha512::digest(data),
320 }
321 }
322}
323
324impl HashAlgorithm {
325 pub fn parse(name: &str) -> Option<Self> {
329 if name.eq_ignore_ascii_case("sha256") {
330 Some(HashAlgorithm::Sha256)
331 } else if name.eq_ignore_ascii_case("sha1") {
332 Some(HashAlgorithm::Sha1)
333 } else {
334 None
335 }
336 }
337
338 pub fn name(&self) -> &'static str {
340 match self {
341 HashAlgorithm::Sha1 => "sha1",
342 HashAlgorithm::Sha256 => "sha256",
343 HashAlgorithm::Sha512 => "sha512",
344 }
345 }
346}
347
348#[derive(Clone)]
353#[non_exhaustive]
354pub enum HashOutput {
355 #[cfg(feature = "aws-lc-rs")]
357 Digest(aws_lc_rs::digest::Digest),
358 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
360 RustCryptoSha1(sha1::digest::Output<sha1::Sha1>),
361 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
363 RustCryptoSha256(sha2::digest::Output<sha2::Sha256>),
364 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
366 RustCryptoSha512(sha2::digest::Output<sha2::Sha512>),
367}
368
369impl AsRef<[u8]> for HashOutput {
370 fn as_ref(&self) -> &[u8] {
371 match self {
372 #[cfg(feature = "aws-lc-rs")]
373 Self::Digest(output) => output.as_ref(),
374 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
375 Self::RustCryptoSha1(output) => output.as_ref(),
376 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
377 Self::RustCryptoSha256(output) => output.as_ref(),
378 #[cfg(all(feature = "rust-crypto", not(feature = "aws-lc-rs")))]
379 Self::RustCryptoSha512(output) => output.as_ref(),
380 }
381 }
382}
383
384impl PartialEq for HashOutput {
385 fn eq(&self, other: &Self) -> bool {
386 self.as_ref() == other.as_ref()
387 }
388}
389
390impl Eq for HashOutput {}
391
392impl std::fmt::Debug for HashOutput {
393 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394 self.as_ref().fmt(f)
395 }
396}
397
398#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
401#[repr(u8)]
402pub enum Algorithm {
403 RsaSha1,
406 #[default]
408 RsaSha256,
409 Ed25519Sha256,
411 MlDsa44Ed25519Sha512,
418}
419
420pub(crate) const R_HASH_SHA1: u64 = 0x01;
421pub(crate) const R_HASH_SHA256: u64 = 0x02;
422pub(crate) const R_HASH_SHA512: u64 = 0x40;
423
424impl Algorithm {
425 pub fn parse(name: &[u8]) -> Option<Self> {
429 hashify::map_ignore_case!(name, Algorithm,
430 b"rsa-sha1" => Algorithm::RsaSha1,
431 b"rsa-sha256" => Algorithm::RsaSha256,
432 b"ed25519-sha256" => Algorithm::Ed25519Sha256,
433 )
434 .copied()
435 }
436
437 pub fn parse_dkim2(name: &[u8]) -> Option<Self> {
442 hashify::map!(name, Algorithm,
443 b"rsa-sha256" => Algorithm::RsaSha256,
444 b"ed25519-sha256" => Algorithm::Ed25519Sha256,
445 b"mldsa44-ed25519-sha512" => Algorithm::MlDsa44Ed25519Sha512,
446 )
447 .copied()
448 }
449
450 pub fn name(&self) -> &'static str {
452 match self {
453 Algorithm::RsaSha1 => "rsa-sha1",
454 Algorithm::RsaSha256 => "rsa-sha256",
455 Algorithm::Ed25519Sha256 => "ed25519-sha256",
456 Algorithm::MlDsa44Ed25519Sha512 => "mldsa44-ed25519-sha512",
457 }
458 }
459
460 pub(crate) fn is_dkim1(&self) -> bool {
461 !matches!(self, Algorithm::MlDsa44Ed25519Sha512)
462 }
463}