Commit
Author: Mauro D [mauro@stalw.art]
Hash: 3a571edfdb454edc98077b7dcf664abd0307ba6c
Timestamp: Sun, 08 Jan 2023 14:07:56 +0000 (1 year ago)

+422 -300 +/-17 browse
DKIM signer and ARC sealer implementation.
1diff --git a/README.md b/README.md
2index 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
52index 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
79index 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
122new file mode 100644
123index 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
231index 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
273index 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
549index 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
573index 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
586index 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
625new file mode 100644
626index 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
738index 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
794index 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
1158index 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
1173index 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
1186index 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
1208index 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
1281index 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 }