mailauth 6.0.0 → 7.1.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.
Files changed (47) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +113 -39
  4. package/bin/mailauth.js +15 -6
  5. package/cli.md +9 -3
  6. package/docs/arc.md +126 -43
  7. package/docs/bimi.md +54 -37
  8. package/docs/dkim.md +79 -40
  9. package/docs/dmarc.md +80 -33
  10. package/docs/mta-sts.md +140 -42
  11. package/docs/spf.md +69 -4
  12. package/index.d.ts +508 -43
  13. package/lib/arc/index.d.ts +10 -8
  14. package/lib/arc/index.js +505 -124
  15. package/lib/bimi/index.js +162 -81
  16. package/lib/commands/report.js +3 -1
  17. package/lib/commands/seal.js +35 -5
  18. package/lib/commands/sign.js +8 -1
  19. package/lib/commands/spf.js +8 -0
  20. package/lib/dkim/body/relaxed.js +6 -5
  21. package/lib/dkim/body/simple.js +1 -3
  22. package/lib/dkim/dkim-signer.js +219 -33
  23. package/lib/dkim/dkim-verifier.js +577 -231
  24. package/lib/dkim/header/relaxed.js +6 -43
  25. package/lib/dkim/header/signature-opts.js +87 -0
  26. package/lib/dkim/header/simple.js +6 -43
  27. package/lib/dkim/message-parser.js +18 -4
  28. package/lib/dkim/sign.d.ts +13 -7
  29. package/lib/dkim/sign.js +18 -2
  30. package/lib/dkim/syntax.js +31 -0
  31. package/lib/dkim/verify.js +1 -0
  32. package/lib/dmarc/author-domain.js +181 -0
  33. package/lib/dmarc/get-dmarc-record.js +154 -47
  34. package/lib/dmarc/index.d.ts +29 -2
  35. package/lib/dmarc/verify.js +120 -44
  36. package/lib/mailauth.js +77 -36
  37. package/lib/mta-sts.d.ts +77 -11
  38. package/lib/mta-sts.js +497 -182
  39. package/lib/parse-dkim-headers.js +376 -191
  40. package/lib/parse-received.js +104 -32
  41. package/lib/spf/index.d.ts +37 -1
  42. package/lib/spf/index.js +260 -28
  43. package/lib/spf/macro.js +50 -13
  44. package/lib/spf/spf-verify.js +440 -204
  45. package/lib/spf/syntax.js +366 -0
  46. package/lib/tools.js +522 -116
  47. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -55,6 +55,13 @@ export interface AuthenticateOptions {
55
55
  */
56
56
  minBitLength?: number;
57
57
 
58
+ /**
59
+ * If true, rsa-sha1 DKIM signatures get `policy` (weak-algorithm) as in strict mode and are
60
+ * not counted for DMARC, while every other check keeps the lenient default. Verification
61
+ * only (default: false)
62
+ */
63
+ rejectRsaSha1?: boolean;
64
+
58
65
  /**
59
66
  * Custom DNS resolver function
60
67
  */
@@ -84,6 +91,24 @@ export interface AuthenticateOptions {
84
91
  * ARC sealing options
85
92
  */
86
93
  seal?: ARCSealOptions;
94
+
95
+ /**
96
+ * Follow the RFCs exactly instead of the lenient defaults (default: false). Passed on
97
+ * to every check (DKIM, SPF, ARC, DMARC, BIMI and ARC sealing), BIMI only uses it for the
98
+ * format of its Authentication-Results entry. In strict mode:
99
+ *
100
+ * - rsa-sha1 DKIM signatures are reported as `dkim=policy` (`policy.dkim-rules=weak-algorithm`)
101
+ * - DKIM-Signature and key record syntax is validated (RFC 6376 sections 3.2, 3.5, 3.6.1, 6.1.1)
102
+ * - a body hash mismatch is reported as `dkim=fail`, and a signature that can not be
103
+ * processed as `dkim=neutral` instead of being left out
104
+ * - DKIM results report `header.d` and the AUID as `header.i`
105
+ * - email identities in Authentication-Results use the `local-part@domain` form of RFC 8601 section 2.2
106
+ * - Authentication-Results is placed above Received-SPF (RFC 8601 section 5)
107
+ *
108
+ * What the lenient default accepts and strict mode would reject is listed in the
109
+ * `warnings` array of the affected result. It is never written into the headers
110
+ */
111
+ strict?: boolean;
87
112
  }
88
113
 
89
114
  /**
@@ -111,20 +136,47 @@ export interface ARCSealOptions {
111
136
  canonicalization?: string;
112
137
 
113
138
  /**
114
- * Signing algorithm (default: 'rsa-sha256')
115
- * Supported: 'rsa-sha256', 'ed25519-sha256'
139
+ * Signing algorithm, 'rsa-sha256' or 'ed25519-sha256'. Follows the private key type when
140
+ * not set. Used for both the ARC-Message-Signature and the ARC-Seal
116
141
  */
117
142
  algorithm?: string;
118
143
 
119
144
  /**
120
- * Headers to include in signature
145
+ * Chain validation status for the ARC-Seal cv= tag: 'none', 'pass' or 'fail' (compared
146
+ * case-insensitively). Defaults to 'none' for the first set and is required for any later
147
+ * set. `authenticate()` sets it from the ARC validation result
121
148
  */
122
- headerList?: string[];
149
+ cv?: 'none' | 'pass' | 'fail' | string;
150
+
151
+ /**
152
+ * ARC instance (i= tag), 1 to 50. Defaults to one more than the highest instance on the
153
+ * message. An instance that already exists is refused, and one that leaves a gap is
154
+ * refused in strict mode (the default mode seals it and adds an `arc-instance-gap` warning)
155
+ */
156
+ i?: number;
157
+
158
+ /**
159
+ * Authentication-Results payload for the ARC-Authentication-Results header (the part after
160
+ * "i=N;"). `authenticate()` sets it from its own results
161
+ */
162
+ authResults?: string;
163
+
164
+ /**
165
+ * Header fields to include in the ARC-Message-Signature, as an array or a colon separated
166
+ * string. Defaults to DKIM-Signature and the default DKIM header list. ARC header fields and
167
+ * Authentication-Results are never included (RFC 8617 section 4.1.2)
168
+ */
169
+ headerList?: string[] | string;
123
170
 
124
171
  /**
125
172
  * Signing timestamp
126
173
  */
127
174
  signTime?: Date | string | number;
175
+
176
+ /**
177
+ * Follow the RFCs exactly instead of the lenient defaults (default: false)
178
+ */
179
+ strict?: boolean;
128
180
  }
