mailauth 4.6.0 → 4.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.ncurc.js ADDED
@@ -0,0 +1,12 @@
1
+ module.exports = {
2
+ upgrade: true,
3
+ reject: [
4
+ 'marked',
5
+ 'marked-man',
6
+ // only works as ESM
7
+ 'chai',
8
+
9
+ // Fails in Node 16
10
+ 'undici'
11
+ ]
12
+ };
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.6.2](https://github.com/postalsys/mailauth/compare/v4.6.1...v4.6.2) (2024-01-25)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **bimi:** skip bimi with oversized DKIM signatures ([d666d74](https://github.com/postalsys/mailauth/commit/d666d7476cbcae8b3161c78a7e737559ad112fd9))
9
+
10
+ ## [4.6.1](https://github.com/postalsys/mailauth/compare/v4.6.0...v4.6.1) (2024-01-24)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **dkim-verify:** Show the length of the source body in DKIM results ([d28663b](https://github.com/postalsys/mailauth/commit/d28663b30b0bfaf07d395e9d3eaea044c9085657))
16
+
3
17
  ## [4.6.0](https://github.com/postalsys/mailauth/compare/v4.5.2...v4.6.0) (2023-11-02)
4
18
 
5
19
 
package/lib/bimi/index.js CHANGED
@@ -51,6 +51,13 @@ const lookup = async data => {
51
51
  return response;
52
52
  }
53
53
 
54
+ if (dmarc.alignment?.dkim?.overSized) {
55
+ response.status.result = 'skipped';
56
+ response.status.comment = 'Oversized DKIM signature';
57
+ response.info = formatAuthHeaderRow('bimi', response.status);
58
+ return response;
59
+ }
60
+
54
61
  const authorDomain = dmarc.status?.header?.from;
55
62
  const orgDomain = dmarc.domain;
56
63
 
@@ -26,9 +26,14 @@ class RelaxedHash {
26
26
  this.bodyHash = crypto.createHash(algorithm);
27
27
 
28
28
  this.remainder = false;
29
- this.byteLength = 0;
30
29
 
30
+ // total body size
31
+ this.byteLength = 0;
32
+ // total canonicalized body size
33
+ this.canonicalizedLength = 0;
34
+ // hashed canonicalized body size (after l= tag)
31
35
  this.bodyHashedBytes = 0;
36
+
32
37
  this.maxBodyLength = maxBodyLength;
33
38
 
34
39
  this.maxSizeReached = maxBodyLength === 0;
@@ -37,6 +42,8 @@ class RelaxedHash {
37
42
  }
38
43
 
39
44
  _updateBodyHash(chunk) {
45
+ this.canonicalizedLength += chunk.length;
46
+
40
47
  if (this.maxSizeReached) {
41
48
  return;
42
49
  }
@@ -18,8 +18,12 @@ class SimpleHash {
18
18
  this.bodyHash = crypto.createHash(algorithm);
19
19
 
20
20
  this.remainder = [];
21
- this.byteLength = 0;
22
21
 
22
+ // total body size
23
+ this.byteLength = 0;
24
+ // total canonicalized body size
25
+ this.canonicalizedLength = 0;
26
+ // hashed canonicalized body size (after l= tag)
23
27
  this.bodyHashedBytes = 0;
24
28
 
25
29
  this.maxBodyLength = maxBodyLength;
@@ -29,6 +33,8 @@ class SimpleHash {
29
33
  }
30
34
 
31
35
  _updateBodyHash(chunk) {
36
+ this.canonicalizedLength += chunk.length;
37
+
32
38
  if (this.maxSizeReached) {
33
39
  return;
34
40
  }
@@ -259,7 +259,9 @@ class DkimSigner extends MessageParser {
259
259
  // value for the l= tag (if needed)
260
260
  typeof signatureData.maxBodyLength === 'number'
261
261
  ? {
262
- bodyHashedBytes: this.bodyHashes.get(hashKey).hasher.bodyHashedBytes
262
+ bodyHashedBytes: this.bodyHashes.get(hashKey).hasher.bodyHashedBytes,
263
+ canonicalizedLength: this.bodyHashes.get(hashKey).hasher.canonicalizedLength,
264
+ sourceBodyLength: this.bodyHashes.get(hashKey).hasher.byteLength
263
265
  }
264
266
  : {}
265
267
  )
@@ -7,6 +7,7 @@ const { generateCanonicalizedHeader } = require('./header');
7
7
  const { getARChain } = require('../arc');
8
8
  const addressparser = require('nodemailer/lib/addressparser');
9
9
  const crypto = require('crypto');
10
+ const { v4: uuidv4 } = require('uuid');
10
11
 
11
12
  class DkimVerifier extends MessageParser {
12
13
  constructor(options) {
@@ -204,7 +205,9 @@ class DkimVerifier extends MessageParser {
204
205
  };
205
206
 
206
207
  if (signatureHeader.type === 'DKIM' && this.headerFrom?.length) {
207
- status.aligned = this.headerFrom?.length ? getAlignment(this.headerFrom[0].split('@').pop(), [signatureHeader.signingDomain]) : false;
208
+ status.aligned = this.headerFrom?.length
209
+ ? getAlignment(this.headerFrom[0].split('@').pop(), [signatureHeader.signingDomain])?.domain || false
210
+ : false;
208
211
  }
209
212
 
210
213
  let bodyHash = this.bodyHashes.get(signatureHeader.bodyHashKey)?.hash;
@@ -296,6 +299,8 @@ class DkimVerifier extends MessageParser {
296
299
  }
297
300
 
298
301
  signatureHeader.bodyHashedBytes = this.bodyHashes.get(signatureHeader.bodyHashKey)?.bodyHashedBytes;
302
+ signatureHeader.canonicalizedLength = this.bodyHashes.get(signatureHeader.bodyHashKey)?.canonicalizedLength;
303
+ signatureHeader.sourceBodyLength = this.bodyHashes.get(signatureHeader.bodyHashKey)?.byteLength;
299
304
 
300
305
  if (typeof signatureHeader.maxBodyLength === 'number' && signatureHeader.maxBodyLength !== signatureHeader.bodyHashedBytes) {
301
306
  status.result = 'fail';
@@ -303,6 +308,9 @@ class DkimVerifier extends MessageParser {
303
308
  }
304
309
 
305
310
  let result = {
311
+ id: signatureHeader.parsed?.b?.value
312
+ ? crypto.createHash('sha256').update(Buffer.from(signatureHeader.parsed?.b?.value, 'base64')).digest('hex')
313
+ : uuidv4(),
306
314
  signingDomain: signatureHeader.signingDomain,
307
315
  selector: signatureHeader.selector,
308
316
  signature: signatureHeader.parsed?.b?.value,
@@ -314,12 +322,26 @@ class DkimVerifier extends MessageParser {
314
322
  status
315
323
  };
316
324
 
325
+ if (typeof signatureHeader.sourceBodyLength === 'number') {
326
+ result.sourceBodyLength = signatureHeader.sourceBodyLength;
327
+ }
328
+
317
329
  if (typeof signatureHeader.bodyHashedBytes === 'number') {
318
330
  result.canonBodyLength = signatureHeader.bodyHashedBytes;
319
331
  }
320
332
 
333
+ if (typeof signatureHeader.canonicalizedLength === 'number') {
334
+ result.canonBodyLengthTotal = signatureHeader.canonicalizedLength;
335
+ }
336
+
321
337
  if (typeof signatureHeader.maxBodyLength === 'number') {
322
- result.bodyLengthCount = signatureHeader.maxBodyLength;
338
+ result.canonBodyLengthLimited = true;
339
+ result.canonBodyLengthLimit = signatureHeader.maxBodyLength;
340
+ if (result.canonBodyLengthTotal > result.canonBodyLength) {
341
+ status.overSized = result.canonBodyLengthTotal - result.canonBodyLength;
342
+ }
343
+ } else {
344
+ result.canonBodyLengthLimited = false;
323
345
  }
324
346
 
325
347
  if (publicKey) {
@@ -101,8 +101,8 @@ const verifyDmarc = async opts => {
101
101
  rr: dmarcRecord.rr,
102
102
 
103
103
  alignment: {
104
- spf: { result: spfAlignment, strict: dmarcRecord.aspf === 's' },
105
- dkim: { result: dkimAlignment, strict: dmarcRecord.adkim === 's' }
104
+ spf: { result: spfAlignment?.domain, strict: dmarcRecord.aspf === 's' },
105
+ dkim: { result: dkimAlignment?.domain, strict: dmarcRecord.adkim === 's', overSized: dkimAlignment?.overSized }
106
106
  }
107
107
  });
108
108
  };
package/lib/mailauth.js CHANGED
@@ -119,7 +119,14 @@ const authenticate = async (input, opts) => {
119
119
  dmarcResult = await dmarc({
120
120
  headerFrom: dkimResult.headerFrom,
121
121
  spfDomains: [].concat((spfResult && spfResult.status.result === 'pass' && spfResult.domain) || []),
122
- dkimDomains: (dkimResult.results || []).filter(r => r.status.result === 'pass').map(r => r.signingDomain),
122
+ dkimDomains: (dkimResult.results || [])
123
+ .filter(r => r.status.result === 'pass')
124
+ .map(r => ({
125
+ id: r.id,
126
+ domain: r.signingDomain,
127
+ aligned: r.status.aligned,
128
+ overSized: r.status.overSized
129
+ })),
123
130
  arcResult,
124
131
  resolver: opts.resolver
125
132
  });
package/lib/tools.js CHANGED
@@ -398,6 +398,10 @@ const formatAuthHeaderRow = (method, status) => {
398
398
 
399
399
  parts.push(`${method}=${status.result || 'none'}`);
400
400
 
401
+ if (status.overSized) {
402
+ parts.push(`(${escapeCommentValue(`oversized signature ${status.overSized}B`)})`);
403
+ }
404
+
401
405
  if (status.comment) {
402
406
  parts.push(`(${escapeCommentValue(status.comment)})`);
403
407
  }
@@ -443,23 +447,32 @@ const formatDomain = domain => {
443
447
  };
444
448
 
445
449
  const getAlignment = (fromDomain, domainList, strict) => {
446
- domainList = [].concat(domainList || []);
450
+ domainList = []
451
+ .concat(domainList || [])
452
+ .map(entry => {
453
+ if (typeof entry === 'string') {
454
+ return { domain: entry };
455
+ }
456
+ return entry;
457
+ })
458
+ .sort((a, b) => (a.overSized || 0) - (b.overSized || 0));
459
+
447
460
  if (strict) {
448
461
  fromDomain = formatDomain(fromDomain);
449
- for (let domain of domainList) {
450
- domain = formatDomain(psl.get(domain) || domain);
462
+ for (let entry of domainList) {
463
+ let domain = formatDomain(psl.get(entry.domain) || entry.domain);
451
464
  if (formatDomain(domain) === fromDomain) {
452
- return domain;
465
+ return entry;
453
466
  }
454
467
  }
455
468
  }
456
469
 
457
470
  // match org domains
458
471
  fromDomain = formatDomain(psl.get(fromDomain) || fromDomain);
459
- for (let domain of domainList) {
460
- domain = formatDomain(psl.get(domain) || domain);
472
+ for (let entry of domainList) {
473
+ let domain = formatDomain(psl.get(entry.domain) || entry.domain);
461
474
  if (domain === fromDomain) {
462
- return domain;
475
+ return entry;
463
476
  }
464
477
  }
465
478
 
package/man/mailauth.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH "MAILAUTH" "1" "November 2023" "v4.6.0" "Mailauth Help"
1
+ .TH "MAILAUTH" "1" "January 2024" "v4.6.2" "Mailauth Help"
2
2
  .SH "NAME"
3
3
  \fBmailauth\fR
4
4
  .QP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailauth",
3
- "version": "4.6.0",
3
+ "version": "4.6.2",
4
4
  "description": "Email authentication library for Node.js",
5
5
  "main": "lib/mailauth.js",
6
6
  "scripts": {
@@ -33,28 +33,30 @@
33
33
  },
34
34
  "homepage": "https://github.com/postalsys/mailauth",
35
35
  "devDependencies": {
36
- "chai": "4.3.10",
37
- "eslint": "8.52.0",
36
+ "chai": "4.4.1",
37
+ "eslint": "8.56.0",
38
38
  "eslint-config-nodemailer": "1.2.0",
39
- "eslint-config-prettier": "9.0.0",
39
+ "eslint-config-prettier": "9.1.0",
40
40
  "js-yaml": "4.1.0",
41
41
  "license-report": "6.5.0",
42
42
  "marked": "0.7.0",
43
43
  "marked-man": "0.7.0",
44
44
  "mbox-reader": "1.1.5",
45
45
  "mocha": "10.2.0",
46
+ "npm-check-updates": "16.14.12",
46
47
  "pkg": "5.8.1"
47
48
  },
48
49
  "dependencies": {
49
50
  "@postalsys/vmc": "1.0.6",
50
- "fast-xml-parser": "4.3.2",
51
+ "fast-xml-parser": "4.3.3",
51
52
  "ipaddr.js": "2.1.0",
52
- "joi": "17.11.0",
53
+ "joi": "17.12.0",
53
54
  "libmime": "5.2.1",
54
- "nodemailer": "6.9.7",
55
+ "nodemailer": "6.9.8",
55
56
  "psl": "1.9.0",
56
57
  "punycode": "2.3.1",
57
- "undici": "5.27.0",
58
+ "undici": "5.28.2",
59
+ "uuid": "9.0.1",
58
60
  "yargs": "17.7.2"
59
61
  },
60
62
  "engines": {
package/.ncurc.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "upgrade": true,
3
- "reject": ["marked", "marked-man"]
4
- }