mailauth 7.1.1 → 7.3.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 (58) hide show
  1. package/.c8rc.json +11 -0
  2. package/.prettierignore +3 -0
  3. package/.release-please-manifest.json +1 -1
  4. package/CHANGELOG.md +68 -0
  5. package/README.md +131 -31
  6. package/bin/mailauth.js +62 -0
  7. package/cli.md +150 -8
  8. package/docs/arc.md +62 -54
  9. package/docs/bimi.md +153 -54
  10. package/docs/dkim.md +37 -36
  11. package/docs/dkim2.md +154 -0
  12. package/docs/dmarc.md +4 -1
  13. package/docs/mta-sts.md +12 -9
  14. package/docs/spf.md +14 -13
  15. package/index.d.ts +583 -42
  16. package/lib/arc/index.d.ts +6 -2
  17. package/lib/arc/index.js +45 -15
  18. package/lib/bimi/index.js +362 -102
  19. package/lib/bimi/validate-svg.js +493 -60
  20. package/lib/commands/dkim2-hash.js +25 -0
  21. package/lib/commands/dkim2-sign.js +77 -0
  22. package/lib/commands/dkim2-verify.js +59 -0
  23. package/lib/commands/report.js +10 -35
  24. package/lib/commands/resolver.js +51 -0
  25. package/lib/commands/seal.js +10 -38
  26. package/lib/commands/spf.js +4 -35
  27. package/lib/dkim/dkim-signer.js +50 -30
  28. package/lib/dkim/dkim-verifier.js +79 -27
  29. package/lib/dkim/header/simple.js +2 -2
  30. package/lib/dkim/sign.js +7 -2
  31. package/lib/dkim/syntax.js +11 -0
  32. package/lib/dkim/verify.js +1 -0
  33. package/lib/dkim2/dkim2-parser.js +52 -0
  34. package/lib/dkim2/dkim2-signer.js +334 -0
  35. package/lib/dkim2/dkim2-verifier.js +572 -0
  36. package/lib/dkim2/fields.js +570 -0
  37. package/lib/dkim2/hash.d.ts +3 -0
  38. package/lib/dkim2/hash.js +51 -0
  39. package/lib/dkim2/key.js +203 -0
  40. package/lib/dkim2/message-state.js +181 -0
  41. package/lib/dkim2/recipe.js +134 -0
  42. package/lib/dkim2/sign.d.ts +3 -0
  43. package/lib/dkim2/sign.js +38 -0
  44. package/lib/dkim2/verify.d.ts +3 -0
  45. package/lib/dkim2/verify.js +19 -0
  46. package/lib/dmarc/author-domain.js +270 -5
  47. package/lib/dmarc/get-dmarc-record.js +24 -11
  48. package/lib/dmarc/verify.js +7 -3
  49. package/lib/mailauth.js +57 -14
  50. package/lib/mta-sts.js +37 -11
  51. package/lib/parse-received.js +214 -25
  52. package/lib/spf/index.js +21 -4
  53. package/lib/spf/macro.js +5 -3
  54. package/lib/spf/spf-verify.js +14 -5
  55. package/lib/spf/syntax.js +5 -0
  56. package/lib/tools.js +160 -21
  57. package/package.json +15 -6
  58. package/stryker.config.json +16 -0
package/index.d.ts CHANGED
@@ -109,6 +109,24 @@ export interface AuthenticateOptions {
109
109
  * `warnings` array of the affected result. It is never written into the headers
110
110
  */
111
111
  strict?: boolean;
112
+
113
+ /**
114
+ * Verify DKIM2 header fields as well (default: false). Experimental, built against
115
+ * draft-ietf-dkim-dkim2-spec-06. The result is added to Authentication-Results with the
116
+ * `dkim2` method of draft-gondwana-dkim2-authres-00 and does not take part in DMARC
117
+ */
118
+ dkim2?: boolean;
119
+
120
+ /**
121
+ * RCPT TO addresses of the delivery, checked against the rt= tag of the highest
122
+ * DKIM2-Signature. `sender` is checked against its mf= tag
123
+ */
124
+ rcptTo?: string | string[];
125
+
126
+ /**
127
+ * Extra DKIM2 verifier options
128
+ */
129
+ dkim2Options?: Pick<DKIM2VerifyOptions, 'curTime' | 'maxSignatureAge' | 'maxFutureTime' | 'maxInstances' | 'checkReplay'>;
112
130
  }
113
131
 
114
132
  /**
@@ -144,20 +162,22 @@ export interface ARCSealOptions {
144
162
  /**
145
163
  * Chain validation status for the ARC-Seal cv= tag: 'none', 'pass' or 'fail' (compared
146
164
  * 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
165
+ * set. `authenticate()` always sets it from the ARC validation result and ignores this value
148
166
  */
149
167
  cv?: 'none' | 'pass' | 'fail' | string;
150
168
 
151
169
  /**
152
170
  * ARC instance (i= tag), 1 to 50. Defaults to one more than the highest instance on the
153
171
  * 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)
172
+ * refused in strict mode (the default mode seals it and adds an `arc-instance-gap` warning).
173
+ * `authenticate()` ignores it and always uses the next instance
155
174
  */
