Commit
+422 -300 +/-17 browse
1 | diff --git a/README.md b/README.md |
2 | index a1f3217..3e75e48 100644 |
3 | --- a/README.md |
4 | +++ b/README.md |
5 | @@ -52,11 +52,11 @@ Features: |
6 | ```rust |
7 | // Sign an e-mail message using RSA-SHA256 |
8 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
9 | - let signature_rsa = Signature::new() |
10 | - .headers(["From", "To", "Subject"]) |
11 | + let signature_rsa = DkimSigner::from_key(pk_rsa) |
12 | .domain("example.com") |
13 | .selector("default") |
14 | - .sign(RFC5322_MESSAGE.as_bytes(), &pk_rsa) |
15 | + .headers(["From", "To", "Subject"]) |
16 | + .sign(RFC5322_MESSAGE.as_bytes()) |
17 | .unwrap(); |
18 | |
19 | // Sign an e-mail message using ED25519-SHA256 |
20 | @@ -65,12 +65,12 @@ Features: |
21 | &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(), |
22 | ) |
23 | .unwrap(); |
24 | - let signature_ed = Signature::new() |
25 | - .headers(["From", "To", "Subject"]) |
26 | + let signature_ed = DkimSigner::from_key(pk_ed) |
27 | .domain("example.com") |
28 | .selector("default-ed") |
29 | - .sign(RFC5322_MESSAGE.as_bytes(), &pk_ed) |
30 | - .unwrap(); |
31 | + .headers(["From", "To", "Subject"]) |
32 | + .sign(RFC5322_MESSAGE.as_bytes()) |
33 | + .unwrap(); |
34 | |
35 | // Print the message including both signatures to stdout |
36 | println!( |
37 | @@ -119,11 +119,11 @@ Features: |
38 | if arc_result.can_be_sealed() { |
39 | // Seal the e-mail message using RSA-SHA256 |
40 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
41 | - let arc_set = ArcSet::new(&auth_results) |
42 | + let arc_set = ArcSealer::from_key(pk_rsa) |
43 | .domain("example.org") |
44 | .selector("default") |
45 | .headers(["From", "To", "Subject", "DKIM-Signature"]) |
46 | - .seal(&authenticated_message, &arc_result, &pk_rsa) |
47 | + .seal(&authenticated_message, &auth_results, &arc_result) |
48 | .unwrap(); |
49 | |
50 | // Print the sealed message to stdout |
51 | diff --git a/examples/arc_seal.rs b/examples/arc_seal.rs |
52 | index 9a56e62..1c31c3c 100644 |
53 | --- a/examples/arc_seal.rs |
54 | +++ b/examples/arc_seal.rs |
55 | @@ -9,7 +9,7 @@ |
56 | */ |
57 | |
58 | use mail_auth::{ |
59 | - arc::ArcSet, |
60 | + arc::ArcSealer, |
61 | common::{crypto::RsaKey, headers::HeaderWriter}, |
62 | AuthenticatedMessage, AuthenticationResults, Resolver, |
63 | }; |
64 | @@ -54,11 +54,11 @@ async fn main() { |
65 | if arc_result.can_be_sealed() { |
66 | // Seal the e-mail message using RSA-SHA256 |
67 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
68 | - let arc_set = ArcSet::new(&auth_results) |
69 | + let arc_set = ArcSealer::from_key(pk_rsa) |
70 | .domain("example.org") |
71 | .selector("default") |
72 | .headers(["From", "To", "Subject", "DKIM-Signature"]) |
73 | - .seal(&authenticated_message, &arc_result, &pk_rsa) |
74 | + .seal(&authenticated_message, &auth_results, &arc_result) |
75 | .unwrap(); |
76 | |
77 | // Print the sealed message to stdout |
78 | diff --git a/examples/dkim_sign.rs b/examples/dkim_sign.rs |
79 | index 6cd5266..a05e196 100644 |
80 | --- a/examples/dkim_sign.rs |
81 | +++ b/examples/dkim_sign.rs |
82 | @@ -10,7 +10,7 @@ |
83 | |
84 | use mail_auth::{ |
85 | common::{crypto::Ed25519Key, crypto::RsaKey, headers::HeaderWriter}, |
86 | - dkim::Signature, |
87 | + dkim::DkimSigner, |
88 | }; |
89 | use mail_parser::decoders::base64::base64_decode; |
90 | use sha2::Sha256; |
91 | @@ -44,11 +44,11 @@ I'm going to need those TPS reports ASAP. So, if you could do that, that'd be gr |
92 | fn main() { |
93 | // Sign an e-mail message using RSA-SHA256 |
94 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
95 | - let signature_rsa = Signature::new() |
96 | - .headers(["From", "To", "Subject"]) |
97 | + let signature_rsa = DkimSigner::from_key(pk_rsa) |
98 | .domain("example.com") |
99 | .selector("default") |
100 | - .sign(TEST_MESSAGE.as_bytes(), &pk_rsa) |
101 | + .headers(["From", "To", "Subject"]) |
102 | + .sign(TEST_MESSAGE.as_bytes()) |
103 | .unwrap(); |
104 | |
105 | // Sign an e-mail message using ED25519-SHA256 |
106 | @@ -57,11 +57,11 @@ fn main() { |
107 | &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(), |
108 | ) |
109 | .unwrap(); |
110 | - let signature_ed = Signature::new() |
111 | - .headers(["From", "To", "Subject"]) |
112 | + let signature_ed = DkimSigner::from_key(pk_ed) |
113 | .domain("example.com") |
114 | .selector("default-ed") |
115 | - .sign(TEST_MESSAGE.as_bytes(), &pk_ed) |
116 | + .headers(["From", "To", "Subject"]) |
117 | + .sign(TEST_MESSAGE.as_bytes()) |
118 | .unwrap(); |
119 | |
120 | // Print the message including both signatures to stdout |
121 | diff --git a/src/arc/builder.rs b/src/arc/builder.rs |
122 | new file mode 100644 |
123 | index 0000000..347a12b |
124 | --- /dev/null |
125 | +++ b/src/arc/builder.rs |
126 | @@ -0,0 +1,103 @@ |
127 | + use std::borrow::Cow; |
128 | + |
129 | + use sha2::Sha256; |
130 | + |
131 | + use crate::{ |
132 | + common::crypto::SigningKey, |
133 | + dkim::{Canonicalization, Done, NeedDomain, NeedHeaders, NeedSelector}, |
134 | + }; |
135 | + |
136 | + use super::{ArcSealer, Seal, Signature}; |
137 | + |
138 | + impl<'x, T: SigningKey<Hasher = Sha256>> ArcSealer<'x, T> { |
139 | + pub fn from_key(key: T) -> ArcSealer<'x, T, NeedDomain> { |
140 | + ArcSealer { |
141 | + _state: Default::default(), |
142 | + signature: Signature { |
143 | + a: key.algorithm(), |
144 | + ..Default::default() |
145 | + }, |
146 | + seal: Seal { |
147 | + a: key.algorithm(), |
148 | + ..Default::default() |
149 | + }, |
150 | + key, |
151 | + } |
152 | + } |
153 | + } |
154 | + |
155 | + impl<'x, T: SigningKey<Hasher = Sha256>> ArcSealer<'x, T, NeedDomain> { |
156 | + /// Sets the domain to use for signing. |
157 | + pub fn domain( |
158 | + mut self, |
159 | + domain: impl Into<Cow<'x, str>> + Clone, |
160 | + ) -> ArcSealer<'x, T, NeedSelector> { |
161 | + self.signature.d = domain.clone().into(); |
162 | + self.seal.d = domain.into(); |
163 | + ArcSealer { |
164 | + _state: Default::default(), |
165 | + key: self.key, |
166 | + signature: self.signature, |
167 | + seal: self.seal, |
168 | + } |
169 | + } |
170 | + } |
171 | + |
172 | + impl<'x, T: SigningKey<Hasher = Sha256>> ArcSealer<'x, T, NeedSelector> { |
173 | + /// Sets the selector to use for signing. |
174 | + pub fn selector( |
175 | + mut self, |
176 | + selector: impl Into<Cow<'x, str>> + Clone, |
177 | + ) -> ArcSealer<'x, T, NeedHeaders> { |
178 | + self.signature.s = selector.clone().into(); |
179 | + self.seal.s = selector.into(); |
180 | + ArcSealer { |
181 | + _state: Default::default(), |
182 | + key: self.key, |
183 | + signature: self.signature, |
184 | + seal: self.seal, |
185 | + } |
186 | + } |
187 | + } |
188 | + |
189 | + impl<'x, T: SigningKey<Hasher = Sha256>> ArcSealer<'x, T, NeedHeaders> { |
190 | + /// Sets the headers to sign. |
191 | + pub fn headers( |
192 | + mut self, |
193 | + headers: impl IntoIterator<Item = impl Into<Cow<'x, str>>>, |
194 | + ) -> ArcSealer<'x, T, Done> { |
195 | + self.signature.h = headers.into_iter().map(|h| h.into()).collect(); |
196 | + ArcSealer { |
197 | + _state: Default::default(), |
198 | + key: self.key, |
199 | + signature: self.signature, |
200 | + seal: self.seal, |
201 | + } |
202 | + } |
203 | + } |
204 | + |
205 | + impl<'x, T: SigningKey<Hasher = Sha256>> ArcSealer<'x, T, Done> { |
206 | + /// Sets the number of seconds from now to use for the signature expiration. |
207 | + pub fn expiration(mut self, expiration: u64) -> Self { |
208 | + self.signature.x = expiration; |
209 | + self |
210 | + } |
211 | + |
212 | + /// Include the body length in the signature. |
213 | + pub fn body_length(mut self, body_length: bool) -> Self { |
214 | + self.signature.l = u64::from(body_length); |
215 | + self |
216 | + } |
217 | + |
218 | + /// Sets header canonicalization algorithm. |
219 | + pub fn header_canonicalization(mut self, ch: Canonicalization) -> Self { |
220 | + self.signature.ch = ch; |
221 | + self |
222 | + } |
223 | + |
224 | + /// Sets header canonicalization algorithm. |
225 | + pub fn body_canonicalization(mut self, cb: Canonicalization) -> Self { |
226 | + self.signature.cb = cb; |
227 | + self |
228 | + } |
229 | + } |
230 | diff --git a/src/arc/mod.rs b/src/arc/mod.rs |
231 | index 3c506c2..19d5cd6 100644 |
232 | --- a/src/arc/mod.rs |
233 | +++ b/src/arc/mod.rs |
234 | @@ -8,6 +8,7 @@ |
235 | * except according to those terms. |
236 | */ |
237 | |
238 | + pub mod builder; |
239 | pub mod headers; |
240 | pub mod parse; |
241 | pub mod seal; |
242 | @@ -15,13 +16,27 @@ pub mod verify; |
243 | |
244 | use std::borrow::Cow; |
245 | |
246 | + use sha2::Sha256; |
247 | + |
248 | use crate::{ |
249 | - common::{crypto::Algorithm, headers::Header, verify::VerifySignature}, |
250 | - dkim::Canonicalization, |
251 | + common::{ |
252 | + crypto::{Algorithm, SigningKey}, |
253 | + headers::Header, |
254 | + verify::VerifySignature, |
255 | + }, |
256 | + dkim::{Canonicalization, NeedDomain}, |
257 | ArcOutput, AuthenticationResults, DkimResult, |
258 | }; |
259 | |
260 | #[derive(Debug, PartialEq, Eq, Clone, Default)] |
261 | + pub struct ArcSealer<'x, T: SigningKey<Hasher = Sha256>, State = NeedDomain> { |
262 | + _state: std::marker::PhantomData<State>, |
263 | + pub(crate) key: T, |
264 | + pub(crate) signature: Signature<'x>, |
265 | + pub(crate) seal: Seal<'x>, |
266 | + } |
267 | + |
268 | + #[derive(Debug, PartialEq, Eq, Clone, Default)] |
269 | pub struct Signature<'x> { |
270 | pub(crate) i: u32, |
271 | pub(crate) a: Algorithm, |
272 | diff --git a/src/arc/seal.rs b/src/arc/seal.rs |
273 | index fdef1fb..bbeee2a 100644 |
274 | --- a/src/arc/seal.rs |
275 | +++ b/src/arc/seal.rs |
276 | @@ -15,57 +15,52 @@ use sha2::{Digest, Sha256}; |
277 | |
278 | use crate::{ |
279 | common::{crypto::SigningKey, headers::Writer}, |
280 | - dkim::Canonicalization, |
281 | + dkim::{Canonicalization, Done}, |
282 | ArcOutput, AuthenticatedMessage, AuthenticationResults, DkimResult, Error, |
283 | }; |
284 | |
285 | - use super::{ArcSet, ChainValidation, Seal, Signature}; |
286 | - |
287 | - impl<'x> ArcSet<'x> { |
288 | - pub fn new(results: &'x AuthenticationResults) -> Self { |
289 | - ArcSet { |
290 | - signature: Signature::default(), |
291 | - seal: Seal::default(), |
292 | - results, |
293 | - } |
294 | - } |
295 | + use super::{ArcSealer, ArcSet, ChainValidation, Signature}; |
296 | |
297 | + impl<'x, T: SigningKey<Hasher = Sha256>> ArcSealer<'x, T, Done> { |
298 | pub fn seal( |
299 | - mut self, |
300 | + &self, |
301 | message: &'x AuthenticatedMessage<'x>, |
302 | + results: &'x AuthenticationResults, |
303 | arc_output: &ArcOutput, |
304 | - with_key: &impl SigningKey<Hasher = Sha256>, |
305 | - ) -> crate::Result<Self> { |
306 | + ) -> crate::Result<ArcSet<'x>> { |
307 | if !arc_output.can_be_sealed() { |
308 | return Err(Error::ARCInvalidCV); |
309 | } |
310 | |
311 | - // Set a= |
312 | - self.signature.a = with_key.algorithm(); |
313 | - self.seal.a = with_key.algorithm(); |
314 | + // Create set |
315 | + let mut set = ArcSet { |
316 | + signature: self.signature.clone(), |
317 | + seal: self.seal.clone(), |
318 | + results, |
319 | + }; |
320 | |
321 | // Set i= and cv= |
322 | if arc_output.set.is_empty() { |
323 | - self.signature.i = 1; |
324 | - self.seal.i = 1; |
325 | - self.seal.cv = ChainValidation::None; |
326 | + set.signature.i = 1; |
327 | + set.seal.i = 1; |
328 | + set.seal.cv = ChainValidation::None; |
329 | } else { |
330 | let i = arc_output.set.last().unwrap().seal.header.i + 1; |
331 | - self.signature.i = i; |
332 | - self.seal.i = i; |
333 | - self.seal.cv = match &arc_output.result { |
334 | + set.signature.i = i; |
335 | + set.seal.i = i; |
336 | + set.seal.cv = match &arc_output.result { |
337 | DkimResult::Pass => ChainValidation::Pass, |
338 | _ => ChainValidation::Fail, |
339 | }; |
340 | } |
341 | |
342 | // Create hashes |
343 | - let mut body_hasher = with_key.hasher(); |
344 | - let mut header_hasher = with_key.hasher(); |
345 | + let mut body_hasher = self.key.hasher(); |
346 | + let mut header_hasher = self.key.hasher(); |
347 | |
348 | // Canonicalize headers and body |
349 | let (body_len, signed_headers) = |
350 | - self.signature |
351 | + set.signature |
352 | .canonicalize(message, &mut header_hasher, &mut body_hasher)?; |
353 | |
354 | if signed_headers.is_empty() { |
355 | @@ -77,24 +72,24 @@ impl<'x> ArcSet<'x> { |
356 | .duration_since(SystemTime::UNIX_EPOCH) |
357 | .map(|d| d.as_secs()) |
358 | .unwrap_or(0); |
359 | - self.signature.bh = base64_encode(&body_hasher.finalize())?; |
360 | - self.signature.t = now; |
361 | - self.signature.x = if self.signature.x > 0 { |
362 | - now + self.signature.x |
363 | + set.signature.bh = base64_encode(&body_hasher.finalize())?; |
364 | + set.signature.t = now; |
365 | + set.signature.x = if set.signature.x > 0 { |
366 | + now + set.signature.x |
367 | } else { |
368 | 0 |
369 | }; |
370 | - self.signature.h = signed_headers; |
371 | - if self.signature.l > 0 { |
372 | - self.signature.l = body_len as u64; |
373 | + set.signature.h = signed_headers; |
374 | + if set.signature.l > 0 { |
375 | + set.signature.l = body_len as u64; |
376 | } |
377 | |
378 | // Add signature to hash |
379 | - self.signature.write(&mut header_hasher, false); |
380 | + set.signature.write(&mut header_hasher, false); |
381 | |
382 | // Sign |
383 | - let b = with_key.sign(&header_hasher.finalize())?; |
384 | - self.signature.b = base64_encode(&b)?; |
385 | + let b = self.key.sign(&header_hasher.finalize())?; |
386 | + set.signature.b = base64_encode(&b)?; |
387 | |
388 | // Hash ARC chain |
389 | let mut header_hasher = Sha256::new(); |
390 | @@ -112,60 +107,16 @@ impl<'x> ArcSet<'x> { |
391 | } |
392 | |
393 | // Hash ARC headers for the current instance |
394 | - self.results.write(&mut header_hasher, self.seal.i, false); |
395 | - self.signature.write(&mut header_hasher, false); |
396 | + set.results.write(&mut header_hasher, set.seal.i, false); |
397 | + set.signature.write(&mut header_hasher, false); |
398 | header_hasher.write_all(b"\r\n")?; |
399 | - self.seal.write(&mut header_hasher, false); |
400 | + set.seal.write(&mut header_hasher, false); |
401 | |
402 | // Seal |
403 | - let b = with_key.sign(&header_hasher.finalize())?; |
404 | - self.seal.b = base64_encode(&b)?; |
405 | - |
406 | - Ok(self) |
407 | - } |
408 | - |
409 | - /// Sets the headers to sign. |
410 | - pub fn headers(mut self, headers: impl IntoIterator<Item = impl Into<Cow<'x, str>>>) -> Self { |
411 | - self.signature.h = headers.into_iter().map(|h| h.into()).collect(); |
412 | - self |
413 | - } |
414 | - |
415 | - /// Sets the domain to use for signing. |
416 | - pub fn domain(mut self, domain: &'x str) -> Self { |
417 | - self.signature.d = domain.into(); |
418 | - self.seal.d = domain.into(); |
419 | - self |
420 | - } |
421 | - |
422 | - /// Sets the selector to use for signing. |
423 | - pub fn selector(mut self, selector: &'x str) -> Self { |
424 | - self.signature.s = selector.into(); |
425 | - self.seal.s = selector.into(); |
426 | - self |
427 | - } |
428 | - |
429 | - /// Sets the number of seconds from now to use for the signature expiration. |
430 | - pub fn expiration(mut self, expiration: u64) -> Self { |
431 | - self.signature.x = expiration; |
432 | - self |
433 | - } |
434 | - |
435 | - /// Include the body length in the signature. |
436 | - pub fn body_length(mut self, body_length: bool) -> Self { |
437 | - self.signature.l = u64::from(body_length); |
438 | - self |
439 | - } |
440 | - |
441 | - /// Sets header canonicalization algorithm. |
442 | - pub fn header_canonicalization(mut self, ch: Canonicalization) -> Self { |
443 | - self.signature.ch = ch; |
444 | - self |
445 | - } |
446 | + let b = self.key.sign(&header_hasher.finalize())?; |
447 | + set.seal.b = base64_encode(&b)?; |
448 | |
449 | - /// Sets header canonicalization algorithm. |
450 | - pub fn body_canonicalization(mut self, cb: Canonicalization) -> Self { |
451 | - self.signature.cb = cb; |
452 | - self |
453 | + Ok(set) |
454 | } |
455 | } |
456 | |
457 | @@ -218,14 +169,14 @@ mod test { |
458 | use sha2::Sha256; |
459 | |
460 | use crate::{ |
461 | - arc::ArcSet, |
462 | + arc::ArcSealer, |
463 | common::{ |
464 | crypto::{Ed25519Key, RsaKey, SigningKey}, |
465 | headers::HeaderWriter, |
466 | parse::TxtRecordParser, |
467 | verify::DomainKey, |
468 | }, |
469 | - dkim::Signature, |
470 | + dkim::DkimSigner, |
471 | AuthenticatedMessage, AuthenticationResults, DkimResult, Resolver, |
472 | }; |
473 | |
474 | @@ -283,28 +234,38 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
475 | |
476 | // Create private keys |
477 | let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
478 | - let pk_ed = Ed25519Key::from_bytes( |
479 | - &base64_decode(ED25519_PUBLIC_KEY.rsplit_once("p=").unwrap().1.as_bytes()).unwrap(), |
480 | - &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(), |
481 | - ) |
482 | - .unwrap(); |
483 | + let pk_ed_public = |
484 | + base64_decode(ED25519_PUBLIC_KEY.rsplit_once("p=").unwrap().1.as_bytes()).unwrap(); |
485 | + let pk_ed_private = base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(); |
486 | |
487 | // Create DKIM-signed message |
488 | - let mut raw_message = Signature::new() |
489 | - .headers(["From", "To", "Subject"]) |
490 | + let mut raw_message = DkimSigner::from_key(pk_rsa.clone()) |
491 | .domain("manchego.org") |
492 | .selector("rsa") |
493 | - .sign(message.as_bytes(), &pk_rsa) |
494 | + .headers(["From", "To", "Subject"]) |
495 | + .sign(message.as_bytes()) |
496 | .unwrap() |
497 | .to_header() |
498 | + message; |
499 | |
500 | // Verify and seal the message 50 times |
501 | for _ in 0..25 { |
502 | - raw_message = |
503 | - arc_verify_and_seal(&resolver, &raw_message, "scamorza.org", "ed", &pk_ed).await; |
504 | - raw_message = |
505 | - arc_verify_and_seal(&resolver, &raw_message, "manchego.org", "rsa", &pk_rsa).await; |
506 | + raw_message = arc_verify_and_seal( |
507 | + &resolver, |
508 | + &raw_message, |
509 | + "scamorza.org", |
510 | + "ed", |
511 | + Ed25519Key::from_bytes(&pk_ed_public, &pk_ed_private).unwrap(), |
512 | + ) |
513 | + .await; |
514 | + raw_message = arc_verify_and_seal( |
515 | + &resolver, |
516 | + &raw_message, |
517 | + "manchego.org", |
518 | + "rsa", |
519 | + pk_rsa.clone(), |
520 | + ) |
521 | + .await; |
522 | } |
523 | |
524 | //println!("{}", raw_message); |
525 | @@ -315,7 +276,7 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
526 | raw_message: &str, |
527 | d: &str, |
528 | s: &str, |
529 | - pk: &impl SigningKey<Hasher = Sha256>, |
530 | + pk: impl SigningKey<Hasher = Sha256>, |
531 | ) -> String { |
532 | let message = AuthenticatedMessage::parse(raw_message.as_bytes()).unwrap(); |
533 | let dkim_result = resolver.verify_dkim(&message).await; |
534 | @@ -326,11 +287,11 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
535 | arc_result.result() |
536 | ); |
537 | let auth_results = AuthenticationResults::new(d).with_dkim_result(&dkim_result, d); |
538 | - let arc = ArcSet::new(&auth_results) |
539 | + let arc = ArcSealer::from_key(pk) |
540 | .domain(d) |
541 | .selector(s) |
542 | .headers(["From", "To", "Subject", "DKIM-Signature"]) |
543 | - .seal(&message, &arc_result, pk) |
544 | + .seal(&message, &auth_results, &arc_result) |
545 | .unwrap_or_else(|err| panic!("Got {:?} for {}", err, raw_message)); |
546 | format!( |
547 | "{}{}{}", |
548 | diff --git a/src/common/auth_results.rs b/src/common/auth_results.rs |
549 | index 2446ce8..865ecac 100644 |
550 | --- a/src/common/auth_results.rs |
551 | +++ b/src/common/auth_results.rs |
552 | @@ -280,8 +280,8 @@ impl AsAuthResult for Error { |
553 | Error::RevokedPublicKey => "revoked public key", |
554 | Error::IncompatibleAlgorithms => "incompatible record/signature algorithms", |
555 | Error::SignatureExpired => "signature error", |
556 | - Error::DNSError(_) => "dns error", |
557 | - Error::DNSRecordNotFound(_) => "dns record not found", |
558 | + Error::DnsError(_) => "dns error", |
559 | + Error::DnsRecordNotFound(_) => "dns record not found", |
560 | Error::ARCInvalidInstance(i) => { |
561 | write!(header, "invalid ARC instance {})", i).ok(); |
562 | return; |
563 | @@ -347,7 +347,7 @@ mod test { |
564 | "header.s=otherselctor header.b=YWJjZGVm header.from=jdoe@example.org" |
565 | ), |
566 | DkimOutput { |
567 | - result: DkimResult::TempError(Error::DNSError("".to_string())), |
568 | + result: DkimResult::TempError(Error::DnsError("".to_string())), |
569 | signature: (&Signature { |
570 | d: "atps.example.org".into(), |
571 | s: "otherselctor".into(), |
572 | diff --git a/src/common/crypto.rs b/src/common/crypto.rs |
573 | index e74c9b3..1aaa007 100644 |
574 | --- a/src/common/crypto.rs |
575 | +++ b/src/common/crypto.rs |
576 | @@ -25,7 +25,7 @@ pub trait SigningKey { |
577 | fn algorithm(&self) -> Algorithm; |
578 | } |
579 | |
580 | - #[derive(Debug)] |
581 | + #[derive(Debug, Clone)] |
582 | pub struct RsaKey<T> { |
583 | inner: RsaPrivateKey, |
584 | padding: PhantomData<T>, |
585 | diff --git a/src/common/resolver.rs b/src/common/resolver.rs |
586 | index 7fc4927..938abd5 100644 |
587 | --- a/src/common/resolver.rs |
588 | +++ b/src/common/resolver.rs |
589 | @@ -269,9 +269,9 @@ impl Resolver { |
590 | let key = key.into_fqdn().into_owned(); |
591 | return match self.ipv4_lookup(key.as_str()).await { |
592 | Ok(_) => Ok(true), |
593 | - Err(Error::DNSRecordNotFound(_)) => match self.ipv6_lookup(key.as_str()).await { |
594 | + Err(Error::DnsRecordNotFound(_)) => match self.ipv6_lookup(key.as_str()).await { |
595 | Ok(_) => Ok(true), |
596 | - Err(Error::DNSRecordNotFound(_)) => Ok(false), |
597 | + Err(Error::DnsRecordNotFound(_)) => Ok(false), |
598 | Err(err) => Err(err), |
599 | }, |
600 | Err(err) => Err(err), |
601 | @@ -354,9 +354,9 @@ impl From<ResolveError> for Error { |
602 | fn from(err: ResolveError) -> Self { |
603 | match err.kind() { |
604 | ResolveErrorKind::NoRecordsFound { response_code, .. } => { |
605 | - Error::DNSRecordNotFound(*response_code) |
606 | + Error::DnsRecordNotFound(*response_code) |
607 | } |
608 | - _ => Error::DNSError(err.to_string()), |
609 | + _ => Error::DnsError(err.to_string()), |
610 | } |
611 | } |
612 | } |
613 | @@ -543,8 +543,8 @@ fn mock_resolve<T>(domain: &str) -> crate::Result<T> { |
614 | } else if domain.contains("_invalid_record.") { |
615 | Error::InvalidRecordType |
616 | } else if domain.contains("_dns_error.") { |
617 | - Error::DNSError("".to_string()) |
618 | + Error::DnsError("".to_string()) |
619 | } else { |
620 | - Error::DNSRecordNotFound(trust_dns_resolver::proto::op::ResponseCode::NXDomain) |
621 | + Error::DnsRecordNotFound(trust_dns_resolver::proto::op::ResponseCode::NXDomain) |
622 | }) |
623 | } |
624 | diff --git a/src/dkim/builder.rs b/src/dkim/builder.rs |
625 | new file mode 100644 |
626 | index 0000000..f7def04 |
627 | --- /dev/null |
628 | +++ b/src/dkim/builder.rs |
629 | @@ -0,0 +1,107 @@ |
630 | + use crate::common::crypto::{HashAlgorithm, SigningKey}; |
631 | + use std::borrow::Cow; |
632 | + |
633 | + use super::{Canonicalization, DkimSigner, Done, NeedDomain, NeedHeaders, NeedSelector, Signature}; |
634 | + |
635 | + impl<'x, T: SigningKey> DkimSigner<'x, T> { |
636 | + pub fn from_key(key: T) -> DkimSigner<'x, T, NeedDomain> { |
637 | + DkimSigner { |
638 | + _state: Default::default(), |
639 | + template: Signature { |
640 | + v: 1, |
641 | + a: key.algorithm(), |
642 | + ..Default::default() |
643 | + }, |
644 | + key, |
645 | + } |
646 | + } |
647 | + } |
648 | + |
649 | + impl<'x, T: SigningKey> DkimSigner<'x, T, NeedDomain> { |
650 | + /// Sets the domain to use for signing. |
651 | + pub fn domain(mut self, domain: impl Into<Cow<'x, str>>) -> DkimSigner<'x, T, NeedSelector> { |
652 | + self.template.d = domain.into(); |
653 | + DkimSigner { |
654 | + _state: Default::default(), |
655 | + key: self.key, |
656 | + template: self.template, |
657 | + } |
658 | + } |
659 | + } |
660 | + |
661 | + impl<'x, T: SigningKey> DkimSigner<'x, T, NeedSelector> { |
662 | + /// Sets the selector to use for signing. |
663 | + pub fn selector(mut self, selector: impl Into<Cow<'x, str>>) -> DkimSigner<'x, T, NeedHeaders> { |
664 | + self.template.s = selector.into(); |
665 | + DkimSigner { |
666 | + _state: Default::default(), |
667 | + key: self.key, |
668 | + template: self.template, |
669 | + } |
670 | + } |
671 | + } |
672 | + |
673 | + impl<'x, T: SigningKey> DkimSigner<'x, T, NeedHeaders> { |
674 | + /// Sets the headers to sign. |
675 | + pub fn headers( |
676 | + mut self, |
677 | + headers: impl IntoIterator<Item = impl Into<Cow<'x, str>>>, |
678 | + ) -> DkimSigner<'x, T, Done> { |
679 | + self.template.h = headers.into_iter().map(|h| h.into()).collect(); |
680 | + DkimSigner { |
681 | + _state: Default::default(), |
682 | + key: self.key, |
683 | + template: self.template, |
684 | + } |
685 | + } |
686 | + } |
687 | + |
688 | + impl<'x, T: SigningKey> DkimSigner<'x, T, Done> { |
689 | + /// Sets the third party signature. |
690 | + pub fn atps(mut self, atps: impl Into<Cow<'x, str>>) -> Self { |
691 | + self.template.atps = Some(atps.into()); |
692 | + self |
693 | + } |
694 | + |
695 | + /// Sets the third-party signature hashing algorithm. |
696 | + pub fn atpsh(mut self, atpsh: HashAlgorithm) -> Self { |
697 | + self.template.atpsh = atpsh.into(); |
698 | + self |
699 | + } |
700 | + |
701 | + /// Sets the selector to use for signing. |
702 | + pub fn agent_user_identifier(mut self, auid: impl Into<Cow<'x, str>>) -> Self { |
703 | + self.template.i = auid.into(); |
704 | + self |
705 | + } |
706 | + |
707 | + /// Sets the number of seconds from now to use for the signature expiration. |
708 | + pub fn expiration(mut self, expiration: u64) -> Self { |
709 | + self.template.x = expiration; |
710 | + self |
711 | + } |
712 | + |
713 | + /// Include the body length in the signature. |
714 | + pub fn body_length(mut self, body_length: bool) -> Self { |
715 | + self.template.l = u64::from(body_length); |
716 | + self |
717 | + } |
718 | + |
719 | + /// Request reports. |
720 | + pub fn reporting(mut self, reporting: bool) -> Self { |
721 | + self.template.r = reporting; |
722 | + self |
723 | + } |
724 | + |
725 | + /// Sets header canonicalization algorithm. |
726 | + pub fn header_canonicalization(mut self, ch: Canonicalization) -> Self { |
727 | + self.template.ch = ch; |
728 | + self |
729 | + } |
730 | + |
731 | + /// Sets header canonicalization algorithm. |
732 | + pub fn body_canonicalization(mut self, cb: Canonicalization) -> Self { |
733 | + self.template.cb = cb; |
734 | + self |
735 | + } |
736 | + } |
737 | diff --git a/src/dkim/mod.rs b/src/dkim/mod.rs |
738 | index a83b129..846ff4b 100644 |
739 | --- a/src/dkim/mod.rs |
740 | +++ b/src/dkim/mod.rs |
741 | @@ -13,12 +13,13 @@ use std::borrow::Cow; |
742 | use crate::{ |
743 | arc::Set, |
744 | common::{ |
745 | - crypto::{Algorithm, HashAlgorithm}, |
746 | + crypto::{Algorithm, HashAlgorithm, SigningKey}, |
747 | verify::VerifySignature, |
748 | }, |
749 | ArcOutput, DkimOutput, DkimResult, Error, Version, |
750 | }; |
751 | |
752 | + pub mod builder; |
753 | pub mod canonicalize; |
754 | pub mod headers; |
755 | pub mod parse; |
756 | @@ -32,6 +33,18 @@ pub enum Canonicalization { |
757 | } |
758 | |
759 | #[derive(Debug, PartialEq, Eq, Clone, Default)] |
760 | + pub struct DkimSigner<'x, T: SigningKey, State = NeedDomain> { |
761 | + _state: std::marker::PhantomData<State>, |
762 | + pub(crate) key: T, |
763 | + pub(crate) template: Signature<'x>, |
764 | + } |
765 | + |
766 | + pub struct NeedDomain; |
767 | + pub struct NeedSelector; |
768 | + pub struct NeedHeaders; |
769 | + pub struct Done; |
770 | + |
771 | + #[derive(Debug, PartialEq, Eq, Clone, Default)] |
772 | pub struct Signature<'x> { |
773 | pub(crate) v: u32, |
774 | pub(crate) a: Algorithm, |
775 | @@ -197,7 +210,7 @@ impl<'x> DkimOutput<'x> { |
776 | } |
777 | |
778 | pub(crate) fn dns_error(err: Error) -> Self { |
779 | - if matches!(&err, Error::DNSError(_)) { |
780 | + if matches!(&err, Error::DnsError(_)) { |
781 | DkimOutput::temp_err(err) |
782 | } else { |
783 | DkimOutput::perm_err(err) |
784 | @@ -239,7 +252,7 @@ impl<'x> ArcOutput<'x> { |
785 | |
786 | impl From<Error> for DkimResult { |
787 | fn from(err: Error) -> Self { |
788 | - if matches!(&err, Error::DNSError(_)) { |
789 | + if matches!(&err, Error::DnsError(_)) { |
790 | DkimResult::TempError(err) |
791 | } else { |
792 | DkimResult::PermError(err) |
793 | diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs |
794 | index a43297b..b7f38f4 100644 |
795 | --- a/src/dkim/sign.rs |
796 | +++ b/src/dkim/sign.rs |
797 | @@ -8,141 +8,64 @@ |
798 | * except according to those terms. |
799 | */ |
800 | |
801 | - use std::{borrow::Cow, time::SystemTime}; |
802 | + use std::time::SystemTime; |
803 | |
804 | use mail_builder::encoders::base64::base64_encode; |
805 | use sha1::Digest; |
806 | |
807 | - use super::{Canonicalization, HashAlgorithm, Signature}; |
808 | + use super::{DkimSigner, Done, Signature}; |
809 | use crate::{common::crypto::SigningKey, Error}; |
810 | |
811 | - impl<'x> Signature<'x> { |
812 | - /// Creates a new DKIM signature. |
813 | - pub fn new() -> Self { |
814 | - Signature { |
815 | - v: 1, |
816 | - ..Default::default() |
817 | - } |
818 | - } |
819 | - |
820 | + impl<'x, T: SigningKey> DkimSigner<'x, T, Done> { |
821 | /// Signs a message. |
822 | #[inline(always)] |
823 | - pub fn sign(mut self, message: &'x [u8], with_key: &impl SigningKey) -> crate::Result<Self> { |
824 | - if !self.d.is_empty() && !self.s.is_empty() && !self.h.is_empty() { |
825 | - let now = SystemTime::now() |
826 | + pub fn sign(&self, message: &'x [u8]) -> crate::Result<Signature<'x>> { |
827 | + self.sign_( |
828 | + message, |
829 | + SystemTime::now() |
830 | .duration_since(SystemTime::UNIX_EPOCH) |
831 | .map(|d| d.as_secs()) |
832 | - .unwrap_or(0); |
833 | - |
834 | - self.a = with_key.algorithm(); |
835 | - self.sign_(message, with_key, now) |
836 | - } else { |
837 | - Err(Error::MissingParameters) |
838 | - } |
839 | + .unwrap_or(0), |
840 | + ) |
841 | } |
842 | |
843 | - fn sign_( |
844 | - mut self, |
845 | - message: &'x [u8], |
846 | - with_key: &impl SigningKey, |
847 | - now: u64, |
848 | - ) -> crate::Result<Self> { |
849 | - let mut body_hasher = with_key.hasher(); |
850 | - let mut header_hasher = with_key.hasher(); |
851 | + fn sign_(&self, message: &'x [u8], now: u64) -> crate::Result<Signature<'x>> { |
852 | + let mut body_hasher = self.key.hasher(); |
853 | + let mut header_hasher = self.key.hasher(); |
854 | |
855 | // Canonicalize headers and body |
856 | let (body_len, signed_headers) = |
857 | - self.canonicalize(message, &mut header_hasher, &mut body_hasher); |
858 | + self.template |
859 | + .canonicalize(message, &mut header_hasher, &mut body_hasher); |
860 | |
861 | if signed_headers.is_empty() { |
862 | return Err(Error::NoHeadersFound); |
863 | } |
864 | |
865 | // Create Signature |
866 | - self.bh = base64_encode(&body_hasher.finalize())?; |
867 | - self.t = now; |
868 | - self.x = if self.x > 0 { now + self.x } else { 0 }; |
869 | - self.h = signed_headers; |
870 | - if self.l > 0 { |
871 | - self.l = body_len as u64; |
872 | + let mut signature = self.template.clone(); |
873 | + signature.bh = base64_encode(&body_hasher.finalize())?; |
874 | + signature.t = now; |
875 | + signature.x = if signature.x > 0 { |
876 | + now + signature.x |
877 | + } else { |
878 | + 0 |
879 | + }; |
880 | + signature.h = signed_headers; |
881 | + if signature.l > 0 { |
882 | + signature.l = body_len as u64; |
883 | } |
884 | |
885 | // Add signature to hash |
886 | - self.write(&mut header_hasher, false); |
887 | + signature.write(&mut header_hasher, false); |
888 | |
889 | // Sign |
890 | - let b = with_key.sign(&header_hasher.finalize())?; |
891 | + let b = self.key.sign(&header_hasher.finalize())?; |
892 | |
893 | // Encode |
894 | - self.b = base64_encode(&b)?; |
895 | - |
896 | - Ok(self) |
897 | - } |
898 | - |
899 | - /// Sets the headers to sign. |
900 | - pub fn headers(mut self, headers: impl IntoIterator<Item = impl Into<Cow<'x, str>>>) -> Self { |
901 | - self.h = headers.into_iter().map(|h| h.into()).collect(); |
902 | - self |
903 | - } |
904 | - |
905 | - /// Sets the domain to use for signing. |
906 | - pub fn domain(mut self, domain: impl Into<Cow<'x, str>>) -> Self { |
907 | - self.d = domain.into(); |
908 | - self |
909 | - } |
910 | - |
911 | - /// Sets the selector to use for signing. |
912 | - pub fn selector(mut self, selector: impl Into<Cow<'x, str>>) -> Self { |
913 | - self.s = selector.into(); |
914 | - self |
915 | - } |
916 | - |
917 | - /// Sets the third party signature. |
918 | - pub fn atps(mut self, atps: impl Into<Cow<'x, str>>) -> Self { |
919 | - self.atps = Some(atps.into()); |
920 | - self |
921 | - } |
922 | - |
923 | - /// Sets the third-party signature hashing algorithm. |
924 | - pub fn atpsh(mut self, atpsh: HashAlgorithm) -> Self { |
925 | - self.atpsh = atpsh.into(); |
926 | - self |
927 | - } |
928 | - |
929 | - /// Sets the selector to use for signing. |
930 | - pub fn agent_user_identifier(mut self, auid: impl Into<Cow<'x, str>>) -> Self { |
931 | - self.i = auid.into(); |
932 | - self |
933 | - } |
934 | - |
935 | - /// Sets the number of seconds from now to use for the signature expiration. |
936 | - pub fn expiration(mut self, expiration: u64) -> Self { |
937 | - self.x = expiration; |
938 | - self |
939 | - } |
940 | - |
941 | - /// Include the body length in the signature. |
942 | - pub fn body_length(mut self, body_length: bool) -> Self { |
943 | - self.l = u64::from(body_length); |
944 | - self |
945 | - } |
946 | + signature.b = base64_encode(&b)?; |
947 | |
948 | - /// Request reports. |
949 | - pub fn reporting(mut self, reporting: bool) -> Self { |
950 | - self.r = reporting; |
951 | - self |
952 | - } |
953 | - |
954 | - /// Sets header canonicalization algorithm. |
955 | - pub fn header_canonicalization(mut self, ch: Canonicalization) -> Self { |
956 | - self.ch = ch; |
957 | - self |
958 | - } |
959 | - |
960 | - /// Sets header canonicalization algorithm. |
961 | - pub fn body_canonicalization(mut self, cb: Canonicalization) -> Self { |
962 | - self.cb = cb; |
963 | - self |
964 | + Ok(signature) |
965 | } |
966 | } |
967 | |
968 | @@ -160,7 +83,7 @@ mod test { |
969 | parse::TxtRecordParser, |
970 | verify::DomainKey, |
971 | }, |
972 | - dkim::{Atps, Canonicalization, DomainKeyReport, HashAlgorithm, Signature}, |
973 | + dkim::{Atps, Canonicalization, DkimSigner, DomainKeyReport, HashAlgorithm, Signature}, |
974 | AuthenticatedMessage, DkimOutput, DkimResult, Resolver, |
975 | }; |
976 | |
977 | @@ -195,10 +118,10 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
978 | #[test] |
979 | fn dkim_sign() { |
980 | let pk = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
981 | - let signature = Signature::new() |
982 | - .headers(["From", "To", "Subject"]) |
983 | + let signature = DkimSigner::from_key(pk) |
984 | .domain("stalw.art") |
985 | .selector("default") |
986 | + .headers(["From", "To", "Subject"]) |
987 | .sign_( |
988 | concat!( |
989 | "From: hello@stalw.art\r\n", |
990 | @@ -207,7 +130,6 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
991 | "Here goes the test\r\n\r\n" |
992 | ) |
993 | .as_bytes(), |
994 | - &pk, |
995 | 311923920, |
996 | ) |
997 | .unwrap(); |
998 | @@ -278,12 +200,12 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
999 | // Test RSA-SHA256 relaxed/relaxed |
1000 | verify( |
1001 | &resolver, |
1002 | - Signature::new() |
1003 | - .headers(["From", "To", "Subject"]) |
1004 | + DkimSigner::from_key(pk_rsa.clone()) |
1005 | .domain("example.com") |
1006 | .selector("default") |
1007 | + .headers(["From", "To", "Subject"]) |
1008 | .agent_user_identifier("\"John Doe\" <jdoe@example.com>") |
1009 | - .sign(message.as_bytes(), &pk_rsa) |
1010 | + .sign(message.as_bytes()) |
1011 | .unwrap(), |
1012 | message, |
1013 | Ok(()), |
1014 | @@ -293,11 +215,11 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1015 | // Test ED25519-SHA256 relaxed/relaxed |
1016 | verify( |
1017 | &resolver, |
1018 | - Signature::new() |
1019 | - .headers(["From", "To", "Subject"]) |
1020 | + DkimSigner::from_key(pk_ed) |
1021 | .domain("example.com") |
1022 | .selector("ed") |
1023 | - .sign(message.as_bytes(), &pk_ed) |
1024 | + .headers(["From", "To", "Subject"]) |
1025 | + .sign(message.as_bytes()) |
1026 | .unwrap(), |
1027 | message, |
1028 | Ok(()), |
1029 | @@ -307,7 +229,9 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1030 | // Test RSA-SHA256 simple/simple with duplicated headers |
1031 | verify( |
1032 | &resolver, |
1033 | - Signature::new() |
1034 | + DkimSigner::from_key(pk_rsa.clone()) |
1035 | + .domain("example.com") |
1036 | + .selector("default") |
1037 | .headers([ |
1038 | "From", |
1039 | "To", |
1040 | @@ -315,11 +239,9 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1041 | "X-Duplicate-Header", |
1042 | "X-Does-Not-Exist", |
1043 | ]) |
1044 | - .domain("example.com") |
1045 | - .selector("default") |
1046 | .header_canonicalization(Canonicalization::Simple) |
1047 | .body_canonicalization(Canonicalization::Simple) |
1048 | - .sign(message_multiheader.as_bytes(), &pk_rsa) |
1049 | + .sign(message_multiheader.as_bytes()) |
1050 | .unwrap(), |
1051 | message_multiheader, |
1052 | Ok(()), |
1053 | @@ -329,13 +251,13 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1054 | // Test RSA-SHA256 simple/relaxed with fixed body length |
1055 | verify( |
1056 | &resolver, |
1057 | - Signature::new() |
1058 | - .headers(["From", "To", "Subject"]) |
1059 | + DkimSigner::from_key(pk_rsa.clone()) |
1060 | .domain("example.com") |
1061 | .selector("default") |
1062 | + .headers(["From", "To", "Subject"]) |
1063 | .header_canonicalization(Canonicalization::Simple) |
1064 | .body_length(true) |
1065 | - .sign(message.as_bytes(), &pk_rsa) |
1066 | + .sign(message.as_bytes()) |
1067 | .unwrap(), |
1068 | &(message.to_string() + "\r\n----- Mailing list"), |
1069 | Ok(()), |
1070 | @@ -345,12 +267,12 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1071 | // Test AUID not matching domain |
1072 | verify( |
1073 | &resolver, |
1074 | - Signature::new() |
1075 | - .headers(["From", "To", "Subject"]) |
1076 | + DkimSigner::from_key(pk_rsa.clone()) |
1077 | .domain("example.com") |
1078 | .selector("default") |
1079 | + .headers(["From", "To", "Subject"]) |
1080 | .agent_user_identifier("@wrongdomain.com") |
1081 | - .sign(message.as_bytes(), &pk_rsa) |
1082 | + .sign(message.as_bytes()) |
1083 | .unwrap(), |
1084 | message, |
1085 | Err(super::Error::FailedAUIDMatch), |
1086 | @@ -360,13 +282,13 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1087 | // Test expired signature and reporting |
1088 | let r = verify( |
1089 | &resolver, |
1090 | - Signature::new() |
1091 | - .headers(["From", "To", "Subject"]) |
1092 | + DkimSigner::from_key(pk_rsa.clone()) |
1093 | .domain("example.com") |
1094 | .selector("default") |
1095 | + .headers(["From", "To", "Subject"]) |
1096 | .expiration(12345) |
1097 | .reporting(true) |
1098 | - .sign_(message.as_bytes(), &pk_rsa, 12345) |
1099 | + .sign_(message.as_bytes(), 12345) |
1100 | .unwrap(), |
1101 | message, |
1102 | Err(super::Error::SignatureExpired), |
1103 | @@ -380,16 +302,16 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1104 | // Verify ATPS (failure) |
1105 | verify( |
1106 | &resolver, |
1107 | - Signature::new() |
1108 | - .headers(["From", "To", "Subject"]) |
1109 | + DkimSigner::from_key(pk_rsa.clone()) |
1110 | .domain("example.com") |
1111 | .selector("default") |
1112 | + .headers(["From", "To", "Subject"]) |
1113 | .atps("example.com") |
1114 | .atpsh(HashAlgorithm::Sha256) |
1115 | - .sign_(message.as_bytes(), &pk_rsa, 12345) |
1116 | + .sign_(message.as_bytes(), 12345) |
1117 | .unwrap(), |
1118 | message, |
1119 | - Err(super::Error::DNSRecordNotFound(ResponseCode::NXDomain)), |
1120 | + Err(super::Error::DnsRecordNotFound(ResponseCode::NXDomain)), |
1121 | ) |
1122 | .await; |
1123 | |
1124 | @@ -401,13 +323,13 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1125 | ); |
1126 | verify( |
1127 | &resolver, |
1128 | - Signature::new() |
1129 | - .headers(["From", "To", "Subject"]) |
1130 | + DkimSigner::from_key(pk_rsa.clone()) |
1131 | .domain("example.com") |
1132 | .selector("default") |
1133 | + .headers(["From", "To", "Subject"]) |
1134 | .atps("example.com") |
1135 | .atpsh(HashAlgorithm::Sha256) |
1136 | - .sign_(message.as_bytes(), &pk_rsa, 12345) |
1137 | + .sign_(message.as_bytes(), 12345) |
1138 | .unwrap(), |
1139 | message, |
1140 | Ok(()), |
1141 | @@ -422,12 +344,12 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
1142 | ); |
1143 | verify( |
1144 | &resolver, |
1145 | - Signature::new() |
1146 | - .headers(["From", "To", "Subject"]) |
1147 | + DkimSigner::from_key(pk_rsa) |
1148 | .domain("example.com") |
1149 | .selector("default") |
1150 | + .headers(["From", "To", "Subject"]) |
1151 | .atps("example.com") |
1152 | - .sign_(message.as_bytes(), &pk_rsa, 12345) |
1153 | + .sign_(message.as_bytes(), 12345) |
1154 | .unwrap(), |
1155 | message, |
1156 | Ok(()), |
1157 | diff --git a/src/dkim/verify.rs b/src/dkim/verify.rs |
1158 | index c4d4deb..01c60fe 100644 |
1159 | --- a/src/dkim/verify.rs |
1160 | +++ b/src/dkim/verify.rs |
1161 | @@ -219,8 +219,8 @@ impl Resolver { |
1162 | | Error::UnsupportedKeyType |
1163 | | Error::IncompatibleAlgorithms => (record.rr & RR_SIGNATURE) != 0, |
1164 | Error::SignatureExpired => (record.rr & RR_EXPIRATION) != 0, |
1165 | - Error::DNSError(_) |
1166 | - | Error::DNSRecordNotFound(_) |
1167 | + Error::DnsError(_) |
1168 | + | Error::DnsRecordNotFound(_) |
1169 | | Error::InvalidRecordType |
1170 | | Error::ParseError |
1171 | | Error::RevokedPublicKey => (record.rr & RR_DNS) != 0, |
1172 | diff --git a/src/dmarc/mod.rs b/src/dmarc/mod.rs |
1173 | index aadc5b0..93f16b7 100644 |
1174 | --- a/src/dmarc/mod.rs |
1175 | +++ b/src/dmarc/mod.rs |
1176 | @@ -94,7 +94,7 @@ impl URI { |
1177 | |
1178 | impl From<Error> for DmarcResult { |
1179 | fn from(err: Error) -> Self { |
1180 | - if matches!(&err, Error::DNSError(_)) { |
1181 | + if matches!(&err, Error::DnsError(_)) { |
1182 | DmarcResult::TempError(err) |
1183 | } else { |
1184 | DmarcResult::PermError(err) |
1185 | diff --git a/src/dmarc/verify.rs b/src/dmarc/verify.rs |
1186 | index f950b88..9507ab7 100644 |
1187 | --- a/src/dmarc/verify.rs |
1188 | +++ b/src/dmarc/verify.rs |
1189 | @@ -137,7 +137,7 @@ impl Resolver { |
1190 | .await |
1191 | { |
1192 | Ok(_) => true, |
1193 | - Err(Error::DNSError(_)) => return None, |
1194 | + Err(Error::DnsError(_)) => return None, |
1195 | _ => false, |
1196 | } |
1197 | { |
1198 | @@ -169,7 +169,7 @@ impl Resolver { |
1199 | Ok(dmarc) => { |
1200 | return Ok(Some(dmarc)); |
1201 | } |
1202 | - Err(Error::DNSRecordNotFound(_)) | Err(Error::InvalidRecordType) => (), |
1203 | + Err(Error::DnsRecordNotFound(_)) | Err(Error::InvalidRecordType) => (), |
1204 | Err(err) => return Err(err), |
1205 | } |
1206 | |
1207 | diff --git a/src/lib.rs b/src/lib.rs |
1208 | index dd69ed0..47a0947 100644 |
1209 | --- a/src/lib.rs |
1210 | +++ b/src/lib.rs |
1211 | @@ -62,11 +62,11 @@ |
1212 | //! ```rust |
1213 | //! // Sign an e-mail message using RSA-SHA256 |
1214 | //! let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
1215 | - //! let signature_rsa = Signature::new() |
1216 | - //! .headers(["From", "To", "Subject"]) |
1217 | + //! let signature_rsa = DkimSigner::from_key(pk_rsa) |
1218 | //! .domain("example.com") |
1219 | //! .selector("default") |
1220 | - //! .sign(RFC5322_MESSAGE.as_bytes(), &pk_rsa) |
1221 | + //! .headers(["From", "To", "Subject"]) |
1222 | + //! .sign(RFC5322_MESSAGE.as_bytes()) |
1223 | //! .unwrap(); |
1224 | //! |
1225 | //! // Sign an e-mail message using ED25519-SHA256 |
1226 | @@ -75,12 +75,13 @@ |
1227 | //! &base64_decode(ED25519_PRIVATE_KEY.as_bytes()).unwrap(), |
1228 | //! ) |
1229 | //! .unwrap(); |
1230 | - //! let signature_ed = Signature::new() |
1231 | - //! .headers(["From", "To", "Subject"]) |
1232 | + //! |
1233 | + //! let signature_ed = DkimSigner::from_key(pk_ed) |
1234 | //! .domain("example.com") |
1235 | //! .selector("default-ed") |
1236 | - //! .sign(RFC5322_MESSAGE.as_bytes(), &pk_ed) |
1237 | - //! .unwrap(); |
1238 | + //! .headers(["From", "To", "Subject"]) |
1239 | + //! .sign(RFC5322_MESSAGE.as_bytes()) |
1240 | + //! .unwrap(); |
1241 | //! |
1242 | //! // Print the message including both signatures to stdout |
1243 | //! println!( |
1244 | @@ -129,11 +130,11 @@ |
1245 | //! if arc_result.can_be_sealed() { |
1246 | //! // Seal the e-mail message using RSA-SHA256 |
1247 | //! let pk_rsa = RsaKey::<Sha256>::from_pkcs1_pem(RSA_PRIVATE_KEY).unwrap(); |
1248 | - //! let arc_set = ArcSet::new(&auth_results) |
1249 | + //! let arc_set = ArcSealer::from_key(pk_rsa) |
1250 | //! .domain("example.org") |
1251 | //! .selector("default") |
1252 | //! .headers(["From", "To", "Subject", "DKIM-Signature"]) |
1253 | - //! .seal(&authenticated_message, &arc_result, &pk_rsa) |
1254 | + //! .seal(&authenticated_message, &auth_results, &arc_result) |
1255 | //! .unwrap(); |
1256 | //! |
1257 | //! // Print the sealed message to stdout |
1258 | @@ -423,8 +424,8 @@ pub enum Error { |
1259 | IncompatibleAlgorithms, |
1260 | SignatureExpired, |
1261 | |
1262 | - DNSError(String), |
1263 | - DNSRecordNotFound(ResponseCode), |
1264 | + DnsError(String), |
1265 | + DnsRecordNotFound(ResponseCode), |
1266 | |
1267 | ARCChainTooLong, |
1268 | ARCInvalidInstance(u32), |
1269 | @@ -475,8 +476,8 @@ impl Display for Error { |
1270 | Error::ARCBrokenChain => write!(f, "Broken or missing ARC chain"), |
1271 | Error::ARCChainTooLong => write!(f, "Too many ARC headers"), |
1272 | Error::InvalidRecordType => write!(f, "Invalid record"), |
1273 | - Error::DNSError(err) => write!(f, "DNS resolution error: {}", err), |
1274 | - Error::DNSRecordNotFound(code) => write!(f, "DNS record not found: {}", code), |
1275 | + Error::DnsError(err) => write!(f, "DNS resolution error: {}", err), |
1276 | + Error::DnsRecordNotFound(code) => write!(f, "DNS record not found: {}", code), |
1277 | Error::DMARCNotAligned => write!(f, "DMARC policy not aligned"), |
1278 | } |
1279 | } |
1280 | diff --git a/src/spf/verify.rs b/src/spf/verify.rs |
1281 | index 8fcd013..b489a7c 100644 |
1282 | --- a/src/spf/verify.rs |
1283 | +++ b/src/spf/verify.rs |
1284 | @@ -156,7 +156,7 @@ impl Resolver { |
1285 | .await |
1286 | { |
1287 | Ok(true) => true, |
1288 | - Ok(false) | Err(Error::DNSRecordNotFound(_)) => false, |
1289 | + Ok(false) | Err(Error::DnsRecordNotFound(_)) => false, |
1290 | Err(_) => { |
1291 | return output |
1292 | .with_result(SpfResult::TempError) |
1293 | @@ -194,7 +194,7 @@ impl Resolver { |
1294 | matches = true; |
1295 | break; |
1296 | } |
1297 | - Ok(false) | Err(Error::DNSRecordNotFound(_)) => (), |
1298 | + Ok(false) | Err(Error::DnsRecordNotFound(_)) => (), |
1299 | Err(_) => { |
1300 | return output |
1301 | .with_result(SpfResult::TempError) |
1302 | @@ -203,7 +203,7 @@ impl Resolver { |
1303 | } |
1304 | } |
1305 | } |
1306 | - Err(Error::DNSRecordNotFound(_)) => (), |
1307 | + Err(Error::DnsRecordNotFound(_)) => (), |
1308 | Err(_) => { |
1309 | return output |
1310 | .with_result(SpfResult::TempError) |
1311 | @@ -234,7 +234,7 @@ impl Resolver { |
1312 | continue; |
1313 | } |
1314 | Err( |
1315 | - Error::DNSRecordNotFound(_) |
1316 | + Error::DnsRecordNotFound(_) |
1317 | | Error::InvalidRecordType |
1318 | | Error::ParseError, |
1319 | ) => { |
1320 | @@ -336,7 +336,7 @@ impl Resolver { |
1321 | continue; |
1322 | } |
1323 | Err( |
1324 | - Error::DNSRecordNotFound(_) |
1325 | + Error::DnsRecordNotFound(_) |
1326 | | Error::InvalidRecordType |
1327 | | Error::ParseError, |
1328 | ) => { |
1329 | @@ -459,7 +459,7 @@ impl From<&Qualifier> for SpfResult { |
1330 | impl From<Error> for SpfResult { |
1331 | fn from(err: Error) -> Self { |
1332 | match err { |
1333 | - Error::DNSRecordNotFound(_) | Error::InvalidRecordType => SpfResult::None, |
1334 | + Error::DnsRecordNotFound(_) | Error::InvalidRecordType => SpfResult::None, |
1335 | Error::ParseError => SpfResult::PermError, |
1336 | _ => SpfResult::TempError, |
1337 | } |