mailauth 2.2.3 → 2.3.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/LICENSE.txt +1 -1
- package/README.md +36 -36
- package/cli.md +9 -1
- package/lib/dkim/dkim-verifier.js +12 -1
- package/lib/spf/spf-verify.js +11 -4
- package/lib/tools.js +45 -16
- package/man/mailauth.1 +19 -15
- package/man/man.md +12 -12
- package/package.json +18 -17
- package/licenses.txt +0 -11
package/LICENSE.txt
CHANGED
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- **BIMI** resolving
|
|
14
14
|
- **MTA-STS** helpers
|
|
15
15
|
|
|
16
|
-
Pure JavaScript implementation, no external applications or compilation needed.
|
|
16
|
+
Pure JavaScript implementation, no external applications or compilation needed. It runs on any server/device that has Node 14+ installed.
|
|
17
17
|
|
|
18
18
|
## Command line usage
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ See [command line documentation](cli.md) for the `mailauth` command.
|
|
|
23
23
|
|
|
24
24
|
## Authentication
|
|
25
25
|
|
|
26
|
-
Validate DKIM signatures, SPF, DMARC, ARC and BIMI for an email.
|
|
26
|
+
Validate DKIM signatures, SPF, DMARC, ARC, and BIMI for an email.
|
|
27
27
|
|
|
28
28
|
```js
|
|
29
29
|
await authenticate(message [,options]) ->
|
|
@@ -32,23 +32,23 @@ await authenticate(message [,options]) ->
|
|
|
32
32
|
|
|
33
33
|
Where
|
|
34
34
|
|
|
35
|
-
- **message** is either a String, a Buffer or a Readable stream that represents an email message
|
|
35
|
+
- **message** is either a String, a Buffer, or a Readable stream that represents an email message
|
|
36
36
|
- **options** (_object_) is an optional options object
|
|
37
|
-
- **sender** (_string_) is the email address from MAIL FROM command. If not set then it is parsed from the `Return-Path` header
|
|
38
|
-
- **ip** (_string_) is the IP of remote client that sent this message
|
|
37
|
+
- **sender** (_string_) is the email address from MAIL FROM command. If not set, then it is parsed from the `Return-Path` header
|
|
38
|
+
- **ip** (_string_) is the IP of the remote client that sent this message
|
|
39
39
|
- **helo** (_string_) is the hostname value from HELO/EHLO command
|
|
40
|
-
- **trustReceived** (_boolean_) if true then parses values for `ip` and `helo` from the latest `Received` header if you have not set these values yourself. Defaults to `false
|
|
40
|
+
- **trustReceived** (_boolean_) if true, then parses values for `ip` and `helo` from the latest `Received` header if you have not set these values yourself. Defaults to `false`.
|
|
41
41
|
- **mta** (_string_) is the hostname of the server performing the authentication (defaults to `os.hostname()`)
|
|
42
|
-
- **minBitLength** (_number_) is the minimum allowed bits of RSA public keys (defaults to 1024). If a DKIM or ARC key has
|
|
42
|
+
- **minBitLength** (_number_) is the minimum allowed bits of RSA public keys (defaults to 1024). If a DKIM or ARC key has fewer bits, then validation is considered as failed
|
|
43
43
|
- **disableArc** (_boolean_) if true then skip ARC checks
|
|
44
|
-
- **disableDmarc** (_boolean_) if true then skip DMARC checks.
|
|
45
|
-
- **disableBimi** (_boolean_) if true then skip BIMI checks
|
|
44
|
+
- **disableDmarc** (_boolean_) if true then skip DMARC checks. It also disables checks that are dependent on DMARC (e.g., BIMI)
|
|
45
|
+
- **disableBimi** (_boolean_) if true, then skip BIMI checks
|
|
46
46
|
- **seal** (_object_) if set and message does not have a broken ARC chain, then seals the message using these values
|
|
47
47
|
- **signingDomain** (_string_) ARC key domain name
|
|
48
48
|
- **selector** (_string_) ARC key selector
|
|
49
|
-
- **privateKey** (_string_ or _buffer_) Private key for signing.
|
|
49
|
+
- **privateKey** (_string_ or _buffer_) Private key for signing. Either an RSA or an Ed25519 key
|
|
50
50
|
- **resolver** (_async function_) is an optional async function for DNS requests. Defaults to [dns.promises.resolve](https://nodejs.org/api/dns.html#dns_dnspromises_resolve_hostname_rrtype)
|
|
51
|
-
- **maxResolveCount** (_number_ defaults to _50_) is the DNS lookup limit for SPF. [RFC7208](https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4) requires this limit to be 10
|
|
51
|
+
- **maxResolveCount** (_number_ defaults to _50_) is the DNS lookup limit for SPF. [RFC7208](https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4) requires this limit to be 10. Mailauth is less strict and defaults to 50.
|
|
52
52
|
|
|
53
53
|
**Example**
|
|
54
54
|
|
|
@@ -90,11 +90,11 @@ Authentication-Results: mx.ethereal.email;
|
|
|
90
90
|
From: ...
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
You can see full output (structured data for DKIM, SPF, DMARC and ARC) from [this example](https://gist.github.com/andris9/6514b5e7c59154a5b08636f99052ce37).
|
|
93
|
+
You can see the full output (structured data for DKIM, SPF, DMARC, and ARC) from [this example](https://gist.github.com/andris9/6514b5e7c59154a5b08636f99052ce37).
|
|
94
94
|
|
|
95
95
|
### receivedChain
|
|
96
96
|
|
|
97
|
-
`receivedChain` property is an array of parsed representations of the `Received:` headers
|
|
97
|
+
`receivedChain` property is an array of parsed representations of the `Received:` headers.
|
|
98
98
|
|
|
99
99
|
## DKIM
|
|
100
100
|
|
|
@@ -132,7 +132,7 @@ const signResult = await dkimSign(
|
|
|
132
132
|
// Optional signature specifc canonicalization, overrides whatever was set in parent object
|
|
133
133
|
canonicalization: 'relaxed/relaxed' // c=
|
|
134
134
|
|
|
135
|
-
// Maximum number of
|
|
135
|
+
// Maximum number of canonicalized body bytes to sign (eg. the "l=" tag).
|
|
136
136
|
// Do not use though. This is available only for compatibility testing.
|
|
137
137
|
// maxBodyLength: 12345
|
|
138
138
|
}
|
|
@@ -216,7 +216,7 @@ const { arc } = await authenticate(
|
|
|
216
216
|
console.log(arc);
|
|
217
217
|
```
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
The output is something like this:
|
|
220
220
|
|
|
221
221
|
```
|
|
222
222
|
{
|
|
@@ -233,7 +233,7 @@ Output being something like this:
|
|
|
233
233
|
|
|
234
234
|
#### During authentication
|
|
235
235
|
|
|
236
|
-
You can seal messages with ARC automatically in the authentication step by providing the sealing key. In this case you can not modify the message
|
|
236
|
+
You can seal messages with ARC automatically in the authentication step by providing the sealing key. In this case, you can not modify the message any more as this would break the seal.
|
|
237
237
|
|
|
238
238
|
```js
|
|
239
239
|
const { authenticate } = require('mailauth');
|
|
@@ -258,7 +258,7 @@ process.stdout.write(message);
|
|
|
258
258
|
|
|
259
259
|
#### After modifications
|
|
260
260
|
|
|
261
|
-
If you want to modify the message before sealing
|
|
261
|
+
If you want to modify the message before sealing, you have to authenticate the message first and then use authentication results as input for the sealing step.
|
|
262
262
|
|
|
263
263
|
```js
|
|
264
264
|
const { authenticate, sealMessage } = require('@postalsys/mailauth');
|
|
@@ -297,7 +297,7 @@ process.stdout.write(message);
|
|
|
297
297
|
|
|
298
298
|
Brand Indicators for Message Identification (BIMI) support is based on [draft-blank-ietf-bimi-01](https://tools.ietf.org/html/draft-blank-ietf-bimi-01).
|
|
299
299
|
|
|
300
|
-
BIMI information is resolved in the authentication step and the results can be found from the `bimi` property.
|
|
300
|
+
BIMI information is resolved in the authentication step, and the results can be found from the `bimi` property. The message must pass DMARC validation to be processed for BIMI. DMARC policy can not be "none" for BIMI to pass.
|
|
301
301
|
|
|
302
302
|
```js
|
|
303
303
|
const { bimi } = await authenticate(
|
|
@@ -314,7 +314,7 @@ if (bimi?.location) {
|
|
|
314
314
|
}
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
-
`BIMI-Location` header is ignored by `mailauth`, it is not checked for and it is not modified in any way if it is present. `BIMI-Selector` is used for selector selection (if available).
|
|
317
|
+
`BIMI-Location` header is ignored by `mailauth`, it is not checked for, and it is not modified in any way if it is present. `BIMI-Selector` is used for selector selection (if available).
|
|
318
318
|
|
|
319
319
|
### Verified Mark Certificate
|
|
320
320
|
|
|
@@ -327,14 +327,14 @@ Some example authority evidence documents:
|
|
|
327
327
|
- [from default.\_bimi.cnn.com](https://amplify.valimail.com/bimi/time-warner/LysAFUdG-Hw-cnn_vmc.pem)
|
|
328
328
|
- [from default.\_bimi.entrustdatacard.com](https://www.entrustdatacard.com/-/media/certificate/Entrust%20VMC%20July%2014%202020.pem)
|
|
329
329
|
|
|
330
|
-
You can parse logos from these certificate files
|
|
330
|
+
You can parse logos from these certificate files using the `parseLogoFromX509` function.
|
|
331
331
|
|
|
332
332
|
```js
|
|
333
333
|
const { parseLogoFromX509 } = require('mailauth/lib/tools');
|
|
334
334
|
let { altnNames, svg } = await parseLogoFromX509(fs.readFileSync('vmc.pem'));
|
|
335
335
|
```
|
|
336
336
|
|
|
337
|
-
> **NB!** `parseLogoFromX509` does not verify the validity of the VMC certificate. It could be self
|
|
337
|
+
> **NB!** `parseLogoFromX509` does not verify the validity of the VMC certificate. It could be self-signed or expired and still be processed.
|
|
338
338
|
|
|
339
339
|
## MTA-STS
|
|
340
340
|
|
|
@@ -372,10 +372,10 @@ async getPolicy(domain [,knownPolicy]) -> {policy, status}
|
|
|
372
372
|
|
|
373
373
|
Where
|
|
374
374
|
|
|
375
|
-
- **domain** is the domain to check for (
|
|
376
|
-
- **knownPolicy** (optional) is the policy object from last check for this domain. This is used to check if the policy is still valid or it was updated.
|
|
375
|
+
- **domain** is the domain to check for (e.g. "gmail.com")
|
|
376
|
+
- **knownPolicy** (optional) is the policy object from the last check for this domain. This is used to check if the policy is still valid or it was updated.
|
|
377
377
|
|
|
378
|
-
|
|
378
|
+
The function returns an object with the following properties:
|
|
379
379
|
|
|
380
380
|
- **policy** (object)
|
|
381
381
|
- **id** (string or `false`) ID of the policy
|
|
@@ -387,11 +387,11 @@ Function returns an object with the following properties:
|
|
|
387
387
|
- _"cached"_ no changes detected, current policy is still valid and can be used
|
|
388
388
|
- _"found"_ new or updated policy was found. Cache this in your system until _policy.expires_
|
|
389
389
|
- _"renew"_ existing policy is still valid, renew cached version until _policy.expires_
|
|
390
|
-
- _"errored"_ policy discovery failed for some temporary error (
|
|
390
|
+
- _"errored"_ policy discovery failed for some temporary error (e.g., failing DNS queries). See _policy.error_ for details
|
|
391
391
|
|
|
392
392
|
### Validate MX hostname
|
|
393
393
|
|
|
394
|
-
Check if a resolved MX hostname is valid by MTA-STS policy or not
|
|
394
|
+
Check if a resolved MX hostname is valid by MTA-STS policy or not.
|
|
395
395
|
|
|
396
396
|
```
|
|
397
397
|
validateMx(mx, policy) -> Boolean
|
|
@@ -402,7 +402,7 @@ Where
|
|
|
402
402
|
- **mx** is the resolved MX hostname (eg. "gmail-smtp-in.l.google.com")
|
|
403
403
|
- **policy** is the policy object returned by `getPolicy()`
|
|
404
404
|
|
|
405
|
-
|
|
405
|
+
The function returns a boolean. If it is `true`, then MX hostname is allowed to use.
|
|
406
406
|
|
|
407
407
|
## Testing
|
|
408
408
|
|
|
@@ -412,23 +412,23 @@ Function returns a boolean. If it is `true` then MX hostname is allowed to use.
|
|
|
412
412
|
|
|
413
413
|
[OpenSPF test suite](http://www.openspf.org/Test_Suite) ([archive.org mirror](https://web.archive.org/web/20190130131432/http://www.openspf.org/Test_Suite)) with the following differences:
|
|
414
414
|
|
|
415
|
-
- No PTR support in `mailauth
|
|
416
|
-
- Less strict whitespace checks (`mailauth` accepts multiple spaces between tags etc)
|
|
415
|
+
- No PTR support in `mailauth`. All PTR related tests are ignored
|
|
416
|
+
- Less strict whitespace checks (`mailauth` accepts multiple spaces between tags etc.)
|
|
417
417
|
- Some macro tests are skipped (macro expansion is supported _in most parts_)
|
|
418
|
-
- Some tests where invalid component is listed after a matching part (mailauth processes from left to right and returns on first match found)
|
|
419
|
-
- Other than that all tests pass
|
|
418
|
+
- Some tests where the invalid component is listed after a matching part (mailauth processes from left to right and returns on the first match found)
|
|
419
|
+
- Other than that, all tests pass
|
|
420
420
|
|
|
421
421
|
### ARC test suite from ValiMail
|
|
422
422
|
|
|
423
423
|
ValiMail [arc_test_suite](https://github.com/ValiMail/arc_test_suite)
|
|
424
424
|
|
|
425
|
-
- `mailauth` is less strict on header tags and casing
|
|
426
|
-
- Signing test suite is used for input only. All listed messages are signed using provided keys but signatures are not matched against reference. Instead `mailauth` validates the signatures itself and looks for the same cv= output that the ARC-Seal header in the test suite has
|
|
427
|
-
- Other than that all tests pass
|
|
425
|
+
- `mailauth` is less strict on header tags and casing. For example, uppercase `S=` for a selector passes in `mailauth` but fails in ValiMail.
|
|
426
|
+
- Signing test suite is used for input only. All listed messages are signed using provided keys, but signatures are not matched against the reference. Instead, `mailauth` validates the signatures itself and looks for the same cv= output that the ARC-Seal header in the test suite has
|
|
427
|
+
- Other than that, all tests pass
|
|
428
428
|
|
|
429
429
|
## Setup
|
|
430
430
|
|
|
431
|
-
First install the module from npm:
|
|
431
|
+
First, install the module from npm:
|
|
432
432
|
|
|
433
433
|
```
|
|
434
434
|
$ npm install mailauth
|
|
@@ -442,6 +442,6 @@ const { authenticate } = require('mailauth');
|
|
|
442
442
|
|
|
443
443
|
## License
|
|
444
444
|
|
|
445
|
-
© 2020-
|
|
445
|
+
© 2020-2022 Postal Systems OÜ
|
|
446
446
|
|
|
447
447
|
Licensed under MIT license
|
package/cli.md
CHANGED
|
@@ -17,10 +17,18 @@
|
|
|
17
17
|
Download `mailauth` for your platform:
|
|
18
18
|
|
|
19
19
|
- [MacOS](https://github.com/postalsys/mailauth/releases/latest/download/mailauth.pkg)
|
|
20
|
-
- [Linux](https://github.com/postalsys/mailauth/releases/latest/download/mailauth.gz)
|
|
20
|
+
- [Linux](https://github.com/postalsys/mailauth/releases/latest/download/mailauth.tar.gz)
|
|
21
21
|
- [Windows](https://github.com/postalsys/mailauth/releases/latest/download/mailauth.exe)
|
|
22
22
|
- Or install from the NPM registry: `npm install -g mailauth`
|
|
23
23
|
|
|
24
|
+
> **NB!** Downloadable files are quite large because these are packaged Node.js applications
|
|
25
|
+
|
|
26
|
+
Alternatively you can install `mailauth` from [npm](https://npmjs.com/package/mailauth).
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
npm install -g mailauth
|
|
30
|
+
```
|
|
31
|
+
|
|
24
32
|
## Help
|
|
25
33
|
|
|
26
34
|
```
|
|
@@ -172,7 +172,12 @@ class DkimVerifier extends MessageParser {
|
|
|
172
172
|
instance: ['ARC', 'AS'].includes(signatureHeader.type) ? signatureHeader.parsed?.i?.value : false
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
-
let
|
|
175
|
+
let signingHeaders = {
|
|
176
|
+
keys: signingHeaderLines.keys,
|
|
177
|
+
headers: signingHeaderLines.headers.map(l => l.line.toString())
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
let publicKey, rr, modulusLength;
|
|
176
181
|
let status = {
|
|
177
182
|
result: 'neutral',
|
|
178
183
|
comment: false,
|
|
@@ -208,6 +213,7 @@ class DkimVerifier extends MessageParser {
|
|
|
208
213
|
|
|
209
214
|
publicKey = res?.publicKey;
|
|
210
215
|
rr = res?.rr;
|
|
216
|
+
modulusLength = res?.modulusLength;
|
|
211
217
|
|
|
212
218
|
try {
|
|
213
219
|
status.result = crypto.verify(
|
|
@@ -283,6 +289,7 @@ class DkimVerifier extends MessageParser {
|
|
|
283
289
|
format: signatureHeader.parsed?.c?.value,
|
|
284
290
|
bodyHash,
|
|
285
291
|
bodyHashExpecting: signatureHeader.parsed?.bh?.value,
|
|
292
|
+
signingHeaders,
|
|
286
293
|
status
|
|
287
294
|
};
|
|
288
295
|
|
|
@@ -298,6 +305,10 @@ class DkimVerifier extends MessageParser {
|
|
|
298
305
|
result.publicKey = publicKey.toString();
|
|
299
306
|
}
|
|
300
307
|
|
|
308
|
+
if (modulusLength) {
|
|
309
|
+
result.modulusLength = modulusLength;
|
|
310
|
+
}
|
|
311
|
+
|
|
301
312
|
if (rr) {
|
|
302
313
|
result.rr = rr;
|
|
303
314
|
}
|
package/lib/spf/spf-verify.js
CHANGED
|
@@ -15,7 +15,7 @@ const matchIp = (addr, range) => {
|
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
const parseCidrValue = (val, defaultValue) => {
|
|
18
|
+
const parseCidrValue = (val, defaultValue, type) => {
|
|
19
19
|
val = val || '';
|
|
20
20
|
let domain = '';
|
|
21
21
|
let cidr4 = '';
|
|
@@ -29,8 +29,15 @@ const parseCidrValue = (val, defaultValue) => {
|
|
|
29
29
|
throw err;
|
|
30
30
|
}
|
|
31
31
|
domain = cidrMatch[1] || '';
|
|
32
|
+
|
|
32
33
|
cidr4 = cidrMatch[2] ? Number(cidrMatch[2].substr(1)) : '';
|
|
33
34
|
cidr6 = cidrMatch[3] ? Number(cidrMatch[3].substr(2)) : '';
|
|
35
|
+
|
|
36
|
+
if (type === 'ip6' && cidr4 && !cidr6) {
|
|
37
|
+
// there is no dual cidr for IP addresses
|
|
38
|
+
cidr6 = cidr4;
|
|
39
|
+
cidr4 = '';
|
|
40
|
+
}
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
domain = domain.toLowerCase().trim() || defaultValue;
|
|
@@ -270,7 +277,7 @@ const spfVerify = async (domain, opts) => {
|
|
|
270
277
|
case 'ip4':
|
|
271
278
|
case 'ip6':
|
|
272
279
|
{
|
|
273
|
-
let { domain: range, cidr4, cidr6 } = parseCidrValue(val);
|
|
280
|
+
let { domain: range, cidr4, cidr6 } = parseCidrValue(val, false, type);
|
|
274
281
|
if (!range) {
|
|
275
282
|
let err = new Error('SPF failure');
|
|
276
283
|
err.spfResult = { error: 'permerror', text: `bare IP address` };
|
|
@@ -315,7 +322,7 @@ const spfVerify = async (domain, opts) => {
|
|
|
315
322
|
|
|
316
323
|
case 'a':
|
|
317
324
|
{
|
|
318
|
-
let { domain: a, cidr4, cidr6 } = parseCidrValue(val, domain);
|
|
325
|
+
let { domain: a, cidr4, cidr6 } = parseCidrValue(val, domain, type);
|
|
319
326
|
let cidr = net.isIPv6(opts.ip) ? cidr6 : cidr4;
|
|
320
327
|
|
|
321
328
|
a = macro(a, opts);
|
|
@@ -339,7 +346,7 @@ const spfVerify = async (domain, opts) => {
|
|
|
339
346
|
|
|
340
347
|
case 'mx':
|
|
341
348
|
{
|
|
342
|
-
let { domain: mxDomain, cidr4, cidr6 } = parseCidrValue(val, domain);
|
|
349
|
+
let { domain: mxDomain, cidr4, cidr6 } = parseCidrValue(val, domain, type);
|
|
343
350
|
let cidr = net.isIPv6(opts.ip) ? cidr6 : cidr4;
|
|
344
351
|
|
|
345
352
|
try {
|
package/lib/tools.js
CHANGED
|
@@ -6,7 +6,6 @@ const punycode = require('punycode/');
|
|
|
6
6
|
const libmime = require('libmime');
|
|
7
7
|
const dns = require('dns').promises;
|
|
8
8
|
const crypto = require('crypto');
|
|
9
|
-
const pki = require('node-forge').pki;
|
|
10
9
|
const https = require('https');
|
|
11
10
|
const packageData = require('../package');
|
|
12
11
|
const parseDkimHeaders = require('./parse-dkim-headers');
|
|
@@ -15,6 +14,9 @@ const { Certificate } = require('@fidm/x509');
|
|
|
15
14
|
const zlib = require('zlib');
|
|
16
15
|
const util = require('util');
|
|
17
16
|
const gunzip = util.promisify(zlib.gunzip);
|
|
17
|
+
const pki = require('node-forge').pki;
|
|
18
|
+
const Joi = require('joi');
|
|
19
|
+
const base64Schema = Joi.string().base64({ paddingRequired: false });
|
|
18
20
|
|
|
19
21
|
const defaultDKIMFieldNames =
|
|
20
22
|
'From:Sender:Reply-To:Subject:Date:Message-ID:To:' +
|
|
@@ -247,14 +249,23 @@ const getPublicKey = async (type, name, minBitLength, resolver) => {
|
|
|
247
249
|
// prefix value for parsing as there is no default value
|
|
248
250
|
let entry = parseDkimHeaders(`DNS: TXT;${rr}`);
|
|
249
251
|
|
|
250
|
-
|
|
251
|
-
if (!
|
|
252
|
+
const publicKeyValue = entry?.parsed?.p?.value;
|
|
253
|
+
if (!publicKeyValue) {
|
|
252
254
|
let err = new Error('Missing key value');
|
|
253
255
|
err.code = 'EINVALIDVAL';
|
|
254
256
|
err.rr = rr;
|
|
255
257
|
throw err;
|
|
256
258
|
}
|
|
257
259
|
|
|
260
|
+
let validation = base64Schema.validate(publicKeyValue);
|
|
261
|
+
if (validation.error) {
|
|
262
|
+
let err = new Error('Invalid base64 format for public key');
|
|
263
|
+
err.code = 'EINVALIDVAL';
|
|
264
|
+
err.rr = rr;
|
|
265
|
+
err.details = validation.error;
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
268
|
+
|
|
258
269
|
if (type === 'DKIM' && entry?.parsed?.v && (entry?.parsed?.v?.value || '').toString().toLowerCase().trim() !== 'dkim1') {
|
|
259
270
|
let err = new Error('Unknown key version');
|
|
260
271
|
err.code = 'EINVALIDVER';
|
|
@@ -262,28 +273,46 @@ const getPublicKey = async (type, name, minBitLength, resolver) => {
|
|
|
262
273
|
throw err;
|
|
263
274
|
}
|
|
264
275
|
|
|
265
|
-
|
|
266
|
-
|
|
276
|
+
let paddingNeeded = publicKeyValue.length % 4 ? 4 - (publicKeyValue.length % 4) : 0;
|
|
277
|
+
|
|
278
|
+
const publicKeyPem = Buffer.from(
|
|
279
|
+
`-----BEGIN PUBLIC KEY-----\n${(publicKeyValue + '='.repeat(paddingNeeded)).replace(/.{64}/g, '$&\n')}\n-----END PUBLIC KEY-----`
|
|
280
|
+
);
|
|
281
|
+
const publicKeyObj = crypto.createPublicKey({
|
|
282
|
+
key: publicKeyPem,
|
|
283
|
+
format: 'pem'
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
let keyType = publicKeyObj.asymmetricKeyType;
|
|
267
287
|
|
|
268
288
|
if (!['rsa', 'ed25519'].includes(keyType) || (entry?.parsed?.k && entry?.parsed?.k?.value?.toLowerCase() !== keyType)) {
|
|
269
|
-
let err = new Error('Unknown key type');
|
|
289
|
+
let err = new Error('Unknown key type (${keyType})');
|
|
270
290
|
err.code = 'EINVALIDTYPE';
|
|
271
291
|
err.rr = rr;
|
|
272
292
|
throw err;
|
|
273
293
|
}
|
|
274
294
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
295
|
+
let modulusLength;
|
|
296
|
+
if (publicKeyObj.asymmetricKeyDetails) {
|
|
297
|
+
modulusLength = publicKeyObj.asymmetricKeyDetails.modulusLength;
|
|
298
|
+
} else {
|
|
299
|
+
// fall back to node-forge
|
|
300
|
+
const pubKeyData = pki.publicKeyFromPem(publicKeyPem.toString());
|
|
301
|
+
modulusLength = pubKeyData.n.bitLength();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (keyType === 'rsa' && modulusLength < 1024) {
|
|
305
|
+
let err = new Error('RSA key too short');
|
|
306
|
+
err.code = 'ESHORTKEY';
|
|
307
|
+
err.rr = rr;
|
|
308
|
+
throw err;
|
|
284
309
|
}
|
|
285
310
|
|
|
286
|
-
return {
|
|
311
|
+
return {
|
|
312
|
+
publicKey: publicKeyPem,
|
|
313
|
+
rr,
|
|
314
|
+
modulusLength
|
|
315
|
+
};
|
|
287
316
|
}
|
|
288
317
|
|
|
289
318
|
let err = new Error('Missing key value');
|
package/man/mailauth.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH "MAILAUTH" "1" "
|
|
1
|
+
.TH "MAILAUTH" "1" "April 2022" "v2.3.1" "Mailauth Help"
|
|
2
2
|
.SH "NAME"
|
|
3
3
|
\fBmailauth\fR
|
|
4
4
|
.QP
|
|
@@ -15,7 +15,7 @@ mailauth \- authenticate, sign and seal emails
|
|
|
15
15
|
\fBmailauth\fP \fIcommand\fR \fBhelp\fP
|
|
16
16
|
.SH DESCRIPTION
|
|
17
17
|
.P
|
|
18
|
-
Mailauth is an email authentication application to validate SPF, DKIM, DMARC and ARC\. You can also sign emails with DKIM digital signatures and seal messages with ARC\.
|
|
18
|
+
Mailauth is an email authentication application to validate SPF, DKIM, DMARC, and ARC\. You can also sign emails with DKIM digital signatures and seal messages with ARC\.
|
|
19
19
|
.SH COMMANDS
|
|
20
20
|
.P
|
|
21
21
|
\fBreport\fR
|
|
@@ -33,9 +33,13 @@ Authenticates an email and seals it with an ARC digital signature
|
|
|
33
33
|
\fBspf\fR
|
|
34
34
|
.br
|
|
35
35
|
Authenticates SPF for an IP address and email address
|
|
36
|
+
.P
|
|
37
|
+
\fBlicense\fR
|
|
38
|
+
.br
|
|
39
|
+
Display licenses for mailauth and included modules
|
|
36
40
|
.SH Website
|
|
37
41
|
.P
|
|
38
|
-
\fIhttps://github\.com/
|
|
42
|
+
\fIhttps://github\.com/postalsys/mailauth\fR
|
|
39
43
|
.SH EXAMPLES
|
|
40
44
|
.P
|
|
41
45
|
\fBnpm install mailauth \-g\fP
|
|
@@ -49,7 +53,7 @@ Authenticates SPF for an IP address and email address
|
|
|
49
53
|
\fBmailauth spf \-f andris@wildduck\.email \-i 217\.146\.76\.20\fP
|
|
50
54
|
.SH EMAIL ARGUMENT
|
|
51
55
|
.P
|
|
52
|
-
Email argument defines path to the email message file in EML format\. If not specified then
|
|
56
|
+
Email argument defines the path to the email message file in EML format\. If not specified, then
|
|
53
57
|
content is read from standard input\.
|
|
54
58
|
.SH OPTIONS
|
|
55
59
|
.RS 0
|
|
@@ -61,19 +65,19 @@ Enable silly verbose mode
|
|
|
61
65
|
Print application version
|
|
62
66
|
.IP \(bu 2
|
|
63
67
|
\fB\-\-client\-ip\fP, \fB\-i <ip>\fP
|
|
64
|
-
Client IP used for SPF checks\. If not set then parsed from the latest Received header\. (\fBreport\fP, \fBseal\fP, \fBspf\fP)
|
|
68
|
+
Client IP used for SPF checks\. If not set, then parsed from the latest Received header\. (\fBreport\fP, \fBseal\fP, \fBspf\fP)
|
|
65
69
|
.IP \(bu 2
|
|
66
70
|
\fB\-\-mta\fP, \fB\-m <hostname>\fP
|
|
67
|
-
|
|
71
|
+
The hostname of this machine, used in the \fBAuthentication\-Results\fP header\. (\fBreport\fP, \fBseal\fP, \fBspf\fP)
|
|
68
72
|
.IP \(bu 2
|
|
69
73
|
\fB\-\-helo\fP, \fB\-e <hostname>\fP
|
|
70
74
|
Client hostname from the EHLO/HELO command, used in some specific SPF checks\. (\fBreport\fP, \fBseal\fP, \fBspf\fP)
|
|
71
75
|
.IP \(bu 2
|
|
72
76
|
\fB\-\-sender\fP, \fB\-f <address>\fP
|
|
73
|
-
|
|
77
|
+
The email address from the \fBMAIL FROM\fP command\. If not set, the address from the latest \fIReturn\-Path\fR header is used instead\. (\fBreport\fP, \fBseal\fP, \fBspf\fP)
|
|
74
78
|
.IP \(bu 2
|
|
75
79
|
\fB\-\-dns\-cache\fP, \fB\-n <file>\fP
|
|
76
|
-
Path to a JSON file with cached DNS responses\. If this file is given then no actual DNS requests are performed\. (\fBreport\fP, \fBseal\fP, \fBspf\fP)
|
|
80
|
+
Path to a JSON file with cached DNS responses\. If this file is given, then no actual DNS requests are performed\. Anything that is not listed returns an \fBENOTFOUND\fP error\. (\fBreport\fP, \fBseal\fP, \fBspf\fP)
|
|
77
81
|
.IP \(bu 2
|
|
78
82
|
\fB\-\-private\-key\fP, \fB\-k <file>\fP
|
|
79
83
|
Path to a private key for signing\. Allowed key types are RSA and Ed25519 (\fBsign\fP, \fBseal\fP)
|
|
@@ -91,16 +95,16 @@ Signing algorithm\. Defaults either to \fIrsa\-sha256\fR or \fIed25519\-sha256\f
|
|
|
91
95
|
Canonicalization algorithm\. Defaults to \fIrelaxed/relaxed\fR\|\. (\fBsign\fP)
|
|
92
96
|
.IP \(bu 2
|
|
93
97
|
\fB\-\-body\-length\fP, \fB\-l <number>\fP
|
|
94
|
-
|
|
98
|
+
The maximum length of the canonicalized body to sign\. (\fBsign\fP)
|
|
95
99
|
.IP \(bu 2
|
|
96
100
|
\fB\-\-time\fP, \fB\-t <number>\fP
|
|
97
|
-
Signing time as a
|
|
101
|
+
Signing time as a Unix timestamp\. (\fBsign\fP, \fBseal\fP)
|
|
98
102
|
.IP \(bu 2
|
|
99
103
|
\fB\-\-header\-fields\fP, \fB\-h <list>\fP
|
|
100
104
|
Colon separated list of header field names to sign\. (\fBsign\fP, \fBseal\fP)
|
|
101
105
|
.IP \(bu 2
|
|
102
106
|
\fB\-\-headers\-only\fP, \fB\-o\fP
|
|
103
|
-
Return signing headers only\. By default the entire message is printed to console\. (\fBsign\fP, \fBseal\fP, \fBspf\fP)
|
|
107
|
+
Return signing headers only\. By default, the entire message is printed to the console\. (\fBsign\fP, \fBseal\fP, \fBspf\fP)
|
|
104
108
|
.IP \(bu 2
|
|
105
109
|
\fB\-\-max\-lookups\fP, \fB\-x\fP
|
|
106
110
|
How many DNS lookups allowed for SPF validation\. Defaults to 50\. (\fBreport\fP, \fBspf\fP)
|
|
@@ -108,7 +112,7 @@ How many DNS lookups allowed for SPF validation\. Defaults to 50\. (\fBreport\fP
|
|
|
108
112
|
.RE
|
|
109
113
|
.SH DNS CACHE
|
|
110
114
|
.P
|
|
111
|
-
For cached DNS requests use the following JSON structure
|
|
115
|
+
For cached DNS requests, use the following JSON object structure: primary keys are domain names, and subkeys are resource record types\.
|
|
112
116
|
.P
|
|
113
117
|
.RS 2
|
|
114
118
|
.nf
|
|
@@ -125,13 +129,13 @@ For cached DNS requests use the following JSON structure where main keys are dom
|
|
|
125
129
|
.fi
|
|
126
130
|
.RE
|
|
127
131
|
.P
|
|
128
|
-
|
|
132
|
+
You can split longer TXT strings into multiple strings\. There is no length limit, unlike in actual DNS so you can put the entire public key into a single string\.
|
|
129
133
|
.SH BUGS
|
|
130
134
|
.P
|
|
131
|
-
Please report any bugs to https://github\.com/
|
|
135
|
+
Please report any bugs to https://github\.com/postalsys/mailauth/issues\.
|
|
132
136
|
.SH LICENSE
|
|
133
137
|
.P
|
|
134
|
-
Copyright (c) 2020,
|
|
138
|
+
Copyright (c) 2020\-2022, Postal Systems (MIT)\.
|
|
135
139
|
.SH SEE ALSO
|
|
136
140
|
.P
|
|
137
141
|
node\.js(1)
|
package/man/man.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
## DESCRIPTION
|
|
14
14
|
|
|
15
|
-
Mailauth is an email authentication application to validate SPF, DKIM, DMARC and ARC. You can also sign emails with DKIM digital signatures and seal messages with ARC.
|
|
15
|
+
Mailauth is an email authentication application to validate SPF, DKIM, DMARC, and ARC. You can also sign emails with DKIM digital signatures and seal messages with ARC.
|
|
16
16
|
|
|
17
17
|
## COMMANDS
|
|
18
18
|
|
|
@@ -49,7 +49,7 @@ Display licenses for mailauth and included modules
|
|
|
49
49
|
|
|
50
50
|
## EMAIL ARGUMENT
|
|
51
51
|
|
|
52
|
-
Email argument defines path to the email message file in EML format. If not specified then
|
|
52
|
+
Email argument defines the path to the email message file in EML format. If not specified, then
|
|
53
53
|
content is read from standard input.
|
|
54
54
|
|
|
55
55
|
## OPTIONS
|
|
@@ -61,19 +61,19 @@ content is read from standard input.
|
|
|
61
61
|
Print application version
|
|
62
62
|
|
|
63
63
|
- `--client-ip`, `-i <ip>`
|
|
64
|
-
Client IP used for SPF checks. If not set then parsed from the latest Received header. (`report`, `seal`, `spf`)
|
|
64
|
+
Client IP used for SPF checks. If not set, then parsed from the latest Received header. (`report`, `seal`, `spf`)
|
|
65
65
|
|
|
66
66
|
- `--mta`, `-m <hostname>`
|
|
67
|
-
|
|
67
|
+
The hostname of this machine, used in the `Authentication-Results` header. (`report`, `seal`, `spf`)
|
|
68
68
|
|
|
69
69
|
- `--helo`, `-e <hostname>`
|
|
70
70
|
Client hostname from the EHLO/HELO command, used in some specific SPF checks. (`report`, `seal`, `spf`)
|
|
71
71
|
|
|
72
72
|
- `--sender`, `-f <address>`
|
|
73
|
-
|
|
73
|
+
The email address from the `MAIL FROM` command. If not set, the address from the latest _Return-Path_ header is used instead. (`report`, `seal`, `spf`)
|
|
74
74
|
|
|
75
75
|
- `--dns-cache`, `-n <file>`
|
|
76
|
-
Path to a JSON file with cached DNS responses. If this file is given then no actual DNS requests are performed. (`report`, `seal`, `spf`)
|
|
76
|
+
Path to a JSON file with cached DNS responses. If this file is given, then no actual DNS requests are performed. Anything that is not listed returns an `ENOTFOUND` error. (`report`, `seal`, `spf`)
|
|
77
77
|
|
|
78
78
|
- `--private-key`, `-k <file>`
|
|
79
79
|
Path to a private key for signing. Allowed key types are RSA and Ed25519 (`sign`, `seal`)
|
|
@@ -91,23 +91,23 @@ content is read from standard input.
|
|
|
91
91
|
Canonicalization algorithm. Defaults to _relaxed/relaxed_. (`sign`)
|
|
92
92
|
|
|
93
93
|
- `--body-length`, `-l <number>`
|
|
94
|
-
|
|
94
|
+
The maximum length of the canonicalized body to sign. (`sign`)
|
|
95
95
|
|
|
96
96
|
- `--time`, `-t <number>`
|
|
97
|
-
Signing time as a
|
|
97
|
+
Signing time as a Unix timestamp. (`sign`, `seal`)
|
|
98
98
|
|
|
99
99
|
- `--header-fields`, `-h <list>`
|
|
100
100
|
Colon separated list of header field names to sign. (`sign`, `seal`)
|
|
101
101
|
|
|
102
102
|
- `--headers-only`, `-o`
|
|
103
|
-
Return signing headers only. By default the entire message is printed to console. (`sign`, `seal`, `spf`)
|
|
103
|
+
Return signing headers only. By default, the entire message is printed to the console. (`sign`, `seal`, `spf`)
|
|
104
104
|
|
|
105
105
|
- `--max-lookups`, `-x`
|
|
106
106
|
How many DNS lookups allowed for SPF validation. Defaults to 50. (`report`, `spf`)
|
|
107
107
|
|
|
108
108
|
## DNS CACHE
|
|
109
109
|
|
|
110
|
-
For cached DNS requests use the following JSON structure
|
|
110
|
+
For cached DNS requests, use the following JSON object structure: primary keys are domain names, and subkeys are resource record types.
|
|
111
111
|
|
|
112
112
|
```
|
|
113
113
|
{
|
|
@@ -122,7 +122,7 @@ For cached DNS requests use the following JSON structure where main keys are dom
|
|
|
122
122
|
}
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
You can split longer TXT strings into multiple strings. There is no length limit, unlike in actual DNS so you can put the entire public key into a single string.
|
|
126
126
|
|
|
127
127
|
## BUGS
|
|
128
128
|
|
|
@@ -130,7 +130,7 @@ Please report any bugs to https://github.com/postalsys/mailauth/issues.
|
|
|
130
130
|
|
|
131
131
|
## LICENSE
|
|
132
132
|
|
|
133
|
-
Copyright (c) 2020-
|
|
133
|
+
Copyright (c) 2020-2022, Postal Systems (MIT).
|
|
134
134
|
|
|
135
135
|
## SEE ALSO
|
|
136
136
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailauth",
|
|
3
|
-
"version": "2.2
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Email authentication library for Node.js",
|
|
5
5
|
"main": "lib/mailauth.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "eslint \"lib/**/*.js\" \"test/**/*.js\" && mocha --recursive \"./test/**/*.js\" --reporter spec",
|
|
8
8
|
"prepublish": "npm run man || true",
|
|
9
9
|
"man": "cd man && marked-man --version `node -e \"console.log('v'+require('../package.json').version)\"` --manual 'Mailauth Help' --section 1 man.md > mailauth.1",
|
|
10
|
-
"build-dist": "npm run man && npm run licenses && pkg package.json",
|
|
10
|
+
"build-dist": "npm run man && npm run licenses && pkg --compress Brotli package.json",
|
|
11
11
|
"licenses": "license-report --only=prod --output=table --config license-report-config.json > licenses.txt"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
@@ -31,28 +31,28 @@
|
|
|
31
31
|
},
|
|
32
32
|
"homepage": "https://github.com/postalsys/mailauth",
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"chai": "4.3.
|
|
35
|
-
"eslint": "8.
|
|
34
|
+
"chai": "4.3.6",
|
|
35
|
+
"eslint": "8.14.0",
|
|
36
36
|
"eslint-config-nodemailer": "1.2.0",
|
|
37
|
-
"eslint-config-prettier": "8.
|
|
37
|
+
"eslint-config-prettier": "8.5.0",
|
|
38
38
|
"js-yaml": "4.1.0",
|
|
39
|
-
"license-report": "
|
|
39
|
+
"license-report": "5.0.2",
|
|
40
40
|
"marked": "0.7.0",
|
|
41
41
|
"marked-man": "0.7.0",
|
|
42
42
|
"mbox-reader": "1.1.5",
|
|
43
|
-
"mocha": "9.
|
|
44
|
-
"pkg": "5.
|
|
43
|
+
"mocha": "9.2.2",
|
|
44
|
+
"pkg": "5.6.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@fidm/x509": "1.2.1",
|
|
48
48
|
"ipaddr.js": "2.0.1",
|
|
49
|
-
"joi": "17.
|
|
49
|
+
"joi": "17.6.0",
|
|
50
50
|
"libmime": "5.0.0",
|
|
51
|
-
"node-forge": "
|
|
52
|
-
"nodemailer": "6.7.
|
|
51
|
+
"node-forge": "1.3.1",
|
|
52
|
+
"nodemailer": "6.7.3",
|
|
53
53
|
"psl": "1.8.0",
|
|
54
54
|
"punycode": "2.1.1",
|
|
55
|
-
"yargs": "17.
|
|
55
|
+
"yargs": "17.4.1"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
58
58
|
"node": ">=14.0.0"
|
|
@@ -60,20 +60,21 @@
|
|
|
60
60
|
"bin": {
|
|
61
61
|
"mailauth": "bin/mailauth.js"
|
|
62
62
|
},
|
|
63
|
-
"
|
|
64
|
-
"man"
|
|
65
|
-
|
|
63
|
+
"man": [
|
|
64
|
+
"man/mailauth.1"
|
|
65
|
+
],
|
|
66
66
|
"pkg": {
|
|
67
67
|
"scripts": [
|
|
68
68
|
"workers/**/*.js"
|
|
69
69
|
],
|
|
70
70
|
"assets": [
|
|
71
71
|
"man/**/*",
|
|
72
|
-
"licenses.txt"
|
|
72
|
+
"licenses.txt",
|
|
73
|
+
"LICENSE.txt"
|
|
73
74
|
],
|
|
74
75
|
"_targets": [
|
|
75
76
|
"node16-macos-x64"
|
|
76
77
|
],
|
|
77
|
-
"outputPath": "dist"
|
|
78
|
+
"outputPath": "ee-dist"
|
|
78
79
|
}
|
|
79
80
|
}
|
package/licenses.txt
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
name license type link author
|
|
2
|
-
---- ------------ ---- ------
|
|
3
|
-
@fidm/x509 MIT git+ssh://git@github.com/fidm/x509.git n/a
|
|
4
|
-
ipaddr.js MIT git://github.com/whitequark/ipaddr.js.git whitequark
|
|
5
|
-
joi BSD-3-Clause git://github.com/sideway/joi.git n/a
|
|
6
|
-
libmime MIT git://github.com/andris9/libmime.git Andris Reinman
|
|
7
|
-
node-forge (BSD-3-Clause OR GPL-2.0) git+https://github.com/digitalbazaar/forge.git Digital Bazaar, Inc.
|
|
8
|
-
nodemailer MIT git+https://github.com/nodemailer/nodemailer.git Andris Reinman
|
|
9
|
-
psl MIT git+ssh://git@github.com/lupomontero/psl.git Lupo Montero
|
|
10
|
-
punycode MIT git+https://github.com/bestiejs/punycode.js.git Mathias Bynens
|
|
11
|
-
yargs MIT git+https://github.com/yargs/yargs.git n/a
|