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.
Files changed (48) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +23 -0
  3. package/README.md +117 -41
  4. package/bin/mailauth.js +14 -6
  5. package/cli.md +8 -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 +82 -31
  10. package/docs/mta-sts.md +140 -42
  11. package/docs/spf.md +69 -4
  12. package/index.d.ts +499 -45
  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 +2 -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 +574 -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 +267 -53
  34. package/lib/dmarc/index.d.ts +29 -2
  35. package/lib/dmarc/index.js +2 -1
  36. package/lib/dmarc/verify.js +205 -53
  37. package/lib/mailauth.js +86 -40
  38. package/lib/mta-sts.d.ts +77 -11
  39. package/lib/mta-sts.js +497 -182
  40. package/lib/parse-dkim-headers.js +376 -191
  41. package/lib/parse-received.js +104 -32
  42. package/lib/spf/index.d.ts +37 -1
  43. package/lib/spf/index.js +260 -28
  44. package/lib/spf/macro.js +50 -13
  45. package/lib/spf/spf-verify.js +440 -204
  46. package/lib/spf/syntax.js +366 -0
  47. package/lib/tools.js +524 -118
  48. 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 (default: 'rsa-sha256')
115
- * Supported: 'rsa-sha256', 'ed25519-sha256'
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
- * Headers to include in signature
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 (e.g., 'weak-key' when key is undersized)
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
- result: 'pass' | 'fail' | 'neutral' | 'none' | 'temperror' | 'temperr' | 'permerror' | 'policy' | 'softfail' | 'skipped';
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: string;
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
- * Signature algorithm
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
- * Canonicalization method
396
+ * @deprecated Never set, use `format`
205
397
  */
206
398
  canonicalization?: string;
207
399
 
208
400
  /**
209
- * Signing timestamp
401
+ * @deprecated Never set, use `signTime`
210
402
  */
211
403
  signingTime?: Date;
212
404
 
213
405
  /**
214
- * Signature expiration
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
- * Domain from From header
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; line: string }>;
244
- [key: string]: any;
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?: Error[];
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
- * Organization domain
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
- * Percentage of messages subject to filtering (0-100)
616
+ * Policy for non-existent subdomains, if published
390
617
  */
391
- pct?: number;
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
- * Additional comment (often contains IP in parentheses)
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 (default: 'rsa-sha256')
624
- * Supported: 'rsa-sha256', 'rsa-sha1', 'ed25519-sha256'
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
- * List of header fields to sign
630
- * Default includes From, Subject, Date, To, etc.
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
- * Any errors encountered during signing
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
- errors: Error[];
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
- * Any errors encountered during signing
1186
+ * The signatures that could not be created, set when the stream ends
873
1187
  */
874
- errors: Error[] | null;
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, empty if the ARC-Message-Signature could not be signed, and any errors
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: any): Promise<{ headers: string[]; errors: Error[] }>;
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)