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 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
@@ -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
- let resultLine = [];
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
- resultLine.unshift(line[i]);
145
+ lineBuf[writePos--] = line[i];
134
146
  if (i === 0 || line[i - 1] !== CHAR_CR) {
135
147
  // add missing carriage return
136
- resultLine.unshift(CHAR_CR);
148
+ lineBuf[writePos--] = CHAR_CR;
137
149
  }
138
150
  continue;
139
151
  }
140
152
 
141
153
  if (line[i] === CHAR_CR) {
142
- resultLine.unshift(line[i]);
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
- resultLine.unshift(CHAR_SPACE);
166
+ lineBuf[writePos--] = CHAR_SPACE;
155
167
  prevWsp = false;
156
168
  }
157
169
 
158
170
  nonWspFound = true;
159
- resultLine.unshift(line[i]);
171
+ lineBuf[writePos--] = line[i];
160
172
  }
161
173
 
162
174
  if (prevWsp && nonWspFound) {
163
- resultLine.unshift(CHAR_SPACE);
175
+ lineBuf[writePos--] = CHAR_SPACE;
164
176
  }
165
177
 
166
- return Buffer.from(resultLine);
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
- : uuidv4(),
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
- module.exports = { dkimSign };
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 };
@@ -41,9 +41,9 @@ class GathererStream extends Transform {
41
41
  return stream;
42
42
  }
43
43
 
44
- _transform(chunk, encodng, done) {
44
+ _transform(chunk, encoding, done) {
45
45
  if (typeof chunk === 'string') {
46
- chunk = Buffer.from(chunk, encodng);
46
+ chunk = Buffer.from(chunk, encoding);
47
47
  }
48
48
 
49
49
  if (!chunk || !chunk.length) {
package/man/mailauth.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH "MAILAUTH" "1" "June 2024" "v4.6.8" "Mailauth Help"
1
+ .TH "MAILAUTH" "1" "October 2024" "v4.7.0" "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.8",
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.5.0",
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.4.0"
45
+ "mocha": "10.7.3"
46
46
  },
47
47
  "dependencies": {
48
48
  "@postalsys/vmc": "1.0.8",
49
- "fast-xml-parser": "4.4.0",
49
+ "fast-xml-parser": "4.5.0",
50
50
  "ipaddr.js": "2.2.0",
51
- "joi": "17.13.1",
51
+ "joi": "17.13.3",
52
52
  "libmime": "5.3.5",
53
- "nodemailer": "6.9.13",
53
+ "nodemailer": "6.9.15",
54
54
  "punycode.js": "2.3.1",
55
- "tldts": "6.1.24",
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": {