mailauth 3.0.0 → 3.0.3

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 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 14+ installed.
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/bin/mailauth.js CHANGED
@@ -294,18 +294,25 @@ const argv = yargs(hideBin(process.argv))
294
294
  ['vmc'],
295
295
  'Validate VMC logo',
296
296
  yargs => {
297
- yargs.option('authorityFile', {
298
- alias: 'f',
299
- type: 'string',
300
- description: 'Path to a VMC file',
301
- demandOption: false
302
- });
303
- yargs.option('authority', {
304
- alias: 'a',
305
- type: 'string',
306
- description: 'URL to a VMC file',
307
- demandOption: false
308
- });
297
+ yargs
298
+ .option('authorityPath', {
299
+ alias: 'p',
300
+ type: 'string',
301
+ description: 'Path to a VMC file',
302
+ demandOption: false
303
+ })
304
+ .option('authority', {
305
+ alias: 'a',
306
+ type: 'string',
307
+ description: 'URL to a VMC file',
308
+ demandOption: false
309
+ })
310
+ .option('domain', {
311
+ alias: 'd',
312
+ type: 'string',
313
+ description: 'Sending domain to validate',
314
+ demandOption: false
315
+ });
309
316
  },
310
317
  argv => {
311
318
  commandVmc(argv)
package/cli.md CHANGED
@@ -1,3 +1,7 @@
1
+ ![](https://github.com/postalsys/mailauth/raw/master/assets/mailauth.png)
2
+
3
+ Command line utility and a [Node.js library](README.md) for email authentication.
4
+
1
5
  # CLI USAGE
2
6
 
3
7
  ## TOC
@@ -22,14 +26,6 @@ Download `mailauth` for your platform:
22
26
  - [Windows](https://github.com/postalsys/mailauth/releases/latest/download/mailauth.exe)
23
27
  - Or install from the NPM registry: `npm install -g mailauth`
24
28
 
25
- > **NB!** Downloadable files are quite large because these are packaged Node.js applications
26
-
27
- Alternatively you can install `mailauth` from [npm](https://npmjs.com/package/mailauth).
28
-
29
- ```
30
- npm install -g mailauth
31
- ```
32
-
33
29
  ## Help
34
30
 
35
31
  ```
@@ -224,25 +220,24 @@ Where
224
220
  **Options**
225
221
 
226
222
  - `--authority <url>` or `-a <url>` is the URL for the VMC resource
227
- - `--authorityFile <path>` or `-f <path>` is the cached file for the authority URL to avoid network requests
223
+ - `--authorityPath <path>` or `-p <path>` is the cached file for the authority URL to avoid network requests
224
+ - `--domain <domain>` or `-d <domain>` is the sender domain to compare the certificate against
228
225
 
229
226
  **Example**
230
227
 
231
228
  ```
232
- $ mailauth vmc -a https://amplify.valimail.com/bimi/time-warner/yV3KRIg4nJW-cnn.pem
229
+ $ mailauth vmc -a https://amplify.valimail.com/bimi/time-warner/yV3KRIg4nJW-cnn.pem -d cnn.com
233
230
  {
234
231
  "url": "https://amplify.valimail.com/bimi/time-warner/yV3KRIg4nJW-cnn.pem",
235
232
  "success": true,
233
+ "domainVerified": true,
236
234
  "vmc": {
237
235
  "mediaType": "image/svg+xml",
238
236
  "hashAlgo": "sha1",
239
237
  "hashValue": "ea8c81da633c66a16262134a78576cdf067638e9",
240
- "logoFile": "PD94bWwgdmVyc...",
238
+ "logoFile": "<2300B base64 encoded file>",
241
239
  "validHash": true,
242
240
  "certificate": {
243
- "subjectAltName": [
244
- "cnn.com"
245
- ],
246
241
  "subject": {
247
242
  "businessCategory": "Private Organization",
248
243
  "jurisdictionCountryName": "US",
@@ -257,8 +252,13 @@ $ mailauth vmc -a https://amplify.valimail.com/bimi/time-warner/yV3KRIg4nJW-cnn.
257
252
  "trademarkCountryOrRegionName": "US",
258
253
  "trademarkRegistration": "5817930"
259
254
  },
255
+ "subjectAltName": [
256
+ "cnn.com"
257
+ ],
260
258
  "fingerprint": "17:B3:94:97:E6:6B:C8:6B:33:B8:0A:D2:F0:79:6B:08:A2:A6:84:BD",
261
259
  "serialNumber": "0821B8FE0A9CBC3BAC10DA08C088EEF4",
260
+ "validFrom": "2021-08-12T00:00:00.000Z",
261
+ "validTo": "2022-08-12T23:59:59.000Z",
262
262
  "issuer": {
263
263
  "countryName": "US",
264
264
  "organizationName": "DigiCert, Inc.",
@@ -269,26 +269,55 @@ $ mailauth vmc -a https://amplify.valimail.com/bimi/time-warner/yV3KRIg4nJW-cnn.
269
269
  }
270
270
  ```
271
271
 
272
- If the certificate verification fails, then the contents are not returned.
272
+ If the certificate verification fails, then the logo contents are not returned.
273
273
 
274
274
  ```
275
- $ mailauth vmc -f /path/to/random/cert-bundle.pem
275
+ $ mailauth vmc -p /path/to/random/cert-bundle.pem
276
276
  {
277
277
  "success": false,
278
278
  "error": {
279
279
  "message": "Self signed certificate in certificate chain",
280
280
  "details": {
281
- "subject": "CN=catchall.delivery",
282
- "fingerprint": "35:EF:C9:9A:52:D5:A9:94:00:68:C6:D4:17:F1:26:61:01:0F:70:6D",
283
- "fingerprint235": "09:AB:0F:6B:F5:4F:16:58:F8:94:80:DE:E2:1A:D1:47:CC:64:F2:BF:63:E7:73:E4:02:F9:D3:C3:F6:9E:CC:86",
284
- "validFrom": "Jul 6 23:10:49 2022 GMT",
285
- "validTo": "Oct 4 23:10:48 2022 GMT"
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
+ }
286
298
  },
287
299
  "code": "SELF_SIGNED_CERT_IN_CHAIN"
288
300
  }
289
301
  }
290
302
  ```
291
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
+
292
321
  ### license
293
322
 
294
323
  Display licenses for `mailauth` and included modules.
package/lib/bimi/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const crypto = require('crypto');
4
4
  const dns = require('dns');
5
- const { formatAuthHeaderRow, parseDkimHeaders } = require('../tools');
5
+ const { formatAuthHeaderRow, parseDkimHeaders, formatDomain, getAlignment } = require('../tools');
6
6
  const Joi = require('joi');
7
7
  const packageData = require('../../package.json');
8
8
  const httpsSchema = Joi.string().uri({
@@ -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;
@@ -244,26 +245,30 @@ const validateVMC = async bimiData => {
244
245
  return false;
245
246
  }
246
247
 
248
+ let selector = bimiData?.status?.header?.selector;
249
+ let d = bimiData?.status?.header?.d;
250
+
247
251
  let promises = [];
248
252
 
249
- promises.push(downloadPromise(bimiData.location, bimiData.locationFile));
250
- promises.push(downloadPromise(bimiData.authority, bimiData.authorityFile));
253
+ promises.push(downloadPromise(bimiData.location, bimiData.locationPath));
254
+ promises.push(downloadPromise(bimiData.authority, bimiData.authorityPath));
251
255
 
252
256
  if (!promises.length) {
253
257
  return false;
254
258
  }
255
259
 
256
- let results = await Promise.allSettled(promises);
260
+ let [{ reason: locationError, value: locationValue, status: locationStatus }, { reason: authorityError, value: authorityValue, status: authorityStatus }] =
261
+ await Promise.allSettled(promises);
257
262
 
258
263
  let result = {};
259
- if (results[0].value || results[0].reason) {
264
+ if (locationValue || locationError) {
260
265
  result.location = {
261
266
  url: bimiData.location,
262
- success: results[0].status === 'fulfilled'
267
+ success: locationStatus === 'fulfilled'
263
268
  };
264
269
 
265
- if (results[0].reason) {
266
- let err = results[0].reason;
270
+ if (locationError) {
271
+ let err = locationError;
267
272
  result.location.error = { message: err.message };
268
273
  if (err.redirect) {
269
274
  result.location.error.redirect = err.redirect;
@@ -274,18 +279,18 @@ const validateVMC = async bimiData => {
274
279
  }
275
280
 
276
281
  if (result.location.success) {
277
- result.location.logoFile = results[0].value.toString('base64');
282
+ result.location.logoFile = locationValue.toString('base64');
278
283
  }
279
284
  }
280
285
 
281
- if (results[1].value || results[1].reason) {
286
+ if (authorityValue || authorityError) {
282
287
  result.authority = {
283
288
  url: bimiData.authority,
284
- success: results[1].status === 'fulfilled'
289
+ success: authorityStatus === 'fulfilled'
285
290
  };
286
291
 
287
- if (results[1].reason) {
288
- let err = results[1].reason;
292
+ if (authorityError) {
293
+ let err = authorityError;
289
294
  result.authority.error = { message: err.message };
290
295
  if (err.redirect) {
291
296
  result.authority.error.redirect = err.redirect;
@@ -295,9 +300,90 @@ const validateVMC = async bimiData => {
295
300
  }
296
301
  }
297
302
 
298
- if (results[1].value) {
303
+ if (authorityValue) {
299
304
  try {
300
- result.authority.vmc = await vmc(results[1].value);
305
+ let vmcData = await vmc(authorityValue);
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
+
313
+ if (vmcData?.mediaType?.toLowerCase() !== 'image/svg+xml') {
314
+ let error = new Error('Invalid media type for the logo file');
315
+ error.details = {
316
+ mediaType: vmcData.mediaType
317
+ };
318
+ error.code = 'INVALID_MEDIATYPE';
319
+ throw error;
320
+ }
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
+
349
+ if (d) {
350
+ // validate domain
351
+ let selectorSet = [];
352
+ let domainSet = [];
353
+ vmcData?.certificate?.subjectAltName?.map(formatDomain)?.forEach(domain => {
354
+ if (/\b_bimi\./.test(domain)) {
355
+ selectorSet.push(domain);
356
+ } else {
357
+ domainSet.push(domain);
358
+ }
359
+ });
360
+
361
+ let domainVerified = false;
362
+
363
+ if (selector && selectorSet.includes(formatDomain(`${selector}._bimi.${d}`))) {
364
+ domainVerified = true;
365
+ } else {
366
+ let alignedDomain = getAlignment(d, domainSet, false);
367
+ if (alignedDomain) {
368
+ domainVerified = true;
369
+ }
370
+ }
371
+
372
+ if (!domainVerified) {
373
+ let error = new Error('Domain can not be verified');
374
+ error.details = {
375
+ subjectAltName: vmcData?.certificate?.subjectAltName,
376
+ selector,
377
+ d
378
+ };
379
+ error.code = 'VMC_DOMAIN_MISMATCH';
380
+ throw error;
381
+ } else {
382
+ result.authority.domainVerified = true;
383
+ }
384
+ }
385
+
386
+ result.authority.vmc = vmcData;
301
387
  } catch (err) {
302
388
  result.authority.success = false;
303
389
  result.authority.error = { message: err.message };
@@ -313,7 +399,7 @@ const validateVMC = async bimiData => {
313
399
  if (result.location && result.location.success && result.authority.success) {
314
400
  try {
315
401
  if (result.location.success && result.authority.vmc.hashAlgo && result.authority.vmc.validHash) {
316
- let hash = crypto.createHash(result.authority.vmc.hashAlgo).update(results[0].value).digest('hex');
402
+ let hash = crypto.createHash(result.authority.vmc.hashAlgo).update(locationValue).digest('hex');
317
403
  result.location.hashAlgo = result.authority.vmc.hashAlgo;
318
404
  result.location.hashValue = hash;
319
405
  result.authority.hashMatch = hash === result.authority.vmc.hashValue;
@@ -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 };
@@ -6,13 +6,18 @@ const fs = require('fs').promises;
6
6
 
7
7
  const cmd = async argv => {
8
8
  let bimiData = {};
9
- if (argv.authorityFile) {
10
- bimiData.authorityFile = await fs.readFile(argv.authorityFile);
9
+ if (argv.authorityPath) {
10
+ bimiData.authorityPath = await fs.readFile(argv.authorityPath);
11
11
  }
12
+
12
13
  if (argv.authority) {
13
14
  bimiData.authority = argv.authority;
14
15
  }
15
16
 
17
+ if (argv.domain) {
18
+ bimiData.status = { header: { d: argv.domain } };
19
+ }
20
+
16
21
  const result = await validateVMC(bimiData);
17
22
  process.stdout.write(JSON.stringify(result.authority, false, 2) + '\n');
18
23
  };
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 = { authenticate, sealMessage, validateBimiVmc };
184
+ module.exports = {
185
+ authenticate,
186
+ sealMessage,
187
+ validateBimiVmc,
188
+ validateBimiSvg
189
+ };
package/lib/tools.js CHANGED
@@ -490,5 +490,6 @@ module.exports = {
490
490
 
491
491
  getAlignment,
492
492
 
493
- formatRelaxedLine
493
+ formatRelaxedLine,
494
+ formatDomain
494
495
  };
package/licenses.txt CHANGED
@@ -1,11 +1,12 @@
1
- name license type link installed version author
2
- ---- ------------ ---- ----------------- ------
3
- @fidm/x509 MIT git+ssh://git@github.com/fidm/x509.git 1.2.1
4
- ipaddr.js MIT git://github.com/whitequark/ipaddr.js.git 2.0.1 whitequark whitequark@whitequark.org
5
- joi BSD-3-Clause git://github.com/sideway/joi.git 17.6.0
6
- libmime MIT git://github.com/andris9/libmime.git 5.1.0 Andris Reinman andris@kreata.ee
7
- 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/
8
- nodemailer MIT git+https://github.com/nodemailer/nodemailer.git 6.7.5 Andris Reinman
9
- psl MIT git+ssh://git@github.com/lupomontero/psl.git 1.8.0 Lupo Montero lupomontero@gmail.com https://lupomontero.com/
10
- punycode MIT git+https://github.com/bestiejs/punycode.js.git 2.1.1 Mathias Bynens https://mathiasbynens.be/
11
- yargs MIT git+https://github.com/yargs/yargs.git 17.5.1
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
@@ -1,4 +1,4 @@
1
- .TH "MAILAUTH" "1" "July 2022" "v2.3.4" "Mailauth Help"
1
+ .TH "MAILAUTH" "1" "July 2022" "v3.0.2" "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": "3.0.0",
3
+ "version": "3.0.3",
4
4
  "description": "Email authentication library for Node.js",
5
5
  "main": "lib/mailauth.js",
6
6
  "scripts": {
@@ -33,7 +33,7 @@
33
33
  "homepage": "https://github.com/postalsys/mailauth",
34
34
  "devDependencies": {
35
35
  "chai": "4.3.6",
36
- "eslint": "8.19.0",
36
+ "eslint": "8.20.0",
37
37
  "eslint-config-nodemailer": "1.2.0",
38
38
  "eslint-config-prettier": "8.5.0",
39
39
  "js-yaml": "4.1.0",
@@ -42,11 +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.7.0"
45
+ "pkg": "5.8.0"
46
46
  },
47
47
  "dependencies": {
48
- "@fidm/x509": "1.2.1",
49
- "@postalsys/vmc": "1.0.1",
48
+ "@postalsys/vmc": "1.0.5",
49
+ "fast-xml-parser": "4.0.9",
50
50
  "ipaddr.js": "2.0.1",
51
51
  "joi": "17.6.0",
52
52
  "libmime": "5.1.0",