mail-auth 0.15.1

DKIM, ARC, SPF and DMARC library for Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 */

use super::Dkim2Hash;
use std::fmt::{Display, Formatter};

/// Reason a DKIM2 header field failed to parse, a DKIM2 chain failed
/// verification or a message could not be signed.
///
/// Carried as [`Error::Dkim2`](crate::Error::Dkim2) inside a
/// [`Dkim2Result`](super::Dkim2Result) or returned by the parsing and signing
/// functions. The `i` payloads are the `i=` of the signature involved and the
/// `m` payloads the `m=` of the instance involved. The
/// [`Display`] text is the human-readable string of §14 without the
/// `PERMERROR:`/`FAIL:` prefix, which [`Dkim2Result`](super::Dkim2Result)
/// conveys.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Dkim2Error {
    /// No `Message-Instance` with this `m=`, or the instance numbering has a
    /// gap.
    InstanceMissing(u32),
    /// The `Message-Instance` has a syntax error or a repeated tag.
    InstanceSyntax(u32),
    /// A required `Message-Instance` tag is missing.
    InstanceTagMissing {
        /// Revision number (`m=`) of the instance.
        m: u32,
        /// Name of the missing tag.
        tag: &'static str,
    },
    /// The `Message-Instance` has a higher `m=` than any signature.
    InstanceNotSigned(u32),
    /// The `Message-Instance` lists the same hash algorithm twice.
    InstanceDuplicateHash(u32),
    /// The recipe (`r=` tag) of the `Message-Instance` is not valid JSON.
    InvalidJson(u32),
    /// The recipe (`r=` tag) of the `Message-Instance` is valid JSON but does
    /// not follow the recipe rules (§5).
    InvalidRecipe {
        /// Revision number (`m=`) of the instance.
        m: u32,
        /// The start of the recipe JSON, at most 64 bytes.
        value: Box<str>,
    },
    /// An `h=` hash name is not an algorithm this crate implements.
    UnknownHash {
        /// Revision number (`m=`) of the instance.
        m: u32,
        /// The unknown hash name.
        name: Box<str>,
    },
    /// No `|`-separated group of `h=` hashes passed.
    NoHashGroupPassed(u32),
    /// No `DKIM2-Signature` with this `i=`, for example a message with a
    /// `Message-Instance` but no signature.
    SignatureMissing(u32),
    /// The `DKIM2-Signature` has a syntax error, a repeated tag, an
    /// undecodable value, an invalid nonce, an address without angle
    /// brackets, or an `m=` lower than that of the previous signature.
    SignatureSyntax(u32),
    /// A required `DKIM2-Signature` tag is missing.
    SignatureTagMissing {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Name of the missing tag.
        tag: &'static str,
    },
    /// A `DKIM2-Signature` tag is not allowed in this position, such as
    /// `nd=` together with `mf=`/`rt=`, `nd=` on the most recent signature,
    /// or the `exploded` flag (`f=`) on the first signature.
    SignatureTagUnexpected {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Name of the unexpected tag.
        tag: &'static str,
    },
    /// The `s=` tag names the same selector twice.
    DuplicateSelector(u32),
    /// The `s=` tag has more than eight entries, a local limit on the public
    /// key lookups a signature can cause.
    TooManySelectors(u32),
    /// The signature sequence numbering does not run from 1 without gaps;
    /// carries the first missing `i=`.
    SequenceGap(u32),
    /// A new signature or instance number would exceed `u32::MAX`.
    SequenceOverflow,
    /// The message has more than 50 `DKIM2-Signature` or `Message-Instance`
    /// header fields.
    ChainTooLong,
    /// The signature timestamp (`t=`) is more than 14 days old.
    SignatureExpired(u32),
    /// The signature timestamp (`t=`) is more than one day in the future.
    SignatureFromFuture(u32),
    /// The SMTP `MAIL FROM` does not match `mf=` of the most recent
    /// signature.
    MailFromMismatch {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// The SMTP `MAIL FROM` value.
        value: Box<str>,
    },
    /// An SMTP `RCPT TO` address is not listed in `rt=` of the most recent
    /// signature.
    RcptToMismatch {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// The SMTP `RCPT TO` value.
        value: Box<str>,
    },
    /// The `mf=` domain is neither `d=` nor a subdomain of it (§10.8).
    MailFromDomainMismatch(u32),
    /// `d=` differs from the `nd=` of the previous signature.
    NextDomainMismatch(u32),
    /// The `mf=` domain does not relaxed-match any `rt=` recipient of the
    /// previous signature (§7.1).
    CustodyMailFromMismatch {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Sequence number (`i=`) of the previous signature.
        previous: u32,
    },
    /// No `rt=` recipient of the previous signature relaxed-matches `d=`,
    /// for a signature with a null `mf=` or with `nd=` (§7.1).
    CustodyDomainMismatch {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Sequence number (`i=`) of the previous signature.
        previous: u32,
    },
    /// The public key lookup failed with a temporary DNS error.
    PublicKeyFetch {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Selector of the key.
        selector: Box<str>,
    },
    /// The public key record does not exist.
    PublicKeyMissing {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Selector of the key.
        selector: Box<str>,
    },
    /// The selector has more than one TXT record.
    PublicKeyMultiple {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Selector of the key.
        selector: Box<str>,
    },
    /// The public key record has a syntax error.
    PublicKeySyntax {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Selector of the key.
        selector: Box<str>,
    },
    /// The public key is malformed: an Ed25519 key that is not 32 octets, an
    /// ML-DSA-44 key that is not 1312 octets, an RSA key that cannot be
    /// decoded or is shorter than 1024 bits (§14.5).
    PublicKeyMalformed {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Selector of the key.
        selector: Box<str>,
    },
    /// The public key type (`k=`) does not match the `s=` algorithm.
    PublicKeyAlgorithmMismatch {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Selector of the key.
        selector: Box<str>,
    },
    /// The public key record has an empty `p=` tag (revoked key).
    PublicKeyRevoked {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Selector of the key.
        selector: Box<str>,
    },
    /// An `s=` signature value does not verify.
    IncorrectSignature {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Selector of the entry that failed.
        selector: Box<str>,
    },
    /// An `s=` entry names an algorithm this crate does not implement.
    UnknownAlgorithm {
        /// Sequence number (`i=`) of the signature.
        i: u32,
        /// Selector of the entry.
        selector: Box<str>,
    },
    /// No `|`-separated group of `s=` signatures passed.
    NoSignatureGroupPassed(u32),
    /// The header fields hash does not match the `Message-Instance`.
    HeaderHashMismatch {
        /// Revision number (`m=`) of the instance.
        m: u32,
        /// Hash algorithm that did not match.
        hash: Dkim2Hash,
    },
    /// The body hash does not match the `Message-Instance`.
    BodyHashMismatch {
        /// Revision number (`m=`) of the instance.
        m: u32,
        /// Hash algorithm that did not match.
        hash: Dkim2Hash,
    },
    /// A `Message-Instance` with this `m=` has a null body recipe, so the
    /// body hashes of the instances below it cannot be checked and the
    /// message is not a Fully Verified DKIM2 Message (§13).
    NullBodyRecipe(u32),
    /// A `From` header field is not Authentic (§11): its domain does not
    /// relaxed-match `d=` of the signature that covers it, or it has no
    /// single address with a domain name.
    FromNotAuthentic(u32),
    /// [`Recipe::diff`](super::Recipe::diff) found a changed header field
    /// value with a bare CR or LF outside folding, which a recipe cannot
    /// recreate.
    HeaderNotRepresentable,
    /// [`Recipe::apply`](super::Recipe::apply) found a body recipe that
    /// declares the previous body unreconstructable (`"b": null`).
    BodyUnreconstructable,
    /// A recipe could not be serialized to or parsed from JSON, or does not
    /// follow the recipe schema (§5).
    RecipeSyntax,
    /// The message to sign already has DKIM2 header fields but no passing
    /// full verification of it was supplied, and the signer has no
    /// out-of-band arrangement (§12).
    NotFullyVerified,
}