156
175
  i?: number;
157
176
 
158
177
  /**
159
178
  * Authentication-Results payload for the ARC-Authentication-Results header (the part after
160
- * "i=N;"). `authenticate()` sets it from its own results
179
+ * "i=N;"). Required, and a line break is only allowed as folding (CRLF followed by a space or
180
+ * tab). `authenticate()` always sets it from its own results and ignores this value
161
181
  */
162
182
  authResults?: string;
163
183
 
@@ -218,16 +238,20 @@ export interface AuthStatus {
218
238
  *
219
239
  * - `rsa-sha1`: the signature uses rsa-sha1 (RFC 8301 section 3.1)
220
240
  * - `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)
241
+ * names, a malformed c=, t=, x= or l= value, an l= larger than the canonicalized body, or a d=
242
+ * or s= that is not a domain name)
222
243
  * - `missing-v`, `missing-h` (and the other `missing-*`): a required tag is missing
223
244
  * - `invalid-v`: v= is not "1"
224
245
  * - `identity-domain`: the i= domain is not d= or its subdomain, or it is a subdomain while
225
246
  * the key has t=s
226
247
  * - `invalid-expiration`: x= is equal to t=
227
248
  * - `query-method`: q= does not list dns/txt
228
- * - `key-syntax`: the key record is not valid tag-list syntax
249
+ * - `key-syntax`: the key record is not valid tag-list syntax, or a k=, s= or t= value has
250
+ * whitespace inside it
229
251
  * - `key-v-syntax`: the key record v= is not the first tag, or not exactly "DKIM1"
230
252
  * - `key-type-inferred`: an ed25519 key was found from its length, without k=ed25519
253
+ * - `key-ed25519-spki`: an ed25519 key was published as a SubjectPublicKeyInfo structure, not
254
+ * as the bare 32 octet key of RFC 8463 section 4.2
231
255
  */
232
256
  export type DKIMWarning =
233
257
  | 'rsa-sha1'
@@ -245,7 +269,8 @@ export type DKIMWarning =
245
269
  | 'query-method'
246
270
  | 'key-syntax'
247
271
  | 'key-v-syntax'
248
- | 'key-type-inferred';
272
+ | 'key-type-inferred'
273
+ | 'key-ed25519-spki';
249
274
 
250
275
  /**
251
276
  * DKIM verification result for a single signature
@@ -307,7 +332,8 @@ export interface DKIMResult {
307
332
  /**
308
333
  * Signing domain if it aligns with the From domain, false otherwise. For a passing
309
334
  * signature this follows the DMARC verdict once DMARC found a record, before that it
310
- * only guesses relaxed alignment from the Public Suffix List.
335
+ * only guesses relaxed alignment from the Public Suffix List. Always false when the From
336
+ * header field does not yield a single Author Domain, as DMARC requires.
311
337
  */
312
338
  aligned?: string | false;
313
339
 
@@ -430,6 +456,14 @@ export interface DKIMVerifyResult {
430
456
  */
431
457
  fromFields: number;
432
458
 
459
+ /**
460
+ * Syntax of the From header fields: "valid" (RFC 5322), "lax" (accepted only by the lenient
461
+ * parsing, such as an unquoted comma in a display name) or "invalid" (no reliable addresses,
462
+ * for example text after the angle-addr). DMARC is not evaluated for an "invalid" field, nor
463
+ * for a "lax" one in strict mode
464
+ */
465
+ fromSyntax: 'valid' | 'lax' | 'invalid';
466
+
433
467
  /**
434
468
  * Domain from Return-Path header
435
469
  */
@@ -542,6 +576,10 @@ export interface ARCResult {
542
576
  * Verification status
543
577
  */
544
578
  status: AuthStatus & {
579
+ /**
580
+ * Set for a failed chain: whether it is sealed with cv=fail, false when the newest
581
+ * ARC-Seal on the message already says cv=fail (RFC 8617 section 5.1 step 2)
582
+ */
545
583
  shouldSeal?: boolean;
546
584
  };
547
585
 
@@ -600,9 +638,11 @@ export interface DMARCResult {
600
638
 
601
639
  /**
602
640
  * 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
641
+ * record, np or sp for an inherited one, one level lower with t=y. Like p, sp, testMode and
642
+ * alignment, only set when a policy record was applied, not for a none result or a
643
+ * temperror before a record was found
604
644
  */
605
- policy: string;
645
+ policy?: string;
606
646
 
607
647
  /**
608
648
  * Domain whose DMARC record the policy was taken from
@@ -612,12 +652,12 @@ export interface DMARCResult {
612
652
  /**
613
653
  * Policy for organizational domain
614
654
  */
615
- p: string;
655
+ p?: string;
616
656
 
617
657
  /**
618
658
  * Policy for subdomains
619
659
  */
620
- sp: string;
660
+ sp?: string;
621
661
 
622
662
  /**
623
663
  * Policy for non-existent subdomains, if published
@@ -627,7 +667,7 @@ export interface DMARCResult {
627
667
  /**
628
668
  * True when the record has t=y, in which case policy is one level below the published one
629
669
  */
630
- testMode: boolean;
670
+ testMode?: boolean;
631
671
 
632
672
  /**
633
673
  * DMARC DNS record
@@ -640,7 +680,7 @@ export interface DMARCResult {
640
680
  status: AuthStatus & {
641
681
  header?: {
642
682
  /**
643
- * Author Domain
683
+ * Author Domain (RFC 9989 section 9.1 header.from)
644
684
  */
645
685
  from?: string;
646
686
 
@@ -652,16 +692,16 @@ export interface DMARCResult {
652
692
 
653
693
  policy?: {
654
694
  /**
655
- * The policy that was applied (RFC 8601 section 2.7.2 policy.dmarc)
695
+ * The policy that was applied (RFC 9989 section 9.1 policy.dmarc)
656
696
  */
657
697
  dmarc?: string;
658
698
  };
659
699
  };
660
700
 
661
701
  /**
662
- * Alignment results
702
+ * Alignment results, set when a policy record was applied
663
703
  */
664
- alignment: {
704
+ alignment?: {
665
705
  spf: {
666
706
  /**
667
707
  * Aligned SPF domain, or undefined if no domain aligned
@@ -729,6 +769,18 @@ export interface BIMIResult {
729
769
  */
730
770
  authority?: string;
731
771
 
772
+ /**
773
+ * Avatar preference from the avp= tag, set only for the valid values
774
+ */
775
+ preference?: 'personal' | 'brand';
776
+
777
+ /**
778
+ * Assertion Record and BIMI-Selector syntax deviations that were accepted because `strict` was not set:
779
+ * 'record-version-case', 'record-tag-case', 'record-syntax', 'uri-comma', 'location-format',
780
+ * 'selector-version-case', 'selector-syntax'
781
+ */
782
+ warnings?: string[];
783
+
732
784
  /**
733
785
  * Authentication-Results formatted info
734
786
  */
@@ -818,6 +870,11 @@ export interface AuthenticateResult {
818
870
  */
819
871
  dkim: DKIMVerifyResult;
820
872
 
873
+ /**
874
+ * DKIM2 verification result, false unless the `dkim2` option is set
875
+ */
876
+ dkim2: DKIM2VerifyResult | false;
877
+
821
878
  /**
822
879
  * SPF verification result
823
880
  */
@@ -870,9 +927,9 @@ export function authenticate(input: MessageInput, opts?: AuthenticateOptions): P
870
927
  // ============================================================================
871
928
 
872
929
  /**
873
- * DKIM signing options
930
+ * A single DKIM signature to create, an entry of `DKIMSignOptions.signatureData`
874
931
  */
875
- export interface DKIMSignOptions {
932
+ export interface DKIMSignatureData {
876
933
  /**
877
934
  * Signing domain (d= tag)
878
935
  */
@@ -884,10 +941,44 @@ export interface DKIMSignOptions {
884
941
  selector: string;
885
942
 
886
943
  /**
887
- * Private key for signing (PEM format)
944
+ * Private key for signing (PEM format). An entry without a key is not signed and gets an
945
+ * `ENOKEY` error
888
946
  */
889
947
  privateKey: string | Buffer;
890
948
 
949
+ /**
950
+ * Overrides the shared `canonicalization`
951
+ */
952
+ canonicalization?: string;
953
+
954
+ /**
955
+ * Overrides the shared `algorithm`
956
+ */
957
+ algorithm?: string;
958
+
959
+ /**
960
+ * Overrides the shared `headerList`
961
+ */
962
+ headerList?: string[] | string;
963
+
964
+ /**
965
+ * Maximum body length to sign (l= tag)
966
+ */
967
+ maxBodyLength?: number;
968
+
969
+ /**
970
+ * Identity (i= tag), an address whose domain is the signing domain or its subdomain.
971
+ * Given unencoded, it is written as dkim-quoted-printable (`jõgi@example.com` as
972
+ * `i=j=C3=B5gi@example.com`)
973
+ */
974
+ identity?: string;
975
+ }
976
+
977
+ /**
978
+ * DKIM signing options. The values of each signature are set in `signatureData`, the other
979
+ * options apply to all of them
980
+ */
981
+ export interface DKIMSignOptions {
891
982
  /**
892
983
  * Canonicalization algorithm (default: 'relaxed/relaxed')
893
984
  * Format: 'header/body' where each can be 'simple' or 'relaxed'
@@ -903,7 +994,9 @@ export interface DKIMSignOptions {
903
994
 
904
995
  /**
905
996
  * 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
997
+ * From, Subject, Date, To, etc. Set per signature in `signatureData` or for all of them.
998
+ * A name listed more than once is over-signed: it appears in h= as many times as it is
999
+ * listed or as the message has that field, whichever is more (`'From:From'`)
907
1000
  */
908
1001
  headerList?: string[] | string;
909
1002
 
@@ -918,19 +1011,10 @@ export interface DKIMSignOptions {
918
1011
  expires?: Date | string | number;
919
1012
 
920
1013
  /**
921
- * Maximum body length to sign (l= tag)
1014
+ * The signatures to create. Without any, nothing is signed and `errors` holds an
1015
+ * `ENOSIGNATURE` error
922
1016
  */
923
- maxBodyLength?: number;
924
-
925
- /**
926
- * Identity (i= tag), an address whose domain is the signing domain or its subdomain
927
- */
928
- identity?: string;
929
-
930
- /**
931
- * Multiple signature configurations
932
- */
933
- signatureData?: DKIMSignOptions[];
1017
+ signatureData: DKIMSignatureData[];
934
1018
 
935
1019
  /**
936
1020
  * Follow the RFCs exactly (default: false). Strict mode refuses rsa-sha1, RSA keys
@@ -1119,7 +1203,7 @@ export interface DKIMSignError {
1119
1203
  /**
1120
1204
  * Why the signature was not created. `err.code` is eg. 'ENOFROM', 'EINVALIDALGO',
1121
1205
  * 'EINVALIDTYPE', 'ESHORTKEY', 'EINVALIDDOMAIN', 'EINVALIDSELECTOR', 'EINVALIDIDENTITY',
1122
- * 'EINVALIDTIME', 'EINVALIDCANON' or 'EINVALIDINSTANCE'
1206
+ * 'EINVALIDTIME', 'EINVALIDCANON', 'EINVALIDINSTANCE', 'ENOKEY' or 'ENOSIGNATURE'
1123
1207
  */
1124
1208
  err: Error & { code?: string };
1125
1209
 
@@ -1138,9 +1222,11 @@ export interface DKIMSignError {
1138
1222
  * - `invalid-expiration`: the expiration is not after the signing time, or does not fit into x=
1139
1223
  * - `invalid-signtime`: the signing time does not fit into t=, which was left out
1140
1224
  * - `d-syntax`, `s-syntax`: d= or s= is not valid RFC 6376 syntax
1225
+ * - `identity-syntax`: the identity is not valid RFC 6376 i= syntax
1141
1226
  * - `identity-domain`: the identity domain is not the signing domain or its subdomain
1142
1227
  */
1143
- export type DKIMSignWarning = 'rsa-sha1' | 'weak-key' | 'invalid-expiration' | 'invalid-signtime' | 'd-syntax' | 's-syntax' | 'identity-domain';
1228
+ export type DKIMSignWarning =
1229
+ 'rsa-sha1' | 'weak-key' | 'invalid-expiration' | 'invalid-signtime' | 'd-syntax' | 's-syntax' | 'identity-syntax' | 'identity-domain';
1144
1230
 
1145
1231
  /**
1146
1232
  * DKIM signing result
@@ -1255,6 +1341,342 @@ export interface DKIMVerifyOptions {
1255
1341
  */
1256
1342
  export function dkimVerify(input: MessageInput, options?: DKIMVerifyOptions): Promise<DKIMVerifyResult>;
1257
1343
 
1344
+ // ============================================================================
1345
+ // DKIM2 (experimental, draft-ietf-dkim-dkim2-spec-06, draft-ietf-dkim-dkim2-dns-00)
1346
+ // ============================================================================
1347
+
1348
+ /**
1349
+ * A DKIM2 Recipe (draft-ietf-dkim-dkim2-spec-06 section 5). Each step either copies a range of
1350
+ * header fields (numbered from the bottom) or body lines (numbered from the top), or emits data
1351
+ */
1352
+ export type DKIM2RecipeStep = { c: [number, number] } | { d: string[] };
1353
+
1354
+ export interface DKIM2Recipe {
1355
+ /**
1356
+ * Steps per lower case header field name. An empty array removes every field of that name
1357
+ */
1358
+ h?: Record<string, DKIM2RecipeStep[]>;
1359
+
1360
+ /**
1361
+ * Steps for the body, or null when the previous body can not be recreated
1362
+ */
1363
+ b?: DKIM2RecipeStep[] | null;
1364
+ }
1365
+
1366
+ export interface DKIM2SignatureKey {
1367
+ /**
1368
+ * Key selector
1369
+ */
1370
+ selector: string;
1371
+
1372
+ /**
1373
+ * Private key, PEM or a raw 32 byte Ed25519 key
1374
+ */
1375
+ privateKey: string | Buffer;
1376
+
1377
+ /**
1378
+ * "rsa-sha256" or "ed25519-sha256", follows the key type when not set
1379
+ */
1380
+ algorithm?: 'rsa-sha256' | 'ed25519-sha256';
1381
+ }
1382
+
1383
+ /**
1384
+ * DKIM2 signing options
1385
+ */
1386
+ export interface DKIM2SignOptions {
1387
+ /**
1388
+ * Signing domain (d= tag)
1389
+ */
1390
+ signingDomain: string;
1391
+
1392
+ /**
1393
+ * Signing keys. At most two keys per algorithm, each with its own selector
1394
+ */
1395
+ signatureData?: DKIM2SignatureKey[];
1396
+
1397
+ /**
1398
+ * Selector of a single key, used with `privateKey` when `signatureData` is not set
1399
+ */
1400
+ selector?: string;
1401
+
1402
+ /**
1403
+ * A single private key, used with `selector` when `signatureData` is not set
1404
+ */
1405
+ privateKey?: string | Buffer;
1406
+
1407
+ /**
1408
+ * Algorithm of the single key
1409
+ */
1410
+ algorithm?: 'rsa-sha256' | 'ed25519-sha256';
1411
+
1412
+ /**
1413
+ * SMTP MAIL FROM the message is sent with (mf= tag), "" or "<>" for the null sender.
1414
+ * Required unless `nextDomain` is set
1415
+ */
1416
+ mailFrom?: string;
1417
+
1418
+ /**
1419
+ * SMTP RCPT TO addresses the message is sent to (rt= tag). Required unless `nextDomain` is set
1420
+ */
1421
+ rcptTo?: string | string[];
1422
+
1423
+ /**
1424
+ * Domain of the next DKIM2-Signature for an imaginary hop (nd= tag), instead of mailFrom and rcptTo
1425
+ */
1426
+ nextDomain?: string;
1427
+
1428
+ /**
1429
+ * Nonce (n= tag), at most 64 printable ASCII characters without ";"
1430
+ */
1431
+ nonce?: string;
1432
+
1433
+ /**
1434
+ * Flags (f= tag), such as "donotmodify", "donotexplode", "exploded", "feedback" or "feedhere"
1435
+ */
1436
+ flags?: string[];
1437
+
1438
+ /**
1439
+ * Hash algorithms of a new Message-Instance (default: ["sha256"])
1440
+ */
1441
+ hashAlgorithms?: ('sha256' | 'sha512')[];
1442
+
1443
+ /**
1444
+ * Recipe that recreates the previous instance of a message that was changed since its
1445
+ * highest Message-Instance. It is checked against that instance before signing
1446
+ */
1447
+ recipe?: DKIM2Recipe;
1448
+
1449
+ /**
1450
+ * Signing time (t= tag), defaults to now
1451
+ */
1452
+ signTime?: Date | string | number;
1453
+ }
1454
+
1455
+ /**
1456
+ * DKIM2 signing result
1457
+ */
1458
+ export interface DKIM2SignResult {
1459
+ /**
1460
+ * Header fields to prepend to the message, each ending with CRLF
1461
+ */
1462
+ signatures: string;
1463
+
1464
+ /**
1465
+ * The new DKIM2-Signature header field
1466
+ */
1467
+ signature: string;
1468
+
1469
+ /**
1470
+ * The new Message-Instance header field, null when the message did not change
1471
+ */
1472
+ messageInstance: string | null;
1473
+
1474
+ /**
1475
+ * i= of the new signature
1476
+ */
1477
+ i: number;
1478
+
1479
+ /**
1480
+ * m= of the new signature
1481
+ */
1482
+ m: number;
1483
+ }
1484
+
1485
+ /**
1486
+ * Signs a message with DKIM2. Rejects for invalid options, an invalid DKIM2 chain on the
1487
+ * message, or a recipe that does not recreate the previous instance
1488
+ */
1489
+ export function dkim2Sign(input: MessageInput, options: DKIM2SignOptions): Promise<DKIM2SignResult>;
1490
+
1491
+ /**
1492
+ * Transform stream that prepends the DKIM2 header fields to the message
1493
+ */
1494
+ export class Dkim2SignStream extends Transform {
1495
+ constructor(options: DKIM2SignOptions);
1496
+ }
1497
+
1498
+ /**
1499
+ * DKIM2 verification options
1500
+ */
1501
+ export interface DKIM2VerifyOptions {
1502
+ /**
1503
+ * Custom DNS resolver function
1504
+ */
1505
+ resolver?: DNSResolver;
1506
+
1507
+ /**
1508
+ * SMTP MAIL FROM of the delivery, "" for the null sender. Compared with mf= of the highest
1509
+ * DKIM2-Signature when set
1510
+ */
1511
+ mailFrom?: string;
1512
+
1513
+ /**
1514
+ * SMTP RCPT TO addresses of the delivery, each of which has to be listed in rt= of the highest
1515
+ * DKIM2-Signature
1516
+ */
1517
+ rcptTo?: string | string[];
1518
+
1519
+ /**
1520
+ * Seconds after t= when a signature expires (default: 1209600, 14 days), false to not check
1521
+ */
1522
+ maxSignatureAge?: number | false;
1523
+
1524
+ /**
1525
+ * Seconds a t= may be ahead of the current time (default: 300), false to not check. Section 8.4
1526
+ * allows ignoring signatures from the future
1527
+ */
1528
+ maxFutureTime?: number | false;
1529
+
1530
+ /**
1531
+ * Time to verify against (default: now)
1532
+ */
1533
+ curTime?: Date | string | number;
1534
+
1535
+ /**
1536
+ * Most Message-Instance, and most DKIM2-Signature, header fields processed (default: 20)
1537
+ */
1538
+ maxInstances?: number;
1539
+
1540
+ /**
1541
+ * Called with the replay key (the m=1 hashes) of the message. Return true when a message with
1542
+ * the same key was seen before; without an exploded flag the result is then FAIL
1543
+ */
1544
+ checkReplay?: (replay: { key: string; exploded: boolean }) => Promise<boolean> | boolean;
1545
+ }
1546
+
1547
+ export type DKIM2Result = 'pass' | 'fail' | 'permerror' | 'temperror';
1548
+
1549
+ export type DKIM2Warning = 'mail-from-not-checked' | 'rcpt-to-not-checked';
1550
+
1551
+ export interface DKIM2Error {
1552
+ result: DKIM2Result;
1553
+
1554
+ /**
1555
+ * Human-readable string of draft-ietf-dkim-dkim2-spec-06 section 11
1556
+ */
1557
+ message: string;
1558
+
1559
+ /**
1560
+ * i= of the DKIM2-Signature the error is reported against
1561
+ */
1562
+ i?: number;
1563
+
1564
+ /**
1565
+ * m= of the Message-Instance the error is about
1566
+ */
1567
+ m?: number;
1568
+ }
1569
+
1570
+ export interface DKIM2InstanceResult {
1571
+ m: number;
1572
+ hashes: { algorithm: string; header: 'pass' | 'fail'; body: 'pass' | 'fail' | 'unknown' }[];
1573
+ recipe?: { headers: string[]; body: 'unchanged' | 'recipe' | 'unrecoverable' };
1574
+ }
1575
+
1576
+ export interface DKIM2SignatureValueResult {
1577
+ selector: string;
1578
+ algorithm: string;
1579
+
1580
+ /**
1581
+ * "none" for an algorithm that is not supported and ignored
1582
+ */
1583
+ result?: DKIM2Result | 'none';
1584
+ comment?: string;
1585
+ rr?: string;
1586
+ modulusLength?: number;
1587
+
1588
+ /**
1589
+ * The key record has the t=y testing flag
1590
+ */
1591
+ testing?: boolean;
1592
+ }
1593
+
1594
+ export interface DKIM2SignatureResult {
1595
+ i: number;
1596
+ m: number;
1597
+ signingDomain: string;
1598
+ timestamp: number;
1599
+ signTime: string;
1600
+ flags: string[];
1601
+ mailFrom?: string;
1602
+ rcptTo?: string[];
1603
+ nextDomain?: string;
1604
+ nonce?: string;
1605
+ status: { result: DKIM2Result | 'skipped'; comment?: string };
1606
+ values: DKIM2SignatureValueResult[];
1607
+ }
1608
+
1609
+ /**
1610
+ * DKIM2 verification result
1611
+ */
1612
+ export interface DKIM2VerifyResult {
1613
+ status: {
1614
+ result: DKIM2Result | 'none';
1615
+
1616
+ /**
1617
+ * The human-readable string of the reported failure
1618
+ */
1619
+ comment?: string;
1620
+ header?: { d?: string; i?: number };
1621
+
1622
+ /**
1623
+ * The parts of the SMTP envelope that were not given, so the highest DKIM2-Signature was
1624
+ * not compared with them and an unchanged copy sent to someone else would pass as well.
1625
+ * Only present when there is something to report, never written into `info`
1626
+ */
1627
+ warnings?: DKIM2Warning[];
1628
+ };
1629
+
1630
+ /**
1631
+ * Authentication-Results resinfo
1632
+ */
1633
+ info: string;
1634
+ errors: DKIM2Error[];
1635
+ instances: DKIM2InstanceResult[];
1636
+ signatures: DKIM2SignatureResult[];
1637
+
1638
+ /**
1639
+ * The key for replay detection and whether a signature allows copies
1640
+ */
1641
+ replay?: { key: string; exploded: boolean };
1642
+ }
1643
+
1644
+ /**
1645
+ * DKIM2 hashing options
1646
+ */
1647
+ export interface DKIM2HashOptions {
1648
+ /**
1649
+ * Hash algorithms (default: ["sha256"])
1650
+ */
1651
+ algorithms?: ('sha256' | 'sha512')[];
1652
+ }
1653
+
1654
+ /**
1655
+ * DKIM2 hashes of a message
1656
+ */
1657
+ export interface DKIM2HashResult {
1658
+ /**
1659
+ * Names of the hashed header fields, in the order they are hashed
1660
+ */
1661
+ headers: string[];
1662
+
1663
+ /**
1664
+ * The hash-sets of the h= tag of a Message-Instance
1665
+ */
1666
+ hashes: { algorithm: 'sha256' | 'sha512'; headerHash: string; bodyHash: string }[];
1667
+ }
1668
+
1669
+ /**
1670
+ * Computes the DKIM2 header and body hashes of a message, as they go into the h= tag of a
1671
+ * Message-Instance header field. Rejects for an unsupported hash algorithm
1672
+ */
1673
+ export function dkim2Hash(input: MessageInput, options?: DKIM2HashOptions): Promise<DKIM2HashResult>;
1674
+
1675
+ /**
1676
+ * Verifies the DKIM2 header fields of a message
1677
+ */
1678
+ export function dkim2Verify(input: MessageInput, options?: DKIM2VerifyOptions): Promise<DKIM2VerifyResult>;
1679
+
1258
1680
  // ============================================================================
1259
1681
  // SPF
1260
1682
  // ============================================================================
@@ -1337,6 +1759,13 @@ export interface DMARCOptions {
1337
1759
  */
1338
1760
  fromFields?: number;
1339
1761
 
1762
+ /**
1763
+ * Syntax of the From header field the addresses came from (see DKIMVerifyResult.fromSyntax).
1764
+ * With "invalid", or "lax" in strict mode, DMARC validation is not possible (reason
1765
+ * "invalid-author-domain"). "lax" adds the "from-syntax" warning in the default mode
1766
+ */
1767
+ fromSyntax?: 'valid' | 'lax' | 'invalid';
1768
+
1340
1769
  /**
1341
1770
  * Domains that passed SPF
1342
1771
  */
@@ -1398,6 +1827,13 @@ export interface ARCData {
1398
1827
  * Error encountered during ARC chain parsing
1399
1828
  */
1400
1829
  error?: Error;
1830
+
1831
+ /**
1832
+ * The lower case cv= of the newest ARC-Seal in the header fields, or false if there is
1833
+ * none, set by the DKIM verifier. Read from the header fields even when the chain could not
1834
+ * be parsed, to decide whether a failed chain is sealed with cv=fail
1835
+ */
1836
+ latestCv?: string | false;
1401
1837
  }
1402
1838
 
1403
1839
  /**
@@ -1409,7 +1845,9 @@ export interface ARCSealError {
1409
1845
  * instance out of 1-50, a chain that already has 50 sets, one that already exists, or in
1410
1846
  * strict mode one that leaves a gap),
1411
1847
  * 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)
1848
+ * EINVALIDALGO and EINVALIDTYPE (an algorithm that is not supported or does not match the key),
1849
+ * EINVALIDAUTHRESULTS (a missing or empty authResults, or one with a line break that is not
1850
+ * CRLF folding)
1413
1851
  */
1414
1852
  err: Error & { code?: string };
1415
1853
  type?: string;
@@ -1516,11 +1954,14 @@ export function verifyASChain(data: ARCData, opts: ARCOptions & { warnings?: str
1516
1954
  *
1517
1955
  * @param input - RFC822 formatted message or false for pre-calculated data
1518
1956
  * @param data - Seal creation data
1519
- * @returns Seal headers (empty when no set was created), the errors that say why, and what the
1957
+ * @returns Seal headers (empty when no set was created), the errors that say why, what the
1520
1958
  * default mode sealed that strict mode would have refused ('arc-instance-gap',
1521
- * 'arc-cv-instance', and the DKIM signing warnings)
1959
+ * 'arc-cv-instance', and the DKIM signing warnings), and the instance (i=) of a created set
1522
1960
  */
1523
- export function createSeal(input: MessageInput | false, data: ARCCreateSealData): Promise<{ headers: string[]; errors: ARCSealError[]; warnings: string[] }>;
1961
+ export function createSeal(
1962
+ input: MessageInput | false,
1963
+ data: ARCCreateSealData
1964
+ ): Promise<{ headers: string[]; errors: ARCSealError[]; warnings: string[]; instance?: number }>;
1524
1965
 
1525
1966
  // ============================================================================
1526
1967
  // BIMI
@@ -1551,8 +1992,10 @@ export interface BIMIOptions {
1551
1992
  resolver?: DNSResolver;
1552
1993
 
1553
1994
  /**
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
1995
+ * Parse the Assertion Record exactly (default: false): `v=BIMI1` and the tag names are
1996
+ * case-sensitive, syntax errors such as duplicate tags fail the record, and so do an `l=` URI with
1997
+ * another image format suffix and unencoded commas in URIs. Without it these are accepted and
1998
+ * reported in `warnings`. Also formats the Authentication-Results entry in the RFC 8601 section 2.2 form
1556
1999
  */
1557
2000
  strict?: boolean;
1558
2001
  }
@@ -1589,6 +2032,11 @@ export interface VMCValidationResult {
1589
2032
  */
1590
2033
  code?: string;
1591
2034
 
2035
+ /**
2036
+ * Additional error details, for `SVG_VALIDATION_FAILED` the message and code of the SVG validation error
2037
+ */
2038
+ details?: any;
2039
+
1592
2040
  /**
1593
2041
  * Redirect URL if logo location redirected
1594
2042
  */
@@ -1596,7 +2044,8 @@ export interface VMCValidationResult {
1596
2044
  };
1597
2045
 
1598
2046
  /**
1599
- * Base64-encoded SVG logo file content
2047
+ * Base64-encoded SVG logo file content, uncompressed if the file was SVGZ. Set only when the
2048
+ * file passed SVG validation
1600
2049
  */
1601
2050
  logoFile?: string;
1602
2051
 
@@ -1665,6 +2114,44 @@ export interface VMCValidationResult {
1665
2114
  */
1666
2115
  hashMatch?: boolean;
1667
2116
  };
2117
+
2118
+ /**
2119
+ * The BIMI status of `bimiData` updated with the validation results (7.7): `fail` with a comment
2120
+ * when the indicator or the evidence document failed, `policy.authority` set to `pass` or `fail`
2121
+ * when an evidence document was checked. Set only when `bimiData.status.result` was `pass`
2122
+ */
2123
+ status?: BIMIResult['status'];
2124
+
2125
+ /**
2126
+ * Authentication-Results entry for the updated status, use it instead of the `info` of `bimi()`
2127
+ */
2128
+ info?: string;
2129
+
2130
+ /**
2131
+ * SVG Tiny PS profile deviations of the logo files that were accepted because `strict` was not set,
2132
+ * for example 'svg-version'
2133
+ */
2134
+ warnings?: string[];
2135
+
2136
+ /**
2137
+ * BIMI headers for the message, set only when the logo and the evidence document (if any) validated
2138
+ */
2139
+ headers?: {
2140
+ /**
2141
+ * BIMI-Indicator header with the base64-encoded SVG logo
2142
+ */
2143
+ indicator: string;
2144
+
2145
+ /**
2146
+ * BIMI-Location header, with a= when the evidence document was verified
2147
+ */
2148
+ location: string;
2149
+
2150
+ /**
2151
+ * BIMI-Logo-Preference header
2152
+ */
2153
+ preference?: string;
2154
+ };
1668
2155
  }
1669
2156
 
1670
2157
  /**
@@ -1679,6 +2166,33 @@ export interface BIMIData extends BIMIResult {
1679
2166
  * VMC validation options
1680
2167
  */
1681
2168
  export interface VMCValidationOptions {
2169
+ /**
2170
+ * Validate the logo files exactly against the SVG Tiny PS profile (default: false). Without it,
2171
+ * the deviations that do not make a logo unsafe are accepted and listed in `warnings`. Also
2172
+ * formats `info` in the RFC 8601 section 2.2 form
2173
+ */
2174
+ strict?: boolean;
2175
+
2176
+ /**
2177
+ * Maximum size of the logo file in bytes, also applied to an uncompressed SVGZ file (default: 65536)
2178
+ */
2179
+ maxLogoSize?: number;
2180
+
2181
+ /**
2182
+ * Maximum size of the evidence document in bytes (default: 262144)
2183
+ */
2184
+ maxEvidenceSize?: number;
2185
+
2186
+ /**
2187
+ * Time limit for each download in milliseconds, including redirects and the body (default: 30000)
2188
+ */
2189
+ timeout?: number;
2190
+
2191
+ /**
2192
+ * undici Dispatcher used for the downloads
2193
+ */
2194
+ dispatcher?: any;
2195
+
1682
2196
  /**
1683
2197
  * Custom VMC validation options passed to @postalsys/vmc
1684
2198
  */
@@ -1702,13 +2216,30 @@ export function bimi(opts: BIMIOptions): Promise<BIMIResult | false>;
1702
2216
  */
1703
2217
  export function validateBimiVmc(bimiData: BIMIData | null, opts?: VMCValidationOptions): Promise<VMCValidationResult | false>;
1704
2218
 
2219
+ /**
2220
+ * SVG logo validation options
2221
+ */
2222
+ export interface BIMISvgValidationOptions {
2223
+ /**
2224
+ * Apply the SVG Tiny PS profile exactly (default: false)
2225
+ */
2226
+ strict?: boolean;
2227
+
2228
+ /**
2229
+ * Markers of the accepted profile deviations are added to this array when `strict` is not set
2230
+ */
2231
+ warnings?: string[];
2232
+ }
2233
+
1705
2234
  /**
1706
2235
  * Validates BIMI SVG logo file
1707
2236
  *
1708
2237
  * @param logo - SVG logo file buffer
2238
+ * @param opts - validation options
2239
+ * @returns true
1709
2240
  * @throws Error if validation fails
1710
2241
  */
1711
- export function validateBimiSvg(logo: Buffer): void;
2242
+ export function validateBimiSvg(logo: Buffer | string, opts?: BIMISvgValidationOptions): true;
1712
2243
 
1713
2244
  // ============================================================================
1714
2245
  // MTA-STS
@@ -1752,6 +2283,16 @@ export interface MTASTSPolicy {
1752
2283
  * Error encountered during policy fetch
1753
2284
  */
1754
2285
  error?: Error;
2286
+
2287
+ /**
2288
+ * Policy ID from DNS that could not be fetched, set on a cached policy that is kept in use instead
2289
+ */
2290
+ retryId?: string;
2291
+
2292
+ /**
2293
+ * ISO 8601 timestamp before which the policy for `retryId` is not fetched again (RFC 8461 3.3)
2294
+ */
2295
+ retryAfter?: string;
1755
2296
  }
1756
2297
 
1757
2298
  /**