mailauth 3.0.1 → 3.0.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/README.md +2 -2
- package/cli.md +40 -17
- package/lib/bimi/index.js +34 -0
- package/lib/bimi/validate-svg.js +96 -0
- package/lib/mailauth.js +7 -1
- package/licenses.txt +12 -12
- package/man/mailauth.1 +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
- ARC sealing
|
|
11
11
|
- Sealing on authentication
|
|
12
12
|
- Sealing after modifications
|
|
13
|
-
- **BIMI** resolving
|
|
13
|
+
- **BIMI** resolving and **VMC** validation
|
|
14
14
|
- **MTA-STS** helpers
|
|
15
15
|
|
|
16
|
-
Pure JavaScript implementation, no external applications or compilation needed. It runs on any server/device that has Node
|
|
16
|
+
Pure JavaScript implementation, no external applications or compilation needed. It runs on any server/device that has Node 16+ installed.
|
|
17
17
|
|
|
18
18
|
## Command line usage
|
|
19
19
|
|
package/cli.md
CHANGED
|
@@ -26,14 +26,6 @@ Download `mailauth` for your platform:
|
|
|
26
26
|
- [Windows](https://github.com/postalsys/mailauth/releases/latest/download/mailauth.exe)
|
|
27
27
|
- Or install from the NPM registry: `npm install -g mailauth`
|
|
28
28
|
|
|
29
|
-
> **NB!** Downloadable files are quite large because these are packaged Node.js applications
|
|
30
|
-
|
|
31
|
-
Alternatively you can install `mailauth` from [npm](https://npmjs.com/package/mailauth).
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
npm install -g mailauth
|
|
35
|
-
```
|
|
36
|
-
|
|
37
29
|
## Help
|
|
38
30
|
|
|
39
31
|
```
|
|
@@ -246,9 +238,6 @@ $ mailauth vmc -a https://amplify.valimail.com/bimi/time-warner/yV3KRIg4nJW-cnn.
|
|
|
246
238
|
"logoFile": "<2300B base64 encoded file>",
|
|
247
239
|
"validHash": true,
|
|
248
240
|
"certificate": {
|
|
249
|
-
"subjectAltName": [
|
|
250
|
-
"cnn.com"
|
|
251
|
-
],
|
|
252
241
|
"subject": {
|
|
253
242
|
"businessCategory": "Private Organization",
|
|
254
243
|
"jurisdictionCountryName": "US",
|
|
@@ -263,8 +252,13 @@ $ mailauth vmc -a https://amplify.valimail.com/bimi/time-warner/yV3KRIg4nJW-cnn.
|
|
|
263
252
|
"trademarkCountryOrRegionName": "US",
|
|
264
253
|
"trademarkRegistration": "5817930"
|
|
265
254
|
},
|
|
255
|
+
"subjectAltName": [
|
|
256
|
+
"cnn.com"
|
|
257
|
+
],
|
|
266
258
|
"fingerprint": "17:B3:94:97:E6:6B:C8:6B:33:B8:0A:D2:F0:79:6B:08:A2:A6:84:BD",
|
|
267
259
|
"serialNumber": "0821B8FE0A9CBC3BAC10DA08C088EEF4",
|
|
260
|
+
"validFrom": "2021-08-12T00:00:00.000Z",
|
|
261
|
+
"validTo": "2022-08-12T23:59:59.000Z",
|
|
268
262
|
"issuer": {
|
|
269
263
|
"countryName": "US",
|
|
270
264
|
"organizationName": "DigiCert, Inc.",
|
|
@@ -275,7 +269,7 @@ $ mailauth vmc -a https://amplify.valimail.com/bimi/time-warner/yV3KRIg4nJW-cnn.
|
|
|
275
269
|
}
|
|
276
270
|
```
|
|
277
271
|
|
|
278
|
-
If the certificate verification fails, then the contents are not returned.
|
|
272
|
+
If the certificate verification fails, then the logo contents are not returned.
|
|
279
273
|
|
|
280
274
|
```
|
|
281
275
|
$ mailauth vmc -p /path/to/random/cert-bundle.pem
|
|
@@ -284,17 +278,46 @@ $ mailauth vmc -p /path/to/random/cert-bundle.pem
|
|
|
284
278
|
"error": {
|
|
285
279
|
"message": "Self signed certificate in certificate chain",
|
|
286
280
|
"details": {
|
|
287
|
-
"
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
281
|
+
"certificate": {
|
|
282
|
+
"subject": {
|
|
283
|
+
"commonName": "postal.vmc.local",
|
|
284
|
+
"organizationName": "Postal Systems OU.",
|
|
285
|
+
"countryName": "EE"
|
|
286
|
+
},
|
|
287
|
+
"subjectAltName": [],
|
|
288
|
+
"fingerprint": "CC:49:83:ED:3F:6B:77:45:5B:A5:3B:9E:EC:99:0E:A1:EF:D7:FF:97",
|
|
289
|
+
"serialNumber": "B61FBFBA917B15D9",
|
|
290
|
+
"validFrom": "2022-07-09T06:13:33.000Z",
|
|
291
|
+
"validTo": "2023-07-09T06:13:33.000Z",
|
|
292
|
+
"issuer": {
|
|
293
|
+
"commonName": "postal.vmc.local",
|
|
294
|
+
"organizationName": "Postal Systems OU.",
|
|
295
|
+
"countryName": "EE"
|
|
296
|
+
}
|
|
297
|
+
}
|
|
292
298
|
},
|
|
293
299
|
"code": "SELF_SIGNED_CERT_IN_CHAIN"
|
|
294
300
|
}
|
|
295
301
|
}
|
|
296
302
|
```
|
|
297
303
|
|
|
304
|
+
The embedded SVG file is also validated.
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
$ mailauth vmc -p /path/to/vmc-with-invalid-svg.pem
|
|
308
|
+
{
|
|
309
|
+
"success": false,
|
|
310
|
+
"error": {
|
|
311
|
+
"message": "VMC logo SVG validation failed",
|
|
312
|
+
"details": {
|
|
313
|
+
"message": "Not a Tiny PS profile",
|
|
314
|
+
"code": "INVALID_BASE_PROFILE"
|
|
315
|
+
},
|
|
316
|
+
"code": "SVG_VALIDATION_FAILED"
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
298
321
|
### license
|
|
299
322
|
|
|
300
323
|
Display licenses for `mailauth` and included modules.
|
package/lib/bimi/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const httpsSchema = Joi.string().uri({
|
|
|
12
12
|
const https = require('https');
|
|
13
13
|
const http = require('http');
|
|
14
14
|
const { vmc } = require('@postalsys/vmc');
|
|
15
|
+
const { validateSvg } = require('./validate-svg');
|
|
15
16
|
|
|
16
17
|
const lookup = async data => {
|
|
17
18
|
let { dmarc, headers, resolver } = data;
|
|
@@ -303,6 +304,12 @@ const validateVMC = async bimiData => {
|
|
|
303
304
|
try {
|
|
304
305
|
let vmcData = await vmc(authorityValue);
|
|
305
306
|
|
|
307
|
+
if (!vmcData.logoFile) {
|
|
308
|
+
let error = new Error('VMC does not contain a log file');
|
|
309
|
+
error.code = 'MISSING_VMC_LOGO';
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
|
|
306
313
|
if (vmcData?.mediaType?.toLowerCase() !== 'image/svg+xml') {
|
|
307
314
|
let error = new Error('Invalid media type for the logo file');
|
|
308
315
|
error.details = {
|
|
@@ -312,6 +319,33 @@ const validateVMC = async bimiData => {
|
|
|
312
319
|
throw error;
|
|
313
320
|
}
|
|
314
321
|
|
|
322
|
+
if (!vmcData.validHash) {
|
|
323
|
+
let error = new Error('VMC hash does not match logo file');
|
|
324
|
+
error.details = {
|
|
325
|
+
hashAlgo: vmcData.hashAlgo,
|
|
326
|
+
hashValue: vmcData.hashValue,
|
|
327
|
+
logoFile: vmcData.logoFile
|
|
328
|
+
};
|
|
329
|
+
error.code = 'INVALID_LOGO_HASH';
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// throws on invalid logo file
|
|
334
|
+
try {
|
|
335
|
+
validateSvg(Buffer.from(vmcData.logoFile, 'base64'));
|
|
336
|
+
} catch (err) {
|
|
337
|
+
let error = new Error('VMC logo SVG validation failed');
|
|
338
|
+
error.details = Object.assign(
|
|
339
|
+
{
|
|
340
|
+
message: err.message
|
|
341
|
+
},
|
|
342
|
+
error.details || {},
|
|
343
|
+
err.code ? { code: err.code } : {}
|
|
344
|
+
);
|
|
345
|
+
error.code = 'SVG_VALIDATION_FAILED';
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
|
|
315
349
|
if (d) {
|
|
316
350
|
// validate domain
|
|
317
351
|
let selectorSet = [];
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { XMLParser } = require('fast-xml-parser');
|
|
4
|
+
|
|
5
|
+
function validateSvg(logo) {
|
|
6
|
+
const parser = new XMLParser({
|
|
7
|
+
ignoreAttributes: false,
|
|
8
|
+
attributeNamePrefix: '@_'
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
let logoObj;
|
|
12
|
+
try {
|
|
13
|
+
logoObj = parser.parse(logo);
|
|
14
|
+
if (!logoObj) {
|
|
15
|
+
throw new Error('Emtpy file');
|
|
16
|
+
}
|
|
17
|
+
} catch (err) {
|
|
18
|
+
let error = new Error('Invalid SVG file');
|
|
19
|
+
error._err = err;
|
|
20
|
+
error.code = 'INVALID_XML_FILE';
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!logoObj.svg) {
|
|
25
|
+
let error = new Error('Invalid SVG file');
|
|
26
|
+
error.code = 'INVALID_SVG_FILE';
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (logoObj.svg['@_baseProfile'] !== 'tiny-ps') {
|
|
31
|
+
let error = new Error('Not a Tiny PS profile');
|
|
32
|
+
error.code = 'INVALID_BASE_PROFILE';
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!logoObj.svg.title) {
|
|
37
|
+
let error = new Error('Logo file is missing title');
|
|
38
|
+
error.code = 'LOGO_MISSING_TITLE';
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if ('@_x' in logoObj.svg || '@_y' in logoObj.svg) {
|
|
43
|
+
let error = new Error('Logo root includes x/y attributes');
|
|
44
|
+
error.code = 'LOGO_INVALID_ROOT_ATTRS';
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let walkElm = (node, name, path) => {
|
|
49
|
+
if (!node) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(node)) {
|
|
53
|
+
for (let entry of node) {
|
|
54
|
+
walkElm(entry, name, path + '.' + name + '[]');
|
|
55
|
+
}
|
|
56
|
+
} else if (typeof node === 'object') {
|
|
57
|
+
if (node['@_xlink:href'] && !/^#/.test(node['@_xlink:href'])) {
|
|
58
|
+
let error = new Error('External reference found from file');
|
|
59
|
+
error.details = {
|
|
60
|
+
element: name,
|
|
61
|
+
link: node['@_xlink:href'],
|
|
62
|
+
path
|
|
63
|
+
};
|
|
64
|
+
error.code = 'LOGO_INCLUDES_REFERENCE';
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (let key of Object.keys(node)) {
|
|
69
|
+
if (['script', 'animate', 'animatemotion', 'animatetransform', 'discard', 'set'].includes(key.toLowerCase())) {
|
|
70
|
+
let error = new Error('Unallowed element found from file');
|
|
71
|
+
error.details = {
|
|
72
|
+
element: key,
|
|
73
|
+
path: path + '.' + key
|
|
74
|
+
};
|
|
75
|
+
error.code = 'LOGO_INVALID_ELEMENT';
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (Array.isArray(node[key])) {
|
|
80
|
+
for (let entry of node[key]) {
|
|
81
|
+
walkElm(entry, key, path + '.' + key + '[]');
|
|
82
|
+
}
|
|
83
|
+
} else if (node[key] && typeof node[key] === 'object') {
|
|
84
|
+
walkElm(node[key], key, path + '.' + key);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
walkElm(logoObj, 'root', '');
|
|
91
|
+
|
|
92
|
+
// all validations passed
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { validateSvg };
|
package/lib/mailauth.js
CHANGED
|
@@ -5,6 +5,7 @@ const { spf } = require('./spf');
|
|
|
5
5
|
const { dmarc } = require('./dmarc');
|
|
6
6
|
const { arc, createSeal } = require('./arc');
|
|
7
7
|
const { bimi, validateVMC: validateBimiVmc } = require('./bimi');
|
|
8
|
+
const { validateSvg: validateBimiSvg } = require('./bimi/validate-svg');
|
|
8
9
|
const { parseReceived } = require('./parse-received');
|
|
9
10
|
const { sealMessage } = require('./arc');
|
|
10
11
|
const libmime = require('libmime');
|
|
@@ -180,4 +181,9 @@ const authenticate = async (input, opts) => {
|
|
|
180
181
|
};
|
|
181
182
|
};
|
|
182
183
|
|
|
183
|
-
module.exports = {
|
|
184
|
+
module.exports = {
|
|
185
|
+
authenticate,
|
|
186
|
+
sealMessage,
|
|
187
|
+
validateBimiVmc,
|
|
188
|
+
validateBimiSvg
|
|
189
|
+
};
|
package/licenses.txt
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
name
|
|
2
|
-
----
|
|
3
|
-
@
|
|
4
|
-
|
|
5
|
-
ipaddr.js
|
|
6
|
-
joi
|
|
7
|
-
libmime
|
|
8
|
-
node-forge
|
|
9
|
-
nodemailer
|
|
10
|
-
psl
|
|
11
|
-
punycode
|
|
12
|
-
yargs
|
|
1
|
+
name license type link installed version author
|
|
2
|
+
---- ------------ ---- ----------------- ------
|
|
3
|
+
@postalsys/vmc MIT https://registry.npmjs.org/@postalsys/vmc/-/vmc-1.0.4.tgz 1.0.4 Postal Systems OÜ
|
|
4
|
+
fast-xml-parser MIT git+https://github.com/NaturalIntelligence/fast-xml-parser.git 4.0.9 Amit Gupta https://amitkumargupta.work/
|
|
5
|
+
ipaddr.js MIT git://github.com/whitequark/ipaddr.js.git 2.0.1 whitequark whitequark@whitequark.org
|
|
6
|
+
joi BSD-3-Clause git://github.com/sideway/joi.git 17.6.0
|
|
7
|
+
libmime MIT git://github.com/andris9/libmime.git 5.1.0 Andris Reinman andris@kreata.ee
|
|
8
|
+
node-forge (BSD-3-Clause OR GPL-2.0) git+https://github.com/digitalbazaar/forge.git 1.3.1 Digital Bazaar, Inc. support@digitalbazaar.com http://digitalbazaar.com/
|
|
9
|
+
nodemailer MIT git+https://github.com/nodemailer/nodemailer.git 6.7.7 Andris Reinman
|
|
10
|
+
psl MIT git+ssh://git@github.com/lupomontero/psl.git 1.9.0 Lupo Montero lupomontero@gmail.com https://lupomontero.com/
|
|
11
|
+
punycode MIT git+https://github.com/bestiejs/punycode.js.git 2.1.1 Mathias Bynens https://mathiasbynens.be/
|
|
12
|
+
yargs MIT git+https://github.com/yargs/yargs.git 17.5.1
|
package/man/mailauth.1
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailauth",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Email authentication library for Node.js",
|
|
5
5
|
"main": "lib/mailauth.js",
|
|
6
6
|
"scripts": {
|
|
@@ -42,10 +42,11 @@
|
|
|
42
42
|
"marked-man": "0.7.0",
|
|
43
43
|
"mbox-reader": "1.1.5",
|
|
44
44
|
"mocha": "10.0.0",
|
|
45
|
-
"pkg": "5.
|
|
45
|
+
"pkg": "5.8.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@postalsys/vmc": "1.0.
|
|
48
|
+
"@postalsys/vmc": "1.0.4",
|
|
49
|
+
"fast-xml-parser": "4.0.9",
|
|
49
50
|
"ipaddr.js": "2.0.1",
|
|
50
51
|
"joi": "17.6.0",
|
|
51
52
|
"libmime": "5.1.0",
|