1use super::headers::{Writable, Writer};
8use crate::{Result, dkim::Canonicalization};
9
10#[derive(Debug, PartialEq, Eq, Clone)]
11pub enum CryptoError {
12 Library(String),
13 FailedVerification,
14 IncompatibleAlgorithms,
15}
16
17impl std::fmt::Display for CryptoError {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 match self {
20 CryptoError::Library(err) => write!(f, "Cryptography layer error: {err}"),
21 CryptoError::FailedVerification => write!(f, "Signature verification failed"),
22 CryptoError::IncompatibleAlgorithms => write!(
23 f,
24 "Incompatible algorithms used in signature and DKIM DNS record"
25 ),
26 }
27 }
28}
29
30#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
31mod ring_impls;
32#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
33pub use ring_impls::{Ed25519Key, RsaKey};
34#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
35pub(crate) use ring_impls::{Ed25519PublicKey, RsaPublicKey};
36
37#[cfg(all(
38 feature = "rust-crypto",
39 not(any(feature = "ring", feature = "aws-lc-rs"))
40))]
41mod rust_crypto;
42#[cfg(all(
43 feature = "rust-crypto",
44 not(any(feature = "ring", feature = "aws-lc-rs"))
45))]
46pub use rust_crypto::{Ed25519Key, RsaKey};
47#[cfg(all(
48 feature = "rust-crypto",
49 not(any(feature = "ring", feature = "aws-lc-rs"))
50))]
51pub(crate) use rust_crypto::{Ed25519PublicKey, RsaPublicKey};
52
53pub trait SigningKey {
54 type Hasher: HashImpl;
55
56 fn sign(&self, input: impl Writable) -> Result<Vec<u8>>;
57
58 fn hash(&self, data: impl Writable) -> HashOutput {
59 let mut hasher = <Self::Hasher as HashImpl>::hasher();
60 data.write(&mut hasher);
61 hasher.complete()
62 }
63
64 fn algorithm(&self) -> Algorithm;
65}
66
67#[cfg(any(feature = "ring", feature = "aws-lc-rs", feature = "rust-crypto"))]
69pub enum DkimKey {
70 Rsa(RsaKey<Sha256>),
71 Ed25519(Ed25519Key),
72}
73
74#[cfg(any(feature = "ring", feature = "aws-lc-rs", feature = "rust-crypto"))]
75impl SigningKey for DkimKey {
76 type Hasher = Sha256;
77
78 fn sign(&self, input: impl Writable) -> Result<Vec<u8>> {
79 match self {
80 DkimKey::Rsa(key) => key.sign(input),
81 DkimKey::Ed25519(key) => key.sign(input),
82 }
83 }
84
85 fn algorithm(&self) -> Algorithm {
86 match self {
87 DkimKey::Rsa(key) => key.algorithm(),
88 DkimKey::Ed25519(key) => key.algorithm(),
89 }
90 }
91}
92
93#[cfg(any(feature = "ring", feature = "aws-lc-rs", feature = "rust-crypto"))]
94impl From<RsaKey<Sha256>> for DkimKey {
95 fn from(key: RsaKey<Sha256>) -> Self {
96 DkimKey::Rsa(key)
97 }
98}
99
100#[cfg(any(feature = "ring", feature = "aws-lc-rs", feature = "rust-crypto"))]
101impl From<Ed25519Key> for DkimKey {
102 fn from(key: Ed25519Key) -> Self {
103 DkimKey::Ed25519(key)
104 }
105}
106
107pub trait VerifyingKey {
108 fn verify<'a>(
109 &self,
110 headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>,
111 signature: &[u8],
112 canonicalization: Canonicalization,
113 algorithm: Algorithm,
114 ) -> Result<()>;
115
116 fn verify_bytes(&self, input: &[u8], signature: &[u8], algorithm: Algorithm) -> Result<()>;
118
119 fn public_key_bits(&self) -> usize {
121 usize::MAX
122 }
123}
124
125pub(crate) enum VerifyingKeyType {
126 Rsa,
127 Ed25519,
128}
129
130impl VerifyingKeyType {
131 pub(crate) fn verifying_key(
132 &self,
133 bytes: &[u8],
134 ) -> Result<Box<dyn VerifyingKey + Send + Sync>> {
135 match self {
136 #[cfg(any(feature = "ring", feature = "aws-lc-rs", feature = "rust-crypto"))]
137 Self::Rsa => RsaPublicKey::verifying_key_from_bytes(bytes),
138 #[cfg(any(feature = "ring", feature = "aws-lc-rs", feature = "rust-crypto"))]
139 Self::Ed25519 => Ed25519PublicKey::verifying_key_from_bytes(bytes),
140 }
141 }
142}
143
144pub trait HashContext: Writer + Sized {
145 fn complete(self) -> HashOutput;
146}
147
148pub trait HashImpl {
149 type Context: HashContext;
150
151 fn hasher() -> Self::Context;
152}
153
154#[derive(Clone, Copy)]
155pub struct Sha1;
156
157#[derive(Clone, Copy)]
158pub struct Sha256;
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161#[repr(u64)]
162pub enum HashAlgorithm {
163 Sha1 = R_HASH_SHA1,
164 Sha256 = R_HASH_SHA256,
165}
166
167#[cfg(feature = "aws-lc-rs")]
168use aws_lc_rs as crypto_backend;
169#[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))]
170use ring as crypto_backend;
171
172#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
173impl HashAlgorithm {
174 pub fn hash(&self, data: impl Writable) -> HashOutput {
175 match self {
176 Self::Sha1 => {
177 let mut hasher = crypto_backend::digest::Context::new(
178 &crypto_backend::digest::SHA1_FOR_LEGACY_USE_ONLY,
179 );
180 data.write(&mut hasher);
181 HashOutput::Digest(hasher.finish())
182 }
183 Self::Sha256 => {
184 let mut hasher =
185 crypto_backend::digest::Context::new(&crypto_backend::digest::SHA256);
186 data.write(&mut hasher);
187 HashOutput::Digest(hasher.finish())
188 }
189 }
190 }
191}
192
193#[cfg(all(
194 feature = "rust-crypto",
195 not(any(feature = "ring", feature = "aws-lc-rs"))
196))]
197impl HashAlgorithm {
198 pub fn hash(&self, data: impl Writable) -> HashOutput {
199 use sha2::Digest as _;
200 match self {
201 Self::Sha1 => {
202 let mut hasher = sha1::Sha1::new();
203 data.write(&mut hasher);
204 HashOutput::RustCryptoSha1(hasher.finalize())
205 }
206 Self::Sha256 => {
207 let mut hasher = sha2::Sha256::new();
208 data.write(&mut hasher);
209 HashOutput::RustCryptoSha256(hasher.finalize())
210 }
211 }
212 }
213}
214
215impl HashAlgorithm {
216 pub fn parse(name: &str) -> Option<Self> {
217 if name.eq_ignore_ascii_case("sha256") {
218 Some(HashAlgorithm::Sha256)
219 } else if name.eq_ignore_ascii_case("sha1") {
220 Some(HashAlgorithm::Sha1)
221 } else {
222 None
223 }
224 }
225
226 pub fn name(&self) -> &'static str {
227 match self {
228 HashAlgorithm::Sha1 => "sha1",
229 HashAlgorithm::Sha256 => "sha256",
230 }
231 }
232}
233
234#[derive(Clone)]
235#[non_exhaustive]
236pub enum HashOutput {
237 #[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
238 Digest(crypto_backend::digest::Digest),
239 #[cfg(all(
240 feature = "rust-crypto",
241 not(any(feature = "ring", feature = "aws-lc-rs"))
242 ))]
243 RustCryptoSha1(sha1::digest::Output<sha1::Sha1>),
244 #[cfg(all(
245 feature = "rust-crypto",
246 not(any(feature = "ring", feature = "aws-lc-rs"))
247 ))]
248 RustCryptoSha256(sha2::digest::Output<sha2::Sha256>),
249}
250
251impl AsRef<[u8]> for HashOutput {
252 fn as_ref(&self) -> &[u8] {
253 match self {
254 #[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
255 Self::Digest(output) => output.as_ref(),
256 #[cfg(all(
257 feature = "rust-crypto",
258 not(any(feature = "ring", feature = "aws-lc-rs"))
259 ))]
260 Self::RustCryptoSha1(output) => output.as_ref(),
261 #[cfg(all(
262 feature = "rust-crypto",
263 not(any(feature = "ring", feature = "aws-lc-rs"))
264 ))]
265 Self::RustCryptoSha256(output) => output.as_ref(),
266 }
267 }
268}
269
270impl PartialEq for HashOutput {
271 fn eq(&self, other: &Self) -> bool {
272 self.as_ref() == other.as_ref()
273 }
274}
275
276impl Eq for HashOutput {}
277
278impl std::fmt::Debug for HashOutput {
279 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280 self.as_ref().fmt(f)
281 }
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
285#[repr(u8)]
286pub enum Algorithm {
287 RsaSha1,
288 #[default]
289 RsaSha256,
290 Ed25519Sha256,
291}
292
293pub(crate) const R_HASH_SHA1: u64 = 0x01;
294pub(crate) const R_HASH_SHA256: u64 = 0x02;
295
296impl Algorithm {
297 pub fn parse(name: &[u8]) -> Option<Self> {
298 hashify::tiny_map_ignore_case!(name,
299 b"rsa-sha1" => Algorithm::RsaSha1,
300 b"rsa-sha256" => Algorithm::RsaSha256,
301 b"ed25519-sha256" => Algorithm::Ed25519Sha256,
302 )
303 }
304
305 pub fn name(&self) -> &'static str {
306 match self {
307 Algorithm::RsaSha1 => "rsa-sha1",
308 Algorithm::RsaSha256 => "rsa-sha256",
309 Algorithm::Ed25519Sha256 => "ed25519-sha256",
310 }
311 }
312}