mailauth 4.6.8 → 4.7.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.
- package/CHANGELOG.md +15 -0
- package/README.md +46 -0
- package/lib/dkim/body/relaxed.js +20 -8
- package/lib/dkim/dkim-verifier.js +1 -2
- package/lib/dkim/sign.js +65 -1
- package/lib/gatherer-stream.js +2 -2
- package/man/mailauth.1 +1 -1
- package/package.json +7 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.7.0](https://github.com/postalsys/mailauth/compare/v4.6.9...v4.7.0) (2024-10-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **dkim-sign:** Added new Transfor stream class DkimSignStream to sign emails in a stream processing pipeline ([130a1a3](https://github.com/postalsys/mailauth/commit/130a1a3812fac2ad710f244510ca60887c2d33a9))
|
|
9
|
+
|
|
10
|
+
## [4.6.9](https://github.com/postalsys/mailauth/compare/v4.6.8...v4.6.9) (2024-08-22)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **deps:** Removed uuid dependency in favor of crypto.randomUUID() ([0b5d8f5](https://github.com/postalsys/mailauth/commit/0b5d8f5328d0b82f75daea7fdbd74e1e76e8b642))
|
|
16
|
+
* **dkim-relaxed:** Faster DKIM hash calculation for relaxed body if the body contains extremely long lines ([fd8c89e](https://github.com/postalsys/mailauth/commit/fd8c89edd87a114464f99ebf79a1e903a8287876))
|
|
17
|
+
|
|
3
18
|
## [4.6.8](https://github.com/postalsys/mailauth/compare/v4.6.7...v4.6.8) (2024-06-04)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
CHANGED
|
@@ -157,6 +157,52 @@ DKIM-Signature: a=rsa-sha256; v=1; c=relaxed/relaxed; d=tahvel.info;
|
|
|
157
157
|
From: ...
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
+
### Signing as a PassThrough Stream
|
|
161
|
+
|
|
162
|
+
Use `DkimSignStream` stream if you want to use DKIM signing as part of a stream processing pipeline.
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
const { DkimSignStream } = require('mailauth/lib/dkim/sign');
|
|
166
|
+
|
|
167
|
+
const dkimSignStream = new DkimSignStream({
|
|
168
|
+
// Optional, default canonicalization, default is "relaxed/relaxed"
|
|
169
|
+
canonicalization: 'relaxed/relaxed', // c=
|
|
170
|
+
|
|
171
|
+
// Optional, default signing and hashing algorithm
|
|
172
|
+
// Mostly useful when you want to use rsa-sha1, otherwise no need to set
|
|
173
|
+
algorithm: 'rsa-sha256',
|
|
174
|
+
|
|
175
|
+
// Optional, default is current time
|
|
176
|
+
signTime: new Date(), // t=
|
|
177
|
+
|
|
178
|
+
// Keys for one or more signatures
|
|
179
|
+
// Different signatures can use different algorithms (mostly useful when
|
|
180
|
+
// you want to sign a message both with RSA and Ed25519)
|
|
181
|
+
signatureData: [
|
|
182
|
+
{
|
|
183
|
+
signingDomain: 'tahvel.info', // d=
|
|
184
|
+
selector: 'test.rsa', // s=
|
|
185
|
+
// supported key types: RSA, Ed25519
|
|
186
|
+
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'),
|
|
187
|
+
|
|
188
|
+
// Optional algorithm, default is derived from the key.
|
|
189
|
+
// Overrides whatever was set in parent object
|
|
190
|
+
algorithm: 'rsa-sha256',
|
|
191
|
+
|
|
192
|
+
// Optional signature specifc canonicalization, overrides whatever was set in parent object
|
|
193
|
+
canonicalization: 'relaxed/relaxed' // c=
|
|
194
|
+
|
|
195
|
+
// Maximum number of canonicalized body bytes to sign (eg. the "l=" tag).
|
|
196
|
+
// Do not use though. This is available only for compatibility testing.
|
|
197
|
+
// maxBodyLength: 12345
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Writes a signed message to the output
|
|
203
|
+
process.stdin.pipe(dkimSignStream).pipe(process.stdout);
|
|
204
|
+
```
|
|
205
|
+
|
|
160
206
|
### Verifying
|
|
161
207
|
|
|
162
208
|
```js
|
package/lib/dkim/body/relaxed.js
CHANGED
|
@@ -122,24 +122,36 @@ class RelaxedHash {
|
|
|
122
122
|
this._updateBodyHash(chunk);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Performs the following modifications for a single line:
|
|
127
|
+
* - Replace all <LF> chars with <CR><LF>
|
|
128
|
+
* - Replace all spaces and tabs with a single space.
|
|
129
|
+
* - Remove trailing whitespace
|
|
130
|
+
* @param {Buffer} line
|
|
131
|
+
* @returns {Buffer} fixed line
|
|
132
|
+
*/
|
|
125
133
|
fixLineBuffer(line) {
|
|
126
|
-
|
|
134
|
+
// Allocate maximum expected buffer length
|
|
135
|
+
// If the line is only filled with <LF> bytes then we need 2 times the size of the line
|
|
136
|
+
let lineBuf = Buffer.alloc(line.length * 2);
|
|
137
|
+
// Start processing the line from the end to beginning
|
|
138
|
+
let writePos = lineBuf.length - 1;
|
|
127
139
|
|
|
128
140
|
let nonWspFound = false;
|
|
129
141
|
let prevWsp = false;
|
|
130
142
|
|
|
131
143
|
for (let i = line.length - 1; i >= 0; i--) {
|
|
132
144
|
if (line[i] === CHAR_LF) {
|
|
133
|
-
|
|
145
|
+
lineBuf[writePos--] = line[i];
|
|
134
146
|
if (i === 0 || line[i - 1] !== CHAR_CR) {
|
|
135
147
|
// add missing carriage return
|
|
136
|
-
|
|
148
|
+
lineBuf[writePos--] = CHAR_CR;
|
|
137
149
|
}
|
|
138
150
|
continue;
|
|
139
151
|
}
|
|
140
152
|
|
|
141
153
|
if (line[i] === CHAR_CR) {
|
|
142
|
-
|
|
154
|
+
lineBuf[writePos--] = line[i];
|
|
143
155
|
continue;
|
|
144
156
|
}
|
|
145
157
|
|
|
@@ -151,19 +163,19 @@ class RelaxedHash {
|
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
if (prevWsp) {
|
|
154
|
-
|
|
166
|
+
lineBuf[writePos--] = CHAR_SPACE;
|
|
155
167
|
prevWsp = false;
|
|
156
168
|
}
|
|
157
169
|
|
|
158
170
|
nonWspFound = true;
|
|
159
|
-
|
|
171
|
+
lineBuf[writePos--] = line[i];
|
|
160
172
|
}
|
|
161
173
|
|
|
162
174
|
if (prevWsp && nonWspFound) {
|
|
163
|
-
|
|
175
|
+
lineBuf[writePos--] = CHAR_SPACE;
|
|
164
176
|
}
|
|
165
177
|
|
|
166
|
-
return
|
|
178
|
+
return lineBuf.subarray(writePos + 1);
|
|
167
179
|
}
|
|
168
180
|
|
|
169
181
|
update(chunk, final) {
|
|
@@ -8,7 +8,6 @@ const { generateCanonicalizedHeader } = require('./header');
|
|
|
8
8
|
const { getARChain } = require('../arc');
|
|
9
9
|
const addressparser = require('nodemailer/lib/addressparser');
|
|
10
10
|
const crypto = require('node:crypto');
|
|
11
|
-
const { v4: uuidv4 } = require('uuid');
|
|
12
11
|
const libmime = require('libmime');
|
|
13
12
|
|
|
14
13
|
class DkimVerifier extends MessageParser {
|
|
@@ -333,7 +332,7 @@ class DkimVerifier extends MessageParser {
|
|
|
333
332
|
let result = {
|
|
334
333
|
id: signatureHeader.parsed?.b?.value
|
|
335
334
|
? crypto.createHash('sha256').update(Buffer.from(signatureHeader.parsed?.b?.value, 'base64')).digest('hex')
|
|
336
|
-
:
|
|
335
|
+
: crypto.randomUUID(),
|
|
337
336
|
signingDomain: signatureHeader.signingDomain,
|
|
338
337
|
selector: signatureHeader.selector,
|
|
339
338
|
signature: signatureHeader.parsed?.b?.value,
|
package/lib/dkim/sign.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { writeToStream } = require('../../lib/tools');
|
|
4
4
|
const { DkimSigner } = require('./dkim-signer');
|
|
5
|
+
const { Transform } = require('node:stream');
|
|
5
6
|
|
|
6
7
|
const dkimSign = async (input, options) => {
|
|
7
8
|
let dkimSigner = new DkimSigner(options);
|
|
@@ -10,4 +11,67 @@ const dkimSign = async (input, options) => {
|
|
|
10
11
|
return { signatures: dkimSigner.signatureHeaders.join('\r\n') + '\r\n', arc: dkimSigner.arc, errors: dkimSigner.errors };
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
class DkimSignStream extends Transform {
|
|
15
|
+
constructor(options) {
|
|
16
|
+
super(options);
|
|
17
|
+
this.signer = new DkimSigner(options);
|
|
18
|
+
|
|
19
|
+
this.chunks = [];
|
|
20
|
+
this.chunklen = 0;
|
|
21
|
+
|
|
22
|
+
this.errors = null;
|
|
23
|
+
|
|
24
|
+
this.finished = false;
|
|
25
|
+
this.finishCb = null;
|
|
26
|
+
this.signer.on('end', () => this.finishStream());
|
|
27
|
+
this.signer.on('finish', () => this.finishStream());
|
|
28
|
+
this.signer.on('error', err => {
|
|
29
|
+
this.finished = true;
|
|
30
|
+
this.destroy(err);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
finishStream() {
|
|
35
|
+
if (this.finished || !this.finishCb) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.finished = true;
|
|
39
|
+
let done = this.finishCb;
|
|
40
|
+
this.finishCb = null;
|
|
41
|
+
|
|
42
|
+
this.errors = this.signer.errors;
|
|
43
|
+
|
|
44
|
+
this.push(Buffer.from(this.signer.signatureHeaders.join('\r\n') + '\r\n'));
|
|
45
|
+
this.push(Buffer.concat(this.chunks, this.chunklen));
|
|
46
|
+
done();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_transform(chunk, encoding, done) {
|
|
50
|
+
if (!chunk || !chunk.length || this.finished) {
|
|
51
|
+
return done();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeof chunk === 'string') {
|
|
55
|
+
chunk = Buffer.from(chunk, encoding);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.chunks.push(chunk);
|
|
59
|
+
this.chunklen += chunk.length;
|
|
60
|
+
|
|
61
|
+
if (this.signer.write(chunk) === false) {
|
|
62
|
+
// wait for drain
|
|
63
|
+
return this.signer.once('drain', done);
|
|
64
|
+
}
|
|
65
|
+
done();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_flush(done) {
|
|
69
|
+
if (this.finished) {
|
|
70
|
+
return done();
|
|
71
|
+
}
|
|
72
|
+
this.finishCb = done;
|
|
73
|
+
this.signer.end();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { dkimSign, DkimSignStream };
|
package/lib/gatherer-stream.js
CHANGED
|
@@ -41,9 +41,9 @@ class GathererStream extends Transform {
|
|
|
41
41
|
return stream;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
_transform(chunk,
|
|
44
|
+
_transform(chunk, encoding, done) {
|
|
45
45
|
if (typeof chunk === 'string') {
|
|
46
|
-
chunk = Buffer.from(chunk,
|
|
46
|
+
chunk = Buffer.from(chunk, encoding);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
if (!chunk || !chunk.length) {
|
package/man/mailauth.1
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailauth",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"description": "Email authentication library for Node.js",
|
|
5
5
|
"main": "lib/mailauth.js",
|
|
6
6
|
"scripts": {
|
|
@@ -38,23 +38,22 @@
|
|
|
38
38
|
"eslint-config-nodemailer": "1.2.0",
|
|
39
39
|
"eslint-config-prettier": "9.1.0",
|
|
40
40
|
"js-yaml": "4.1.0",
|
|
41
|
-
"license-report": "6.
|
|
41
|
+
"license-report": "6.7.0",
|
|
42
42
|
"marked": "0.7.0",
|
|
43
43
|
"marked-man": "0.7.0",
|
|
44
44
|
"mbox-reader": "1.2.0",
|
|
45
|
-
"mocha": "10.
|
|
45
|
+
"mocha": "10.7.3"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@postalsys/vmc": "1.0.8",
|
|
49
|
-
"fast-xml-parser": "4.
|
|
49
|
+
"fast-xml-parser": "4.5.0",
|
|
50
50
|
"ipaddr.js": "2.2.0",
|
|
51
|
-
"joi": "17.13.
|
|
51
|
+
"joi": "17.13.3",
|
|
52
52
|
"libmime": "5.3.5",
|
|
53
|
-
"nodemailer": "6.9.
|
|
53
|
+
"nodemailer": "6.9.15",
|
|
54
54
|
"punycode.js": "2.3.1",
|
|
55
|
-
"tldts": "6.1.
|
|
55
|
+
"tldts": "6.1.49",
|
|
56
56
|
"undici": "5.28.4",
|
|
57
|
-
"uuid": "9.0.1",
|
|
58
57
|
"yargs": "17.7.2"
|
|
59
58
|
},
|
|
60
59
|
"engines": {
|