129
181
 
130
182
  /**
@@ -132,7 +184,8 @@ export interface ARCSealOptions {
132
184
  */
133
185
  export interface AuthPolicy {
134
186
  /**
135
- * DKIM policy rules (e.g., 'weak-key' when key is undersized)
187
+ * DKIM policy rules: 'weak-key' when the key is shorter than minBitLength, 'weak-algorithm'
188
+ * for an rsa-sha1 signature in strict mode
136
189
  */
137
190
  'dkim-rules'?: string;
138
191
 
@@ -146,7 +199,10 @@ export interface AuthPolicy {
146
199
  * Status result from authentication checks
147
200
  */
148
201
  export interface AuthStatus {
149
- result: 'pass' | 'fail' | 'neutral' | 'none' | 'temperror' | 'temperr' | 'permerror' | 'policy' | 'softfail' | 'skipped';
202
+ /**
203
+ * The result keyword. `temperr` is deprecated, BIMI reports `temperror` now
204
+ */
205
+ result: 'pass' | 'fail' | 'neutral' | 'none' | 'temperror' | 'temperr' | 'permerror' | 'policy' | 'softfail' | 'skipped' | 'declined';
150
206
  comment?: string;
151
207
  header?: Record<string, any>;
152
208
  smtp?: {
@@ -156,25 +212,94 @@ export interface AuthStatus {
156
212
  policy?: AuthPolicy;
157
213
  }
158
214
 
215
+ /**
216
+ * Lax acceptance markers of a DKIM result: what the default mode accepted and strict mode
217
+ * would reject
218
+ *
219
+ * - `rsa-sha1`: the signature uses rsa-sha1 (RFC 8301 section 3.1)
220
+ * - `tag-syntax`: the signature is not valid tag-list syntax (duplicate tags, upper case tag
221
+ * names, a malformed t=, x= or l= value, or a d= or s= that is not a domain name)
222
+ * - `missing-v`, `missing-h` (and the other `missing-*`): a required tag is missing
223
+ * - `invalid-v`: v= is not "1"
224
+ * - `identity-domain`: the i= domain is not d= or its subdomain, or it is a subdomain while
225
+ * the key has t=s
226
+ * - `invalid-expiration`: x= is equal to t=
227
+ * - `query-method`: q= does not list dns/txt
228
+ * - `key-syntax`: the key record is not valid tag-list syntax
229
+ * - `key-v-syntax`: the key record v= is not the first tag, or not exactly "DKIM1"
230
+ * - `key-type-inferred`: an ed25519 key was found from its length, without k=ed25519
231
+ */
232
+ export type DKIMWarning =
233
+ | 'rsa-sha1'
234
+ | 'tag-syntax'
235
+ | 'missing-v'
236
+ | 'missing-a'
237
+ | 'missing-b'
238
+ | 'missing-bh'
239
+ | 'missing-d'
240
+ | 'missing-h'
241
+ | 'missing-s'
242
+ | 'invalid-v'
243
+ | 'identity-domain'
244
+ | 'invalid-expiration'
245
+ | 'query-method'
246
+ | 'key-syntax'
247
+ | 'key-v-syntax'
248
+ | 'key-type-inferred';
249
+
159
250
  /**
160
251
  * DKIM verification result for a single signature
161
252
  */
162
253
  export interface DKIMResult {
163
254
  /**
164
- * Signature identifier
255
+ * Signature identifier, the sha256 hash of the signature value
165
256
  */
166
257
  id?: string;
167
258
 
168
259
  /**
169
- * Signing domain
260
+ * Signing domain (d= tag)
170
261
  */
171
- signingDomain: string;
262
+ signingDomain?: string;
172
263
 
173
264
  /**
174
- * Key selector
265
+ * Key selector (s= tag)
175
266
  */
176
267
  selector?: string;
177
268
 
269
+ /**
270
+ * Signature value (b= tag)
271
+ */
272
+ signature?: string;
273
+
274
+ /**
275
+ * Signature algorithm (a= tag), eg. 'rsa-sha256'
276
+ */
277
+ algo?: string;
278
+
279
+ /**
280
+ * Canonicalization (c= tag), eg. 'relaxed/relaxed'
281
+ */
282
+ format?: string;
283
+
284
+ /**
285
+ * Body hash calculated for the message
286
+ */
287
+ bodyHash?: string;
288
+
289
+ /**
290
+ * Body hash from the bh= tag
291
+ */
292
+ bodyHashExpecting?: string;
293
+
294
+ /**
295
+ * Signed header fields and the canonicalized header data (base64)
296
+ */
297
+ signingHeaders?: {
298
+ keys: string;
299
+ headers: string[];
300
+ canonicalizedHeader: string;
301
+ };
302
+
178
303
  /**
179
304
  * Verification status
180
305
  */
@@ -190,6 +315,18 @@ export interface DKIMResult {
190
315
  * Number of body bytes left unsigned by an l= tag
191
316
  */
192
317
  underSized?: number;
318
+
319
+ /**
320
+ * True when the key record has the t=y flag, the signing domain is testing DKIM
321
+ * (RFC 6376 section 3.6.1)
322
+ */
323
+ testing?: boolean;
324
+
325
+ /**
326
+ * What the default mode accepted and strict mode would reject. Only present when
327
+ * there is something to report, never written into the Authentication-Results text
328
+ */
329
+ warnings?: DKIMWarning[];
193
330
  };
194
331
 
195
332
  /**
@@ -198,22 +335,82 @@ export interface DKIMResult {
198
335
  info: string;
199
336
 
200
337
  /**
201
- * Signature algorithm
338
+ * Signing time from the t= tag, as an ISO string
339
+ */
340
+ signTime?: string | null;
341
+
342
+ /**
343
+ * Expiration time from the x= tag, as an ISO string
344
+ */
345
+ expiresAfter?: string | null;
346
+
347
+ /**
348
+ * False when t= is in the future or x= has passed
349
+ */
350
+ signatureTimeValid?: boolean;
351
+
352
+ /**
353
+ * Size of the message body in bytes
354
+ */
355
+ sourceBodyLength?: number;
356
+
357
+ /**
358
+ * Number of canonicalized body bytes covered by the signature
359
+ */
360
+ canonBodyLength?: number;
361
+
362
+ /**
363
+ * Number of canonicalized body bytes in total
364
+ */
365
+ canonBodyLengthTotal?: number;
366
+
367
+ /**
368
+ * True when the signature has an l= tag
369
+ */
370
+ canonBodyLengthLimited?: boolean;
371
+
372
+ /**
373
+ * The l= value
374
+ */
375
+ canonBodyLengthLimit?: number;
376
+
377
+ /**
378
+ * Where the MIME structure starts in the body, for multipart messages
379
+ */
380
+ mimeStructureStart?: number;
381
+
382
+ /**
383
+ * Public key in PEM format
384
+ */
385
+ publicKey?: string;
386
+
387
+ /**
388
+ * RSA key size in bits
389
+ */
390
+ modulusLength?: number;
391
+
392
+ /**
393
+ * Key record as found in DNS
394
+ */
395
+ rr?: string;
396
+
397
+ /**
398
+ * @deprecated Never set, use `algo`
202
399
  */
203
400
  algorithm?: string;
204
401
 
205
402
  /**
206
- * Canonicalization method
403
+ * @deprecated Never set, use `format`
207
404
  */
208
405
  canonicalization?: string;
209
406
 
210
407
  /**
211
- * Signing timestamp
408
+ * @deprecated Never set, use `signTime`
212
409
  */
213
410
  signingTime?: Date;
214
411
 
215
412
  /**
216
- * Signature expiration
413
+ * @deprecated Never set, use `expiresAfter`
217
414
  */
218
415
  expiration?: Date;
219
416
  }
@@ -223,10 +420,16 @@ export interface DKIMResult {
223
420
  */
224
421
  export interface DKIMVerifyResult {
225
422
  /**
226
- * Domain from From header
423
+ * Addresses from the From header, as full addr-specs (the domain is what follows the last
424
+ * "@"). The mailboxes of a group (RFC 6854) are included
227
425
  */
228
426
  headerFrom: string[];
229
427
 
428
+ /**
429
+ * Number of From header fields in the message. RFC 5322 allows only one
430
+ */
431
+ fromFields: number;
432
+
230
433
  /**
231
434
  * Domain from Return-Path header
232
435
  */
@@ -242,8 +445,8 @@ export interface DKIMVerifyResult {
242
445
  * Access with result.headers or Object.getOwnPropertyDescriptor()
243
446
  */
244
447
  readonly headers?: {
245
- parsed: Array<{ key: string; line: string }>;
246
- [key: string]: any;
448
+ parsed: Array<{ key: string; casedKey: string; line: Buffer }>;
449
+ original: Buffer;
247
450
  };
248
451
 
249
452
  /**
@@ -312,6 +515,18 @@ export interface SPFResult {
312
515
  void: number;
313
516
  subqueries: Record<string, number>;
314
517
  };
518
+
519
+ /**
520
+ * Explanation string published by the domain owner with the "exp" modifier
521
+ * (RFC 7208 section 6.2). Only set for a "fail" result. This is third party text
522
+ */
523
+ explanation?: string;
524
+
525
+ /**
526
+ * Set in the default mode when something was accepted that strict mode would have
527
+ * rejected: "syntax-error", "void-lookup-limit" or "non-ascii-local-part"
528
+ */
529
+ warnings?: string[];
315
530
  }
316
531
 
317
532
  /**
@@ -354,7 +569,18 @@ export interface ARCResult {
354
569
  * Why sealing failed, when a seal was requested but could not be created.
355
570
  * The message is left unsealed in that case
356
571
  */
357
- sealErrors?: Error[];
572
+ sealErrors?: ARCSealError[];
573
+
574
+ /**
575
+ * What the default mode accepted in the chain that strict mode would reject, for example
576
+ * 'arc-tag-syntax' (a tag-list syntax error or a tag name that is not lower case),
577
+ * 'arc-instance-syntax' (an i= value that is not 1*2DIGIT, an ARC-Authentication-Results
578
+ * whose i= is not first, or an ARC header field that was ignored for its i= value),
579
+ * 'ams-c-default' (an ARC-Message-Signature without c= that only verified with
580
+ * relaxed/relaxed), and the key record warnings of the DKIM result. Not set for a failing
581
+ * chain, and never written into the headers
582
+ */
583
+ warnings?: string[];
358
584
 
359
585
  /**
360
586
  * ARC chain entries (non-enumerable property)
@@ -373,10 +599,16 @@ export interface DMARCResult {
373
599
  domain: string;
374
600
 
375
601
  /**
376
- * DMARC policy ('none', 'quarantine', 'reject')
602
+ * Effective DMARC policy ('none', 'quarantine', 'reject'): p for the author domain's own
603
+ * record, np or sp for an inherited one, one level lower with t=y
377
604
  */
378
605
  policy: string;
379
606
 
607
+ /**
608
+ * Domain whose DMARC record the policy was taken from
609
+ */
610
+ policyDomain?: string;
611
+
380
612
  /**
381
613
  * Policy for organizational domain
382
614
  */
@@ -388,9 +620,14 @@ export interface DMARCResult {
388
620
  sp: string;
389
621
 
390
622
  /**
391
- * Percentage of messages subject to filtering (0-100)
623
+ * Policy for non-existent subdomains, if published
392
624
  */
393
- pct?: number;
625
+ np?: string;
626
+
627
+ /**
628
+ * True when the record has t=y, in which case policy is one level below the published one
629
+ */
630
+ testMode: boolean;
394
631
 
395
632
  /**
396
633
  * DMARC DNS record
@@ -400,7 +637,26 @@ export interface DMARCResult {
400
637
  /**
401
638
  * Verification status
402
639
  */
403
- status: AuthStatus;
640
+ status: AuthStatus & {
641
+ header?: {
642
+ /**
643
+ * Author Domain
644
+ */
645
+ from?: string;
646
+
647
+ /**
648
+ * Domain of the DMARC record, omitted in strict mode (not a registered property for dmarc)
649
+ */
650
+ d?: string;
651
+ };
652
+
653
+ policy?: {
654
+ /**
655
+ * The policy that was applied (RFC 8601 section 2.7.2 policy.dmarc)
656
+ */
657
+ dmarc?: string;
658
+ };
659
+ };
404
660
 
405
661
  /**
406
662
  * Alignment results
@@ -438,6 +694,12 @@ export interface DMARCResult {
438
694
  * Error message if verification failed
439
695
  */
440
696
  error?: string;
697
+
698
+ /**
699
+ * What the default mode accepted and strict mode would reject, only present when there
700
+ * is something to report
701
+ */
702
+ warnings?: string[];
441
703
  }
442
704
 
443
705
  /**
@@ -445,9 +707,12 @@ export interface DMARCResult {
445
707
  */
446
708
  export interface BIMIResult {
447
709
  /**
448
- * Verification status
710
+ * Verification status. `declined` means the domain published a declination record,
711
+ * `temperror` a DNS failure. `temperr` is deprecated and no longer reported
449
712
  */
450
- status: AuthStatus;
713
+ status: AuthStatus & {
714
+ result: 'pass' | 'none' | 'fail' | 'skipped' | 'declined' | 'temperror' | 'temperr';
715
+ };
451
716
 
452
717
  /**
453
718
  * BIMI DNS record
@@ -484,7 +749,8 @@ export interface ReceivedChainEntry {
484
749
  value: string;
485
750
 
486
751
  /**
487
- * Additional comment (often contains IP in parentheses)
752
+ * The comments after the value, joined with spaces. Usually the TCP-info of
753
+ * RFC 5321 section 4.4, whose last address literal is the connecting IP address
488
754
  */
489
755
  comment?: string;
490
756
  };
@@ -581,6 +847,13 @@ export interface AuthenticateResult {
581
847
  * Combined authentication headers to prepend to message
582
848
  */
583
849
  headers: string;
850
+
851
+ /**
852
+ * Why DMARC was not evaluated when `dmarc` is false because the From header has no
853
+ * domain or more than one (RFC 9989 section 5.3.1). Such a message can be treated as
854
+ * suspicious (RFC 9989 section 11.5)
855
+ */
856
+ dmarcSkipReason?: 'no-author-domain' | 'multiple-author-domains' | 'invalid-author-domain' | 'multiple-from-fields';
584
857
  }
585
858
 
586
859
  /**
@@ -622,16 +895,17 @@ export interface DKIMSignOptions {
622
895
  canonicalization?: string;
623
896
 
624
897
  /**
625
- * Signing algorithm (default: 'rsa-sha256')
626
- * Supported: 'rsa-sha256', 'rsa-sha1', 'ed25519-sha256'
898
+ * Signing algorithm, defaults to 'rsa-sha256' or 'ed25519-sha256' depending on the key type.
899
+ * Supported: 'rsa-sha256', 'ed25519-sha256' and 'rsa-sha1'. rsa-sha1 is historic
900
+ * (RFC 8301): it adds the 'rsa-sha1' warning, and strict mode refuses it
627
901
  */
628
902
  algorithm?: string;
629
903
 
630
904
  /**
631
- * List of header fields to sign
632
- * Default includes From, Subject, Date, To, etc.
905
+ * Header fields to sign, as an array or a colon separated string. Default includes
906
+ * From, Subject, Date, To, etc. Set per signature in `signatureData` or for all of them
633
907
  */
634
- headerList?: string[];
908
+ headerList?: string[] | string;
635
909
 
636
910
  /**
637
911
  * Signing timestamp (defaults to current time)
@@ -649,7 +923,7 @@ export interface DKIMSignOptions {
649
923
  maxBodyLength?: number;
650
924
 
651
925
  /**
652
- * Identity (i= tag)
926
+ * Identity (i= tag), an address whose domain is the signing domain or its subdomain
653
927
  */
654
928
  identity?: string;
655
929
 
@@ -657,6 +931,15 @@ export interface DKIMSignOptions {
657
931
  * Multiple signature configurations
658
932
  */
659
933
  signatureData?: DKIMSignOptions[];
934
+
935
+ /**
936
+ * Follow the RFCs exactly (default: false). Strict mode refuses rsa-sha1, RSA keys
937
+ * shorter than 1024 bits, an expiration that is not after the signing time, a signing
938
+ * time that does not fit into t=, and a d=, s= or i= that is not valid RFC 6376 syntax.
939
+ * The default mode signs these and lists them in `warnings`. A d=, s= or identity that
940
+ * would break out of its tag is refused in both modes
941
+ */
942
+ strict?: boolean;
660
943
  }
661
944
 
662
945
  /**
@@ -829,12 +1112,43 @@ export interface ARCSigningData {
829
1112
  privateKey?: string | Buffer;
830
1113
  }
831
1114
 
1115
+ /**
1116
+ * A signature that could not be created
1117
+ */
1118
+ export interface DKIMSignError {
1119
+ /**
1120
+ * Why the signature was not created. `err.code` is eg. 'ENOFROM', 'EINVALIDALGO',
1121
+ * 'EINVALIDTYPE', 'ESHORTKEY', 'EINVALIDDOMAIN', 'EINVALIDSELECTOR', 'EINVALIDIDENTITY',
1122
+ * 'EINVALIDTIME', 'EINVALIDCANON' or 'EINVALIDINSTANCE'
1123
+ */
1124
+ err: Error & { code?: string };
1125
+
1126
+ type?: 'DKIM' | 'ARC';
1127
+ selector?: string;
1128
+ signingDomain?: string;
1129
+ algorithm?: string;
1130
+ canonicalization?: string;
1131
+ }
1132
+
1133
+ /**
1134
+ * What the default mode signed although strict mode would have refused it
1135
+ *
1136
+ * - `rsa-sha1`: signed with rsa-sha1
1137
+ * - `weak-key`: signed with an RSA key shorter than 1024 bits
1138
+ * - `invalid-expiration`: the expiration is not after the signing time, or does not fit into x=
1139
+ * - `invalid-signtime`: the signing time does not fit into t=, which was left out
1140
+ * - `d-syntax`, `s-syntax`: d= or s= is not valid RFC 6376 syntax
1141
+ * - `identity-domain`: the identity domain is not the signing domain or its subdomain
1142
+ */
1143
+ export type DKIMSignWarning = 'rsa-sha1' | 'weak-key' | 'invalid-expiration' | 'invalid-signtime' | 'd-syntax' | 's-syntax' | 'identity-domain';
1144
+
832
1145
  /**
833
1146
  * DKIM signing result
834
1147
  */
835
1148
  export interface DKIMSignResult {
836
1149
  /**
837
- * DKIM-Signature header(s) to prepend to message
1150
+ * DKIM-Signature header(s) to prepend to message, each ending with a CRLF. An empty
1151
+ * string when no signature was created
838
1152
  */
839
1153
  signatures: string;
840
1154
 
@@ -844,9 +1158,14 @@ export interface DKIMSignResult {
844
1158
  arc?: ARCSigningData;
845
1159
 
846
1160
  /**
847
- * Any errors encountered during signing
1161
+ * The signatures that could not be created, and why
848
1162
  */
849
- errors: Error[];
1163
+ errors: DKIMSignError[];
1164
+
1165
+ /**
1166
+ * What was signed although strict mode would have refused it
1167
+ */
1168
+ warnings: DKIMSignWarning[];
850
1169
  }
851
1170
 
852
1171
  /**
@@ -871,9 +1190,14 @@ export class DkimSignStream extends Transform {
871
1190
  constructor(options: DKIMSignOptions);
872
1191
 
873
1192
  /**
874
- * Any errors encountered during signing
1193
+ * The signatures that could not be created, set when the stream ends
875
1194
  */
876
- errors: Error[] | null;
1195
+ errors: DKIMSignError[] | null;
1196
+
1197
+ /**
1198
+ * What was signed although strict mode would have refused it, set when the stream ends
1199
+ */
1200
+ warnings: DKIMSignWarning[] | null;
877
1201
  }
878
1202
 
879
1203
  // ============================================================================
@@ -899,6 +1223,12 @@ export interface DKIMVerifyOptions {
899
1223
  */
900
1224
  minBitLength?: number;
901
1225
 
1226
+ /**
1227
+ * If true, rsa-sha1 signatures get `policy` (weak-algorithm) as in strict mode, while every
1228
+ * other check keeps the lenient default (default: false)
1229
+ */
1230
+ rejectRsaSha1?: boolean;
1231
+
902
1232
  /**
903
1233
  * Current time for signature expiration checks
904
1234
  */
@@ -908,6 +1238,12 @@ export interface DKIMVerifyOptions {
908
1238
  * ARC sealing options (if sealing should be prepared)
909
1239
  */
910
1240
  seal?: ARCSealOptions;
1241
+
1242
+ /**
1243
+ * Follow RFC 6376, RFC 8301 and RFC 8463 exactly instead of the lenient default
1244
+ * (default: false). See `DKIMWarning` for what the default mode accepts
1245
+ */
1246
+ strict?: boolean;
911
1247
  }
912
1248
 
913
1249
  /**
@@ -961,6 +1297,17 @@ export interface SPFOptions {
961
1297
  * Custom DNS resolver function
962
1298
  */
963
1299
  resolver?: DNSResolver;
1300
+
1301
+ /**
1302
+ * Follow RFC 7208 exactly instead of the lenient default (default: false)
1303
+ */
1304
+ strict?: boolean;
1305
+
1306
+ /**
1307
+ * Maximum time in milliseconds for the whole evaluation, after which the result is
1308
+ * temperror (RFC 7208 section 4.6.4). No limit by default
1309
+ */
1310
+ maxElapsedTime?: number;
964
1311
  }
965
1312
 
966
1313
  /**
@@ -984,6 +1331,12 @@ export interface DMARCOptions {
984
1331
  */
985
1332
  headerFrom: string | string[];
986
1333
 
1334
+ /**
1335
+ * Number of From header fields the addresses came from. With more than one, DMARC
1336
+ * validation is not possible (reason "multiple-from-fields")
1337
+ */
1338
+ fromFields?: number;
1339
+
987
1340
  /**
988
1341
  * Domains that passed SPF
989
1342
  */
@@ -1008,6 +1361,11 @@ export interface DMARCOptions {
1008
1361
  * Custom DNS resolver function
1009
1362
  */
1010
1363
  resolver?: DNSResolver;
1364
+
1365
+ /**
1366
+ * Follow RFC 9989 exactly instead of the lenient default (default: false)
1367
+ */
1368
+ strict?: boolean;
1011
1369
  }
1012
1370
 
1013
1371
  /**
@@ -1042,6 +1400,24 @@ export interface ARCData {
1042
1400
  error?: Error;
1043
1401
  }
1044
1402
 
1403
+ /**
1404
+ * An error from ARC sealing
1405
+ */
1406
+ export interface ARCSealError {
1407
+ /**
1408
+ * The error. Codes include EINVALIDCV (a missing or invalid cv), EINVALIDINSTANCE (an
1409
+ * instance out of 1-50, a chain that already has 50 sets, one that already exists, or in
1410
+ * strict mode one that leaves a gap),
1411
+ * EARCCHAINFAILED (the newest ARC-Seal already has cv=fail, RFC 8617 section 5.1 step 2),
1412
+ * EINVALIDALGO and EINVALIDTYPE (an algorithm that is not supported or does not match the key)
1413
+ */
1414
+ err: Error & { code?: string };
1415
+ type?: string;
1416
+ selector?: string;
1417
+ signingDomain?: string;
1418
+ [key: string]: any;
1419
+ }
1420
+
1045
1421
  /**
1046
1422
  * ARC verification options
1047
1423
  */
@@ -1055,6 +1431,47 @@ export interface ARCOptions {
1055
1431
  * Minimal allowed public key length in bits (default: 1024)
1056
1432
  */
1057
1433
  minBitLength?: number;
1434
+
1435
+ /**
1436
+ * Follow RFC 8617 exactly instead of the lenient default (default: false). In strict mode
1437
+ * ARC-Seal and ARC-Message-Signature tag lists must be valid RFC 6376 section 3.2 syntax
1438
+ * with lower case tag names and every required tag, instance values must be 1*2DIGIT from
1439
+ * 1 to 50 (and come first in ARC-Authentication-Results), ARC header fields with an
1440
+ * invalid instance fail the chain instead of being ignored, an ARC-Message-Signature
1441
+ * without c= is only verified as simple/simple, and the arc= result also reports
1442
+ * arc=none and smtp.remote-ip
1443
+ */
1444
+ strict?: boolean;
1445
+
1446
+ /**
1447
+ * Client IP address, reported as smtp.remote-ip in strict mode (RFC 8617 section 6)
1448
+ */
1449
+ ip?: string;
1450
+ }
1451
+
1452
+ /**
1453
+ * Seal creation data for createSeal()
1454
+ */
1455
+ export interface ARCCreateSealData {
1456
+ /**
1457
+ * Parsed message headers, when `input` is false
1458
+ */
1459
+ headers?: any;
1460
+
1461
+ /**
1462
+ * ARC chain data of the message, when `input` is false
1463
+ */
1464
+ arc?: ARCData | ARCSigningData;
1465
+
1466
+ /**
1467
+ * Sealing options, with `bodyHash` (the relaxed/relaxed sha256 body hash) when `input` is false
1468
+ */
1469
+ seal: ARCSealOptions & { bodyHash?: string };
1470
+
1471
+ /**
1472
+ * Follow the RFCs exactly (default: false, or `seal.strict`)
1473
+ */
1474
+ strict?: boolean;
1058
1475
  }
1059
1476
 
1060
1477
  /**
@@ -1076,30 +1493,34 @@ export function arc(data: ARCData, opts?: ARCOptions): Promise<ARCResult>;
1076
1493
  export function sealMessage(input: MessageInput, seal: ARCSealOptions): Promise<Buffer>;
1077
1494
 
1078
1495
  /**
1079
- * Gets ARC chain from parsed headers
1496
+ * Gets ARC chain from parsed headers and validates its structure. Throws for a chain that is not valid
1080
1497
  *
1081
1498
  * @param headers - Parsed message headers
1499
+ * @param opts - `strict` applies the RFC 8617 syntax rules, lenient acceptances are added to `warnings`
1082
1500
  * @returns ARC chain or false if no chain found
1083
1501
  */
1084
- export function getARChain(headers: any): ARCChainEntry[] | false;
1502
+ export function getARChain(headers: any, opts?: { strict?: boolean; warnings?: string[] }): ARCChainEntry[] | false;
1085
1503
 
1086
1504
  /**
1087
- * Verifies ARC seal chain
1505
+ * Verifies ARC seal chain. Throws if any seal of the chain is not valid, including a chain whose
1506
+ * newest seal has cv=fail
1088
1507
  *
1089
1508
  * @param data - ARC chain data
1090
1509
  * @param opts - ARC verification options
1091
- * @returns true if chain is valid
1510
+ * @returns true if chain is valid, false if there is no chain
1092
1511
  */
1093
- export function verifyASChain(data: ARCData, opts: ARCOptions): Promise<boolean>;
1512
+ export function verifyASChain(data: ARCData, opts: ARCOptions & { warnings?: string[] }): Promise<boolean>;
1094
1513
 
1095
1514
  /**
1096
1515
  * Creates ARC seal headers
1097
1516
  *
1098
1517
  * @param input - RFC822 formatted message or false for pre-calculated data
1099
1518
  * @param data - Seal creation data
1100
- * @returns Seal headers, empty if the ARC-Message-Signature could not be signed, and any errors
1519
+ * @returns Seal headers (empty when no set was created), the errors that say why, and what the
1520
+ * default mode sealed that strict mode would have refused ('arc-instance-gap',
1521
+ * 'arc-cv-instance', and the DKIM signing warnings)
1101
1522
  */
1102
- export function createSeal(input: MessageInput | false, data: any): Promise<{ headers: string[]; errors: Error[] }>;
1523
+ export function createSeal(input: MessageInput | false, data: ARCCreateSealData): Promise<{ headers: string[]; errors: ARCSealError[]; warnings: string[] }>;
1103
1524
 
1104
1525
  // ============================================================================
1105
1526
  // BIMI
@@ -1128,6 +1549,12 @@ export interface BIMIOptions {
1128
1549
  * Custom DNS resolver function
1129
1550
  */
1130
1551
  resolver?: DNSResolver;
1552
+
1553
+ /**
1554
+ * Format the Authentication-Results entry in the RFC 8601 section 2.2 form (default: false).
1555
+ * BIMI has no other strict-only rules, the draft checks apply in both modes
1556
+ */
1557
+ strict?: boolean;
1131
1558
  }
1132
1559
 
1133
1560
  /**
@@ -1338,10 +1765,33 @@ export interface MTASTSPolicyResult {
1338
1765
 
1339
1766
  /**
1340
1767
  * Status of the fetch operation
1768
+ *
1769
+ * - `found`: a new or changed policy was fetched
1770
+ * - `renewed`: the cached policy has the same id and has not expired, it is returned without a fetch
1771
+ * - `not_found`: the domain has no MTA-STS policy
1772
+ * - `errored`: the policy could not be fetched. With a valid cached policy, that policy is
1773
+ * returned with `error` set
1341
1774
  */
1342
1775
  status: 'found' | 'renewed' | 'not_found' | 'errored';
1776
+
1777
+ /**
1778
+ * Lax acceptance markers, only present when the default lax mode accepted something strict mode would reject
1779
+ */
1780
+ warnings?: MTASTSWarning[];
1343
1781
  }
1344
1782
 
1783
+ /**
1784
+ * Markers for input that the default lax mode accepted but `strict: true` would reject
1785
+ *
1786
+ * - `txt-syntax`: the TXT record does not match the RFC 8461 3.1 syntax
1787
+ * - `http-status`: the policy was served with a 2xx status other than 200
1788
+ * - `content-type`: the policy was not served as `text/plain`
1789
+ * - `policy-syntax`: the policy file does not match the RFC 8461 3.2 syntax
1790
+ * - `cert-identity`: the Policy Host certificate matched only through the CN or a partial-label wildcard
1791
+ * - `expired-cache`: an expired cached policy was returned because the policy could not be fetched
1792
+ */
1793
+ export type MTASTSWarning = 'txt-syntax' | 'http-status' | 'content-type' | 'policy-syntax' | 'cert-identity' | 'expired-cache';
1794
+
1345
1795
  /**
1346
1796
  * MTA-STS MX validation result
1347
1797
  */
@@ -1375,6 +1825,21 @@ export interface MTASTSOptions {
1375
1825
  * Custom DNS resolver function
1376
1826
  */
1377
1827
  resolver?: DNSResolver;
1828
+
1829
+ /**
1830
+ * Apply the RFC 8461 rules exactly (default false)
1831
+ */
1832
+ strict?: boolean;
1833
+
1834
+ /**
1835
+ * Overall time limit for the HTTPS policy request in milliseconds (default 60000)
1836
+ */
1837
+ timeout?: number;
1838
+
1839
+ /**
1840
+ * Maximum size of the policy file in bytes (default 65536)
1841
+ */
1842
+ maxPolicySize?: number;
1378
1843
  }
1379
1844
 
1380
1845
  // Note: MTA-STS functions (resolvePolicy, fetchPolicy, parsePolicy, validateMx, getPolicy)