Skip to main content

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