Commit
Author: Mauro D [mauro@stalw.art]
Committer: GitHub [noreply@github.com] Thu, 26 Jan 2023 17:38:17 +0000
Hash: a5a873bab586aca5645d512632b50867792befc1
Timestamp: Thu, 26 Jan 2023 17:38:17 +0000 (1 year ago)

+695 -195 +/-15 browse
Merge pull request #10 from InstantDomain/ring-3
Merge pull request #10 from InstantDomain/ring-3

Implement signing and verification using *ring*
1diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
2index 387b9c4..04df1f9 100644
3--- a/.github/workflows/rust.yml
4+++ b/.github/workflows/rust.yml
5 @@ -31,3 +31,5 @@ jobs:
6 run: cargo build --verbose
7 - name: Run tests
8 run: cargo test --verbose
9+ - name: Run tests (ring)
10+ run: cargo test --verbose --no-default-features --features ring,rustls-pemfile
11 diff --git a/Cargo.toml b/Cargo.toml
12index 7afcf4f..e02e2ae 100644
13--- a/Cargo.toml
14+++ b/Cargo.toml
15 @@ -15,25 +15,30 @@ resolver = "2"
16 [lib]
17 doctest = false
18
19+ [features]
20+ default = ["rust-crypto"]
21+ rust-crypto = ["ed25519-dalek", "rsa", "sha1", "sha2"]
22+ test = []
23+
24 [dependencies]
25- mail-parser = { version = "0.8", git = "https://github.com/stalwartlabs/mail-parser", features = ["ludicrous_mode", "full_encoding"] }
26- mail-builder = { version = "0.2.5", git = "https://github.com/stalwartlabs/mail-builder", features = ["ludicrous_mode"] }
27- rsa = {version = "0.7.0"}
28- ed25519-dalek = "1.0.1"
29- sha1 = {version = "0.10", features = ["oid"]}
30- sha2 = {version = "0.10.6", features = ["oid"]}
31- trust-dns-resolver = { version = "0.22.0", features = ["dns-over-rustls", "dnssec-ring"] }
32+ ahash = "0.8.0"
33+ ed25519-dalek = { version = "1.0.1", optional = true }
34+ flate2 = "1.0.25"
35 lru-cache = "0.1.2"
36+ mail-parser = { version = "0.8", git = "https://github.com/stalwartlabs/mail-parser", features = ["ludicrous_mode", "full_encoding"] }
37+ mail-builder = { version = "0.2.5", git = "https://github.com/stalwartlabs/mail-builder", features = ["ludicrous_mode"] }
38 parking_lot = "0.12.0"
39- ahash = "0.8.0"
40 quick-xml = "0.27.1"
41+ ring = { version = "0.16.20", optional = true }
42+ rsa = { version = "0.7.0", optional = true }
43+ rustls-pemfile = { version = "1", optional = true }
44 serde = { version = "1.0", features = ["derive"] }
45 serde_json = "1.0"
46+ sha1 = { version = "0.10", features = ["oid"], optional = true }
47+ sha2 = { version = "0.10.6", features = ["oid"], optional = true }
48+ trust-dns-resolver = { version = "0.22.0", features = ["dns-over-rustls", "dnssec-ring"] }
49 zip = "0.6.3"
50- flate2 = "1.0.25"
51
52 [dev-dependencies]
53 tokio = { version = "1.16", features = ["net", "io-util", "time", "rt-multi-thread", "macros"] }
54-
55- [features]
56- test = []
57+ rustls-pemfile = "1"
58 diff --git a/examples/arc_seal.rs b/examples/arc_seal.rs
59index beda3a6..b7d32cb 100644
60--- a/examples/arc_seal.rs
61+++ b/examples/arc_seal.rs
62 @@ -55,7 +55,11 @@ async fn main() {
63 // Seal message
64 if arc_result.can_be_sealed() {
65 // Seal the e-mail message using RSA-SHA256
66+ #[cfg(feature = "rust-crypto")]
67 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
68+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
69+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
70+
71 let arc_set = ArcSealer::from_key(pk_rsa)
72 .domain("example.org")
73 .selector("default")
74 diff --git a/examples/dkim_sign.rs b/examples/dkim_sign.rs
75index edf9fa0..bb96b6e 100644
76--- a/examples/dkim_sign.rs
77+++ b/examples/dkim_sign.rs
78 @@ -46,7 +46,15 @@ I'm going to need those TPS reports ASAP. So, if you could do that, that'd be gr
79
80 fn main() {
81 // Sign an e-mail message using RSA-SHA256
82+ #[cfg(feature = "rust-crypto")]
83 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
84+ #[cfg(all(
85+ feature = "ring",
86+ feature = "rustls-pemfile",
87+ not(feature = "rust-crypto")
88+ ))]
89+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
90+
91 let signature_rsa = DkimSigner::from_key(pk_rsa)
92 .domain("example.com")
93 .selector("default")
94 @@ -55,11 +63,19 @@ fn main() {
95 .unwrap();
96
97 // Sign an e-mail message using ED25519-SHA256
98+ #[cfg(feature = "rust-crypto")]
99 let pk_ed = Ed25519Key::from_bytes(
100 &base64_decode(ED25519_PUBLIC_KEY.as_bytes()).unwrap(),
101 &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(),
102 )
103 .unwrap();
104+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
105+ let pk_ed = Ed25519Key::from_seed_and_public_key(
106+ &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(),
107+ &base64_decode(ED25519_PUBLIC_KEY.rsplit_once("p=").unwrap().1.as_bytes()).unwrap(),
108+ )
109+ .unwrap();
110+
111 let signature_ed = DkimSigner::from_key(pk_ed)
112 .domain("example.com")
113 .selector("default-ed")
114 diff --git a/resources/rsa-private.pem b/resources/rsa-private.pem
115new file mode 100644
116index 0000000..be08f06
117--- /dev/null
118+++ b/resources/rsa-private.pem
119 @@ -0,0 +1,27 @@
120+ -----BEGIN RSA PRIVATE KEY-----
121+ MIIEowIBAAKCAQEAv9XYXG3uK95115mB4nJ37nGeNe2CrARm1agrbcnSk5oIaEfM
122+ ZLUR/X8gPzoiNHZcfMZEVR6bAytxUhc5EvZIZrjSuEEeny+fFd/cTvcm3cOUUbIa
123+ UmSACj0dL2/KwW0LyUaza9z9zor7I5XdIl1M53qVd5GI62XBB76FH+Q0bWPZNkT4
124+ NclzTLspD/MTpNCCPhySM4Kdg5CuDczTH4aNzyS0TqgXdtw6A4Sdsp97VXT9fkPW
125+ 9rso3lrkpsl/9EQ1mR/DWK6PBmRfIuSFuqnLKY6v/z2hXHxF7IoojfZLa2kZr9Ae
126+ d4l9WheQOTA19k5r2BmlRw/W9CrgCBo0Sdj+KQIDAQABAoIBAFPChEi/OvnulReB
127+ ECQWhOUYuNKlFKQU++2YEvZJ4+bMn5UgnE7wfJ1pj2Pr9xlfALz+OMHNrjMxGbaV
128+ KzdrT2uCkYcf78XjnhuH9gKIiXDUv4L4N+P3u6w8yOx4bFgOS9IjS53yDOPM7SC5
129+ g6dIg5aigHaHlffqIuFFv4yQMI/+Ai+zBKxS7wRhxK/7nnAuo28fe5MEdp57ho9/
130+ AGlDNsdg9zCgjwhokwFE3+AaD+bkUFm4gQ1XjkUFrlmnQn8vDQ0i9toEWhCj+UPY
131+ iOKL63MJnr90MXTXWLHoFj99wBp//mYygbF9Lj8fa28/oa8LWp3Jhb7QeMgH46iv
132+ 3aLHbTECgYEA5M2dAw+nyMw9vYlkMejhwObKYP8Mr/6zcGMLCalYvRJM5iUAM0JI
133+ H6sM6pV9/nv167cbKocj3xYPdtE7FPOn4132MLM8Ne1f8nPE64Qrcbj5WBXvLnU8
134+ hpWbwe2Z8h7UUMKx6q4F1/TXYkc3ScxYwfjM4mP/pLsAOgVzRSEEgrUCgYEA1qNQ
135+ xaQHNWZ1O8WuTnqWd5JSsic6iURAmUcLeFDZY2PWhVoaQ8L/xMQhDYs1FIbLWArW
136+ 4Qq3Ibu8AbSejAKuaJz7Uf26PX+PYVUwAOO0qamCJ8d/qd6So7qWMDyAY2yXI39Y
137+ 1nMqRjr7bkEsggAZao7BKqA7ZtmogjOusBT38iUCgYEA06agJ8TDoKvOMRZ26PRU
138+ YO0dKLzGL8eclcoI29cbj0rud7aiiMg3j5PbTuUat95TjsjDCIQaWrM9etvxm2AJ
139+ Xfn9Uu96MyhyKQWOk46f4YMKpMElkARDCPw8KRhx39dE77AqhLyWCz8iPndCXbH6
140+ KPTOEl4OjYOuof2Is9nnIkECgYBh948RdsnXhNlzm8nwhiGRmBbou+EK8D0v+O5y
141+ Tyy6IcKzgSnFzgZh8EdJ4EUtBk1f9SqY8wQdgIvSl3daXorusuA/TzkngsaV3YUY
142+ ktZOLlF7CKLrjOyPkMWmZKcROmpNyH1q/IvKHHfQnizLdXIkYd4nL5WNX0F7lE1i
143+ j1+QhQKBgB2lviBK7rJFwlFYdQUP1NAN2dKxMZk8uJS8JglHrM0+8nRI83HbTdEQ
144+ vB0ManEKBkbS4T5n+gRtdEqKSDmWDTXDlrBfcdCHNQLwYtBpOotCqQn/AmfjcPBl
145+ byAbwh4+HiZ5JISoRZpiZqy67aJNVoXmdtb/E9mi7ozzytpxMNql
146+ -----END RSA PRIVATE KEY-----
147 diff --git a/src/arc/seal.rs b/src/arc/seal.rs
148index 0ba3101..bc44bbe 100644
149--- a/src/arc/seal.rs
150+++ b/src/arc/seal.rs
151 @@ -14,10 +14,10 @@ use mail_builder::encoders::base64::base64_encode;
152
153 use crate::{
154 common::{
155- crypto::{HashAlgorithm, HashContext, Sha256, SigningKey},
156- headers::Writer,
157+ crypto::{HashAlgorithm, Sha256, SigningKey},
158+ headers::{Writable, Writer},
159 },
160- dkim::{Canonicalization, Done},
161+ dkim::{canonicalize::CanonicalHeaders, Canonicalization, Done},
162 ArcOutput, AuthenticatedMessage, AuthenticationResults, DkimResult, Error,
163 };
164
165 @@ -55,12 +55,9 @@ impl<T: SigningKey<Hasher = Sha256>> ArcSealer<T, Done> {
166 _ => ChainValidation::Fail,
167 };
168 }
169- // Canonicalize headers
170- let mut header_hasher = self.key.hasher();
171- let signed_headers = set
172- .signature
173- .canonicalize_headers(message, &mut header_hasher)?;
174
175+ // Canonicalize headers
176+ let (canonical_headers, signed_headers) = set.signature.canonicalize_headers(message)?;
177 if signed_headers.is_empty() {
178 return Err(Error::NoHeadersFound);
179 }
180 @@ -78,15 +75,16 @@ impl<T: SigningKey<Hasher = Sha256>> ArcSealer<T, Done> {
181 // Use cached hash
182 set.signature.bh = base64_encode(bh)?;
183 } else {
184- let mut body_hasher = self.key.hasher();
185- set.signature.cb.canonicalize_body(
186- message
187- .raw_message
188- .get(message.body_offset..)
189- .unwrap_or_default(),
190- &mut body_hasher,
191+ let hash = self.key.hash(
192+ set.signature.cb.canonical_body(
193+ message
194+ .raw_message
195+ .get(message.body_offset..)
196+ .unwrap_or_default(),
197+ u64::MAX,
198+ ),
199 );
200- set.signature.bh = base64_encode(body_hasher.complete().as_ref())?;
201+ set.signature.bh = base64_encode(hash.as_ref())?;
202 }
203
204 // Create Signature
205 @@ -103,39 +101,60 @@ impl<T: SigningKey<Hasher = Sha256>> ArcSealer<T, Done> {
206 };
207 set.signature.h = signed_headers;
208
209- // Add signature to hash
210- set.signature.write(&mut header_hasher, false);
211-
212 // Sign
213- let b = self.key.sign(header_hasher.complete())?;
214+ let b = self.key.sign(SignableSet {
215+ set: &set,
216+ headers: canonical_headers,
217+ })?;
218 set.signature.b = base64_encode(&b)?;
219
220- // Hash ARC chain
221- let mut header_hasher = self.key.hasher();
222- if !arc_output.set.is_empty() {
223+ // Seal
224+ let b = self.key.sign(SignableChain {
225+ arc_output,
226+ set: &set,
227+ })?;
228+ set.seal.b = base64_encode(&b)?;
229+
230+ Ok(set)
231+ }
232+ }
233+
234+ struct SignableSet<'a> {
235+ set: &'a ArcSet<'a>,
236+ headers: CanonicalHeaders<'a>,
237+ }
238+
239+ impl<'a> Writable for SignableSet<'a> {
240+ fn write(self, writer: &mut impl Writer) {
241+ self.headers.write(writer);
242+ self.set.signature.write(writer, false);
243+ }
244+ }
245+
246+ struct SignableChain<'a> {
247+ arc_output: &'a ArcOutput<'a>,
248+ set: &'a ArcSet<'a>,
249+ }
250+
251+ impl<'a> Writable for SignableChain<'a> {
252+ fn write(self, writer: &mut impl Writer) {
253+ if !self.arc_output.set.is_empty() {
254 Canonicalization::Relaxed.canonicalize_headers(
255- &mut arc_output.set.iter().flat_map(|set| {
256+ self.arc_output.set.iter().flat_map(|set| {
257 [
258 (set.results.name, set.results.value),
259 (set.signature.name, set.signature.value),
260 (set.seal.name, set.seal.value),
261 ]
262 }),
263- &mut header_hasher,
264+ writer,
265 );
266 }
267
268- // Hash ARC headers for the current instance
269- set.results.write(&mut header_hasher, set.seal.i, false);
270- set.signature.write(&mut header_hasher, false);
271- header_hasher.write(b"\r\n");
272- set.seal.write(&mut header_hasher, false);
273-
274- // Seal
275- let b = self.key.sign(header_hasher.complete())?;
276- set.seal.b = base64_encode(&b)?;
277-
278- Ok(set)
279+ self.set.results.write(writer, self.set.seal.i, false);
280+ self.set.signature.write(writer, false);
281+ writer.write(b"\r\n");
282+ self.set.seal.write(writer, false);
283 }
284 }
285
286 @@ -143,8 +162,7 @@ impl Signature {
287 pub(crate) fn canonicalize_headers<'x>(
288 &self,
289 message: &'x AuthenticatedMessage<'x>,
290- header_hasher: &mut impl Writer,
291- ) -> crate::Result<Vec<String>> {
292+ ) -> crate::Result<(CanonicalHeaders<'x>, Vec<String>)> {
293 let mut headers = Vec::with_capacity(self.h.len());
294 let mut found_headers = vec![false; self.h.len()];
295 let mut signed_headers = Vec::with_capacity(self.h.len());
296 @@ -161,8 +179,7 @@ impl Signature {
297 }
298 }
299
300- self.ch
301- .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher);
302+ let canonical_headers = self.ch.canonical_headers(headers);
303
304 // Add any missing headers
305 signed_headers.reverse();
306 @@ -172,7 +189,7 @@ impl Signature {
307 }
308 }
309
310- Ok(signed_headers)
311+ Ok((canonical_headers, signed_headers))
312 }
313 }
314
315 @@ -195,34 +212,28 @@ mod test {
316 AuthenticatedMessage, AuthenticationResults, DkimResult, Resolver,
317 };
318
319- const RSA_PRIVATE_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY-----
320- MIICXwIBAAKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFC
321- jxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/RtdC2UzJ1lWT947qR+Rcac2gb
322- to/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB
323- AoGBALmn+XwWk7akvkUlqb+dOxyLB9i5VBVfje89Teolwc9YJT36BGN/l4e0l6QX
324- /1//6DWUTB3KI6wFcm7TWJcxbS0tcKZX7FsJvUz1SbQnkS54DJck1EZO/BLa5ckJ
325- gAYIaqlA9C0ZwM6i58lLlPadX/rtHb7pWzeNcZHjKrjM461ZAkEA+itss2nRlmyO
326- n1/5yDyCluST4dQfO8kAB3toSEVc7DeFeDhnC1mZdjASZNvdHS4gbLIA1hUGEF9m
327- 3hKsGUMMPwJBAPW5v/U+AWTADFCS22t72NUurgzeAbzb1HWMqO4y4+9Hpjk5wvL/
328- eVYizyuce3/fGke7aRYw/ADKygMJdW8H/OcCQQDz5OQb4j2QDpPZc0Nc4QlbvMsj
329- 7p7otWRO5xRa6SzXqqV3+F0VpqvDmshEBkoCydaYwc2o6WQ5EBmExeV8124XAkEA
330- qZzGsIxVP+sEVRWZmW6KNFSdVUpk3qzK0Tz/WjQMe5z0UunY9Ax9/4PVhp/j61bf
331- eAYXunajbBSOLlx4D+TunwJBANkPI5S9iylsbLs6NkaMHV6k5ioHBBmgCak95JGX
332- GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
333- -----END RSA PRIVATE KEY-----"#;
334+ const RSA_PRIVATE_KEY: &str = include_str!("../../resources/rsa-private.pem");
335
336 const RSA_PUBLIC_KEY: &str = concat!(
337- "v=DKIM1; t=s; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ",
338- "KBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYt",
339- "IxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v",
340- "/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhi",
341- "tdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB",
342+ "v=DKIM1; t=s; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ",
343+ "8AMIIBCgKCAQEAv9XYXG3uK95115mB4nJ37nGeNe2CrARm",
344+ "1agrbcnSk5oIaEfMZLUR/X8gPzoiNHZcfMZEVR6bAytxUh",
345+ "c5EvZIZrjSuEEeny+fFd/cTvcm3cOUUbIaUmSACj0dL2/K",
346+ "wW0LyUaza9z9zor7I5XdIl1M53qVd5GI62XBB76FH+Q0bW",
347+ "PZNkT4NclzTLspD/MTpNCCPhySM4Kdg5CuDczTH4aNzyS0",
348+ "TqgXdtw6A4Sdsp97VXT9fkPW9rso3lrkpsl/9EQ1mR/DWK",
349+ "6PBmRfIuSFuqnLKY6v/z2hXHxF7IoojfZLa2kZr9Aed4l9",
350+ "WheQOTA19k5r2BmlRw/W9CrgCBo0Sdj+KQIDAQAB",
351 );
352
353 const ED25519_PRIVATE_KEY: &str = "nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A=";
354 const ED25519_PUBLIC_KEY: &str =
355 "v=DKIM1; k=ed25519; p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=";
356
357+ #[cfg(any(
358+ feature = "rust-crypto",
359+ all(feature = "ring", feature = "rustls-pemfile")
360+ ))]
361 #[tokio::test]
362 async fn arc_seal() {
363 let message = concat!(
364 @@ -256,7 +267,10 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
365 let pk_ed_private = base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap();
366
367 // Create DKIM-signed message
368+ #[cfg(feature = "rust-crypto")]
369 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
370+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
371+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
372 let mut raw_message = DkimSigner::from_key(pk_rsa)
373 .domain("manchego.org")
374 .selector("rsa")
375 @@ -268,14 +282,20 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
376
377 // Verify and seal the message 50 times
378 for _ in 0..25 {
379+ #[cfg(feature = "rust-crypto")]
380 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
381+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
382+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
383
384 raw_message = arc_verify_and_seal(
385 &resolver,
386 &raw_message,
387 "scamorza.org",
388 "ed",
389+ #[cfg(feature = "rust-crypto")]
390 Ed25519Key::from_bytes(&pk_ed_public, &pk_ed_private).unwrap(),
391+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
392+ Ed25519Key::from_seed_and_public_key(&pk_ed_private, &pk_ed_public).unwrap(),
393 )
394 .await;
395 raw_message =
396 diff --git a/src/common/crypto/mod.rs b/src/common/crypto/mod.rs
397index 6c8693d..caadf17 100644
398--- a/src/common/crypto/mod.rs
399+++ b/src/common/crypto/mod.rs
400 @@ -1,20 +1,33 @@
401+ #[cfg(feature = "sha1")]
402 use sha1::{digest::Output, Digest};
403
404 use crate::{dkim::Canonicalization, Result};
405
406- use super::headers::Writer;
407+ use super::headers::{Writable, Writer};
408
409+ #[cfg(feature = "rust-crypto")]
410 mod rust_crypto;
411+ #[cfg(feature = "rust-crypto")]
412 pub use rust_crypto::{Ed25519Key, RsaKey};
413+ #[cfg(feature = "rust-crypto")]
414 pub(crate) use rust_crypto::{Ed25519PublicKey, RsaPublicKey};
415
416+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
417+ mod ring_impls;
418+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
419+ pub use ring_impls::{Ed25519Key, RsaKey};
420+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
421+ pub(crate) use ring_impls::{Ed25519PublicKey, RsaPublicKey};
422+
423 pub trait SigningKey {
424 type Hasher: HashImpl;
425
426- fn sign(&self, data: HashOutput) -> Result<Vec<u8>>;
427+ fn sign(&self, input: impl Writable) -> Result<Vec<u8>>;
428
429- fn hasher(&self) -> <Self::Hasher as HashImpl>::Context {
430- <Self::Hasher as HashImpl>::hasher()
431+ fn hash(&self, data: impl Writable) -> HashOutput {
432+ let mut hasher = <Self::Hasher as HashImpl>::hasher();
433+ data.write(&mut hasher);
434+ hasher.complete()
435 }
436
437 fn algorithm(&self) -> Algorithm;
438 @@ -41,7 +54,13 @@ impl VerifyingKeyType {
439 bytes: &[u8],
440 ) -> Result<Box<dyn VerifyingKey + Send + Sync>> {
441 match self {
442+ #[cfg(feature = "rust-crypto")]
443+ Self::Rsa => RsaPublicKey::verifying_key_from_bytes(bytes),
444+ #[cfg(feature = "rust-crypto")]
445+ Self::Ed25519 => Ed25519PublicKey::verifying_key_from_bytes(bytes),
446+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
447 Self::Rsa => RsaPublicKey::verifying_key_from_bytes(bytes),
448+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
449 Self::Ed25519 => Ed25519PublicKey::verifying_key_from_bytes(bytes),
450 }
451 }
452 @@ -71,24 +90,56 @@ pub enum HashAlgorithm {
453 }
454
455 impl HashAlgorithm {
456- pub fn hash(&self, data: &[u8]) -> HashOutput {
457+ pub fn hash(&self, data: impl Writable) -> HashOutput {
458 match self {
459- Self::Sha1 => HashOutput::Sha1(sha1::Sha1::digest(data)),
460- Self::Sha256 => HashOutput::Sha256(sha2::Sha256::digest(data)),
461+ #[cfg(feature = "sha1")]
462+ Self::Sha1 => {
463+ let mut hasher = sha1::Sha1::new();
464+ data.write(&mut hasher);
465+ HashOutput::RustCryptoSha1(hasher.finalize())
466+ }
467+ #[cfg(feature = "sha2")]
468+ Self::Sha256 => {
469+ let mut hasher = sha2::Sha256::new();
470+ data.write(&mut hasher);
471+ HashOutput::RustCryptoSha256(hasher.finalize())
472+ }
473+ #[cfg(all(feature = "ring", not(feature = "sha1")))]
474+ Self::Sha1 => {
475+ let mut hasher =
476+ ring::digest::Context::new(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY);
477+ data.write(&mut hasher);
478+ HashOutput::Ring(hasher.finish())
479+ }
480+ #[cfg(all(feature = "ring", not(feature = "sha2")))]
481+ Self::Sha256 => {
482+ let mut hasher = ring::digest::Context::new(&ring::digest::SHA256);
483+ data.write(&mut hasher);
484+ HashOutput::Ring(hasher.finish())
485+ }
486 }
487 }
488 }
489
490+ #[non_exhaustive]
491 pub enum HashOutput {
492- Sha1(Output<sha1::Sha1>),
493- Sha256(Output<sha2::Sha256>),
494+ #[cfg(feature = "ring")]
495+ Ring(ring::digest::Digest),
496+ #[cfg(feature = "sha1")]
497+ RustCryptoSha1(Output<sha1::Sha1>),
498+ #[cfg(feature = "sha2")]
499+ RustCryptoSha256(Output<sha2::Sha256>),
500 }
501
502 impl AsRef<[u8]> for HashOutput {
503 fn as_ref(&self) -> &[u8] {
504 match self {
505- Self::Sha1(output) => output.as_ref(),
506- Self::Sha256(output) => output.as_ref(),
507+ #[cfg(feature = "ring")]
508+ Self::Ring(output) => output.as_ref(),
509+ #[cfg(feature = "sha1")]
510+ Self::RustCryptoSha1(output) => output.as_ref(),
511+ #[cfg(feature = "sha2")]
512+ Self::RustCryptoSha256(output) => output.as_ref(),
513 }
514 }
515 }
516 diff --git a/src/common/crypto/ring_impls.rs b/src/common/crypto/ring_impls.rs
517new file mode 100644
518index 0000000..7c454b6
519--- /dev/null
520+++ b/src/common/crypto/ring_impls.rs
521 @@ -0,0 +1,287 @@
522+ use std::marker::PhantomData;
523+
524+ use ring::digest::{Context, SHA1_FOR_LEGACY_USE_ONLY, SHA256};
525+ use ring::rand::SystemRandom;
526+ use ring::signature::{
527+ Ed25519KeyPair, RsaKeyPair, UnparsedPublicKey, ED25519,
528+ RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY, RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY,
529+ RSA_PKCS1_SHA256,
530+ };
531+
532+ use crate::{
533+ common::headers::{Writable, Writer},
534+ dkim::Canonicalization,
535+ Error, Result,
536+ };
537+
538+ use super::{Algorithm, HashContext, HashImpl, HashOutput, Sha1, Sha256, SigningKey, VerifyingKey};
539+
540+ #[derive(Debug)]
541+ pub struct RsaKey<T> {
542+ inner: RsaKeyPair,
543+ rng: SystemRandom,
544+ padding: PhantomData<T>,
545+ }
546+
547+ impl<T: HashImpl> RsaKey<T> {
548+ #[cfg(feature = "rustls-pemfile")]
549+ pub fn from_pkcs8_pem(pkcs8_pem: &str) -> Result<Self> {
550+ let item = rustls_pemfile::read_one(&mut pkcs8_pem.as_bytes())
551+ .map_err(|err| Error::CryptoError(err.to_string()))?;
552+
553+ let pkcs8_der = match item {
554+ Some(rustls_pemfile::Item::PKCS8Key(key)) => key,
555+ _ => return Err(Error::CryptoError("No PKCS8 key found in PEM".to_string())),
556+ };
557+
558+ Self::from_pkcs8_der(&pkcs8_der)
559+ }
560+
561+ /// Creates a new RSA private key from PKCS8 DER-encoded bytes.
562+ pub fn from_pkcs8_der(pkcs8_der: &[u8]) -> Result<Self> {
563+ Ok(Self {
564+ inner: RsaKeyPair::from_pkcs8(pkcs8_der)
565+ .map_err(|err| Error::CryptoError(err.to_string()))?,
566+ rng: SystemRandom::new(),
567+ padding: PhantomData,
568+ })
569+ }
570+
571+ #[cfg(feature = "rustls-pemfile")]
572+ pub fn from_rsa_pem(rsa_pem: &str) -> Result<Self> {
573+ let item = rustls_pemfile::read_one(&mut rsa_pem.as_bytes())
574+ .map_err(|err| Error::CryptoError(err.to_string()))?;
575+
576+ let rsa_der = match item {
577+ Some(rustls_pemfile::Item::RSAKey(key)) => key,
578+ _ => return Err(Error::CryptoError("No RSA key found in PEM".to_string())),
579+ };
580+
581+ Self::from_der(&rsa_der)
582+ }
583+
584+ /// Creates a new RSA private key from a PKCS1 binary slice.
585+ pub fn from_der(der: &[u8]) -> Result<Self> {
586+ Ok(Self {
587+ inner: RsaKeyPair::from_der(der).map_err(|err| Error::CryptoError(err.to_string()))?,
588+ rng: SystemRandom::new(),
589+ padding: PhantomData,
590+ })
591+ }
592+ }
593+
594+ impl SigningKey for RsaKey<Sha256> {
595+ type Hasher = Sha256;
596+
597+ fn sign(&self, input: impl Writable) -> Result<Vec<u8>> {
598+ let mut data = Vec::with_capacity(256);
599+ input.write(&mut data);
600+
601+ let mut signature = vec![0; self.inner.public_modulus_len()];
602+ self.inner
603+ .sign(&RSA_PKCS1_SHA256, &self.rng, &data, &mut signature)
604+ .map_err(|err| Error::CryptoError(err.to_string()))?;
605+ Ok(signature)
606+ }
607+
608+ fn algorithm(&self) -> Algorithm {
609+ Algorithm::RsaSha256
610+ }
611+ }
612+
613+ pub struct Ed25519Key {
614+ inner: Ed25519KeyPair,
615+ }
616+
617+ impl Ed25519Key {
618+ pub fn from_pkcs8_der(pkcs8_der: &[u8]) -> Result<Self> {
619+ Ok(Self {
620+ inner: Ed25519KeyPair::from_pkcs8(pkcs8_der)
621+ .map_err(|err| Error::CryptoError(err.to_string()))?,
622+ })
623+ }
624+
625+ pub fn from_pkcs8_maybe_unchecked_der(pkcs8_der: &[u8]) -> Result<Self> {
626+ Ok(Self {
627+ inner: Ed25519KeyPair::from_pkcs8_maybe_unchecked(pkcs8_der)
628+ .map_err(|err| Error::CryptoError(err.to_string()))?,
629+ })
630+ }
631+
632+ pub fn from_seed_and_public_key(seed: &[u8], public_key: &[u8]) -> Result<Self> {
633+ Ok(Self {
634+ inner: Ed25519KeyPair::from_seed_and_public_key(seed, public_key)
635+ .map_err(|err| Error::CryptoError(err.to_string()))?,
636+ })
637+ }
638+ }
639+
640+ impl SigningKey for Ed25519Key {
641+ type Hasher = Sha256;
642+
643+ fn sign(&self, input: impl Writable) -> Result<Vec<u8>> {
644+ let mut data = Vec::with_capacity(256);
645+ input.write(&mut data);
646+ Ok(self.inner.sign(&data).as_ref().to_vec())
647+ }
648+
649+ fn algorithm(&self) -> Algorithm {
650+ Algorithm::Ed25519Sha256
651+ }
652+ }
653+
654+ pub(crate) struct RsaPublicKey {
655+ sha1: UnparsedPublicKey<Vec<u8>>,
656+ sha2: UnparsedPublicKey<Vec<u8>>,
657+ }
658+
659+ impl RsaPublicKey {
660+ pub(crate) fn verifying_key_from_bytes(
661+ bytes: &[u8],
662+ ) -> Result<Box<dyn VerifyingKey + Send + Sync>> {
663+ let key = try_strip_rsa_prefix(bytes);
664+ Ok(Box::new(Self {
665+ sha1: UnparsedPublicKey::new(
666+ &RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY,
667+ key.to_vec(),
668+ ),
669+ sha2: UnparsedPublicKey::new(
670+ &RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY,
671+ key.to_vec(),
672+ ),
673+ }))
674+ }
675+ }
676+
677+ /// Try to strip an ASN.1 DER-encoded RSA public key prefix
678+ ///
679+ /// Returns the original slice if the prefix is not found.
680+ fn try_strip_rsa_prefix(bytes: &[u8]) -> &[u8] {
681+ let orig = bytes;
682+ if bytes[0] != DER_SEQUENCE_TAG {
683+ return orig;
684+ }
685+
686+ let (_, bytes) = decode_multi_byte_len(&bytes[1..]);
687+ if bytes[0] != DER_SEQUENCE_TAG {
688+ return orig;
689+ }
690+
691+ let (byte_len, bytes) = decode_multi_byte_len(&bytes[1..]);
692+ if bytes[0] != DER_OBJECT_ID_TAG || byte_len != 13 {
693+ return orig;
694+ }
695+
696+ let bytes = &bytes[13..]; // skip the RSA encryption OID
697+ if bytes[0] != DER_BIT_STRING_TAG {
698+ return orig;
699+ }
700+
701+ let (_, bytes) = decode_multi_byte_len(&bytes[1..]);
702+ &bytes[1..] // skip the unused bits byte
703+ }
704+
705+ fn decode_multi_byte_len(bytes: &[u8]) -> (usize, &[u8]) {
706+ if bytes[0] & 0x80 == 0 {
707+ return (bytes[0] as usize, &bytes[1..]);
708+ }
709+
710+ let len_len = (bytes[0] & 0x7f) as usize;
711+ let mut len = 0;
712+ for i in 0..len_len {
713+ len = (len << 8) | bytes[1 + i] as usize;
714+ }
715+
716+ (len, &bytes[len_len + 1..])
717+ }
718+
719+ const DER_OBJECT_ID_TAG: u8 = 0x06;
720+ const DER_BIT_STRING_TAG: u8 = 0x03;
721+ const DER_SEQUENCE_TAG: u8 = 0x30;
722+
723+ impl VerifyingKey for RsaPublicKey {
724+ fn verify<'a>(
725+ &self,
726+ headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>,
727+ signature: &[u8],
728+ canonicalization: Canonicalization,
729+ algorithm: Algorithm,
730+ ) -> Result<()> {
731+ let mut data = Vec::with_capacity(256);
732+ canonicalization.canonicalize_headers(headers, &mut data);
733+
734+ match algorithm {
735+ Algorithm::RsaSha256 => self
736+ .sha2
737+ .verify(&data, signature)
738+ .map_err(|_| Error::FailedVerification),
739+ Algorithm::RsaSha1 => self
740+ .sha1
741+ .verify(&data, signature)
742+ .map_err(|_| Error::FailedVerification),
743+ Algorithm::Ed25519Sha256 => Err(Error::IncompatibleAlgorithms),
744+ }
745+ }
746+ }
747+
748+ pub(crate) struct Ed25519PublicKey {
749+ inner: UnparsedPublicKey<Vec<u8>>,
750+ }
751+
752+ impl Ed25519PublicKey {
753+ pub(crate) fn verifying_key_from_bytes(
754+ bytes: &[u8],
755+ ) -> Result<Box<dyn VerifyingKey + Send + Sync>> {
756+ Ok(Box::new(Self {
757+ inner: UnparsedPublicKey::new(&ED25519, bytes.to_vec()),
758+ }))
759+ }
760+ }
761+
762+ impl VerifyingKey for Ed25519PublicKey {
763+ fn verify<'a>(
764+ &self,
765+ headers: &mut dyn Iterator<Item = (&'a [u8], &'a [u8])>,
766+ signature: &[u8],
767+ canonicalization: Canonicalization,
768+ algorithm: Algorithm,
769+ ) -> Result<()> {
770+ if !matches!(algorithm, Algorithm::Ed25519Sha256) {
771+ return Err(Error::IncompatibleAlgorithms);
772+ }
773+
774+ let mut data = Vec::with_capacity(256);
775+ canonicalization.canonicalize_headers(headers, &mut data);
776+ self.inner
777+ .verify(&data, signature)
778+ .map_err(|err| Error::CryptoError(err.to_string()))
779+ }
780+ }
781+
782+ impl HashImpl for Sha1 {
783+ type Context = Context;
784+
785+ fn hasher() -> Self::Context {
786+ Context::new(&SHA1_FOR_LEGACY_USE_ONLY)
787+ }
788+ }
789+
790+ impl HashImpl for Sha256 {
791+ type Context = Context;
792+
793+ fn hasher() -> Self::Context {
794+ Context::new(&SHA256)
795+ }
796+ }
797+
798+ impl HashContext for Context {
799+ fn complete(self) -> HashOutput {
800+ HashOutput::Ring(self.finish())
801+ }
802+ }
803+
804+ impl Writer for Context {
805+ fn write(&mut self, data: &[u8]) {
806+ self.update(data);
807+ }
808+ }
809 diff --git a/src/common/crypto/rust_crypto.rs b/src/common/crypto/rust_crypto.rs
810index 2d6c34d..c5b16fc 100644
811--- a/src/common/crypto/rust_crypto.rs
812+++ b/src/common/crypto/rust_crypto.rs
813 @@ -4,7 +4,11 @@ use ed25519_dalek::Signer;
814 use rsa::{pkcs1::DecodeRsaPrivateKey, PaddingScheme, PublicKey as _, RsaPrivateKey};
815 use sha2::digest::Digest;
816
817- use crate::{common::headers::Writer, dkim::Canonicalization, Error, Result};
818+ use crate::{
819+ common::headers::{Writable, Writer},
820+ dkim::Canonicalization,
821+ Error, Result,
822+ };
823
824 use super::{Algorithm, HashContext, HashImpl, HashOutput, Sha1, Sha256, SigningKey, VerifyingKey};
825
826 @@ -41,11 +45,12 @@ impl<T: HashImpl> RsaKey<T> {
827 impl SigningKey for RsaKey<Sha1> {
828 type Hasher = Sha1;
829
830- fn sign(&self, data: HashOutput) -> Result<Vec<u8>> {
831+ fn sign(&self, input: impl Writable) -> Result<Vec<u8>> {
832+ let hash = self.hash(input);
833 self.inner
834 .sign(
835 PaddingScheme::new_pkcs1v15_sign::<<Self::Hasher as HashImpl>::Context>(),
836- data.as_ref(),
837+ hash.as_ref(),
838 )
839 .map_err(|err| Error::CryptoError(err.to_string()))
840 }
841 @@ -58,11 +63,12 @@ impl SigningKey for RsaKey<Sha1> {
842 impl SigningKey for RsaKey<Sha256> {
843 type Hasher = Sha256;
844
845- fn sign(&self, data: HashOutput) -> Result<Vec<u8>> {
846+ fn sign(&self, input: impl Writable) -> Result<Vec<u8>> {
847+ let hash = self.hash(input);
848 self.inner
849 .sign(
850 PaddingScheme::new_pkcs1v15_sign::<<Self::Hasher as HashImpl>::Context>(),
851- data.as_ref(),
852+ hash.as_ref(),
853 )
854 .map_err(|err| Error::CryptoError(err.to_string()))
855 }
856 @@ -93,8 +99,9 @@ impl Ed25519Key {
857 impl SigningKey for Ed25519Key {
858 type Hasher = Sha256;
859
860- fn sign(&self, data: HashOutput) -> Result<Vec<u8>> {
861- Ok(self.inner.sign(data.as_ref()).to_bytes().to_vec())
862+ fn sign(&self, input: impl Writable) -> Result<Vec<u8>> {
863+ let hash = self.hash(input);
864+ Ok(self.inner.sign(hash.as_ref()).to_bytes().to_vec())
865 }
866
867 fn algorithm(&self) -> Algorithm {
868 @@ -128,7 +135,10 @@ impl VerifyingKey for RsaPublicKey {
869 ) -> Result<()> {
870 match algorithm {
871 Algorithm::RsaSha256 => {
872- let hash = canonicalization.hash_headers::<Sha256>(headers);
873+ let mut hasher = sha2::Sha256::new();
874+ canonicalization.canonicalize_headers(headers, &mut hasher);
875+ let hash = hasher.finalize();
876+
877 self.inner
878 .verify(
879 PaddingScheme::new_pkcs1v15_sign::<sha2::Sha256>(),
880 @@ -138,7 +148,10 @@ impl VerifyingKey for RsaPublicKey {
881 .map_err(|_| Error::FailedVerification)
882 }
883 Algorithm::RsaSha1 => {
884- let hash = canonicalization.hash_headers::<Sha1>(headers);
885+ let mut hasher = sha1::Sha1::new();
886+ canonicalization.canonicalize_headers(headers, &mut hasher);
887+ let hash = hasher.finalize();
888+
889 self.inner
890 .verify(
891 PaddingScheme::new_pkcs1v15_sign::<sha1::Sha1>(),
892 @@ -179,7 +192,10 @@ impl VerifyingKey for Ed25519PublicKey {
893 return Err(Error::IncompatibleAlgorithms);
894 }
895
896- let hash = canonicalization.hash_headers::<Sha256>(headers);
897+ let mut hasher = sha2::Sha256::new();
898+ canonicalization.canonicalize_headers(headers, &mut hasher);
899+ let hash = hasher.finalize();
900+
901 self.inner
902 .verify_strict(
903 hash.as_ref(),
904 @@ -220,12 +236,12 @@ impl HashImpl for Sha256 {
905
906 impl HashContext for sha1::Sha1 {
907 fn complete(self) -> HashOutput {
908- HashOutput::Sha1(self.finalize())
909+ HashOutput::RustCryptoSha1(self.finalize())
910 }
911 }
912
913 impl HashContext for sha2::Sha256 {
914 fn complete(self) -> HashOutput {
915- HashOutput::Sha256(self.finalize())
916+ HashOutput::RustCryptoSha256(self.finalize())
917 }
918 }
919 diff --git a/src/common/headers.rs b/src/common/headers.rs
920index 4b24ea2..51cdb07 100644
921--- a/src/common/headers.rs
922+++ b/src/common/headers.rs
923 @@ -343,6 +343,16 @@ pub trait HeaderWriter: Sized {
924 }
925 }
926
927+ pub trait Writable {
928+ fn write(self, writer: &mut impl Writer);
929+ }
930+
931+ impl Writable for &[u8] {
932+ fn write(self, writer: &mut impl Writer) {
933+ writer.write(self);
934+ }
935+ }
936+
937 pub trait Writer {
938 fn write(&mut self, buf: &[u8]);
939
940 diff --git a/src/common/message.rs b/src/common/message.rs
941index c6ed497..b5a33c1 100644
942--- a/src/common/message.rs
943+++ b/src/common/message.rs
944 @@ -10,11 +10,7 @@
945
946 use mail_parser::{parsers::MessageStream, HeaderValue};
947
948- use crate::{
949- arc,
950- common::crypto::{HashAlgorithm, Sha1, Sha256},
951- dkim, AuthenticatedMessage,
952- };
953+ use crate::{arc, common::crypto::HashAlgorithm, dkim, AuthenticatedMessage};
954
955 use super::headers::{AuthenticatedHeader, Header, HeaderParser};
956
957 @@ -159,10 +155,7 @@ impl<'x> AuthenticatedMessage<'x> {
958
959 // Calculate body hashes
960 for (cb, ha, l, bh) in &mut message.body_hashes {
961- *bh = match ha {
962- HashAlgorithm::Sha256 => cb.hash_body::<Sha256>(body, *l).as_ref().to_vec(),
963- HashAlgorithm::Sha1 => cb.hash_body::<Sha1>(body, *l).as_ref().to_vec(),
964- };
965+ *bh = ha.hash(cb.canonical_body(body, *l)).as_ref().to_vec();
966 }
967
968 // Sort ARC headers
969 diff --git a/src/dkim/canonicalize.rs b/src/dkim/canonicalize.rs
970index a0a8bd1..4184675 100644
971--- a/src/dkim/canonicalize.rs
972+++ b/src/dkim/canonicalize.rs
973 @@ -8,22 +8,24 @@
974 * except according to those terms.
975 */
976
977- use crate::common::{
978- crypto::{HashContext, HashImpl},
979- headers::{HeaderStream, Writer},
980- };
981+ use crate::common::headers::{HeaderStream, Writable, Writer};
982
983 use super::{Canonicalization, Signature};
984
985- impl Canonicalization {
986- pub fn canonicalize_body(&self, body: &[u8], hasher: &mut impl Writer) {
987+ pub struct CanonicalBody<'a> {
988+ canonicalization: Canonicalization,
989+ body: &'a [u8],
990+ }
991+
992+ impl Writable for CanonicalBody<'_> {
993+ fn write(self, hasher: &mut impl Writer) {
994 let mut crlf_seq = 0;
995
996- match self {
997+ match self.canonicalization {
998 Canonicalization::Relaxed => {
999 let mut last_ch = 0;
1000
1001- for &ch in body {
1002+ for &ch in self.body {
1003 match ch {
1004 b' ' | b'\t' => {
1005 while crlf_seq > 0 {
1006 @@ -53,7 +55,7 @@ impl Canonicalization {
1007 }
1008 }
1009 Canonicalization::Simple => {
1010- for &ch in body {
1011+ for &ch in self.body {
1012 match ch {
1013 b'\n' => {
1014 crlf_seq += 1;
1015 @@ -73,10 +75,12 @@ impl Canonicalization {
1016
1017 hasher.write(b"\r\n");
1018 }
1019+ }
1020
1021- pub fn canonicalize_headers<'x>(
1022+ impl Canonicalization {
1023+ pub fn canonicalize_headers<'a>(
1024 &self,
1025- headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>,
1026+ headers: impl Iterator<Item = (&'a [u8], &'a [u8])>,
1027 hasher: &mut impl Writer,
1028 ) {
1029 match self {
1030 @@ -87,6 +91,7 @@ impl Canonicalization {
1031 hasher.write(&[ch.to_ascii_lowercase()]);
1032 }
1033 }
1034+
1035 hasher.write(b":");
1036 let mut bw = 0;
1037 let mut last_ch = 0;
1038 @@ -100,6 +105,7 @@ impl Canonicalization {
1039 }
1040 last_ch = ch;
1041 }
1042+
1043 if last_ch == b'\n' {
1044 hasher.write(b"\r\n");
1045 }
1046 @@ -115,26 +121,25 @@ impl Canonicalization {
1047 }
1048 }
1049
1050- pub fn hash_headers<'x, T: HashImpl>(
1051+ pub fn canonical_headers<'a>(
1052 &self,
1053- headers: &mut dyn Iterator<Item = (&'x [u8], &'x [u8])>,
1054- ) -> impl AsRef<[u8]> {
1055- let mut hasher = T::hasher();
1056- self.canonicalize_headers(headers, &mut hasher);
1057- hasher.complete()
1058+ headers: Vec<(&'a [u8], &'a [u8])>,
1059+ ) -> CanonicalHeaders<'a> {
1060+ CanonicalHeaders {
1061+ canonicalization: *self,
1062+ headers,
1063+ }
1064 }
1065
1066- pub fn hash_body<T: HashImpl>(&self, body: &[u8], l: u64) -> impl AsRef<[u8]> {
1067- let mut hasher = T::hasher();
1068- self.canonicalize_body(
1069- if l == 0 || body.is_empty() {
1070+ pub fn canonical_body<'a>(&self, body: &'a [u8], l: u64) -> CanonicalBody<'a> {
1071+ CanonicalBody {
1072+ canonicalization: *self,
1073+ body: if l == 0 || body.is_empty() {
1074 body
1075 } else {
1076 &body[..std::cmp::min(l as usize, body.len())]
1077 },
1078- &mut hasher,
1079- );
1080- hasher.complete()
1081+ }
1082 }
1083
1084 pub fn serialize_name(&self, writer: &mut impl Writer) {
1085 @@ -146,13 +151,10 @@ impl Canonicalization {
1086 }
1087
1088 impl Signature {
1089- #[allow(clippy::while_let_on_iterator)]
1090 pub(crate) fn canonicalize<'x>(
1091 &self,
1092 mut message: impl HeaderStream<'x>,
1093- header_hasher: &mut impl Writer,
1094- body_hasher: &mut impl Writer,
1095- ) -> (usize, Vec<String>) {
1096+ ) -> (usize, CanonicalHeaders<'x>, Vec<String>, CanonicalBody<'x>) {
1097 let mut headers = Vec::with_capacity(self.h.len());
1098 let mut found_headers = vec![false; self.h.len()];
1099 let mut signed_headers = Vec::with_capacity(self.h.len());
1100 @@ -171,9 +173,8 @@ impl Signature {
1101
1102 let body = message.body();
1103 let body_len = body.len();
1104- self.ch
1105- .canonicalize_headers(&mut headers.into_iter().rev(), header_hasher);
1106- self.cb.canonicalize_body(body, body_hasher);
1107+ let canonical_headers = self.ch.canonical_headers(headers);
1108+ let canonical_body = self.ch.canonical_body(&body, u64::MAX);
1109
1110 // Add any missing headers
1111 signed_headers.reverse();
1112 @@ -183,13 +184,29 @@ impl Signature {
1113 }
1114 }
1115
1116- (body_len, signed_headers)
1117+ (body_len, canonical_headers, signed_headers, canonical_body)
1118+ }
1119+ }
1120+
1121+ pub struct CanonicalHeaders<'a> {
1122+ canonicalization: Canonicalization,
1123+ headers: Vec<(&'a [u8], &'a [u8])>,
1124+ }
1125+
1126+ impl<'a> Writable for CanonicalHeaders<'a> {
1127+ fn write(self, writer: &mut impl Writer) {
1128+ self.canonicalization
1129+ .canonicalize_headers(self.headers.into_iter().rev(), writer)
1130 }
1131 }
1132
1133 #[cfg(test)]
1134 mod test {
1135- use crate::{common::headers::HeaderIterator, dkim::Canonicalization};
1136+ use super::{CanonicalBody, CanonicalHeaders};
1137+ use crate::{
1138+ common::headers::{HeaderIterator, Writable},
1139+ dkim::Canonicalization,
1140+ };
1141
1142 #[test]
1143 #[allow(clippy::needless_collect)]
1144 @@ -253,13 +270,19 @@ mod test {
1145 (Canonicalization::Simple, simple_headers, simple_body),
1146 ] {
1147 let mut headers = Vec::new();
1148- let mut body = Vec::new();
1149-
1150- canonicalization
1151- .canonicalize_headers(&mut parsed_headers.clone().into_iter(), &mut headers);
1152- canonicalization.canonicalize_body(raw_body, &mut body);
1153-
1154+ CanonicalHeaders {
1155+ canonicalization,
1156+ headers: parsed_headers.iter().cloned().rev().collect(),
1157+ }
1158+ .write(&mut headers);
1159 assert_eq!(expected_headers, String::from_utf8(headers).unwrap());
1160+
1161+ let mut body = Vec::new();
1162+ CanonicalBody {
1163+ canonicalization,
1164+ body: &raw_body,
1165+ }
1166+ .write(&mut body);
1167 assert_eq!(expected_body, String::from_utf8(body).unwrap());
1168 }
1169 }
1170 diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs
1171index 5b47db7..ea3dd24 100644
1172--- a/src/dkim/sign.rs
1173+++ b/src/dkim/sign.rs
1174 @@ -12,11 +12,12 @@ use std::time::SystemTime;
1175
1176 use mail_builder::encoders::base64::base64_encode;
1177
1178- use super::{DkimSigner, Done, Signature};
1179+ use super::{canonicalize::CanonicalHeaders, DkimSigner, Done, Signature};
1180+
1181 use crate::{
1182 common::{
1183- crypto::{HashContext, SigningKey},
1184- headers::{ChainedHeaderIterator, HeaderIterator, HeaderStream},
1185+ crypto::SigningKey,
1186+ headers::{ChainedHeaderIterator, HeaderIterator, HeaderStream, Writable, Writer},
1187 },
1188 Error,
1189 };
1190 @@ -54,13 +55,9 @@ impl<T: SigningKey> DkimSigner<T, Done> {
1191 message: impl HeaderStream<'x>,
1192 now: u64,
1193 ) -> crate::Result<Signature> {
1194- let mut body_hasher = self.key.hasher();
1195- let mut header_hasher = self.key.hasher();
1196-
1197 // Canonicalize headers and body
1198- let (body_len, signed_headers) =
1199- self.template
1200- .canonicalize(message, &mut header_hasher, &mut body_hasher);
1201+ let (body_len, canonical_headers, signed_headers, canonical_body) =
1202+ self.template.canonicalize(message);
1203
1204 if signed_headers.is_empty() {
1205 return Err(Error::NoHeadersFound);
1206 @@ -68,7 +65,8 @@ impl<T: SigningKey> DkimSigner<T, Done> {
1207
1208 // Create Signature
1209 let mut signature = self.template.clone();
1210- signature.bh = base64_encode(body_hasher.complete().as_ref())?;
1211+ let body_hash = self.key.hash(canonical_body);
1212+ signature.bh = base64_encode(body_hash.as_ref())?;
1213 signature.t = now;
1214 signature.x = if signature.x > 0 {
1215 now + signature.x
1216 @@ -80,11 +78,11 @@ impl<T: SigningKey> DkimSigner<T, Done> {
1217 signature.l = body_len as u64;
1218 }
1219
1220- // Add signature to hash
1221- signature.write(&mut header_hasher, false);
1222-
1223 // Sign
1224- let b = self.key.sign(header_hasher.complete())?;
1225+ let b = self.key.sign(SignableMessage {
1226+ headers: canonical_headers,
1227+ signature: &signature,
1228+ })?;
1229
1230 // Encode
1231 signature.b = base64_encode(&b)?;
1232 @@ -93,6 +91,18 @@ impl<T: SigningKey> DkimSigner<T, Done> {
1233 }
1234 }
1235
1236+ pub(super) struct SignableMessage<'a> {
1237+ headers: CanonicalHeaders<'a>,
1238+ signature: &'a Signature,
1239+ }
1240+
1241+ impl<'a> Writable for SignableMessage<'a> {
1242+ fn write(self, writer: &mut impl Writer) {
1243+ self.headers.write(writer);
1244+ self.signature.write(writer, false);
1245+ }
1246+ }
1247+
1248 #[cfg(test)]
1249 #[allow(unused)]
1250 mod test {
1251 @@ -112,36 +122,33 @@ mod test {
1252 AuthenticatedMessage, DkimOutput, DkimResult, Resolver,
1253 };
1254
1255- const RSA_PRIVATE_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY-----
1256- MIICXwIBAAKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFC
1257- jxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/RtdC2UzJ1lWT947qR+Rcac2gb
1258- to/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB
1259- AoGBALmn+XwWk7akvkUlqb+dOxyLB9i5VBVfje89Teolwc9YJT36BGN/l4e0l6QX
1260- /1//6DWUTB3KI6wFcm7TWJcxbS0tcKZX7FsJvUz1SbQnkS54DJck1EZO/BLa5ckJ
1261- gAYIaqlA9C0ZwM6i58lLlPadX/rtHb7pWzeNcZHjKrjM461ZAkEA+itss2nRlmyO
1262- n1/5yDyCluST4dQfO8kAB3toSEVc7DeFeDhnC1mZdjASZNvdHS4gbLIA1hUGEF9m
1263- 3hKsGUMMPwJBAPW5v/U+AWTADFCS22t72NUurgzeAbzb1HWMqO4y4+9Hpjk5wvL/
1264- eVYizyuce3/fGke7aRYw/ADKygMJdW8H/OcCQQDz5OQb4j2QDpPZc0Nc4QlbvMsj
1265- 7p7otWRO5xRa6SzXqqV3+F0VpqvDmshEBkoCydaYwc2o6WQ5EBmExeV8124XAkEA
1266- qZzGsIxVP+sEVRWZmW6KNFSdVUpk3qzK0Tz/WjQMe5z0UunY9Ax9/4PVhp/j61bf
1267- eAYXunajbBSOLlx4D+TunwJBANkPI5S9iylsbLs6NkaMHV6k5ioHBBmgCak95JGX
1268- GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1269- -----END RSA PRIVATE KEY-----"#;
1270+ const RSA_PRIVATE_KEY: &str = include_str!("../../resources/rsa-private.pem");
1271
1272 const RSA_PUBLIC_KEY: &str = concat!(
1273- "v=DKIM1; t=s; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ",
1274- "KBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYt",
1275- "IxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v",
1276- "/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhi",
1277- "tdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB",
1278+ "v=DKIM1; t=s; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ",
1279+ "8AMIIBCgKCAQEAv9XYXG3uK95115mB4nJ37nGeNe2CrARm",
1280+ "1agrbcnSk5oIaEfMZLUR/X8gPzoiNHZcfMZEVR6bAytxUh",
1281+ "c5EvZIZrjSuEEeny+fFd/cTvcm3cOUUbIaUmSACj0dL2/K",
1282+ "wW0LyUaza9z9zor7I5XdIl1M53qVd5GI62XBB76FH+Q0bW",
1283+ "PZNkT4NclzTLspD/MTpNCCPhySM4Kdg5CuDczTH4aNzyS0",
1284+ "TqgXdtw6A4Sdsp97VXT9fkPW9rso3lrkpsl/9EQ1mR/DWK",
1285+ "6PBmRfIuSFuqnLKY6v/z2hXHxF7IoojfZLa2kZr9Aed4l9",
1286+ "WheQOTA19k5r2BmlRw/W9CrgCBo0Sdj+KQIDAQAB",
1287 );
1288
1289 const ED25519_PRIVATE_KEY: &str = "nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A=";
1290 const ED25519_PUBLIC_KEY: &str =
1291 "v=DKIM1; k=ed25519; p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=";
1292
1293+ #[cfg(any(
1294+ feature = "rust-crypto",
1295+ all(feature = "ring", feature = "rustls-pemfile")
1296+ ))]
1297 #[test]
1298 fn dkim_sign() {
1299+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
1300+ let pk = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
1301+ #[cfg(feature = "rust-crypto")]
1302 let pk = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
1303 let signature = DkimSigner::from_key(pk)
1304 .domain("stalw.art")
1305 @@ -160,20 +167,28 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1306 311923920,
1307 )
1308 .unwrap();
1309+
1310 assert_eq!(
1311 concat!(
1312 "dkim-signature:v=1; a=rsa-sha256; s=default; d=stalw.art; ",
1313 "c=relaxed/relaxed; h=Subject:To:From; t=311923920; ",
1314 "bh=QoiUNYyUV+1tZ/xUPRcE+gST2zAStvJx1OK078Yl m5s=; ",
1315- "b=F5fBuwEyirUQRZwpEP1fKGil5rNqxL2e5kExeyGdByAvS2lp5",
1316- "M5CqGNzoJ9Pj8sGuGG rdD18uL0xOduqYN7uxifmD4u0BuTzaUSBQ",
1317- "hONWZxFq/BZ8rn6ylZCBS3NDuxFcRkcBtMAuZtGKO wito563yyb+",
1318- "Ujgtpc0DOZtntjyQGc=;",
1319+ "b=B/p1FPSJ+Jl4A94381+DTZZnNO4c3fVqDnj0M0Vk5JuvnKb5",
1320+ "dKSwaoIHPO8UUJsroqH z+R0/eWyW1Vlz+uMIZc2j7MVPJcGaY",
1321+ "Ni85uCQbPd8VpDKWWab6m21ngXYIpagmzKOKYllyOeK3X qwDz",
1322+ "Bo0T2DdNjGyMUOAWHxrKGU+fbcPHQYxTBCpfOxE/nc/uxxqh+i",
1323+ "2uXrsxz7PdCEN01LZiYVV yOzcv0ER9A7aDReE2XPVHnFL8jxE",
1324+ "2BD53HRv3hGkIDcC6wKOKG/lmID+U8tQk5CP0dLmprgjgTv Se",
1325+ "bu6xNc6SSIgpvwryAAzJEVwmaBqvE8RNk3Vg10lBZEuNsj2Q==;",
1326 ),
1327 signature.to_string()
1328 );
1329 }
1330
1331+ #[cfg(any(
1332+ feature = "rust-crypto",
1333+ all(feature = "ring", feature = "rustls-pemfile")
1334+ ))]
1335 #[tokio::test]
1336 async fn dkim_sign_verify() {
1337 let message = concat!(
1338 @@ -199,11 +214,18 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1339 );
1340
1341 // Create private keys
1342+ #[cfg(feature = "rust-crypto")]
1343 let pk_ed = Ed25519Key::from_bytes(
1344 &base64_decode(ED25519_PUBLIC_KEY.rsplit_once("p=").unwrap().1.as_bytes()).unwrap(),
1345 &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(),
1346 )
1347 .unwrap();
1348+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
1349+ let pk_ed = Ed25519Key::from_seed_and_public_key(
1350+ &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(),
1351+ &base64_decode(ED25519_PUBLIC_KEY.rsplit_once("p=").unwrap().1.as_bytes()).unwrap(),
1352+ )
1353+ .unwrap();
1354
1355 // Create resolver
1356 let resolver = Resolver::new_system_conf().unwrap();
1357 @@ -226,8 +248,11 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1358 );
1359 }
1360
1361- // Test RSA-SHA256 relaxed/relaxed
1362+ dbg!("Test RSA-SHA256 relaxed/relaxed");
1363+ #[cfg(feature = "rust-crypto")]
1364 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
1365+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
1366+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
1367 verify(
1368 &resolver,
1369 DkimSigner::from_key(pk_rsa)
1370 @@ -242,7 +267,7 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1371 )
1372 .await;
1373
1374- // Test ED25519-SHA256 relaxed/relaxed
1375+ dbg!("Test ED25519-SHA256 relaxed/relaxed");
1376 verify(
1377 &resolver,
1378 DkimSigner::from_key(pk_ed)
1379 @@ -256,8 +281,11 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1380 )
1381 .await;
1382
1383- // Test RSA-SHA256 simple/simple with duplicated headers
1384+ dbg!("Test RSA-SHA256 simple/simple with duplicated headers");
1385+ #[cfg(feature = "rust-crypto")]
1386 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
1387+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
1388+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
1389 verify(
1390 &resolver,
1391 DkimSigner::from_key(pk_rsa)
1392 @@ -279,8 +307,11 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1393 )
1394 .await;
1395
1396- // Test RSA-SHA256 simple/relaxed with fixed body length
1397+ dbg!("Test RSA-SHA256 simple/relaxed with fixed body length");
1398+ #[cfg(feature = "rust-crypto")]
1399 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
1400+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
1401+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
1402 verify(
1403 &resolver,
1404 DkimSigner::from_key(pk_rsa)
1405 @@ -296,8 +327,11 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1406 )
1407 .await;
1408
1409- // Test AUID not matching domain
1410+ dbg!("Test AUID not matching domains");
1411+ #[cfg(feature = "rust-crypto")]
1412 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
1413+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
1414+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
1415 verify(
1416 &resolver,
1417 DkimSigner::from_key(pk_rsa)
1418 @@ -312,8 +346,11 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1419 )
1420 .await;
1421
1422- // Test expired signature and reporting
1423+ dbg!("Test expired signature and reporting");
1424+ #[cfg(feature = "rust-crypto")]
1425 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
1426+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
1427+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
1428 let r = verify(
1429 &resolver,
1430 DkimSigner::from_key(pk_rsa)
1431 @@ -333,8 +370,11 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1432 .report;
1433 assert_eq!(r.as_deref(), Some("dkim-failures@example.com"));
1434
1435- // Verify ATPS (failure)
1436+ dbg!("Verify ATPS (failure)");
1437+ #[cfg(feature = "rust-crypto")]
1438 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
1439+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
1440+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
1441 verify(
1442 &resolver,
1443 DkimSigner::from_key(pk_rsa)
1444 @@ -350,8 +390,11 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1445 )
1446 .await;
1447
1448- // Verify ATPS (success)
1449+ dbg!("Verify ATPS (success)");
1450+ #[cfg(feature = "rust-crypto")]
1451 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
1452+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
1453+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
1454 #[cfg(any(test, feature = "test"))]
1455 resolver.txt_add(
1456 "UN42N5XOV642KXRXRQIYANHCOUPGQL5LT4WTBKYT2IJFLBWODFDQ._atps.example.com.".to_string(),
1457 @@ -373,8 +416,11 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc=
1458 )
1459 .await;
1460
1461- // Verify ATPS (success - no hash)
1462+ dbg!("Verify ATPS (success - no hash)");
1463+ #[cfg(feature = "rust-crypto")]
1464 let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap();
1465+ #[cfg(all(feature = "ring", not(feature = "rust-crypto")))]
1466+ let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(RSA_PRIVATE_KEY).unwrap();
1467 #[cfg(any(test, feature = "test"))]
1468 resolver.txt_add(
1469 "example.com._atps.example.com.".to_string(),
1470 diff --git a/src/dmarc/verify.rs b/src/dmarc/verify.rs
1471index 9b230d9..bdb9544 100644
1472--- a/src/dmarc/verify.rs
1473+++ b/src/dmarc/verify.rs
1474 @@ -351,7 +351,7 @@ mod test {
1475 let resolver = Resolver::new_system_conf().unwrap();
1476 #[cfg(any(test, feature = "test"))]
1477 resolver.txt_add(
1478- "example.org.report.dmarc.external.org.",
1479+ "example.org._report._dmarc.external.org.",
1480 Dmarc::parse(b"v=DMARC1").unwrap(),
1481 Instant::now() + Duration::new(3200, 0),
1482 );
1483 diff --git a/src/lib.rs b/src/lib.rs
1484index dbe2e1e..cc5bf7f 100644
1485--- a/src/lib.rs
1486+++ b/src/lib.rs
1487 @@ -81,7 +81,7 @@
1488 //! .selector("default-ed")
1489 //! .headers(["From", "To", "Subject"])
1490 //! .sign(RFC5322_MESSAGE.as_bytes())
1491- //! .unwrap();
1492+ //! .unwrap();
1493 //!
1494 //! // Print the message including both signatures to stdout
1495 //! println!(
1496 @@ -283,8 +283,6 @@ pub mod report;
1497 pub mod spf;
1498
1499 pub use flate2;
1500- pub use sha1;
1501- pub use sha2;
1502 pub use trust_dns_resolver;
1503 pub use zip;
1504
1505 @@ -572,12 +570,14 @@ impl From<io::Error> for Error {
1506 }
1507 }
1508
1509+ #[cfg(feature = "rsa")]
1510 impl From<rsa::errors::Error> for Error {
1511 fn from(err: rsa::errors::Error) -> Self {
1512 Error::CryptoError(err.to_string())
1513 }
1514 }
1515
1516+ #[cfg(feature = "ed25519-dalek")]
1517 impl From<ed25519_dalek::ed25519::Error> for Error {
1518 fn from(err: ed25519_dalek::ed25519::Error) -> Self {
1519 Error::CryptoError(err.to_string())