mail_auth/dkim/
mod.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use crate::{
8    ArcOutput, DkimOutput, DkimResult, Error, Version,
9    arc::Set,
10    common::{
11        crypto::{Algorithm, HashAlgorithm, SigningKey},
12        verify::VerifySignature,
13    },
14};
15
16pub mod builder;
17pub mod canonicalize;
18#[cfg(feature = "generate")]
19pub mod generate;
20pub mod headers;
21pub mod parse;
22pub mod sign;
23pub mod streaming;
24pub mod verify;
25
26pub use streaming::DkimSigningStream;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29pub enum Canonicalization {
30    #[default]
31    Relaxed,
32    Simple,
33}
34
35#[derive(Debug, PartialEq, Eq, Clone, Default)]
36pub struct DkimSigner<T: SigningKey, State = NeedDomain> {
37    _state: std::marker::PhantomData<State>,
38    pub key: T,
39    pub template: Signature,
40}
41
42pub struct NeedDomain;
43pub struct NeedSelector;
44pub struct NeedHeaders;
45pub struct Done;
46
47#[derive(Debug, PartialEq, Eq, Clone, Default)]
48pub struct Signature {
49    pub v: u32,
50    pub a: Algorithm,
51    pub d: String,
52    pub s: String,
53    pub b: Vec<u8>,
54    pub bh: Vec<u8>,
55    pub h: Vec<String>,
56    pub z: Vec<String>,
57    pub i: String,
58    pub l: u64,
59    pub x: u64,
60    pub t: u64,
61    pub r: bool,                      // RFC 6651
62    pub atps: Option<String>,         // RFC 6541
63    pub atpsh: Option<HashAlgorithm>, // RFC 6541
64    pub ch: Canonicalization,
65    pub cb: Canonicalization,
66}
67
68#[derive(Debug, PartialEq, Eq, Clone)]
69pub struct DomainKeyReport {
70    pub(crate) ra: String,
71    pub(crate) rp: u8,
72    pub(crate) rr: u8,
73    pub(crate) rs: Option<String>,
74}
75
76#[derive(Debug, PartialEq, Eq, Clone)]
77pub struct Atps {
78    pub(crate) v: Version,
79    pub(crate) d: Option<String>,
80}
81
82pub(crate) const R_SVC_ALL: u64 = 0x04;
83pub(crate) const R_SVC_EMAIL: u64 = 0x08;
84pub(crate) const R_FLAG_TESTING: u64 = 0x10;
85pub(crate) const R_FLAG_MATCH_DOMAIN: u64 = 0x20;
86
87pub(crate) const RR_DNS: u8 = 0x01;
88pub(crate) const RR_OTHER: u8 = 0x02;
89pub(crate) const RR_POLICY: u8 = 0x04;
90pub(crate) const RR_SIGNATURE: u8 = 0x08;
91pub(crate) const RR_UNKNOWN_TAG: u8 = 0x10;
92pub(crate) const RR_VERIFICATION: u8 = 0x20;
93pub(crate) const RR_EXPIRATION: u8 = 0x40;
94
95#[derive(Debug, PartialEq, Eq, Clone)]
96#[repr(u64)]
97pub(crate) enum Service {
98    All = R_SVC_ALL,
99    Email = R_SVC_EMAIL,
100}
101
102#[derive(Debug, PartialEq, Eq, Clone)]
103#[repr(u64)]
104pub(crate) enum Flag {
105    Testing = R_FLAG_TESTING,
106    MatchDomain = R_FLAG_MATCH_DOMAIN,
107}
108
109impl From<Flag> for u64 {
110    fn from(v: Flag) -> Self {
111        v as u64
112    }
113}
114
115impl From<HashAlgorithm> for u64 {
116    fn from(v: HashAlgorithm) -> Self {
117        v as u64
118    }
119}
120
121impl From<Service> for u64 {
122    fn from(v: Service) -> Self {
123        v as u64
124    }
125}
126
127impl From<Algorithm> for HashAlgorithm {
128    fn from(a: Algorithm) -> Self {
129        match a {
130            Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => HashAlgorithm::Sha256,
131            Algorithm::RsaSha1 => HashAlgorithm::Sha1,
132        }
133    }
134}
135
136impl VerifySignature for Signature {
137    fn signature(&self) -> &[u8] {
138        &self.b
139    }
140
141    fn algorithm(&self) -> Algorithm {
142        self.a
143    }
144
145    fn selector(&self) -> &str {
146        &self.s
147    }
148
149    fn domain(&self) -> &str {
150        &self.d
151    }
152}
153
154impl Signature {
155    pub fn identity(&self) -> &str {
156        &self.i
157    }
158}
159
160impl<'x> DkimOutput<'x> {
161    pub fn pass() -> Self {
162        DkimOutput {
163            result: DkimResult::Pass,
164            signature: None,
165            report: None,
166            is_atps: false,
167        }
168    }
169
170    pub fn perm_err(err: Error) -> Self {
171        DkimOutput {
172            result: DkimResult::PermError(err),
173            signature: None,
174            report: None,
175            is_atps: false,
176        }
177    }
178
179    pub fn temp_err(err: Error) -> Self {
180        DkimOutput {
181            result: DkimResult::TempError(err),
182            signature: None,
183            report: None,
184            is_atps: false,
185        }
186    }
187
188    pub fn fail(err: Error) -> Self {
189        DkimOutput {
190            result: DkimResult::Fail(err),
191            signature: None,
192            report: None,
193            is_atps: false,
194        }
195    }
196
197    pub fn neutral(err: Error) -> Self {
198        DkimOutput {
199            result: DkimResult::Neutral(err),
200            signature: None,
201            report: None,
202            is_atps: false,
203        }
204    }
205
206    pub fn dns_error(err: Error) -> Self {
207        if matches!(&err, Error::DnsError(_)) {
208            DkimOutput::temp_err(err)
209        } else {
210            DkimOutput::perm_err(err)
211        }
212    }
213
214    pub fn with_signature(mut self, signature: &'x Signature) -> Self {
215        self.signature = signature.into();
216        self
217    }
218
219    pub fn with_report(mut self, report: String) -> Self {
220        self.report = Some(report);
221        self
222    }
223
224    pub fn with_atps(mut self) -> Self {
225        self.is_atps = true;
226        self
227    }
228
229    pub fn result(&self) -> &DkimResult {
230        &self.result
231    }
232
233    pub fn signature(&self) -> Option<&Signature> {
234        self.signature
235    }
236
237    pub fn failure_report_addr(&self) -> Option<&str> {
238        self.report.as_deref()
239    }
240}
241
242impl ArcOutput<'_> {
243    pub fn result(&self) -> &DkimResult {
244        &self.result
245    }
246
247    pub fn sets(&'_ self) -> &'_ [Set<'_>] {
248        &self.set
249    }
250}
251
252impl From<Error> for DkimResult {
253    fn from(err: Error) -> Self {
254        if matches!(&err, Error::DnsError(_)) {
255            DkimResult::TempError(err)
256        } else {
257            DkimResult::PermError(err)
258        }
259    }
260}