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