mailauth 4.4.1 → 4.5.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/bin/mailauth.js +51 -0
- package/cli.md +30 -0
- package/lib/bimi/index.js +12 -1
- package/lib/commands/bodyhash.js +66 -0
- package/lib/commands/sign.js +3 -3
- package/lib/dkim/body/index.js +5 -2
- package/lib/dkim/dkim-signer.js +6 -2
- package/lib/dkim/header/index.js +5 -2
- package/lib/mta-sts.js +12 -1
- package/lib/tools.js +6 -2
- package/licenses.txt +2 -2
- package/man/mailauth.1 +1 -1
- package/package.json +7 -7
package/bin/mailauth.js
CHANGED
|
@@ -12,6 +12,7 @@ const commandSign = require('../lib/commands/sign');
|
|
|
12
12
|
const commandSeal = require('../lib/commands/seal');
|
|
13
13
|
const commandSpf = require('../lib/commands/spf');
|
|
14
14
|
const commandVmc = require('../lib/commands/vmc');
|
|
15
|
+
const commandBodyhash = require('../lib/commands/bodyhash');
|
|
15
16
|
|
|
16
17
|
const fs = require('fs');
|
|
17
18
|
const pathlib = require('path');
|
|
@@ -100,6 +101,12 @@ const argv = yargs(hideBin(process.argv))
|
|
|
100
101
|
description: 'Key selector for signing (s= tag)',
|
|
101
102
|
demandOption: true
|
|
102
103
|
})
|
|
104
|
+
.option('algo', {
|
|
105
|
+
alias: 'a',
|
|
106
|
+
type: 'string',
|
|
107
|
+
description: 'Signing algorithm. Defaults either to rsa-sha256 or ed25519-sha256 depending on the private key format.',
|
|
108
|
+
default: 'rsa-sha256'
|
|
109
|
+
})
|
|
103
110
|
.option('canonicalization', {
|
|
104
111
|
alias: 'c',
|
|
105
112
|
type: 'string',
|
|
@@ -344,6 +351,50 @@ const argv = yargs(hideBin(process.argv))
|
|
|
344
351
|
});
|
|
345
352
|
}
|
|
346
353
|
)
|
|
354
|
+
.command(
|
|
355
|
+
['bodyhash [email]'],
|
|
356
|
+
'Generate a signature body hash for an email',
|
|
357
|
+
yargs => {
|
|
358
|
+
yargs
|
|
359
|
+
|
|
360
|
+
.option('algo', {
|
|
361
|
+
alias: 'a',
|
|
362
|
+
type: 'string',
|
|
363
|
+
description: 'Hashing algorithm. Defaults to sha256.',
|
|
364
|
+
default: 'sha256'
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
.option('canonicalization', {
|
|
368
|
+
alias: 'c',
|
|
369
|
+
type: 'string',
|
|
370
|
+
description: 'Canonicalization algorithm (c= tag)',
|
|
371
|
+
default: 'relaxed'
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
.option('body-length', {
|
|
375
|
+
alias: 'l',
|
|
376
|
+
type: 'number',
|
|
377
|
+
description: 'Maximum length of canonicalizated body to sign (l= tag)'
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
yargs.positional('email', {
|
|
381
|
+
describe: 'Path to the email message file in EML format. If not specified then content is read from stdin'
|
|
382
|
+
});
|
|
383
|
+
},
|
|
384
|
+
argv => {
|
|
385
|
+
commandBodyhash(argv)
|
|
386
|
+
.then(() => {
|
|
387
|
+
process.exit();
|
|
388
|
+
})
|
|
389
|
+
.catch(err => {
|
|
390
|
+
if (!err.suppress) {
|
|
391
|
+
console.error('Failed to calculate body hash for the input message');
|
|
392
|
+
console.error(err);
|
|
393
|
+
}
|
|
394
|
+
process.exit(1);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
)
|
|
347
398
|
.command(
|
|
348
399
|
['license'],
|
|
349
400
|
'Show license information',
|
package/cli.md
CHANGED
|
@@ -14,6 +14,7 @@ Command line utility and a [Node.js library](README.md) for email authentication
|
|
|
14
14
|
- [seal](#seal) - to seal an email with ARC
|
|
15
15
|
- [spf](#spf) - to validate SPF for an IP address and an email address
|
|
16
16
|
- [vmc](#vmc) - to validate BIMI VMC logo files
|
|
17
|
+
- [bodyhash](#bodyhash) - to generate the signature body hash value for an email
|
|
17
18
|
- [license](#license) - display licenses for `mailauth` and included modules
|
|
18
19
|
- [DNS cache file](#dns-cache-file)
|
|
19
20
|
|
|
@@ -320,6 +321,35 @@ $ mailauth vmc -p /path/to/vmc-with-invalid-svg.pem
|
|
|
320
321
|
}
|
|
321
322
|
```
|
|
322
323
|
|
|
324
|
+
### bodyhash
|
|
325
|
+
|
|
326
|
+
`bodyhash` command takes an email message and calculates the body hash value for it
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
$ mailauth bodyhash [options] [email]
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Where
|
|
333
|
+
|
|
334
|
+
- **options** are option flags and arguments
|
|
335
|
+
- **email** is the path to EML formatted email message file. If not provided then email message is read from standard input
|
|
336
|
+
|
|
337
|
+
**Options**
|
|
338
|
+
|
|
339
|
+
- `--algo sha256` or `-a sha256` is the signing algorithm. Defaults to "sha256". Can also use the a= tag format ("rsa-sha256").
|
|
340
|
+
- `--canonicalization algo` or `-c algo` is the body canonicalization algorithm, defaults to "relaxed". Can also use the c= tag format ("relaxed/relaxed").
|
|
341
|
+
- `--body-length 12345` or `-l 12345` is the maximum length of canonicalizated body to sign (l= tag)
|
|
342
|
+
|
|
343
|
+
**Example**
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
$ mailauth bodyhash /path/message.eml -a sha1 --verbose
|
|
347
|
+
Hashing algorithm: sha1
|
|
348
|
+
Body canonicalization algorithm: relaxed
|
|
349
|
+
--------
|
|
350
|
+
j+dD7whKXS1yDmyoWtvClYSyYiQ=
|
|
351
|
+
```
|
|
352
|
+
|
|
323
353
|
### license
|
|
324
354
|
|
|
325
355
|
Display licenses for `mailauth` and included modules.
|
package/lib/bimi/index.js
CHANGED
|
@@ -14,6 +14,8 @@ const http = require('http');
|
|
|
14
14
|
const { vmc } = require('@postalsys/vmc');
|
|
15
15
|
const { validateSvg } = require('./validate-svg');
|
|
16
16
|
|
|
17
|
+
const HTTP_REQUEST_TIMEOUT = 15 * 1000;
|
|
18
|
+
|
|
17
19
|
const lookup = async data => {
|
|
18
20
|
let { dmarc, headers, resolver, bimiWithAlignedDkim } = data;
|
|
19
21
|
let headerRows = (headers && headers.parsed) || [];
|
|
@@ -197,7 +199,9 @@ const downloadPromise = (url, cachedFile) => {
|
|
|
197
199
|
port: 443,
|
|
198
200
|
path: parsedUrl.pathname,
|
|
199
201
|
method: 'GET',
|
|
200
|
-
rejectUnauthorized: true
|
|
202
|
+
rejectUnauthorized: true,
|
|
203
|
+
|
|
204
|
+
timeout: HTTP_REQUEST_TIMEOUT
|
|
201
205
|
};
|
|
202
206
|
|
|
203
207
|
return new Promise((resolve, reject) => {
|
|
@@ -240,6 +244,13 @@ const downloadPromise = (url, cachedFile) => {
|
|
|
240
244
|
res.on('error', err => reject(err));
|
|
241
245
|
});
|
|
242
246
|
|
|
247
|
+
req.on('timeout', () => {
|
|
248
|
+
req.destroy(); // cancel request
|
|
249
|
+
let error = new Error(`Request timeout for ${parsedUrl.href}`);
|
|
250
|
+
error.code = 'HTTP_SOCKET_TIMEOUT';
|
|
251
|
+
reject(error);
|
|
252
|
+
});
|
|
253
|
+
|
|
243
254
|
req.on('error', err => {
|
|
244
255
|
reject(err);
|
|
245
256
|
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { DkimSigner } = require('../dkim/dkim-signer');
|
|
4
|
+
const { writeToStream } = require('../tools');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const cmd = async argv => {
|
|
8
|
+
let source = argv.email;
|
|
9
|
+
let useStdin = false;
|
|
10
|
+
let stream;
|
|
11
|
+
|
|
12
|
+
if (!source) {
|
|
13
|
+
useStdin = true;
|
|
14
|
+
source = 'standard input';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (argv.verbose) {
|
|
18
|
+
console.error(`Reading email message from ${source}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (useStdin) {
|
|
22
|
+
stream = process.stdin;
|
|
23
|
+
} else {
|
|
24
|
+
stream = fs.createReadStream(source);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (isNaN(argv.bodyLength) || argv.bodyLength < 0) {
|
|
28
|
+
argv.bodyLength = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let signatureOpts = {
|
|
32
|
+
type: 'DKIM',
|
|
33
|
+
privateKey: true, // force hash calculation
|
|
34
|
+
canonicalization: argv.canonicalization && (argv.canonicalization.includes('/') ? argv.canonicalization : `/${argv.canonicalization}`),
|
|
35
|
+
algorithm: argv.algo,
|
|
36
|
+
maxBodyLength: argv.bodyLength
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let dkimSigner = new DkimSigner({ signatureData: [signatureOpts] });
|
|
40
|
+
|
|
41
|
+
let { hashAlgo } = dkimSigner.getAlgorithm(signatureOpts);
|
|
42
|
+
let { bodyCanon } = dkimSigner.getCanonicalization(signatureOpts);
|
|
43
|
+
|
|
44
|
+
if (argv.verbose) {
|
|
45
|
+
if (hashAlgo) {
|
|
46
|
+
console.error(`Hashing algorithm: ${hashAlgo}`);
|
|
47
|
+
}
|
|
48
|
+
if (bodyCanon) {
|
|
49
|
+
console.error(`Body canonicalization algorithm: ${bodyCanon}`);
|
|
50
|
+
}
|
|
51
|
+
if (signatureOpts.maxBodyLength) {
|
|
52
|
+
console.error(`Maximum body length: ${signatureOpts.maxBodyLength}`);
|
|
53
|
+
}
|
|
54
|
+
console.error('--------');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await writeToStream(dkimSigner, stream);
|
|
58
|
+
|
|
59
|
+
let hashKey = `${bodyCanon}:${hashAlgo}:${typeof argv.bodyLength === 'number' ? argv.bodyLength : ''}`;
|
|
60
|
+
const bodyHash = dkimSigner.bodyHashes.get(hashKey)?.hash;
|
|
61
|
+
if (bodyHash) {
|
|
62
|
+
process.stdout.write(bodyHash);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
module.exports = cmd;
|
package/lib/commands/sign.js
CHANGED
|
@@ -48,12 +48,12 @@ const cmd = async argv => {
|
|
|
48
48
|
if (signatureOpts.selector) {
|
|
49
49
|
console.error(`Key selector: ${signatureOpts.selector}`);
|
|
50
50
|
}
|
|
51
|
-
if (signatureOpts.canonicalization) {
|
|
52
|
-
console.error(`Canonicalization algorithm: ${signatureOpts.canonicalization}`);
|
|
53
|
-
}
|
|
54
51
|
if (signatureOpts.algorithm) {
|
|
55
52
|
console.error(`Hashing algorithm: ${signatureOpts.algorithm}`);
|
|
56
53
|
}
|
|
54
|
+
if (signatureOpts.canonicalization) {
|
|
55
|
+
console.error(`Canonicalization algorithm: ${signatureOpts.canonicalization}`);
|
|
56
|
+
}
|
|
57
57
|
if (signatureOpts.maxBodyLength) {
|
|
58
58
|
console.error(`Maximum body length: ${signatureOpts.maxBodyLength}`);
|
|
59
59
|
}
|
package/lib/dkim/body/index.js
CHANGED
|
@@ -10,8 +10,11 @@ const dkimBody = (canonicalization, ...options) => {
|
|
|
10
10
|
return new SimpleHash(...options);
|
|
11
11
|
case 'relaxed':
|
|
12
12
|
return new RelaxedHash(...options);
|
|
13
|
-
default:
|
|
14
|
-
|
|
13
|
+
default: {
|
|
14
|
+
let error = new Error('Unknown body canonicalization');
|
|
15
|
+
error.canonicalization = canonicalization;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
15
18
|
}
|
|
16
19
|
};
|
|
17
20
|
|
package/lib/dkim/dkim-signer.js
CHANGED
|
@@ -96,11 +96,15 @@ class DkimSigner extends MessageParser {
|
|
|
96
96
|
let [header, body] = canonicalization.split('/');
|
|
97
97
|
|
|
98
98
|
if (!['relaxed', 'simple'].includes(header)) {
|
|
99
|
-
|
|
99
|
+
let error = new Error('Unknown header canonicalization');
|
|
100
|
+
error.canonicalization = header;
|
|
101
|
+
throw error;
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
if (!['relaxed', 'simple'].includes(body)) {
|
|
103
|
-
|
|
105
|
+
let error = new Error('Unknown body canonicalization');
|
|
106
|
+
error.canonicalization = body;
|
|
107
|
+
throw error;
|
|
104
108
|
}
|
|
105
109
|
} catch (err) {
|
|
106
110
|
err.code = 'EINVALIDCANON';
|
package/lib/dkim/header/index.js
CHANGED
|
@@ -11,8 +11,11 @@ const generateCanonicalizedHeader = (type, signingHeaderLines, options) => {
|
|
|
11
11
|
return simpleHeaders(type, signingHeaderLines, options);
|
|
12
12
|
case 'relaxed':
|
|
13
13
|
return relaxedHeaders(type, signingHeaderLines, options);
|
|
14
|
-
default:
|
|
15
|
-
|
|
14
|
+
default: {
|
|
15
|
+
let error = new Error('Unknown header canonicalization');
|
|
16
|
+
error.canonicalization = canonicalization;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
16
19
|
}
|
|
17
20
|
};
|
|
18
21
|
|
package/lib/mta-sts.js
CHANGED
|
@@ -5,6 +5,8 @@ const dns = require('dns');
|
|
|
5
5
|
const parseDkimHeaders = require('./parse-dkim-headers');
|
|
6
6
|
const https = require('https');
|
|
7
7
|
|
|
8
|
+
const HTTP_REQUEST_TIMEOUT = 15 * 1000;
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Resolve MTA-STS policy ID
|
|
10
12
|
* @param {String} address Either email address or a domain name
|
|
@@ -247,7 +249,9 @@ const fetchPolicy = async (domain, opts) => {
|
|
|
247
249
|
port: 443,
|
|
248
250
|
path,
|
|
249
251
|
method: 'GET',
|
|
250
|
-
rejectUnauthorized: true
|
|
252
|
+
rejectUnauthorized: true,
|
|
253
|
+
|
|
254
|
+
timeout: HTTP_REQUEST_TIMEOUT
|
|
251
255
|
};
|
|
252
256
|
|
|
253
257
|
let data = await new Promise((resolve, reject) => {
|
|
@@ -273,6 +277,13 @@ const fetchPolicy = async (domain, opts) => {
|
|
|
273
277
|
res.on('error', err => reject(err));
|
|
274
278
|
});
|
|
275
279
|
|
|
280
|
+
req.on('timeout', () => {
|
|
281
|
+
req.destroy(); // cancel request
|
|
282
|
+
let error = new Error(`Request timeout for https://${servername}${path}`);
|
|
283
|
+
error.code = 'HTTP_SOCKET_TIMEOUT';
|
|
284
|
+
reject(error);
|
|
285
|
+
});
|
|
286
|
+
|
|
276
287
|
req.on('error', err => {
|
|
277
288
|
reject(err);
|
|
278
289
|
});
|
package/lib/tools.js
CHANGED
|
@@ -450,11 +450,15 @@ const validateAlgorithm = (algorithm, strict) => {
|
|
|
450
450
|
let [signAlgo, hashAlgo] = algorithm.toLowerCase().split('-');
|
|
451
451
|
|
|
452
452
|
if (!['rsa', 'ed25519'].includes(signAlgo)) {
|
|
453
|
-
|
|
453
|
+
let error = new Error('Unknown signing algorithm');
|
|
454
|
+
error.signAlgo = signAlgo;
|
|
455
|
+
throw error;
|
|
454
456
|
}
|
|
455
457
|
|
|
456
458
|
if (!['sha256'].concat(!strict ? 'sha1' : []).includes(hashAlgo)) {
|
|
457
|
-
|
|
459
|
+
let error = new Error('Unknown hashing algorithm');
|
|
460
|
+
error.hashAlgo = hashAlgo;
|
|
461
|
+
throw error;
|
|
458
462
|
}
|
|
459
463
|
} catch (err) {
|
|
460
464
|
err.code = 'EINVALIDALGO';
|
package/licenses.txt
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
name license type link installed version author
|
|
2
2
|
---- ------------ ---- ----------------- ------
|
|
3
3
|
@postalsys/vmc MIT https://registry.npmjs.org/@postalsys/vmc/-/vmc-1.0.6.tgz 1.0.6 Postal Systems OÜ
|
|
4
|
-
fast-xml-parser MIT git+https://github.com/NaturalIntelligence/fast-xml-parser.git 4.2.
|
|
4
|
+
fast-xml-parser MIT git+https://github.com/NaturalIntelligence/fast-xml-parser.git 4.2.6 Amit Gupta (https://amitguptagwl.github.io)
|
|
5
5
|
ipaddr.js MIT git://github.com/whitequark/ipaddr.js.git 2.1.0 whitequark <whitequark@whitequark.org>
|
|
6
6
|
joi BSD-3-Clause git://github.com/hapijs/joi.git 17.9.2 n/a
|
|
7
7
|
libmime MIT git://github.com/andris9/libmime.git 5.2.1 Andris Reinman <andris@kreata.ee>
|
|
8
|
-
nodemailer MIT-0 git+https://github.com/nodemailer/nodemailer.git 6.9.
|
|
8
|
+
nodemailer MIT-0 git+https://github.com/nodemailer/nodemailer.git 6.9.4 Andris Reinman
|
|
9
9
|
psl MIT git+ssh://git@github.com/lupomontero/psl.git 1.9.0 Lupo Montero <lupomontero@gmail.com> (https://lupomontero.com/)
|
|
10
10
|
punycode MIT git+https://github.com/mathiasbynens/punycode.js.git 2.3.0 Mathias Bynens https://mathiasbynens.be/
|
|
11
11
|
yargs MIT git+https://github.com/yargs/yargs.git 17.7.2 n/a
|
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.5.0",
|
|
4
4
|
"description": "Email authentication library for Node.js",
|
|
5
5
|
"main": "lib/mailauth.js",
|
|
6
6
|
"scripts": {
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"homepage": "https://github.com/postalsys/mailauth",
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"chai": "4.3.7",
|
|
36
|
-
"eslint": "8.
|
|
36
|
+
"eslint": "8.46.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
|
-
"eslint-config-prettier": "8.
|
|
38
|
+
"eslint-config-prettier": "8.9.0",
|
|
39
39
|
"js-yaml": "4.1.0",
|
|
40
40
|
"license-report": "6.4.0",
|
|
41
41
|
"marked": "0.7.0",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@postalsys/vmc": "1.0.6",
|
|
49
|
-
"fast-xml-parser": "4.2.
|
|
49
|
+
"fast-xml-parser": "4.2.7",
|
|
50
50
|
"ipaddr.js": "2.1.0",
|
|
51
51
|
"joi": "17.9.2",
|
|
52
52
|
"libmime": "5.2.1",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"LICENSE.txt"
|
|
72
72
|
],
|
|
73
73
|
"targets": [
|
|
74
|
-
"
|
|
75
|
-
"
|
|
74
|
+
"node18-linux-x64",
|
|
75
|
+
"node18-macos-x64",
|
|
76
76
|
"node18-macos-arm64",
|
|
77
|
-
"
|
|
77
|
+
"node18-win-x64"
|
|
78
78
|
],
|
|
79
79
|
"outputPath": "ee-dist"
|
|
80
80
|
}
|