Skip to main content

mail_auth/dkim2/
error.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 super::Dkim2Hash;
8use std::fmt::{Display, Formatter};
9
10/// Reason a DKIM2 header field failed to parse, a DKIM2 chain failed
11/// verification or a message could not be signed.
12///
13/// Carried as [`Error::Dkim2`](crate::Error::Dkim2) inside a
14/// [`Dkim2Result`](super::Dkim2Result) or returned by the parsing and signing
15/// functions. The `i` payloads are the `i=` of the signature involved and the
16/// `m` payloads the `m=` of the instance involved. The
17/// [`Display`] text is the human-readable string of §14 without the
18/// `PERMERROR:`/`FAIL:` prefix, which [`Dkim2Result`](super::Dkim2Result)
19/// conveys.
20#[derive(Debug, PartialEq, Eq, Clone)]
21pub enum Dkim2Error {
22    /// No `Message-Instance` with this `m=`, or the instance numbering has a
23    /// gap.
24    InstanceMissing(u32),
25    /// The `Message-Instance` has a syntax error or a repeated tag.
26    InstanceSyntax(u32),
27    /// A required `Message-Instance` tag is missing.
28    InstanceTagMissing {
29        /// Revision number (`m=`) of the instance.
30        m: u32,
31        /// Name of the missing tag.
32        tag: &'static str,
33    },
34    /// The `Message-Instance` has a higher `m=` than any signature.
35    InstanceNotSigned(u32),
36    /// The `Message-Instance` lists the same hash algorithm twice.
37    InstanceDuplicateHash(u32),
38    /// The recipe (`r=` tag) of the `Message-Instance` is not valid JSON.
39    InvalidJson(u32),
40    /// The recipe (`r=` tag) of the `Message-Instance` is valid JSON but does
41    /// not follow the recipe rules (§5).
42    InvalidRecipe {
43        /// Revision number (`m=`) of the instance.
44        m: u32,
45        /// The start of the recipe JSON, at most 64 bytes.
46        value: Box<str>,
47    },
48    /// An `h=` hash name is not an algorithm this crate implements.
49    UnknownHash {
50        /// Revision number (`m=`) of the instance.
51        m: u32,
52        /// The unknown hash name.
53        name: Box<str>,
54    },
55    /// No `|`-separated group of `h=` hashes passed.
56    NoHashGroupPassed(u32),
57    /// No `DKIM2-Signature` with this `i=`, for example a message with a
58    /// `Message-Instance` but no signature.
59    SignatureMissing(u32),
60    /// The `DKIM2-Signature` has a syntax error, a repeated tag, an
61    /// undecodable value, an invalid nonce, an address without angle
62    /// brackets, or an `m=` lower than that of the previous signature.
63    SignatureSyntax(u32),
64    /// A required `DKIM2-Signature` tag is missing.
65    SignatureTagMissing {
66        /// Sequence number (`i=`) of the signature.
67        i: u32,
68        /// Name of the missing tag.
69        tag: &'static str,
70    },
71    /// A `DKIM2-Signature` tag is not allowed in this position, such as
72    /// `nd=` together with `mf=`/`rt=`, `nd=` on the most recent signature,
73    /// or the `exploded` flag (`f=`) on the first signature.
74    SignatureTagUnexpected {
75        /// Sequence number (`i=`) of the signature.
76        i: u32,
77        /// Name of the unexpected tag.
78        tag: &'static str,
79    },
80    /// The `s=` tag names the same selector twice.
81    DuplicateSelector(u32),
82    /// The `s=` tag has more than eight entries, a local limit on the public
83    /// key lookups a signature can cause.
84    TooManySelectors(u32),
85    /// The signature sequence numbering does not run from 1 without gaps;
86    /// carries the first missing `i=`.
87    SequenceGap(u32),
88    /// A new signature or instance number would exceed `u32::MAX`.
89    SequenceOverflow,
90    /// The message has more than 50 `DKIM2-Signature` or `Message-Instance`
91    /// header fields.
92    ChainTooLong,
93    /// The signature timestamp (`t=`) is more than 14 days old.
94    SignatureExpired(u32),
95    /// The signature timestamp (`t=`) is more than one day in the future.
96    SignatureFromFuture(u32),
97    /// The SMTP `MAIL FROM` does not match `mf=` of the most recent
98    /// signature.
99    MailFromMismatch {
100        /// Sequence number (`i=`) of the signature.
101        i: u32,
102        /// The SMTP `MAIL FROM` value.
103        value: Box<str>,
104    },
105    /// An SMTP `RCPT TO` address is not listed in `rt=` of the most recent
106    /// signature.
107    RcptToMismatch {
108        /// Sequence number (`i=`) of the signature.
109        i: u32,
110        /// The SMTP `RCPT TO` value.
111        value: Box<str>,
112    },
113    /// The `mf=` domain is neither `d=` nor a subdomain of it (§10.8).
114    MailFromDomainMismatch(u32),
115    /// `d=` differs from the `nd=` of the previous signature.
116    NextDomainMismatch(u32),
117    /// The `mf=` domain does not relaxed-match any `rt=` recipient of the
118    /// previous signature (§7.1).
119    CustodyMailFromMismatch {
120        /// Sequence number (`i=`) of the signature.
121        i: u32,
122        /// Sequence number (`i=`) of the previous signature.
123        previous: u32,
124    },
125    /// No `rt=` recipient of the previous signature relaxed-matches `d=`,
126    /// for a signature with a null `mf=` or with `nd=` (§7.1).
127    CustodyDomainMismatch {
128        /// Sequence number (`i=`) of the signature.
129        i: u32,
130        /// Sequence number (`i=`) of the previous signature.
131        previous: u32,
132    },
133    /// The public key lookup failed with a temporary DNS error.
134    PublicKeyFetch {
135        /// Sequence number (`i=`) of the signature.
136        i: u32,
137        /// Selector of the key.
138        selector: Box<str>,
139    },
140    /// The public key record does not exist.
141    PublicKeyMissing {
142        /// Sequence number (`i=`) of the signature.
143        i: u32,
144        /// Selector of the key.
145        selector: Box<str>,
146    },
147    /// The selector has more than one TXT record.
148    PublicKeyMultiple {
149        /// Sequence number (`i=`) of the signature.
150        i: u32,
151        /// Selector of the key.
152        selector: Box<str>,
153    },
154    /// The public key record has a syntax error.
155    PublicKeySyntax {
156        /// Sequence number (`i=`) of the signature.
157        i: u32,
158        /// Selector of the key.
159        selector: Box<str>,
160    },
161    /// The public key is malformed: an Ed25519 key that is not 32 octets, an
162    /// ML-DSA-44 key that is not 1312 octets, an RSA key that cannot be
163    /// decoded or is shorter than 1024 bits (§14.5).
164    PublicKeyMalformed {
165        /// Sequence number (`i=`) of the signature.
166        i: u32,
167        /// Selector of the key.
168        selector: Box<str>,
169    },
170    /// The public key type (`k=`) does not match the `s=` algorithm.
171    PublicKeyAlgorithmMismatch {
172        /// Sequence number (`i=`) of the signature.
173        i: u32,
174        /// Selector of the key.
175        selector: Box<str>,
176    },
177    /// The public key record has an empty `p=` tag (revoked key).
178    PublicKeyRevoked {
179        /// Sequence number (`i=`) of the signature.
180        i: u32,
181        /// Selector of the key.
182        selector: Box<str>,
183    },
184    /// An `s=` signature value does not verify.
185    IncorrectSignature {
186        /// Sequence number (`i=`) of the signature.
187        i: u32,
188        /// Selector of the entry that failed.
189        selector: Box<str>,
190    },
191    /// An `s=` entry names an algorithm this crate does not implement.
192    UnknownAlgorithm {
193        /// Sequence number (`i=`) of the signature.
194        i: u32,
195        /// Selector of the entry.
196        selector: Box<str>,
197    },
198    /// No `|`-separated group of `s=` signatures passed.
199    NoSignatureGroupPassed(u32),
200    /// The header fields hash does not match the `Message-Instance`.
201    HeaderHashMismatch {
202        /// Revision number (`m=`) of the instance.
203        m: u32,
204        /// Hash algorithm that did not match.
205        hash: Dkim2Hash,
206    },
207    /// The body hash does not match the `Message-Instance`.
208    BodyHashMismatch {
209        /// Revision number (`m=`) of the instance.
210        m: u32,
211        /// Hash algorithm that did not match.
212        hash: Dkim2Hash,
213    },
214    /// A `Message-Instance` with this `m=` has a null body recipe, so the
215    /// body hashes of the instances below it cannot be checked and the
216    /// message is not a Fully Verified DKIM2 Message (§13).
217    NullBodyRecipe(u32),
218    /// A `From` header field is not Authentic (§11): its domain does not
219    /// relaxed-match `d=` of the signature that covers it, or it has no
220    /// single address with a domain name.
221    FromNotAuthentic(u32),
222    /// [`Recipe::diff`](super::Recipe::diff) found a changed header field
223    /// value with a bare CR or LF outside folding, which a recipe cannot
224    /// recreate.
225    HeaderNotRepresentable,
226    /// [`Recipe::apply`](super::Recipe::apply) found a body recipe that
227    /// declares the previous body unreconstructable (`"b": null`).
228    BodyUnreconstructable,
229    /// A recipe could not be serialized to or parsed from JSON, or does not
230    /// follow the recipe schema (§5).
231    RecipeSyntax,
232    /// The message to sign already has DKIM2 header fields but no passing
233    /// full verification of it was supplied, and the signer has no
234    /// out-of-band arrangement (§12).
235    NotFullyVerified,
236}
237
238impl Display for Dkim2Error {
239    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
240        match self {
241            Dkim2Error::InstanceMissing(m) => write!(f, "Message-Instance m={m} missing"),
242            Dkim2Error::InstanceSyntax(m) => write!(f, "Message-Instance m={m} syntax error"),
243            Dkim2Error::InstanceTagMissing { m, tag } => {
244                write!(f, "Message-Instance m={m} tag={tag} missing")
245            }
246            Dkim2Error::InstanceNotSigned(m) => write!(f, "Message-Instance m={m} is not signed"),
247            Dkim2Error::InstanceDuplicateHash(m) => {
248                write!(f, "Message-Instance m={m} has a duplicate hash algorithm")
249            }
250            Dkim2Error::InvalidJson(m) => {
251                write!(f, "Message-Instance m={m} contains invalid JSON")
252            }
253            Dkim2Error::InvalidRecipe { m, value } => {
254                write!(f, "Message-Instance m={m} Recipe {value} is invalid")
255            }
256            Dkim2Error::UnknownHash { m, name } => {
257                write!(f, "Message-Instance m={m} algorithm {name} unknown")
258            }
259            Dkim2Error::NoHashGroupPassed(m) => {
260                write!(f, "Message-Instance m={m} no group of hashes passed")
261            }
262            Dkim2Error::SignatureMissing(i) => write!(f, "DKIM2-Signature i={i} missing"),
263            Dkim2Error::SignatureSyntax(i) => write!(f, "DKIM2-Signature i={i} syntax error"),
264            Dkim2Error::SignatureTagMissing { i, tag } => {
265                write!(f, "DKIM2-Signature i={i} tag={tag} missing")
266            }
267            Dkim2Error::SignatureTagUnexpected { i, tag } => {
268                write!(f, "DKIM2-Signature i={i} tag={tag} was unexpected")
269            }
270            Dkim2Error::DuplicateSelector(i) => {
271                write!(f, "DKIM2-Signature i={i} has a duplicate selector")
272            }
273            Dkim2Error::TooManySelectors(i) => {
274                write!(f, "DKIM2-Signature i={i} has more selectors than allowed")
275            }
276            Dkim2Error::SequenceGap(i) => write!(f, "DKIM2-Signature i={i} missing"),
277            Dkim2Error::SequenceOverflow => {
278                write!(f, "DKIM2 sequence numbering would overflow")
279            }
280            Dkim2Error::ChainTooLong => write!(f, "Too many DKIM2 header fields"),
281            Dkim2Error::SignatureExpired(i) => write!(f, "DKIM2-Signature i={i} signature expired"),
282            Dkim2Error::SignatureFromFuture(i) => {
283                write!(f, "DKIM2-Signature i={i} signature is from the future")
284            }
285            Dkim2Error::MailFromMismatch { i, value } => {
286                write!(f, "DKIM2-Signature i={i} MAIL FROM {value} did not match")
287            }
288            Dkim2Error::RcptToMismatch { i, value } => {
289                write!(f, "DKIM2-Signature i={i} RCPT TO {value} did not match")
290            }
291            Dkim2Error::MailFromDomainMismatch(i) => {
292                write!(f, "DKIM2-Signature i={i} MAIL FROM and d= do not match")
293            }
294            Dkim2Error::NextDomainMismatch(i) => {
295                write!(f, "DKIM2-Signature i={i} nd= does not match")
296            }
297            Dkim2Error::CustodyMailFromMismatch { i, previous } => {
298                write!(
299                    f,
300                    "DKIM2-Signature i={i} mf= does not match the rt= of i={previous}"
301                )
302            }
303            Dkim2Error::CustodyDomainMismatch { i, previous } => {
304                write!(
305                    f,
306                    "DKIM2-Signature i={i} d= does not match the rt= of i={previous}"
307                )
308            }
309            Dkim2Error::PublicKeyFetch { i, selector } => {
310                write!(
311                    f,
312                    "DKIM2-Signature i={i} public key {selector} could not be fetched"
313                )
314            }
315            Dkim2Error::PublicKeyMissing { i, selector } => {
316                write!(
317                    f,
318                    "DKIM2-Signature i={i} public key {selector} does not exist"
319                )
320            }
321            Dkim2Error::PublicKeyMultiple { i, selector } => {
322                write!(
323                    f,
324                    "DKIM2-Signature i={i} public key {selector} has multiple records"
325                )
326            }
327            Dkim2Error::PublicKeySyntax { i, selector } => {
328                write!(
329                    f,
330                    "DKIM2-Signature i={i} public key {selector} has a syntax error"
331                )
332            }
333            Dkim2Error::PublicKeyMalformed { i, selector } => {
334                write!(
335                    f,
336                    "DKIM2-Signature i={i} public key {selector} is malformed"
337                )
338            }
339            Dkim2Error::PublicKeyAlgorithmMismatch { i, selector } => {
340                write!(
341                    f,
342                    "DKIM2-Signature i={i} public key {selector} algorithm mismatch"
343                )
344            }
345            Dkim2Error::PublicKeyRevoked { i, selector } => {
346                write!(
347                    f,
348                    "DKIM2-Signature i={i} public key {selector} has been revoked"
349                )
350            }
351            Dkim2Error::IncorrectSignature { i, selector } => {
352                write!(
353                    f,
354                    "DKIM2-Signature i={i} selector {selector} incorrect signature"
355                )
356            }
357            Dkim2Error::UnknownAlgorithm { i, selector } => {
358                write!(
359                    f,
360                    "DKIM2-Signature i={i} selector {selector} algorithm unknown"
361                )
362            }
363            Dkim2Error::NoSignatureGroupPassed(i) => {
364                write!(f, "DKIM2-Signature i={i} no group of signatures passed")
365            }
366            Dkim2Error::HeaderHashMismatch { m, hash } => {
367                write!(
368                    f,
369                    "Message-Instance m={m} header hash {} mismatch",
370                    hash.name()
371                )
372            }
373            Dkim2Error::BodyHashMismatch { m, hash } => {
374                write!(
375                    f,
376                    "Message-Instance m={m} body hash {} mismatch",
377                    hash.name()
378                )
379            }
380            Dkim2Error::NullBodyRecipe(m) => {
381                write!(
382                    f,
383                    "Message-Instance m={m} has a null Recipe, earlier body hashes cannot be checked"
384                )
385            }
386            Dkim2Error::FromNotAuthentic(i) => {
387                write!(f, "DKIM2-Signature i={i} matching From is not authentic")
388            }
389            Dkim2Error::HeaderNotRepresentable => {
390                write!(
391                    f,
392                    "Changed header field has a bare CR or LF and cannot be recorded in a recipe"
393                )
394            }
395            Dkim2Error::BodyUnreconstructable => {
396                write!(f, "Message body cannot be reconstructed (null body recipe)")
397            }
398            Dkim2Error::RecipeSyntax => write!(f, "Message-Instance recipe syntax error"),
399            Dkim2Error::NotFullyVerified => {
400                write!(f, "Message is not a Fully Verified DKIM2 Message")
401            }
402        }
403    }
404}