Skip to main content

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}