mailauth 4.6.4 → 4.6.5
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/CHANGELOG.md +7 -0
- package/lib/dkim/body/relaxed.js +16 -0
- package/lib/dkim/body/simple.js +16 -0
- package/lib/dkim/dkim-verifier.js +26 -1
- package/lib/dkim/mime-structure-start-finder.js +85 -0
- package/man/mailauth.1 +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.6.5](https://github.com/postalsys/mailauth/compare/v4.6.4...v4.6.5) (2024-02-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **dkim:** Added new output property mimeStructureStart ([8f25353](https://github.com/postalsys/mailauth/commit/8f25353fa6a67ba3e1f0c5091325007b2434a29d))
|
|
9
|
+
|
|
3
10
|
## [4.6.4](https://github.com/postalsys/mailauth/compare/v4.6.3...v4.6.4) (2024-02-05)
|
|
4
11
|
|
|
5
12
|
|
package/lib/dkim/body/relaxed.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const crypto = require('crypto');
|
|
6
|
+
const { MimeStructureStartFinder } = require('../mime-structure-start-finder');
|
|
6
7
|
|
|
7
8
|
const CHAR_CR = 0x0d;
|
|
8
9
|
const CHAR_LF = 0x0a;
|
|
@@ -39,9 +40,20 @@ class RelaxedHash {
|
|
|
39
40
|
this.maxSizeReached = maxBodyLength === 0;
|
|
40
41
|
|
|
41
42
|
this.emptyLinesQueue = [];
|
|
43
|
+
|
|
44
|
+
this.mimeStructureStartFinder = new MimeStructureStartFinder();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setContentType(contentTypeObj) {
|
|
48
|
+
if (/^multipart\//i.test(contentTypeObj.value) && contentTypeObj.params.boundary) {
|
|
49
|
+
this.mimeStructureStartFinder.setBoundary(contentTypeObj.params.boundary);
|
|
50
|
+
}
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
_updateBodyHash(chunk) {
|
|
54
|
+
// serach through the entire document, not just signed part
|
|
55
|
+
this.mimeStructureStartFinder.update(chunk);
|
|
56
|
+
|
|
45
57
|
this.canonicalizedLength += chunk.length;
|
|
46
58
|
|
|
47
59
|
if (this.maxSizeReached) {
|
|
@@ -270,6 +282,10 @@ class RelaxedHash {
|
|
|
270
282
|
// finalize
|
|
271
283
|
return this.bodyHash.digest(encoding);
|
|
272
284
|
}
|
|
285
|
+
|
|
286
|
+
getMimeStructureStart() {
|
|
287
|
+
return this.mimeStructureStartFinder.getMimeStructureStart();
|
|
288
|
+
}
|
|
273
289
|
}
|
|
274
290
|
|
|
275
291
|
module.exports = { RelaxedHash };
|
package/lib/dkim/body/simple.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
+
const { MimeStructureStartFinder } = require('../mime-structure-start-finder');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Class for calculating body hash of an email message body stream
|
|
@@ -30,9 +31,20 @@ class SimpleHash {
|
|
|
30
31
|
this.maxSizeReached = maxBodyLength === 0;
|
|
31
32
|
|
|
32
33
|
this.lastNewline = false;
|
|
34
|
+
|
|
35
|
+
this.mimeStructureStartFinder = new MimeStructureStartFinder();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setContentType(contentTypeObj) {
|
|
39
|
+
if (/^multipart\//i.test(contentTypeObj.value) && contentTypeObj.params.boundary) {
|
|
40
|
+
this.mimeStructureStartFinder.setBoundary(contentTypeObj.params.boundary);
|
|
41
|
+
}
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
_updateBodyHash(chunk) {
|
|
45
|
+
// serach through the entire document, not just signed part
|
|
46
|
+
this.mimeStructureStartFinder.update(chunk);
|
|
47
|
+
|
|
36
48
|
this.canonicalizedLength += chunk.length;
|
|
37
49
|
|
|
38
50
|
if (this.maxSizeReached) {
|
|
@@ -115,6 +127,10 @@ class SimpleHash {
|
|
|
115
127
|
|
|
116
128
|
return this.bodyHash.digest(encoding);
|
|
117
129
|
}
|
|
130
|
+
|
|
131
|
+
getMimeStructureStart() {
|
|
132
|
+
return this.mimeStructureStartFinder.getMimeStructureStart();
|
|
133
|
+
}
|
|
118
134
|
}
|
|
119
135
|
|
|
120
136
|
module.exports = { SimpleHash };
|
|
@@ -8,6 +8,7 @@ const { getARChain } = require('../arc');
|
|
|
8
8
|
const addressparser = require('nodemailer/lib/addressparser');
|
|
9
9
|
const crypto = require('crypto');
|
|
10
10
|
const { v4: uuidv4 } = require('uuid');
|
|
11
|
+
const libmime = require('libmime');
|
|
11
12
|
|
|
12
13
|
class DkimVerifier extends MessageParser {
|
|
13
14
|
constructor(options) {
|
|
@@ -147,6 +148,23 @@ class DkimVerifier extends MessageParser {
|
|
|
147
148
|
if (!this.bodyHashes.has(signatureHeader.bodyHashKey)) {
|
|
148
149
|
this.bodyHashes.set(signatureHeader.bodyHashKey, dkimBody(signatureHeader.bodyCanon, signatureHeader.hashAlgo, signatureHeader.maxBodyLength));
|
|
149
150
|
}
|
|
151
|
+
|
|
152
|
+
let headersArray = this.headers.parsed;
|
|
153
|
+
const findLastMethod = typeof headersArray.findLast === 'function' ? headersArray.findLast : headersArray.find;
|
|
154
|
+
if (typeof headersArray.findLast !== 'function') {
|
|
155
|
+
headersArray = [].concat(headersArray).reverse();
|
|
156
|
+
}
|
|
157
|
+
const contentTypeHeader = findLastMethod.call(headersArray, header => header.key === 'content-type');
|
|
158
|
+
if (contentTypeHeader) {
|
|
159
|
+
let line = contentTypeHeader.line.toString();
|
|
160
|
+
if (line.indexOf(':') >= 0) {
|
|
161
|
+
line = line.substring(line.indexOf(':') + 1).trim();
|
|
162
|
+
}
|
|
163
|
+
const parsedContentType = libmime.parseHeaderValue(line);
|
|
164
|
+
for (let hasher of this.bodyHashes.values()) {
|
|
165
|
+
hasher.setContentType(parsedContentType);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
150
168
|
}
|
|
151
169
|
}
|
|
152
170
|
|
|
@@ -165,6 +183,7 @@ class DkimVerifier extends MessageParser {
|
|
|
165
183
|
// convert bodyHashes from hash objects to base64 strings
|
|
166
184
|
for (let [key, bodyHash] of this.bodyHashes.entries()) {
|
|
167
185
|
this.bodyHashes.get(key).hash = bodyHash.digest('base64');
|
|
186
|
+
this.bodyHashes.get(key).mimeStructureStart = bodyHash.getMimeStructureStart();
|
|
168
187
|
}
|
|
169
188
|
|
|
170
189
|
for (let signatureHeader of this.signatureHeaders) {
|
|
@@ -210,7 +229,9 @@ class DkimVerifier extends MessageParser {
|
|
|
210
229
|
: false;
|
|
211
230
|
}
|
|
212
231
|
|
|
213
|
-
|
|
232
|
+
const bodyHash = this.bodyHashes.get(signatureHeader.bodyHashKey)?.hash;
|
|
233
|
+
const mimeStructureStart = this.bodyHashes.get(signatureHeader.bodyHashKey)?.mimeStructureStart;
|
|
234
|
+
|
|
214
235
|
if (signatureHeader.parsed?.bh?.value !== bodyHash) {
|
|
215
236
|
status.result = 'neutral';
|
|
216
237
|
status.comment = `body hash did not verify`;
|
|
@@ -344,6 +365,10 @@ class DkimVerifier extends MessageParser {
|
|
|
344
365
|
result.canonBodyLengthLimited = false;
|
|
345
366
|
}
|
|
346
367
|
|
|
368
|
+
if (typeof mimeStructureStart === 'number') {
|
|
369
|
+
result.mimeStructureStart = mimeStructureStart;
|
|
370
|
+
}
|
|
371
|
+
|
|
347
372
|
if (publicKey) {
|
|
348
373
|
result.publicKey = publicKey.toString();
|
|
349
374
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class MimeStructureStartFinder {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.byteCache = [];
|
|
6
|
+
|
|
7
|
+
this.matchFound = false;
|
|
8
|
+
this.noMatch = false;
|
|
9
|
+
this.lineStart = -1;
|
|
10
|
+
|
|
11
|
+
this.prevChunks = 0;
|
|
12
|
+
|
|
13
|
+
this.mimeStructureStart = -1;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
setBoundary(boundary) {
|
|
17
|
+
this.boundary = (boundary || '').toString().trim();
|
|
18
|
+
|
|
19
|
+
this.boundaryBuf = Array.from(Buffer.from(`--${this.boundary}`));
|
|
20
|
+
this.boundaryBufLen = this.boundaryBuf.length;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
update(chunk) {
|
|
24
|
+
if (this.matchFound || !this.boundary) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (let i = 0, bufLen = chunk.length; i < bufLen; i++) {
|
|
29
|
+
let c = chunk[i];
|
|
30
|
+
|
|
31
|
+
// check ending
|
|
32
|
+
if (c === 0x0a || c === 0x0d) {
|
|
33
|
+
if (!this.noMatch && this.byteCache.length === this.boundaryBufLen) {
|
|
34
|
+
// match found
|
|
35
|
+
this.matchFound = true;
|
|
36
|
+
this.mimeStructureStart = this.lineStart;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
// reset counter
|
|
40
|
+
this.lineStart = -1;
|
|
41
|
+
this.noMatch = false;
|
|
42
|
+
this.byteCache = [];
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (this.noMatch) {
|
|
47
|
+
// no need to look
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (this.lineStart < 0) {
|
|
52
|
+
this.lineStart = this.prevChunks + i;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (this.byteCache.length >= this.boundaryBufLen) {
|
|
56
|
+
this.noMatch = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const expectingByte = this.boundaryBuf[this.byteCache.length];
|
|
61
|
+
if (expectingByte !== c) {
|
|
62
|
+
this.noMatch = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
this.byteCache[this.byteCache.length] = c;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.prevChunks += chunk.length;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getMimeStructureStart() {
|
|
72
|
+
if (!this.boundary) {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!this.matchFound && !this.noMatch && this.byteCache.length === this.boundaryBufLen) {
|
|
77
|
+
this.matchFound = true;
|
|
78
|
+
this.mimeStructureStart = this.lineStart;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return this.mimeStructureStart;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { MimeStructureStartFinder };
|
package/man/mailauth.1
CHANGED