mail_auth/dkim2/builder.rs
1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7//! Typestate builder steps of [`Dkim2Signer`].
8
9use super::{Dkim2Hash, Dkim2Signer, Flag, KeyEntry};
10use crate::{
11 crypto::DkimKey,
12 signer::{NeedDomain, NeedSelector, Ready},
13};
14
15impl Dkim2Signer<NeedDomain> {
16 /// Starts a signer with its first private key (RSA-SHA256,
17 /// Ed25519-SHA256 or MLDSA44-Ed25519-SHA512). Continue with
18 /// [`domain`](Self::domain).
19 pub fn from_key(key: impl Into<DkimKey>) -> Dkim2Signer<NeedDomain> {
20 Dkim2Signer {
21 _state: Default::default(),
22 keys: vec![KeyEntry {
23 key: key.into(),
24 selector: String::new(),
25 }],
26 domain: String::new(),
27 hashes: vec![Dkim2Hash::Sha256],
28 flags: Vec::new(),
29 nonce: None,
30 out_of_band: false,
31 }
32 }
33
34 /// Sets the signing domain (`d=`). Its public keys are looked up under
35 /// `<selector>._domainkey.<domain>`. Continue with
36 /// [`selector`](Dkim2Signer::selector).
37 pub fn domain(self, domain: impl Into<String>) -> Dkim2Signer<NeedSelector> {
38 Dkim2Signer {
39 _state: Default::default(),
40 keys: self.keys,
41 domain: domain.into(),
42 hashes: self.hashes,
43 flags: self.flags,
44 nonce: self.nonce,
45 out_of_band: self.out_of_band,
46 }
47 }
48}
49
50impl Dkim2Signer<NeedSelector> {
51 /// Sets the selector of the first key, which completes the signer.
52 pub fn selector(mut self, selector: impl Into<String>) -> Dkim2Signer<Ready> {
53 if let Some(entry) = self.keys.first_mut() {
54 entry.selector = selector.into();
55 }
56 Dkim2Signer {
57 _state: Default::default(),
58 keys: self.keys,
59 domain: self.domain,
60 hashes: self.hashes,
61 flags: self.flags,
62 nonce: self.nonce,
63 out_of_band: self.out_of_band,
64 }
65 }
66}
67
68impl Dkim2Signer<Ready> {
69 /// Adds another signing key under its own selector.
70 ///
71 /// Each key adds one entry to the `s=` tag of the same signature
72 /// (algorithmic dexterity, §10.9). Selectors must be distinct, and at
73 /// most two keys may use the same algorithm; signing fails with
74 /// [`Dkim2Error::DuplicateSelector`](super::Dkim2Error::DuplicateSelector)
75 /// or [`Dkim2Error::TooManySelectors`](super::Dkim2Error::TooManySelectors)
76 /// otherwise.
77 pub fn additional_key(mut self, key: impl Into<DkimKey>, selector: impl Into<String>) -> Self {
78 self.keys.push(KeyEntry {
79 key: key.into(),
80 selector: selector.into(),
81 });
82 self
83 }
84
85 /// Sets the hash algorithms of the `Message-Instance` header fields this
86 /// signer adds (§3.1 and §9.3). Repeated algorithms are added once; an
87 /// empty list keeps the default, SHA-256 only.
88 pub fn hashes(mut self, hashes: impl IntoIterator<Item = Dkim2Hash>) -> Self {
89 let mut selected = Vec::with_capacity(2);
90 for hash in hashes {
91 if !selected.contains(&hash) {
92 selected.push(hash);
93 }
94 }
95 if !selected.is_empty() {
96 self.hashes = selected;
97 }
98 self
99 }
100
101 /// Adds flags to the `f=` tag of the signature (§10.10). Repeated flags
102 /// are added once. [`Flag::Unknown`] values must be textstrings (letters,
103 /// digits, `-` and `_`), or signing fails with
104 /// [`Dkim2Error::SignatureSyntax`](super::Dkim2Error::SignatureSyntax);
105 /// unknown values that spell a defined flag are treated as that flag.
106 ///
107 /// The `exploded` flag is not allowed on the first signature of a
108 /// message (§12.5). It is added automatically to a later signature
109 /// whose [`Hop`](super::Hop) has more than one `RCPT TO`; a Forwarder that
110 /// sends separate copies to several recipients marks each copy's hop with
111 /// [`Hop::exploded`](super::Hop::exploded) instead.
112 pub fn flags(mut self, flags: impl IntoIterator<Item = Flag>) -> Self {
113 for flag in flags {
114 if !self.flags.contains(&flag) {
115 self.flags.push(flag);
116 }
117 }
118 self
119 }
120
121 /// Sets the nonce (`n=` tag, §10.3): an opaque value meaningful only to
122 /// the signer, for example a key to match a returned DSN. It must be at
123 /// most 64 printable ASCII characters without semicolons; signing fails
124 /// with [`Dkim2Error::SignatureSyntax`](super::Dkim2Error::SignatureSyntax)
125 /// otherwise. An empty nonce means no `n=` tag.
126 pub fn nonce(mut self, nonce: impl Into<String>) -> Self {
127 self.nonce = Some(nonce.into()).filter(|nonce| !nonce.is_empty());
128 self
129 }
130
131 /// Declares an out-of-band arrangement (§12 and §12.3) that allows this
132 /// signer to sign messages that are not Validly Signed DKIM2 Messages,
133 /// such as a message whose most recent signature carries `nd=` for an
134 /// imaginary hop made by the same system. Without it, a message that
135 /// already has DKIM2 header fields is signed only with a passing full
136 /// verification of it. The chain of custody, `mf=`/`d=` and `exploded`
137 /// checks still apply.
138 pub fn out_of_band_arrangement(mut self) -> Self {
139 self.out_of_band = true;
140 self
141 }
142}