1use 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, pub atps: Option<String>, pub atpsh: Option<HashAlgorithm>, 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}