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}