mailauth 5.0.3 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +23 -0
- package/README.md +117 -41
- package/bin/mailauth.js +14 -6
- package/cli.md +8 -3
- package/docs/arc.md +126 -43
- package/docs/bimi.md +54 -37
- package/docs/dkim.md +79 -40
- package/docs/dmarc.md +82 -31
- package/docs/mta-sts.md +140 -42
- package/docs/spf.md +69 -4
- package/index.d.ts +499 -45
- package/lib/arc/index.d.ts +10 -8
- package/lib/arc/index.js +505 -124
- package/lib/bimi/index.js +162 -81
- package/lib/commands/report.js +2 -1
- package/lib/commands/seal.js +35 -5
- package/lib/commands/sign.js +8 -1
- package/lib/commands/spf.js +8 -0
- package/lib/dkim/body/relaxed.js +6 -5
- package/lib/dkim/body/simple.js +1 -3
- package/lib/dkim/dkim-signer.js +219 -33
- package/lib/dkim/dkim-verifier.js +574 -231
- package/lib/dkim/header/relaxed.js +6 -43
- package/lib/dkim/header/signature-opts.js +87 -0
- package/lib/dkim/header/simple.js +6 -43
- package/lib/dkim/message-parser.js +18 -4
- package/lib/dkim/sign.d.ts +13 -7
- package/lib/dkim/sign.js +18 -2
- package/lib/dkim/syntax.js +31 -0
- package/lib/dkim/verify.js +1 -0
- package/lib/dmarc/author-domain.js +181 -0
- package/lib/dmarc/get-dmarc-record.js +267 -53
- package/lib/dmarc/index.d.ts +29 -2
- package/lib/dmarc/index.js +2 -1
- package/lib/dmarc/verify.js +205 -53
- package/lib/mailauth.js +86 -40
- package/lib/mta-sts.d.ts +77 -11
- package/lib/mta-sts.js +497 -182
- package/lib/parse-dkim-headers.js +376 -191
- package/lib/parse-received.js +104 -32
- package/lib/spf/index.d.ts +37 -1
- package/lib/spf/index.js +260 -28
- package/lib/spf/macro.js +50 -13
- package/lib/spf/spf-verify.js +440 -204
- package/lib/spf/syntax.js +366 -0
- package/lib/tools.js +524 -118
- package/package.json +12 -12
package/index.d.ts
CHANGED
|
@@ -84,6 +84,24 @@ export interface AuthenticateOptions {
|
|
|
84
84
|
* ARC sealing options
|
|
85
85
|
*/
|
|
86
86
|
seal?: ARCSealOptions;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Follow the RFCs exactly instead of the lenient defaults (default: false). Passed on
|
|
90
|
+
* to every check (DKIM, SPF, ARC, DMARC, BIMI and ARC sealing), BIMI only uses it for the
|
|
91
|
+
* format of its Authentication-Results entry. In strict mode:
|
|
92
|
+
*
|
|
93
|
+
* - rsa-sha1 DKIM signatures are reported as `dkim=policy` (`policy.dkim-rules=weak-algorithm`)
|
|
94
|
+
* - DKIM-Signature and key record syntax is validated (RFC 6376 sections 3.2, 3.5, 3.6.1, 6.1.1)
|
|
95
|
+
* - a body hash mismatch is reported as `dkim=fail`, and a signature that can not be
|
|
96
|
+
* processed as `dkim=neutral` instead of being left out
|
|
97
|
+
* - DKIM results report `header.d` and the AUID as `header.i`
|
|
98
|
+
* - email identities in Authentication-Results use the `local-part@domain` form of RFC 8601 section 2.2
|
|
99
|
+
* - Authentication-Results is placed above Received-SPF (RFC 8601 section 5)
|
|
100
|
+
*
|
|
101
|
+
* What the lenient default accepts and strict mode would reject is listed in the
|
|
102
|
+
* `warnings` array of the affected result. It is never written into the headers
|
|
103
|
+
*/
|
|
104
|
+
strict?: boolean;
|
|
87
105
|
}
|
|
88
106
|
|
|
89
107
|
/**
|
|
@@ -111,20 +129,47 @@ export interface ARCSealOptions {
|
|
|
111
129
|
canonicalization?: string;
|
|
112
130
|
|
|
113
131
|
/**
|
|
114
|
-
* Signing algorithm
|
|
115
|
-
*
|
|
132
|
+
* Signing algorithm, 'rsa-sha256' or 'ed25519-sha256'. Follows the private key type when
|
|
133
|
+
* not set. Used for both the ARC-Message-Signature and the ARC-Seal
|
|
116
134
|
*/
|
|
117
135
|
algorithm?: string;
|
|
118
136
|
|
|
119
137
|
/**
|
|
120
|
-
*
|
|
138
|
+
* Chain validation status for the ARC-Seal cv= tag: 'none', 'pass' or 'fail' (compared
|
|
139
|
+
* case-insensitively). Defaults to 'none' for the first set and is required for any later
|
|
140
|
+
* set. `authenticate()` sets it from the ARC validation result
|
|
141
|
+
*/
|
|
142
|
+
cv?: 'none' | 'pass' | 'fail' | string;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* ARC instance (i= tag), 1 to 50. Defaults to one more than the highest instance on the
|
|
146
|
+
* message. An instance that already exists is refused, and one that leaves a gap is
|
|
147
|
+
* refused in strict mode (the default mode seals it and adds an `arc-instance-gap` warning)
|
|
148
|
+
*/
|
|
149
|
+
i?: number;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Authentication-Results payload for the ARC-Authentication-Results header (the part after
|
|
153
|
+
* "i=N;"). `authenticate()` sets it from its own results
|
|
154
|
+
*/
|
|
155
|
+
authResults?: string;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Header fields to include in the ARC-Message-Signature, as an array or a colon separated
|
|
159
|
+
* string. Defaults to DKIM-Signature and the default DKIM header list. ARC header fields and
|
|
160
|
+
* Authentication-Results are never included (RFC 8617 section 4.1.2)
|
|
121
161
|
*/
|
|
122
|
-
headerList?: string[];
|
|
162
|
+
headerList?: string[] | string;
|
|
123
163
|
|
|
124
164
|
/**
|
|
125
165
|
* Signing timestamp
|
|
126
166
|
*/
|
|
127
167
|
signTime?: Date | string | number;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Follow the RFCs exactly instead of the lenient defaults (default: false)
|
|
171
|
+
*/
|
|
172
|
+
strict?: boolean;
|
|
128
173
|
}
|
|
129
174
|
|
|
130
175
|
/**
|
|
@@ -132,7 +177,8 @@ export interface ARCSealOptions {
|
|
|
132
177
|
*/
|
|
133
178
|
export interface AuthPolicy {
|
|
134
179
|
/**
|
|
135
|
-
* DKIM policy rules
|
|
180
|
+
* DKIM policy rules: 'weak-key' when the key is shorter than minBitLength, 'weak-algorithm'
|
|
181
|
+
* for an rsa-sha1 signature in strict mode
|
|
136
182
|
*/
|
|
137
183
|
'dkim-rules'?: string;
|
|
138
184
|
|
|
@@ -146,7 +192,10 @@ export interface AuthPolicy {
|
|
|
146
192
|
* Status result from authentication checks
|
|
147
193
|
*/
|
|
148
194
|
export interface AuthStatus {
|
|
149
|
-
|
|
195
|
+
/**
|
|
196
|
+
* The result keyword. `temperr` is deprecated, BIMI reports `temperror` now
|
|
197
|
+
*/
|
|
198
|
+
result: 'pass' | 'fail' | 'neutral' | 'none' | 'temperror' | 'temperr' | 'permerror' | 'policy' | 'softfail' | 'skipped' | 'declined';
|
|
150
199
|
comment?: string;
|
|
151
200
|
header?: Record<string, any>;
|
|
152
201
|
smtp?: {
|
|
@@ -156,31 +205,102 @@ export interface AuthStatus {
|
|
|
156
205
|
policy?: AuthPolicy;
|
|
157
206
|
}
|
|
158
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Lax acceptance markers of a DKIM result: what the default mode accepted and strict mode
|
|
210
|
+
* would reject
|
|
211
|
+
*
|
|
212
|
+
* - `rsa-sha1`: the signature uses rsa-sha1 (RFC 8301 section 3.1)
|
|
213
|
+
* - `tag-syntax`: the signature is not valid tag-list syntax (duplicate tags, upper case tag
|
|
214
|
+
* names, a malformed t=, x= or l= value, or a d= or s= that is not a domain name)
|
|
215
|
+
* - `missing-v`, `missing-h` (and the other `missing-*`): a required tag is missing
|
|
216
|
+
* - `invalid-v`: v= is not "1"
|
|
217
|
+
* - `identity-domain`: the i= domain is not d= or its subdomain, or it is a subdomain while
|
|
218
|
+
* the key has t=s
|
|
219
|
+
* - `invalid-expiration`: x= is equal to t=
|
|
220
|
+
* - `query-method`: q= does not list dns/txt
|
|
221
|
+
* - `key-syntax`: the key record is not valid tag-list syntax
|
|
222
|
+
* - `key-v-syntax`: the key record v= is not the first tag, or not exactly "DKIM1"
|
|
223
|
+
* - `key-type-inferred`: an ed25519 key was found from its length, without k=ed25519
|
|
224
|
+
*/
|
|
225
|
+
export type DKIMWarning =
|
|
226
|
+
| 'rsa-sha1'
|
|
227
|
+
| 'tag-syntax'
|
|
228
|
+
| 'missing-v'
|
|
229
|
+
| 'missing-a'
|
|
230
|
+
| 'missing-b'
|
|
231
|
+
| 'missing-bh'
|
|
232
|
+
| 'missing-d'
|
|
233
|
+
| 'missing-h'
|
|
234
|
+
| 'missing-s'
|
|
235
|
+
| 'invalid-v'
|
|
236
|
+
| 'identity-domain'
|
|
237
|
+
| 'invalid-expiration'
|
|
238
|
+
| 'query-method'
|
|
239
|
+
| 'key-syntax'
|
|
240
|
+
| 'key-v-syntax'
|
|
241
|
+
| 'key-type-inferred';
|
|
242
|
+
|
|
159
243
|
/**
|
|
160
244
|
* DKIM verification result for a single signature
|
|
161
245
|
*/
|
|
162
246
|
export interface DKIMResult {
|
|
163
247
|
/**
|
|
164
|
-
* Signature identifier
|
|
248
|
+
* Signature identifier, the sha256 hash of the signature value
|
|
165
249
|
*/
|
|
166
250
|
id?: string;
|
|
167
251
|
|
|
168
252
|
/**
|
|
169
|
-
* Signing domain
|
|
253
|
+
* Signing domain (d= tag)
|
|
170
254
|
*/
|
|
171
|
-
signingDomain
|
|
255
|
+
signingDomain?: string;
|
|
172
256
|
|
|
173
257
|
/**
|
|
174
|
-
* Key selector
|
|
258
|
+
* Key selector (s= tag)
|
|
175
259
|
*/
|
|
176
260
|
selector?: string;
|
|
177
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Signature value (b= tag)
|
|
264
|
+
*/
|
|
265
|
+
signature?: string;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Signature algorithm (a= tag), eg. 'rsa-sha256'
|
|
269
|
+
*/
|
|
270
|
+
algo?: string;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Canonicalization (c= tag), eg. 'relaxed/relaxed'
|
|
274
|
+
*/
|
|
275
|
+
format?: string;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Body hash calculated for the message
|
|
279
|
+
*/
|
|
280
|
+
bodyHash?: string;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Body hash from the bh= tag
|
|
284
|
+
*/
|
|
285
|
+
bodyHashExpecting?: string;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Signed header fields and the canonicalized header data (base64)
|
|
289
|
+
*/
|
|
290
|
+
signingHeaders?: {
|
|
291
|
+
keys: string;
|
|
292
|
+
headers: string[];
|
|
293
|
+
canonicalizedHeader: string;
|
|
294
|
+
};
|
|
295
|
+
|
|
178
296
|
/**
|
|
179
297
|
* Verification status
|
|
180
298
|
*/
|
|
181
299
|
status: AuthStatus & {
|
|
182
300
|
/**
|
|
183
|
-
* Signing domain if it aligns with the From domain, false otherwise
|
|
301
|
+
* Signing domain if it aligns with the From domain, false otherwise. For a passing
|
|
302
|
+
* signature this follows the DMARC verdict once DMARC found a record, before that it
|
|
303
|
+
* only guesses relaxed alignment from the Public Suffix List.
|
|
184
304
|
*/
|
|
185
305
|
aligned?: string | false;
|
|
186
306
|
|
|
@@ -188,6 +308,18 @@ export interface DKIMResult {
|
|
|
188
308
|
* Number of body bytes left unsigned by an l= tag
|
|
189
309
|
*/
|
|
190
310
|
underSized?: number;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* True when the key record has the t=y flag, the signing domain is testing DKIM
|
|
314
|
+
* (RFC 6376 section 3.6.1)
|
|
315
|
+
*/
|
|
316
|
+
testing?: boolean;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* What the default mode accepted and strict mode would reject. Only present when
|
|
320
|
+
* there is something to report, never written into the Authentication-Results text
|
|
321
|
+
*/
|
|
322
|
+
warnings?: DKIMWarning[];
|
|
191
323
|
};
|
|
192
324
|
|
|
193
325
|
/**
|
|
@@ -196,22 +328,82 @@ export interface DKIMResult {
|
|
|
196
328
|
info: string;
|
|
197
329
|
|
|
198
330
|
/**
|
|
199
|
-
*
|
|
331
|
+
* Signing time from the t= tag, as an ISO string
|
|
332
|
+
*/
|
|
333
|
+
signTime?: string | null;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Expiration time from the x= tag, as an ISO string
|
|
337
|
+
*/
|
|
338
|
+
expiresAfter?: string | null;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* False when t= is in the future or x= has passed
|
|
342
|
+
*/
|
|
343
|
+
signatureTimeValid?: boolean;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Size of the message body in bytes
|
|
347
|
+
*/
|
|
348
|
+
sourceBodyLength?: number;
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Number of canonicalized body bytes covered by the signature
|
|
352
|
+
*/
|
|
353
|
+
canonBodyLength?: number;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Number of canonicalized body bytes in total
|
|
357
|
+
*/
|
|
358
|
+
canonBodyLengthTotal?: number;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* True when the signature has an l= tag
|
|
362
|
+
*/
|
|
363
|
+
canonBodyLengthLimited?: boolean;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* The l= value
|
|
367
|
+
*/
|
|
368
|
+
canonBodyLengthLimit?: number;
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Where the MIME structure starts in the body, for multipart messages
|
|
372
|
+
*/
|
|
373
|
+
mimeStructureStart?: number;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Public key in PEM format
|
|
377
|
+
*/
|
|
378
|
+
publicKey?: string;
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* RSA key size in bits
|
|
382
|
+
*/
|
|
383
|
+
modulusLength?: number;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Key record as found in DNS
|
|
387
|
+
*/
|
|
388
|
+
rr?: string;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* @deprecated Never set, use `algo`
|
|
200
392
|
*/
|
|
201
393
|
algorithm?: string;
|
|
202
394
|
|
|
203
395
|
/**
|
|
204
|
-
*
|
|
396
|
+
* @deprecated Never set, use `format`
|
|
205
397
|
*/
|
|
206
398
|
canonicalization?: string;
|
|
207
399
|
|
|
208
400
|
/**
|
|
209
|
-
*
|
|
401
|
+
* @deprecated Never set, use `signTime`
|
|
210
402
|
*/
|
|
211
403
|
signingTime?: Date;
|
|
212
404
|
|
|
213
405
|
/**
|
|
214
|
-
*
|
|
406
|
+
* @deprecated Never set, use `expiresAfter`
|
|
215
407
|
*/
|
|
216
408
|
expiration?: Date;
|
|
217
409
|
}
|
|
@@ -221,10 +413,16 @@ export interface DKIMResult {
|
|
|
221
413
|
*/
|
|
222
414
|
export interface DKIMVerifyResult {
|
|
223
415
|
/**
|
|
224
|
-
*
|
|
416
|
+
* Addresses from the From header, as full addr-specs (the domain is what follows the last
|
|
417
|
+
* "@"). The mailboxes of a group (RFC 6854) are included
|
|
225
418
|
*/
|
|
226
419
|
headerFrom: string[];
|
|
227
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Number of From header fields in the message. RFC 5322 allows only one
|
|
423
|
+
*/
|
|
424
|
+
fromFields: number;
|
|
425
|
+
|
|
228
426
|
/**
|
|
229
427
|
* Domain from Return-Path header
|
|
230
428
|
*/
|
|
@@ -240,8 +438,8 @@ export interface DKIMVerifyResult {
|
|
|
240
438
|
* Access with result.headers or Object.getOwnPropertyDescriptor()
|
|
241
439
|
*/
|
|
242
440
|
readonly headers?: {
|
|
243
|
-
parsed: Array<{ key: string;
|
|
244
|
-
|
|
441
|
+
parsed: Array<{ key: string; casedKey: string; line: Buffer }>;
|
|
442
|
+
original: Buffer;
|
|
245
443
|
};
|
|
246
444
|
|
|
247
445
|
/**
|
|
@@ -310,6 +508,18 @@ export interface SPFResult {
|
|
|
310
508
|
void: number;
|
|
311
509
|
subqueries: Record<string, number>;
|
|
312
510
|
};
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Explanation string published by the domain owner with the "exp" modifier
|
|
514
|
+
* (RFC 7208 section 6.2). Only set for a "fail" result. This is third party text
|
|
515
|
+
*/
|
|
516
|
+
explanation?: string;
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Set in the default mode when something was accepted that strict mode would have
|
|
520
|
+
* rejected: "syntax-error", "void-lookup-limit" or "non-ascii-local-part"
|
|
521
|
+
*/
|
|
522
|
+
warnings?: string[];
|
|
313
523
|
}
|
|
314
524
|
|
|
315
525
|
/**
|
|
@@ -352,7 +562,18 @@ export interface ARCResult {
|
|
|
352
562
|
* Why sealing failed, when a seal was requested but could not be created.
|
|
353
563
|
* The message is left unsealed in that case
|
|
354
564
|
*/
|
|
355
|
-
sealErrors?:
|
|
565
|
+
sealErrors?: ARCSealError[];
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* What the default mode accepted in the chain that strict mode would reject, for example
|
|
569
|
+
* 'arc-tag-syntax' (a tag-list syntax error or a tag name that is not lower case),
|
|
570
|
+
* 'arc-instance-syntax' (an i= value that is not 1*2DIGIT, an ARC-Authentication-Results
|
|
571
|
+
* whose i= is not first, or an ARC header field that was ignored for its i= value),
|
|
572
|
+
* 'ams-c-default' (an ARC-Message-Signature without c= that only verified with
|
|
573
|
+
* relaxed/relaxed), and the key record warnings of the DKIM result. Not set for a failing
|
|
574
|
+
* chain, and never written into the headers
|
|
575
|
+
*/
|
|
576
|
+
warnings?: string[];
|
|
356
577
|
|
|
357
578
|
/**
|
|
358
579
|
* ARC chain entries (non-enumerable property)
|
|
@@ -366,15 +587,21 @@ export interface ARCResult {
|
|
|
366
587
|
*/
|
|
367
588
|
export interface DMARCResult {
|
|
368
589
|
/**
|
|
369
|
-
*
|
|
590
|
+
* Organizational Domain of the author domain, found with the DNS Tree Walk (RFC 9989 4.10)
|
|
370
591
|
*/
|
|
371
592
|
domain: string;
|
|
372
593
|
|
|
373
594
|
/**
|
|
374
|
-
* DMARC policy ('none', 'quarantine', 'reject')
|
|
595
|
+
* Effective DMARC policy ('none', 'quarantine', 'reject'): p for the author domain's own
|
|
596
|
+
* record, np or sp for an inherited one, one level lower with t=y
|
|
375
597
|
*/
|
|
376
598
|
policy: string;
|
|
377
599
|
|
|
600
|
+
/**
|
|
601
|
+
* Domain whose DMARC record the policy was taken from
|
|
602
|
+
*/
|
|
603
|
+
policyDomain?: string;
|
|
604
|
+
|
|
378
605
|
/**
|
|
379
606
|
* Policy for organizational domain
|
|
380
607
|
*/
|
|
@@ -386,9 +613,14 @@ export interface DMARCResult {
|
|
|
386
613
|
sp: string;
|
|
387
614
|
|
|
388
615
|
/**
|
|
389
|
-
*
|
|
616
|
+
* Policy for non-existent subdomains, if published
|
|
390
617
|
*/
|
|
391
|
-
|
|
618
|
+
np?: string;
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* True when the record has t=y, in which case policy is one level below the published one
|
|
622
|
+
*/
|
|
623
|
+
testMode: boolean;
|
|
392
624
|
|
|
393
625
|
/**
|
|
394
626
|
* DMARC DNS record
|
|
@@ -398,7 +630,26 @@ export interface DMARCResult {
|
|
|
398
630
|
/**
|
|
399
631
|
* Verification status
|
|
400
632
|
*/
|
|
401
|
-
status: AuthStatus
|
|
633
|
+
status: AuthStatus & {
|
|
634
|
+
header?: {
|
|
635
|
+
/**
|
|
636
|
+
* Author Domain
|
|
637
|
+
*/
|
|
638
|
+
from?: string;
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Domain of the DMARC record, omitted in strict mode (not a registered property for dmarc)
|
|
642
|
+
*/
|
|
643
|
+
d?: string;
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
policy?: {
|
|
647
|
+
/**
|
|
648
|
+
* The policy that was applied (RFC 8601 section 2.7.2 policy.dmarc)
|
|
649
|
+
*/
|
|
650
|
+
dmarc?: string;
|
|
651
|
+
};
|
|
652
|
+
};
|
|
402
653
|
|
|
403
654
|
/**
|
|
404
655
|
* Alignment results
|
|
@@ -436,6 +687,12 @@ export interface DMARCResult {
|
|
|
436
687
|
* Error message if verification failed
|
|
437
688
|
*/
|
|
438
689
|
error?: string;
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* What the default mode accepted and strict mode would reject, only present when there
|
|
693
|
+
* is something to report
|
|
694
|
+
*/
|
|
695
|
+
warnings?: string[];
|
|
439
696
|
}
|
|
440
697
|
|
|
441
698
|
/**
|
|
@@ -443,9 +700,12 @@ export interface DMARCResult {
|
|
|
443
700
|
*/
|
|
444
701
|
export interface BIMIResult {
|
|
445
702
|
/**
|
|
446
|
-
* Verification status
|
|
703
|
+
* Verification status. `declined` means the domain published a declination record,
|
|
704
|
+
* `temperror` a DNS failure. `temperr` is deprecated and no longer reported
|
|
447
705
|
*/
|
|
448
|
-
status: AuthStatus
|
|
706
|
+
status: AuthStatus & {
|
|
707
|
+
result: 'pass' | 'none' | 'fail' | 'skipped' | 'declined' | 'temperror' | 'temperr';
|
|
708
|
+
};
|
|
449
709
|
|
|
450
710
|
/**
|
|
451
711
|
* BIMI DNS record
|
|
@@ -482,7 +742,8 @@ export interface ReceivedChainEntry {
|
|
|
482
742
|
value: string;
|
|
483
743
|
|
|
484
744
|
/**
|
|
485
|
-
*
|
|
745
|
+
* The comments after the value, joined with spaces. Usually the TCP-info of
|
|
746
|
+
* RFC 5321 section 4.4, whose last address literal is the connecting IP address
|
|
486
747
|
*/
|
|
487
748
|
comment?: string;
|
|
488
749
|
};
|
|
@@ -579,6 +840,13 @@ export interface AuthenticateResult {
|
|
|
579
840
|
* Combined authentication headers to prepend to message
|
|
580
841
|
*/
|
|
581
842
|
headers: string;
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Why DMARC was not evaluated when `dmarc` is false because the From header has no
|
|
846
|
+
* domain or more than one (RFC 9989 section 5.3.1). Such a message can be treated as
|
|
847
|
+
* suspicious (RFC 9989 section 11.5)
|
|
848
|
+
*/
|
|
849
|
+
dmarcSkipReason?: 'no-author-domain' | 'multiple-author-domains' | 'invalid-author-domain' | 'multiple-from-fields';
|
|
582
850
|
}
|
|
583
851
|
|
|
584
852
|
/**
|
|
@@ -620,16 +888,17 @@ export interface DKIMSignOptions {
|
|
|
620
888
|
canonicalization?: string;
|
|
621
889
|
|
|
622
890
|
/**
|
|
623
|
-
* Signing algorithm
|
|
624
|
-
* Supported: 'rsa-sha256', 'rsa-sha1'
|
|
891
|
+
* Signing algorithm, defaults to 'rsa-sha256' or 'ed25519-sha256' depending on the key type.
|
|
892
|
+
* Supported: 'rsa-sha256', 'ed25519-sha256' and 'rsa-sha1'. rsa-sha1 is historic
|
|
893
|
+
* (RFC 8301): it adds the 'rsa-sha1' warning, and strict mode refuses it
|
|
625
894
|
*/
|
|
626
895
|
algorithm?: string;
|
|
627
896
|
|
|
628
897
|
/**
|
|
629
|
-
*
|
|
630
|
-
*
|
|
898
|
+
* Header fields to sign, as an array or a colon separated string. Default includes
|
|
899
|
+
* From, Subject, Date, To, etc. Set per signature in `signatureData` or for all of them
|
|
631
900
|
*/
|
|
632
|
-
headerList?: string[];
|
|
901
|
+
headerList?: string[] | string;
|
|
633
902
|
|
|
634
903
|
/**
|
|
635
904
|
* Signing timestamp (defaults to current time)
|
|
@@ -647,7 +916,7 @@ export interface DKIMSignOptions {
|
|
|
647
916
|
maxBodyLength?: number;
|
|
648
917
|
|
|
649
918
|
/**
|
|
650
|
-
* Identity (i= tag)
|
|
919
|
+
* Identity (i= tag), an address whose domain is the signing domain or its subdomain
|
|
651
920
|
*/
|
|
652
921
|
identity?: string;
|
|
653
922
|
|
|
@@ -655,6 +924,15 @@ export interface DKIMSignOptions {
|
|
|
655
924
|
* Multiple signature configurations
|
|
656
925
|
*/
|
|
657
926
|
signatureData?: DKIMSignOptions[];
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Follow the RFCs exactly (default: false). Strict mode refuses rsa-sha1, RSA keys
|
|
930
|
+
* shorter than 1024 bits, an expiration that is not after the signing time, a signing
|
|
931
|
+
* time that does not fit into t=, and a d=, s= or i= that is not valid RFC 6376 syntax.
|
|
932
|
+
* The default mode signs these and lists them in `warnings`. A d=, s= or identity that
|
|
933
|
+
* would break out of its tag is refused in both modes
|
|
934
|
+
*/
|
|
935
|
+
strict?: boolean;
|
|
658
936
|
}
|
|
659
937
|
|
|
660
938
|
/**
|
|
@@ -827,12 +1105,43 @@ export interface ARCSigningData {
|
|
|
827
1105
|
privateKey?: string | Buffer;
|
|
828
1106
|
}
|
|
829
1107
|
|
|
1108
|
+
/**
|
|
1109
|
+
* A signature that could not be created
|
|
1110
|
+
*/
|
|
1111
|
+
export interface DKIMSignError {
|
|
1112
|
+
/**
|
|
1113
|
+
* Why the signature was not created. `err.code` is eg. 'ENOFROM', 'EINVALIDALGO',
|
|
1114
|
+
* 'EINVALIDTYPE', 'ESHORTKEY', 'EINVALIDDOMAIN', 'EINVALIDSELECTOR', 'EINVALIDIDENTITY',
|
|
1115
|
+
* 'EINVALIDTIME', 'EINVALIDCANON' or 'EINVALIDINSTANCE'
|
|
1116
|
+
*/
|
|
1117
|
+
err: Error & { code?: string };
|
|
1118
|
+
|
|
1119
|
+
type?: 'DKIM' | 'ARC';
|
|
1120
|
+
selector?: string;
|
|
1121
|
+
signingDomain?: string;
|
|
1122
|
+
algorithm?: string;
|
|
1123
|
+
canonicalization?: string;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* What the default mode signed although strict mode would have refused it
|
|
1128
|
+
*
|
|
1129
|
+
* - `rsa-sha1`: signed with rsa-sha1
|
|
1130
|
+
* - `weak-key`: signed with an RSA key shorter than 1024 bits
|
|
1131
|
+
* - `invalid-expiration`: the expiration is not after the signing time, or does not fit into x=
|
|
1132
|
+
* - `invalid-signtime`: the signing time does not fit into t=, which was left out
|
|
1133
|
+
* - `d-syntax`, `s-syntax`: d= or s= is not valid RFC 6376 syntax
|
|
1134
|
+
* - `identity-domain`: the identity domain is not the signing domain or its subdomain
|
|
1135
|
+
*/
|
|
1136
|
+
export type DKIMSignWarning = 'rsa-sha1' | 'weak-key' | 'invalid-expiration' | 'invalid-signtime' | 'd-syntax' | 's-syntax' | 'identity-domain';
|
|
1137
|
+
|
|
830
1138
|
/**
|
|
831
1139
|
* DKIM signing result
|
|
832
1140
|
*/
|
|
833
1141
|
export interface DKIMSignResult {
|
|
834
1142
|
/**
|
|
835
|
-
* DKIM-Signature header(s) to prepend to message
|
|
1143
|
+
* DKIM-Signature header(s) to prepend to message, each ending with a CRLF. An empty
|
|
1144
|
+
* string when no signature was created
|
|
836
1145
|
*/
|
|
837
1146
|
signatures: string;
|
|
838
1147
|
|
|
@@ -842,9 +1151,14 @@ export interface DKIMSignResult {
|
|
|
842
1151
|
arc?: ARCSigningData;
|
|
843
1152
|
|
|
844
1153
|
/**
|
|
845
|
-
*
|
|
1154
|
+
* The signatures that could not be created, and why
|
|
1155
|
+
*/
|
|
1156
|
+
errors: DKIMSignError[];
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* What was signed although strict mode would have refused it
|
|
846
1160
|
*/
|
|
847
|
-
|
|
1161
|
+
warnings: DKIMSignWarning[];
|
|
848
1162
|
}
|
|
849
1163
|
|
|
850
1164
|
/**
|
|
@@ -869,9 +1183,14 @@ export class DkimSignStream extends Transform {
|
|
|
869
1183
|
constructor(options: DKIMSignOptions);
|
|
870
1184
|
|
|
871
1185
|
/**
|
|
872
|
-
*
|
|
1186
|
+
* The signatures that could not be created, set when the stream ends
|
|
873
1187
|
*/
|
|
874
|
-
errors:
|
|
1188
|
+
errors: DKIMSignError[] | null;
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* What was signed although strict mode would have refused it, set when the stream ends
|
|
1192
|
+
*/
|
|
1193
|
+
warnings: DKIMSignWarning[] | null;
|
|
875
1194
|
}
|
|
876
1195
|
|
|
877
1196
|
// ============================================================================
|
|
@@ -906,6 +1225,12 @@ export interface DKIMVerifyOptions {
|
|
|
906
1225
|
* ARC sealing options (if sealing should be prepared)
|
|
907
1226
|
*/
|
|
908
1227
|
seal?: ARCSealOptions;
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Follow RFC 6376, RFC 8301 and RFC 8463 exactly instead of the lenient default
|
|
1231
|
+
* (default: false). See `DKIMWarning` for what the default mode accepts
|
|
1232
|
+
*/
|
|
1233
|
+
strict?: boolean;
|
|
909
1234
|
}
|
|
910
1235
|
|
|
911
1236
|
/**
|
|
@@ -959,6 +1284,17 @@ export interface SPFOptions {
|
|
|
959
1284
|
* Custom DNS resolver function
|
|
960
1285
|
*/
|
|
961
1286
|
resolver?: DNSResolver;
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Follow RFC 7208 exactly instead of the lenient default (default: false)
|
|
1290
|
+
*/
|
|
1291
|
+
strict?: boolean;
|
|
1292
|
+
|
|
1293
|
+
/**
|
|
1294
|
+
* Maximum time in milliseconds for the whole evaluation, after which the result is
|
|
1295
|
+
* temperror (RFC 7208 section 4.6.4). No limit by default
|
|
1296
|
+
*/
|
|
1297
|
+
maxElapsedTime?: number;
|
|
962
1298
|
}
|
|
963
1299
|
|
|
964
1300
|
/**
|
|
@@ -982,6 +1318,12 @@ export interface DMARCOptions {
|
|
|
982
1318
|
*/
|
|
983
1319
|
headerFrom: string | string[];
|
|
984
1320
|
|
|
1321
|
+
/**
|
|
1322
|
+
* Number of From header fields the addresses came from. With more than one, DMARC
|
|
1323
|
+
* validation is not possible (reason "multiple-from-fields")
|
|
1324
|
+
*/
|
|
1325
|
+
fromFields?: number;
|
|
1326
|
+
|
|
985
1327
|
/**
|
|
986
1328
|
* Domains that passed SPF
|
|
987
1329
|
*/
|
|
@@ -1006,6 +1348,11 @@ export interface DMARCOptions {
|
|
|
1006
1348
|
* Custom DNS resolver function
|
|
1007
1349
|
*/
|
|
1008
1350
|
resolver?: DNSResolver;
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* Follow RFC 9989 exactly instead of the lenient default (default: false)
|
|
1354
|
+
*/
|
|
1355
|
+
strict?: boolean;
|
|
1009
1356
|
}
|
|
1010
1357
|
|
|
1011
1358
|
/**
|
|
@@ -1040,6 +1387,24 @@ export interface ARCData {
|
|
|
1040
1387
|
error?: Error;
|
|
1041
1388
|
}
|
|
1042
1389
|
|
|
1390
|
+
/**
|
|
1391
|
+
* An error from ARC sealing
|
|
1392
|
+
*/
|
|
1393
|
+
export interface ARCSealError {
|
|
1394
|
+
/**
|
|
1395
|
+
* The error. Codes include EINVALIDCV (a missing or invalid cv), EINVALIDINSTANCE (an
|
|
1396
|
+
* instance out of 1-50, a chain that already has 50 sets, one that already exists, or in
|
|
1397
|
+
* strict mode one that leaves a gap),
|
|
1398
|
+
* EARCCHAINFAILED (the newest ARC-Seal already has cv=fail, RFC 8617 section 5.1 step 2),
|
|
1399
|
+
* EINVALIDALGO and EINVALIDTYPE (an algorithm that is not supported or does not match the key)
|
|
1400
|
+
*/
|
|
1401
|
+
err: Error & { code?: string };
|
|
1402
|
+
type?: string;
|
|
1403
|
+
selector?: string;
|
|
1404
|
+
signingDomain?: string;
|
|
1405
|
+
[key: string]: any;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1043
1408
|
/**
|
|
1044
1409
|
* ARC verification options
|
|
1045
1410
|
*/
|
|
@@ -1053,6 +1418,47 @@ export interface ARCOptions {
|
|
|
1053
1418
|
* Minimal allowed public key length in bits (default: 1024)
|
|
1054
1419
|
*/
|
|
1055
1420
|
minBitLength?: number;
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* Follow RFC 8617 exactly instead of the lenient default (default: false). In strict mode
|
|
1424
|
+
* ARC-Seal and ARC-Message-Signature tag lists must be valid RFC 6376 section 3.2 syntax
|
|
1425
|
+
* with lower case tag names and every required tag, instance values must be 1*2DIGIT from
|
|
1426
|
+
* 1 to 50 (and come first in ARC-Authentication-Results), ARC header fields with an
|
|
1427
|
+
* invalid instance fail the chain instead of being ignored, an ARC-Message-Signature
|
|
1428
|
+
* without c= is only verified as simple/simple, and the arc= result also reports
|
|
1429
|
+
* arc=none and smtp.remote-ip
|
|
1430
|
+
*/
|
|
1431
|
+
strict?: boolean;
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* Client IP address, reported as smtp.remote-ip in strict mode (RFC 8617 section 6)
|
|
1435
|
+
*/
|
|
1436
|
+
ip?: string;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* Seal creation data for createSeal()
|
|
1441
|
+
*/
|
|
1442
|
+
export interface ARCCreateSealData {
|
|
1443
|
+
/**
|
|
1444
|
+
* Parsed message headers, when `input` is false
|
|
1445
|
+
*/
|
|
1446
|
+
headers?: any;
|
|
1447
|
+
|
|
1448
|
+
/**
|
|
1449
|
+
* ARC chain data of the message, when `input` is false
|
|
1450
|
+
*/
|
|
1451
|
+
arc?: ARCData | ARCSigningData;
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* Sealing options, with `bodyHash` (the relaxed/relaxed sha256 body hash) when `input` is false
|
|
1455
|
+
*/
|
|
1456
|
+
seal: ARCSealOptions & { bodyHash?: string };
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Follow the RFCs exactly (default: false, or `seal.strict`)
|
|
1460
|
+
*/
|
|
1461
|
+
strict?: boolean;
|
|
1056
1462
|
}
|
|
1057
1463
|
|
|
1058
1464
|
/**
|
|
@@ -1074,30 +1480,34 @@ export function arc(data: ARCData, opts?: ARCOptions): Promise<ARCResult>;
|
|
|
1074
1480
|
export function sealMessage(input: MessageInput, seal: ARCSealOptions): Promise<Buffer>;
|
|
1075
1481
|
|
|
1076
1482
|
/**
|
|
1077
|
-
* Gets ARC chain from parsed headers
|
|
1483
|
+
* Gets ARC chain from parsed headers and validates its structure. Throws for a chain that is not valid
|
|
1078
1484
|
*
|
|
1079
1485
|
* @param headers - Parsed message headers
|
|
1486
|
+
* @param opts - `strict` applies the RFC 8617 syntax rules, lenient acceptances are added to `warnings`
|
|
1080
1487
|
* @returns ARC chain or false if no chain found
|
|
1081
1488
|
*/
|
|
1082
|
-
export function getARChain(headers: any): ARCChainEntry[] | false;
|
|
1489
|
+
export function getARChain(headers: any, opts?: { strict?: boolean; warnings?: string[] }): ARCChainEntry[] | false;
|
|
1083
1490
|
|
|
1084
1491
|
/**
|
|
1085
|
-
* Verifies ARC seal chain
|
|
1492
|
+
* Verifies ARC seal chain. Throws if any seal of the chain is not valid, including a chain whose
|
|
1493
|
+
* newest seal has cv=fail
|
|
1086
1494
|
*
|
|
1087
1495
|
* @param data - ARC chain data
|
|
1088
1496
|
* @param opts - ARC verification options
|
|
1089
|
-
* @returns true if chain is valid
|
|
1497
|
+
* @returns true if chain is valid, false if there is no chain
|
|
1090
1498
|
*/
|
|
1091
|
-
export function verifyASChain(data: ARCData, opts: ARCOptions): Promise<boolean>;
|
|
1499
|
+
export function verifyASChain(data: ARCData, opts: ARCOptions & { warnings?: string[] }): Promise<boolean>;
|
|
1092
1500
|
|
|
1093
1501
|
/**
|
|
1094
1502
|
* Creates ARC seal headers
|
|
1095
1503
|
*
|
|
1096
1504
|
* @param input - RFC822 formatted message or false for pre-calculated data
|
|
1097
1505
|
* @param data - Seal creation data
|
|
1098
|
-
* @returns Seal headers
|
|
1506
|
+
* @returns Seal headers (empty when no set was created), the errors that say why, and what the
|
|
1507
|
+
* default mode sealed that strict mode would have refused ('arc-instance-gap',
|
|
1508
|
+
* 'arc-cv-instance', and the DKIM signing warnings)
|
|
1099
1509
|
*/
|
|
1100
|
-
export function createSeal(input: MessageInput | false, data:
|
|
1510
|
+
export function createSeal(input: MessageInput | false, data: ARCCreateSealData): Promise<{ headers: string[]; errors: ARCSealError[]; warnings: string[] }>;
|
|
1101
1511
|
|
|
1102
1512
|
// ============================================================================
|
|
1103
1513
|
// BIMI
|
|
@@ -1126,6 +1536,12 @@ export interface BIMIOptions {
|
|
|
1126
1536
|
* Custom DNS resolver function
|
|
1127
1537
|
*/
|
|
1128
1538
|
resolver?: DNSResolver;
|
|
1539
|
+
|
|
1540
|
+
/**
|
|
1541
|
+
* Format the Authentication-Results entry in the RFC 8601 section 2.2 form (default: false).
|
|
1542
|
+
* BIMI has no other strict-only rules, the draft checks apply in both modes
|
|
1543
|
+
*/
|
|
1544
|
+
strict?: boolean;
|
|
1129
1545
|
}
|
|
1130
1546
|
|
|
1131
1547
|
/**
|
|
@@ -1336,10 +1752,33 @@ export interface MTASTSPolicyResult {
|
|
|
1336
1752
|
|
|
1337
1753
|
/**
|
|
1338
1754
|
* Status of the fetch operation
|
|
1755
|
+
*
|
|
1756
|
+
* - `found`: a new or changed policy was fetched
|
|
1757
|
+
* - `renewed`: the cached policy has the same id and has not expired, it is returned without a fetch
|
|
1758
|
+
* - `not_found`: the domain has no MTA-STS policy
|
|
1759
|
+
* - `errored`: the policy could not be fetched. With a valid cached policy, that policy is
|
|
1760
|
+
* returned with `error` set
|
|
1339
1761
|
*/
|
|
1340
1762
|
status: 'found' | 'renewed' | 'not_found' | 'errored';
|
|
1763
|
+
|
|
1764
|
+
/**
|
|
1765
|
+
* Lax acceptance markers, only present when the default lax mode accepted something strict mode would reject
|
|
1766
|
+
*/
|
|
1767
|
+
warnings?: MTASTSWarning[];
|
|
1341
1768
|
}
|
|
1342
1769
|
|
|
1770
|
+
/**
|
|
1771
|
+
* Markers for input that the default lax mode accepted but `strict: true` would reject
|
|
1772
|
+
*
|
|
1773
|
+
* - `txt-syntax`: the TXT record does not match the RFC 8461 3.1 syntax
|
|
1774
|
+
* - `http-status`: the policy was served with a 2xx status other than 200
|
|
1775
|
+
* - `content-type`: the policy was not served as `text/plain`
|
|
1776
|
+
* - `policy-syntax`: the policy file does not match the RFC 8461 3.2 syntax
|
|
1777
|
+
* - `cert-identity`: the Policy Host certificate matched only through the CN or a partial-label wildcard
|
|
1778
|
+
* - `expired-cache`: an expired cached policy was returned because the policy could not be fetched
|
|
1779
|
+
*/
|
|
1780
|
+
export type MTASTSWarning = 'txt-syntax' | 'http-status' | 'content-type' | 'policy-syntax' | 'cert-identity' | 'expired-cache';
|
|
1781
|
+
|
|
1343
1782
|
/**
|
|
1344
1783
|
* MTA-STS MX validation result
|
|
1345
1784
|
*/
|
|
@@ -1373,6 +1812,21 @@ export interface MTASTSOptions {
|
|
|
1373
1812
|
* Custom DNS resolver function
|
|
1374
1813
|
*/
|
|
1375
1814
|
resolver?: DNSResolver;
|
|
1815
|
+
|
|
1816
|
+
/**
|
|
1817
|
+
* Apply the RFC 8461 rules exactly (default false)
|
|
1818
|
+
*/
|
|
1819
|
+
strict?: boolean;
|
|
1820
|
+
|
|
1821
|
+
/**
|
|
1822
|
+
* Overall time limit for the HTTPS policy request in milliseconds (default 60000)
|
|
1823
|
+
*/
|
|
1824
|
+
timeout?: number;
|
|
1825
|
+
|
|
1826
|
+
/**
|
|
1827
|
+
* Maximum size of the policy file in bytes (default 65536)
|
|
1828
|
+
*/
|
|
1829
|
+
maxPolicySize?: number;
|
|
1376
1830
|
}
|
|
1377
1831
|
|
|
1378
1832
|
// Note: MTA-STS functions (resolvePolicy, fetchPolicy, parsePolicy, validateMx, getPolicy)
|