Commit
+695 -195 +/-15 browse
1 | diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml |
2 | index 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 |
12 | index 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 |
59 | index 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 |
75 | index 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 |
115 | new file mode 100644 |
116 | index 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 |
148 | index 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 |
397 | index 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 |
517 | new file mode 100644 |
518 | index 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 |
810 | index 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 |
920 | index 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 |
941 | index 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 |
970 | index 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 |
1171 | index 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 |
1471 | index 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 |
1484 | index 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()) |