impl Display for Dkim2Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Dkim2Error::InstanceMissing(m) => write!(f, "Message-Instance m={m} missing"),
            Dkim2Error::InstanceSyntax(m) => write!(f, "Message-Instance m={m} syntax error"),
            Dkim2Error::InstanceTagMissing { m, tag } => {
                write!(f, "Message-Instance m={m} tag={tag} missing")
            }
            Dkim2Error::InstanceNotSigned(m) => write!(f, "Message-Instance m={m} is not signed"),
            Dkim2Error::InstanceDuplicateHash(m) => {
                write!(f, "Message-Instance m={m} has a duplicate hash algorithm")
            }
            Dkim2Error::InvalidJson(m) => {
                write!(f, "Message-Instance m={m} contains invalid JSON")
            }
            Dkim2Error::InvalidRecipe { m, value } => {
                write!(f, "Message-Instance m={m} Recipe {value} is invalid")
            }
            Dkim2Error::UnknownHash { m, name } => {
                write!(f, "Message-Instance m={m} algorithm {name} unknown")
            }
            Dkim2Error::NoHashGroupPassed(m) => {
                write!(f, "Message-Instance m={m} no group of hashes passed")
            }
            Dkim2Error::SignatureMissing(i) => write!(f, "DKIM2-Signature i={i} missing"),
            Dkim2Error::SignatureSyntax(i) => write!(f, "DKIM2-Signature i={i} syntax error"),
            Dkim2Error::SignatureTagMissing { i, tag } => {
                write!(f, "DKIM2-Signature i={i} tag={tag} missing")
            }
            Dkim2Error::SignatureTagUnexpected { i, tag } => {
                write!(f, "DKIM2-Signature i={i} tag={tag} was unexpected")
            }
            Dkim2Error::DuplicateSelector(i) => {
                write!(f, "DKIM2-Signature i={i} has a duplicate selector")
            }
            Dkim2Error::TooManySelectors(i) => {
                write!(f, "DKIM2-Signature i={i} has more selectors than allowed")
            }
            Dkim2Error::SequenceGap(i) => write!(f, "DKIM2-Signature i={i} missing"),
            Dkim2Error::SequenceOverflow => {
                write!(f, "DKIM2 sequence numbering would overflow")
            }
            Dkim2Error::ChainTooLong => write!(f, "Too many DKIM2 header fields"),
            Dkim2Error::SignatureExpired(i) => write!(f, "DKIM2-Signature i={i} signature expired"),
            Dkim2Error::SignatureFromFuture(i) => {
                write!(f, "DKIM2-Signature i={i} signature is from the future")
            }
            Dkim2Error::MailFromMismatch { i, value } => {
                write!(f, "DKIM2-Signature i={i} MAIL FROM {value} did not match")
            }
            Dkim2Error::RcptToMismatch { i, value } => {
                write!(f, "DKIM2-Signature i={i} RCPT TO {value} did not match")
            }
            Dkim2Error::MailFromDomainMismatch(i) => {
                write!(f, "DKIM2-Signature i={i} MAIL FROM and d= do not match")
            }
            Dkim2Error::NextDomainMismatch(i) => {
                write!(f, "DKIM2-Signature i={i} nd= does not match")
            }
            Dkim2Error::CustodyMailFromMismatch { i, previous } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} mf= does not match the rt= of i={previous}"
                )
            }
            Dkim2Error::CustodyDomainMismatch { i, previous } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} d= does not match the rt= of i={previous}"
                )
            }
            Dkim2Error::PublicKeyFetch { i, selector } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} public key {selector} could not be fetched"
                )
            }
            Dkim2Error::PublicKeyMissing { i, selector } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} public key {selector} does not exist"
                )
            }
            Dkim2Error::PublicKeyMultiple { i, selector } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} public key {selector} has multiple records"
                )
            }
            Dkim2Error::PublicKeySyntax { i, selector } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} public key {selector} has a syntax error"
                )
            }
            Dkim2Error::PublicKeyMalformed { i, selector } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} public key {selector} is malformed"
                )
            }
            Dkim2Error::PublicKeyAlgorithmMismatch { i, selector } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} public key {selector} algorithm mismatch"
                )
            }
            Dkim2Error::PublicKeyRevoked { i, selector } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} public key {selector} has been revoked"
                )
            }
            Dkim2Error::IncorrectSignature { i, selector } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} selector {selector} incorrect signature"
                )
            }
            Dkim2Error::UnknownAlgorithm { i, selector } => {
                write!(
                    f,
                    "DKIM2-Signature i={i} selector {selector} algorithm unknown"
                )
            }
            Dkim2Error::NoSignatureGroupPassed(i) => {
                write!(f, "DKIM2-Signature i={i} no group of signatures passed")
            }
            Dkim2Error::HeaderHashMismatch { m, hash } => {
                write!(
                    f,
                    "Message-Instance m={m} header hash {} mismatch",
                    hash.name()
                )
            }
            Dkim2Error::BodyHashMismatch { m, hash } => {
                write!(
                    f,
                    "Message-Instance m={m} body hash {} mismatch",
                    hash.name()
                )
            }
            Dkim2Error::NullBodyRecipe(m) => {
                write!(
                    f,
                    "Message-Instance m={m} has a null Recipe, earlier body hashes cannot be checked"
                )
            }
            Dkim2Error::FromNotAuthentic(i) => {
                write!(f, "DKIM2-Signature i={i} matching From is not authentic")
            }
            Dkim2Error::HeaderNotRepresentable => {
                write!(
                    f,
                    "Changed header field has a bare CR or LF and cannot be recorded in a recipe"
                )
            }
            Dkim2Error::BodyUnreconstructable => {
                write!(f, "Message body cannot be reconstructed (null body recipe)")
            }
            Dkim2Error::RecipeSyntax => write!(f, "Message-Instance recipe syntax error"),
            Dkim2Error::NotFullyVerified => {
                write!(f, "Message is not a Fully Verified DKIM2 Message")
            }
        }
    }
}