Comparing version 4.7.3 to 4.8.0
# Changelog | ||
## [4.8.0](https://github.com/postalsys/mailauth/compare/v4.7.3...v4.8.0) (2024-11-05) | ||
### Features | ||
* **cert-type:** BIMI authority information includes the type of the cert ('VMC' or 'CMC') ([0dd8db8](https://github.com/postalsys/mailauth/commit/0dd8db81b2ffc8b9d84d1a4396c65bfa9a347088)) | ||
## [4.7.3](https://github.com/postalsys/mailauth/compare/v4.7.2...v4.7.3) (2024-10-21) | ||
@@ -4,0 +11,0 @@ |
{ | ||
"name": "mailauth", | ||
"version": "4.7.3", | ||
"version": "4.8.0", | ||
"description": "Email authentication library for Node.js", | ||
@@ -45,6 +45,6 @@ "main": "lib/mailauth.js", | ||
"mbox-reader": "1.2.0", | ||
"mocha": "10.7.3" | ||
"mocha": "10.8.2" | ||
}, | ||
"dependencies": { | ||
"@postalsys/vmc": "1.0.8", | ||
"@postalsys/vmc": "1.1.0", | ||
"fast-xml-parser": "4.5.0", | ||
@@ -54,5 +54,5 @@ "ipaddr.js": "2.2.0", | ||
"libmime": "5.3.5", | ||
"nodemailer": "6.9.15", | ||
"nodemailer": "6.9.16", | ||
"punycode.js": "2.3.1", | ||
"tldts": "6.1.52", | ||
"tldts": "6.1.58", | ||
"undici": "5.28.4", | ||
@@ -77,6 +77,6 @@ "yargs": "17.7.2" | ||
"targets": [ | ||
"latest-linux-x64", | ||
"latest-macos-x64", | ||
"latest-macos-arm64", | ||
"latest-win-x64" | ||
"node20-linux-x64", | ||
"node20-macos-x64", | ||
"node20-macos-arm64", | ||
"node20-win-x64" | ||
], | ||
@@ -83,0 +83,0 @@ "outputPath": "ee-dist" |
709
README.md
@@ -1,82 +0,125 @@ | ||
![](https://github.com/postalsys/mailauth/raw/master/assets/mailauth.png) | ||
# mailauth: Email Authentication for Node.js | ||
[Command line utility](cli.md) and a Node.js library for email authentication. | ||
![mailauth Logo](https://github.com/postalsys/mailauth/raw/master/assets/mailauth.png) | ||
**mailauth** is a comprehensive Node.js library and command-line utility for email authentication. It provides tools to work with various email security protocols, including SPF, DKIM, DMARC, ARC, BIMI, and MTA-STS. With mailauth, you can verify and sign emails, handle authentication results, and enhance your email security setup. | ||
**Key Features:** | ||
- **SPF** verification | ||
- **DKIM** signing | ||
- DKIM verification | ||
- **DKIM** signing and verification | ||
- **DMARC** verification | ||
- **ARC** verification | ||
- ARC sealing | ||
- Sealing on authentication | ||
- Sealing after modifications | ||
- **ARC** verification and sealing | ||
- Sealing during authentication | ||
- Sealing after message modifications | ||
- **BIMI** resolving and **VMC** validation | ||
- **MTA-STS** helpers | ||
- **MTA-STS** helper functions | ||
Pure JavaScript implementation, no external applications or compilation needed. It runs on any server/device that has Node 16+ installed. | ||
mailauth is a pure JavaScript implementation, requiring no external applications or compilation. It runs on any server or device with Node.js version 16 or later. | ||
## Command line usage | ||
## Table of Contents | ||
See [command line documentation](cli.md) for the `mailauth` command. | ||
1. [Installation](#installation) | ||
2. [Command-Line Usage](#command-line-usage) | ||
3. [Library Usage](#library-usage) | ||
- [Authentication](#authentication) | ||
- [DKIM](#dkim) | ||
- [Signing](#dkim-signing) | ||
- [Verification](#dkim-verification) | ||
- [SPF](#spf) | ||
- [Verification](#spf-verification) | ||
- [ARC](#arc) | ||
- [Validation](#arc-validation) | ||
- [Sealing](#arc-sealing) | ||
- [DMARC](#dmarc) | ||
- [Helpers](#dmarc-helpers) | ||
- [BIMI](#bimi) | ||
- [MTA-STS](#mta-sts) | ||
- [Policy Retrieval](#policy-retrieval) | ||
- [MX Validation](#mx-validation) | ||
4. [Testing](#testing) | ||
5. [License](#license) | ||
## Installation | ||
First, install mailauth from npm: | ||
```bash | ||
npm install mailauth | ||
``` | ||
Then, import the desired methods into your script: | ||
```javascript | ||
const { authenticate } = require('mailauth'); | ||
``` | ||
## Command-Line Usage | ||
mailauth includes a command-line utility called `mailauth`. For detailed information on how to use it, see the [command-line documentation](cli.md). | ||
## Library Usage | ||
## Authentication | ||
### Authentication | ||
Validate DKIM signatures, SPF, DMARC, ARC, and BIMI for an email. | ||
Use the `authenticate` function to validate DKIM signatures, SPF, DMARC, ARC, and BIMI for an email. | ||
```js | ||
await authenticate(message [,options]) -> | ||
{ dkim, spf, arc, dmarc, bimi, receivedChain, headers } | ||
#### Syntax | ||
```javascript | ||
await authenticate(message [, options]) | ||
// Returns: { dkim, spf, arc, dmarc, bimi, receivedChain, headers } | ||
``` | ||
Where | ||
#### Parameters | ||
- **message** is either a String, a Buffer, or a Readable stream that represents an email message | ||
- **options** (_object_) is an optional options object | ||
- **sender** (_string_) is the email address from MAIL FROM command. If not set, then it is parsed from the `Return-Path` header | ||
- **ip** (_string_) is the IP of the remote client that sent this message | ||
- **helo** (_string_) is the hostname value from HELO/EHLO command | ||
- **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`. | ||
- **mta** (_string_) is the hostname of the server performing the authentication (defaults to `os.hostname()`) | ||
- **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 | ||
- **disableArc** (_boolean_) if true then skip ARC checks | ||
- **disableDmarc** (_boolean_) if true then skip DMARC checks. It also disables checks that are dependent on DMARC (e.g., BIMI) | ||
- **disableBimi** (_boolean_) if true, then skip BIMI checks | ||
- **seal** (_object_) if set and message does not have a broken ARC chain, then seals the message using these values | ||
- **signingDomain** (_string_) ARC key domain name | ||
- **selector** (_string_) ARC key selector | ||
- **privateKey** (_string_ or _buffer_) Private key for signing. Either an RSA or an Ed25519 key | ||
- **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) | ||
- **maxResolveCount** (_number_ defaults to _10_) 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. | ||
- **maxVoidCount** (_number_ defaults to _2_) is the DNS lookup limit for SPF that produce an empty result. [RFC7208](https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4) requires this limit to be 2. | ||
- **message**: A `String`, `Buffer`, or `Readable` stream representing the email message. | ||
- **options** (optional): | ||
- **sender** (`string`): Email address from the MAIL FROM command. Defaults to the `Return-Path` header if not set. | ||
- **ip** (`string`): IP address of the remote client that sent the message. | ||
- **helo** (`string`): Hostname from the HELO/EHLO command. | ||
- **trustReceived** (`boolean`): If `true`, parses `ip` and `helo` from the latest `Received` header if not provided. Defaults to `false`. | ||
- **mta** (`string`): Hostname of the server performing the authentication. Defaults to `os.hostname()`. Included in Authentication headers. | ||
- **minBitLength** (`number`): Minimum allowed bits for RSA public keys. Defaults to `1024`. Keys with fewer bits will fail validation. | ||
- **disableArc** (`boolean`): If `true`, skips ARC checks. | ||
- **disableDmarc** (`boolean`): If `true`, skips DMARC checks, also disabling dependent checks like BIMI. | ||
- **disableBimi** (`boolean`): If `true`, skips BIMI checks. | ||
- **seal** (`object`): Options for ARC sealing if the message doesn't have a broken ARC chain. | ||
- **signingDomain** (`string`): ARC key domain name. | ||
- **selector** (`string`): ARC key selector. | ||
- **privateKey** (`string` or `Buffer`): Private key for signing (RSA or Ed25519). | ||
- **resolver** (`async function`): Custom DNS resolver function. Defaults to [`dns.promises.resolve`](https://nodejs.org/api/dns.html#dns_dnspromises_resolve_hostname_rrtype). | ||
- **maxResolveCount** (`number`): DNS lookup limit for SPF. Defaults to `10` as per [RFC7208](https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4). | ||
- **maxVoidCount** (`number`): DNS lookup limit for SPF producing empty results. Defaults to `2` as per [RFC7208](https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4). | ||
**Example** | ||
#### Example | ||
```js | ||
```javascript | ||
const { authenticate } = require('mailauth'); | ||
const { dkim, spf, arc, dmarc, bimi, receivedChain, headers } = await authenticate( | ||
message, // either a String, a Buffer or a Readable Stream | ||
{ | ||
// SMTP transmission options if available | ||
ip: '217.146.67.33', // SMTP client IP | ||
helo: 'uvn-67-33.tll01.zonevs.eu', // EHLO/HELO hostname | ||
sender: 'andris@ekiri.ee', // MAIL FROM address | ||
const dns = require('dns'); | ||
// Uncomment if you do not want to provide ip/helo/sender manually but parse from the message | ||
//trustReceived: true, | ||
const message = /* Your email message here */; | ||
// Server processing this message, defaults to os.hostname(). Inserted into Authentication headers | ||
mta: 'mx.ethereal.email', | ||
const { dkim, spf, arc, dmarc, bimi, receivedChain, headers } = await authenticate(message, { | ||
// SMTP transmission options | ||
ip: '217.146.67.33', // SMTP client IP | ||
helo: 'uvn-67-33.tll01.zonevs.eu', // HELO/EHLO hostname | ||
sender: 'andris@ekiri.ee', // MAIL FROM address | ||
// Optional DNS resolver function (defaults to `dns.promises.resolve`) | ||
resolver: async (name, rr) => await dns.promises.resolve(name, rr) | ||
} | ||
); | ||
// output authenticated message | ||
process.stdout.write(headers); // includes terminating line break | ||
// Uncomment to parse `ip` and `helo` from the latest `Received` header | ||
// trustReceived: true, | ||
// Server performing the authentication | ||
mta: 'mx.ethereal.email', | ||
// Optional DNS resolver function | ||
resolver: async (name, rr) => await dns.promises.resolve(name, rr), | ||
}); | ||
// Output authenticated message | ||
process.stdout.write(headers); // Includes terminating line break | ||
process.stdout.write(message); | ||
``` | ||
Example output: | ||
**Sample Output:** | ||
@@ -94,61 +137,68 @@ ``` | ||
You can see the full output (structured data for DKIM, SPF, DMARC, and ARC) from [this example](https://gist.github.com/andris9/6514b5e7c59154a5b08636f99052ce37). | ||
You can see the full output, including structured data for DKIM, SPF, DMARC, and ARC, from [this example](https://gist.github.com/andris9/6514b5e7c59154a5b08636f99052ce37). | ||
### receivedChain | ||
**Note:** The `receivedChain` property is an array of parsed representations of the `Received:` headers. | ||
`receivedChain` property is an array of parsed representations of the `Received:` headers. | ||
### DKIM | ||
## DKIM | ||
#### DKIM Signing | ||
### Signing | ||
Use the `dkimSign` function to sign an email message with DKIM. | ||
```js | ||
##### Syntax | ||
```javascript | ||
const { dkimSign } = require('mailauth/lib/dkim/sign'); | ||
const signResult = await dkimSign( | ||
message, // either a String, a Buffer or a Readable Stream | ||
{ | ||
// Optional, default canonicalization, default is "relaxed/relaxed" | ||
canonicalization: 'relaxed/relaxed', // c= | ||
// Optional, default signing and hashing algorithm | ||
// Mostly useful when you want to use rsa-sha1, otherwise no need to set | ||
algorithm: 'rsa-sha256', | ||
const signResult = await dkimSign(message, options); | ||
// Returns: { signatures: String, errors: Array } | ||
``` | ||
// Optional, default is current time | ||
signTime: new Date(), // t= | ||
##### Parameters | ||
// Keys for one or more signatures | ||
// Different signatures can use different algorithms (mostly useful when | ||
// you want to sign a message both with RSA and Ed25519) | ||
signatureData: [ | ||
{ | ||
signingDomain: 'tahvel.info', // d= | ||
selector: 'test.rsa', // s= | ||
// supported key types: RSA, Ed25519 | ||
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'), | ||
- **message**: A `String`, `Buffer`, or `Readable` stream representing the email message. | ||
- **options**: | ||
- **canonicalization** (`string`): Canonicalization method. Defaults to `'relaxed/relaxed'`. | ||
- **algorithm** (`string`): Signing and hashing algorithm. Defaults to `'rsa-sha256'`. | ||
- **signTime** (`Date`): Signing time. Defaults to current time. | ||
- **signatureData** (`Array`): Array of signature objects. Each object may contain: | ||
- **signingDomain** (`string`): DKIM key domain name. | ||
- **selector** (`string`): DKIM key selector. | ||
- **privateKey** (`string` or `Buffer`): Private key for signing (RSA or Ed25519). | ||
- **algorithm** (`string`, optional): Overrides parent `algorithm`. | ||
- **canonicalization** (`string`, optional): Overrides parent `canonicalization`. | ||
- **maxBodyLength** (`number`, optional): Maximum number of canonicalized body bytes to sign (`l=` tag). Not recommended for general use. | ||
// Optional algorithm, default is derived from the key. | ||
// Overrides whatever was set in parent object | ||
algorithm: 'rsa-sha256', | ||
##### Example | ||
// Optional signature specifc canonicalization, overrides whatever was set in parent object | ||
canonicalization: 'relaxed/relaxed' // c= | ||
```javascript | ||
const { dkimSign } = require('mailauth/lib/dkim/sign'); | ||
const fs = require('fs'); | ||
// Maximum number of canonicalized body bytes to sign (eg. the "l=" tag). | ||
// Do not use though. This is available only for compatibility testing. | ||
// maxBodyLength: 12345 | ||
} | ||
] | ||
} | ||
); // -> {signatures: String, errors: Array} signature headers using \r\n as the line separator | ||
// show signing errors (if any) | ||
const message = /* Your email message here */; | ||
const signResult = await dkimSign(message, { | ||
canonicalization: 'relaxed/relaxed', | ||
algorithm: 'rsa-sha256', | ||
signTime: new Date(), | ||
signatureData: [ | ||
{ | ||
signingDomain: 'tahvel.info', | ||
selector: 'test.rsa', | ||
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'), | ||
}, | ||
], | ||
}); | ||
// Display signing errors if any | ||
if (signResult.errors.length) { | ||
console.log(signResult.errors); | ||
console.error('Signing errors:', signResult.errors); | ||
} | ||
// output signed message | ||
process.stdout.write(signResult.signatures); // includes terminating line break | ||
// Output signed message | ||
process.stdout.write(signResult.signatures); // Includes terminating line break | ||
process.stdout.write(message); | ||
``` | ||
Example output: | ||
**Sample Output:** | ||
@@ -161,40 +211,21 @@ ``` | ||
### Signing as a PassThrough Stream | ||
#### DKIM Signing as a Stream | ||
Use `DkimSignStream` stream if you want to use DKIM signing as part of a stream processing pipeline. | ||
Use `DkimSignStream` to sign messages as part of a stream processing pipeline. | ||
```js | ||
##### Example | ||
```javascript | ||
const { DkimSignStream } = require('mailauth/lib/dkim/sign'); | ||
const fs = require('fs'); | ||
const dkimSignStream = new DkimSignStream({ | ||
// Optional, default canonicalization, default is "relaxed/relaxed" | ||
canonicalization: 'relaxed/relaxed', // c= | ||
// Optional, default signing and hashing algorithm | ||
// Mostly useful when you want to use rsa-sha1, otherwise no need to set | ||
canonicalization: 'relaxed/relaxed', | ||
algorithm: 'rsa-sha256', | ||
// Optional, default is current time | ||
signTime: new Date(), // t= | ||
// Keys for one or more signatures | ||
// Different signatures can use different algorithms (mostly useful when | ||
// you want to sign a message both with RSA and Ed25519) | ||
signTime: new Date(), | ||
signatureData: [ | ||
{ | ||
signingDomain: 'tahvel.info', // d= | ||
selector: 'test.rsa', // s= | ||
// supported key types: RSA, Ed25519 | ||
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'), | ||
// Optional algorithm, default is derived from the key. | ||
// Overrides whatever was set in parent object | ||
algorithm: 'rsa-sha256', | ||
// Optional signature specifc canonicalization, overrides whatever was set in parent object | ||
canonicalization: 'relaxed/relaxed' // c= | ||
// Maximum number of canonicalized body bytes to sign (eg. the "l=" tag). | ||
// Do not use though. This is available only for compatibility testing. | ||
// maxBodyLength: 12345 | ||
signingDomain: 'tahvel.info', | ||
selector: 'test.rsa', | ||
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem') | ||
} | ||
@@ -204,20 +235,36 @@ ] | ||
// Writes a signed message to the output | ||
// Read from stdin, write signed message to stdout | ||
process.stdin.pipe(dkimSignStream).pipe(process.stdout); | ||
``` | ||
### Verifying | ||
#### DKIM Verification | ||
```js | ||
Use the `dkimVerify` function to verify DKIM signatures in an email message. | ||
##### Syntax | ||
```javascript | ||
const { dkimVerify } = require('mailauth/lib/dkim/verify'); | ||
// `message` is either a String, a Buffer or a Readable Stream | ||
const result = await dkimVerify(message); | ||
for (let { info } of result.results) { | ||
console.log(info); | ||
// Returns an object containing verification results | ||
``` | ||
##### Example | ||
```javascript | ||
const { dkimVerify } = require('mailauth/lib/dkim/verify'); | ||
const message = /* Your email message here */; | ||
const result = await dkimVerify(message); | ||
for (const { info } of result.results) { | ||
console.log(info); | ||
} | ||
``` | ||
Example output: | ||
**Sample Output:** | ||
```txt | ||
``` | ||
dkim=neutral (invalid public key) header.i=@tahvel.info header.s=test.invalid header.b="b85yao+1" | ||
@@ -228,10 +275,31 @@ dkim=pass header.i=@tahvel.info header.s=test.rsa header.b="BrEgDN4A" | ||
## SPF | ||
### SPF | ||
### Verifying | ||
#### SPF Verification | ||
```js | ||
Use the `spf` function to verify the SPF record for an email sender. | ||
##### Syntax | ||
```javascript | ||
const { spf } = require('mailauth/lib/spf'); | ||
let result = await spf({ | ||
const result = await spf(options); | ||
// Returns an object containing SPF verification results | ||
``` | ||
##### Parameters | ||
- **options**: | ||
- **sender** (`string`): MAIL FROM address. | ||
- **ip** (`string`): SMTP client IP. | ||
- **helo** (`string`): HELO/EHLO hostname. | ||
- **mta** (`string`): Hostname of the MTA performing the check. | ||
##### Example | ||
```javascript | ||
const { spf } = require('mailauth/lib/spf'); | ||
const result = await spf({ | ||
sender: 'andris@wildduck.email', | ||
@@ -242,8 +310,9 @@ ip: '217.146.76.20', | ||
}); | ||
console.log(result.header); | ||
``` | ||
Example output: | ||
**Sample Output:** | ||
```txt | ||
``` | ||
Received-SPF: pass (mx.myhost.com: domain of andris@wildduck.email | ||
@@ -254,270 +323,286 @@ designates 217.146.76.20 as permitted sender) client-ip=217.146.76.20; | ||
## ARC | ||
### ARC | ||
### Validation | ||
#### ARC Validation | ||
ARC seals are automatically validated during the authentication step. | ||
ARC seals are validated automatically during the authentication step. | ||
```js | ||
##### Example | ||
```javascript | ||
const { authenticate } = require('mailauth'); | ||
const { arc } = await authenticate( | ||
message, // either a String, a Buffer or a Readable Stream | ||
{ | ||
trustReceived: true | ||
} | ||
); | ||
const message = /* Your email message here */; | ||
const { arc } = await authenticate(message, { | ||
trustReceived: true, | ||
}); | ||
console.log(arc); | ||
``` | ||
The output is something like this: | ||
**Sample Output:** | ||
``` | ||
```json | ||
{ | ||
"status": { | ||
"result": "pass", | ||
"comment": "i=2 spf=neutral dkim=pass dkdomain=zonevs.eu dkim=pass dkdomain=srs3.zonevs.eu dmarc=fail fromdomain=zone.ee" | ||
}, | ||
"i": 2, | ||
... | ||
"status": { | ||
"result": "pass", | ||
"comment": "i=2 spf=neutral dkim=pass dkdomain=zonevs.eu dkim=pass dkdomain=srs3.zonevs.eu dmarc=fail fromdomain=zone.ee" | ||
}, | ||
"i": 2 | ||
// Additional properties... | ||
} | ||
``` | ||
### Sealing | ||
#### ARC Sealing | ||
#### During authentication | ||
You can seal messages with ARC either during authentication or after modifications. | ||
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. | ||
##### Sealing During Authentication | ||
```js | ||
Provide the sealing key in the options to seal messages automatically during authentication. | ||
```javascript | ||
const { authenticate } = require('mailauth'); | ||
const { headers } = await authenticate( | ||
message, // either a String, a Buffer or a Readable Stream | ||
{ | ||
trustReceived: true, | ||
const fs = require('fs'); | ||
// ARC seal settings. If this is set then resulting headers include | ||
// a complete ARC header set (unless the message has a failing ARC chain) | ||
seal: { | ||
signingDomain: 'tahvel.info', | ||
selector: 'test.rsa', | ||
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem') | ||
} | ||
} | ||
); | ||
// output authenticated and sealed message | ||
process.stdout.write(headers); // includes terminating line break | ||
const message = /* Your email message here */; | ||
const { headers } = await authenticate(message, { | ||
trustReceived: true, | ||
seal: { | ||
signingDomain: 'tahvel.info', | ||
selector: 'test.rsa', | ||
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'), | ||
}, | ||
}); | ||
// Output authenticated and sealed message | ||
process.stdout.write(headers); // Includes terminating line break | ||
process.stdout.write(message); | ||
``` | ||
#### After modifications | ||
##### Sealing After Modifications | ||
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. | ||
If you need to modify the message before sealing, first authenticate it, modify as needed, then seal using the authentication results. | ||
```js | ||
```javascript | ||
const { authenticate, sealMessage } = require('mailauth'); | ||
const fs = require('fs'); | ||
// 1. authenticate the message | ||
const { arc, headers } = await authenticate( | ||
message, // either a String, a Buffer or a Readable Stream | ||
{ | ||
ip: '217.146.67.33', // SMTP client IP | ||
helo: 'uvn-67-33.tll01.zonevs.eu', // EHLO/HELO hostname | ||
mta: 'mx.ethereal.email', // server processing this message, defaults to os.hostname() | ||
sender: 'andris@ekiri.ee' // MAIL FROM address | ||
} | ||
); | ||
const message = /* Your email message here */; | ||
// 2. perform some modifications with the message ... | ||
// Step 1: Authenticate the message | ||
const { arc, headers } = await authenticate(message, { | ||
ip: '217.146.67.33', | ||
helo: 'uvn-67-33.tll01.zonevs.eu', | ||
mta: 'mx.ethereal.email', | ||
sender: 'andris@ekiri.ee', | ||
}); | ||
// 3. seal the modified message using the initial authentication results | ||
// Step 2: Modify the message as needed | ||
// ... your modifications ... | ||
// Step 3: Seal the modified message | ||
const sealHeaders = await sealMessage(message, { | ||
signingDomain: 'tahvel.info', | ||
selector: 'test.rsa', | ||
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'), | ||
// values from the authentication step | ||
authResults: arc.authResults, | ||
cv: arc.status.result | ||
signingDomain: 'tahvel.info', | ||
selector: 'test.rsa', | ||
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'), | ||
authResults: arc.authResults, | ||
cv: arc.status.result, | ||
}); | ||
// output authenticated message | ||
process.stdout.write(sealHeaders); // ARC set | ||
process.stdout.write(headers); // authentication results | ||
// Output the sealed message | ||
process.stdout.write(sealHeaders); // ARC headers | ||
process.stdout.write(headers); // Authentication results | ||
process.stdout.write(message); | ||
``` | ||
## DMARC | ||
### DMARC | ||
DMARC is verified as part of the authentication process and even as the `dmarc` handler is exported, it requires input from previous steps. | ||
DMARC is verified during the authentication process. Although the `dmarc` handler is exported, it requires input from previous steps like SPF and DKIM. | ||
### Helpers | ||
#### DMARC Helpers | ||
#### getDmarcRecord(domain [,resolver]) | ||
##### `getDmarcRecord(domain [, resolver])` | ||
Returns parsed DMARC DNS record for a domain or a subdomain or `false` is no record exists. | ||
Fetches and parses the DMARC DNS record for a domain or subdomain. Returns `false` if no record exists. | ||
###### Syntax | ||
```javascript | ||
const getDmarcRecord = require('mailauth/lib/dmarc/get-dmarc-record'); | ||
const dmarcRecord = await getDmarcRecord(domain [, resolver]); | ||
// Returns an object with DMARC record details or `false` if not found | ||
``` | ||
###### Parameters | ||
- **domain** (`string`): The domain to check for a DMARC record. | ||
- **resolver** (`function`, optional): Custom DNS resolver function. Defaults to `dns.resolve`. | ||
###### Example | ||
```javascript | ||
const getDmarcRecord = require('mailauth/lib/dmarc/get-dmarc-record'); | ||
const dmarcRecord = await getDmarcRecord("ethereal.email"); | ||
const dmarcRecord = await getDmarcRecord('ethereal.email'); | ||
console.log(dmarcRecord); | ||
``` | ||
**Output** | ||
**Sample Output:** | ||
``` | ||
```json | ||
{ | ||
v: 'DMARC1', | ||
p: 'none', | ||
pct: 100, | ||
rua: 'mailto:re+joqy8fpatm3@dmarc.postmarkapp.com', | ||
sp: 'none', | ||
aspf: 'r', | ||
rr: 'v=DMARC1; p=none; pct=100; rua=mailto:re+joqy8fpatm3@dmarc.postmarkapp.com; sp=none; aspf=r;', | ||
isOrgRecord: false | ||
"v": "DMARC1", | ||
"p": "none", | ||
"pct": 100, | ||
"rua": "mailto:re+joqy8fpatm3@dmarc.postmarkapp.com", | ||
"sp": "none", | ||
"aspf": "r", | ||
"rr": "v=DMARC1; p=none; pct=100; rua=mailto:re+joqy8fpatm3@dmarc.postmarkapp.com; sp=none; aspf=r;", | ||
"isOrgRecord": false | ||
} | ||
``` | ||
`isOrgRecord` is `true` for sudomains, where organizational domain's DMARC policy applies, so use the `sp`, not `p` policy. | ||
### BIMI | ||
Optionally set `resolver` argument with custom resolver (uses `dns.resolve` by default). | ||
Brand Indicators for Message Identification (BIMI) support is based on [draft-blank-ietf-bimi-02](https://tools.ietf.org/html/draft-blank-ietf-bimi-02). BIMI information is resolved during the authentication step, provided the message passes DMARC validation with a policy other than "none". | ||
## BIMI | ||
#### Example | ||
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). | ||
```javascript | ||
const { authenticate } = require('mailauth'); | ||
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. | ||
const message = /* Your email message here */; | ||
```js | ||
const { bimi } = await authenticate( | ||
message, // either a String, a Buffer or a Readable Stream | ||
{ | ||
ip: '217.146.67.33', // SMTP client IP | ||
helo: 'uvn-67-33.tll01.zonevs.eu', // EHLO/HELO hostname | ||
mta: 'mx.ethereal.email', // server processing this message, defaults to os.hostname() | ||
sender: 'andris@ekiri.ee', // MAIL FROM address | ||
const { bimi } = await authenticate(message, { | ||
ip: '217.146.67.33', | ||
helo: 'uvn-67-33.tll01.zonevs.eu', | ||
mta: 'mx.ethereal.email', | ||
sender: 'andris@ekiri.ee', | ||
bimiWithAlignedDkim: false, // If true, ignores SPF in DMARC and requires a valid DKIM signature | ||
}); | ||
bimiWithAlignedDkim: false // If true then ignores SPF in DMARC and requires a valid DKIM signature | ||
} | ||
); | ||
if (bimi?.location) { | ||
console.log(`BIMI location: ${bimi.location}`); | ||
console.log(`BIMI location: ${bimi.location}`); | ||
} | ||
``` | ||
`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). | ||
**Note:** | ||
### Verified Mark Certificate | ||
- The `BIMI-Location` header is ignored by mailauth. | ||
- The `BIMI-Selector` header can be used for selector selection if available. | ||
Authority Evidence Document location is available from the `bimi.authority` property (if set). | ||
#### Verified Mark Certificate (VMC) | ||
VMC (Verified Mark Certificates) for Authority Evidence Documents is a X509 certificate with an `id-pe-logotype` extension (`oid=1.3.6.1.5.5.7.1.12`) that includes a compressed SVG formatted logo file ([read more here](https://bimigroup.org/resources/VMC_Guidelines_latest.pdf)). | ||
If an Authority Evidence Document is specified in the BIMI record, its location is available in `bimi.authority`. mailauth exposes the certificate type (`"VMC"` or `"CMC"`) in `bimi.authority.vmc.type`. | ||
Some example authority evidence documents: | ||
**Example Authority Evidence Documents:** | ||
- [from default.\_bimi.cnn.com](https://amplify.valimail.com/bimi/time-warner/LysAFUdG-Hw-cnn_vmc.pem) | ||
- [from default.\_bimi.entrustdatacard.com](https://www.entrustdatacard.com/-/media/certificate/Entrust%20VMC%20July%2014%202020.pem) | ||
- [CNN's VMC](https://amplify.valimail.com/bimi/time-warner/LysAFUdG-Hw-cnn_vmc.pem) | ||
- [Entrust's VMC](https://www.entrustdatacard.com/-/media/certificate/Entrust%20VMC%20July%2014%202020.pem) | ||
## MTA-STS | ||
### MTA-STS | ||
`mailauth` allows you to fetch MTA-STS information for a domain name. | ||
mailauth provides functions to fetch and validate MTA-STS policies for a domain. | ||
```js | ||
const { getPolicy, validateMx } = require('mailauth/lib/mta-sts'); | ||
#### Policy Retrieval | ||
let knownPolicy = getCachedPolicy('gmail.com'); // optional | ||
let mx = 'alt4.gmail-smtp-in.l.google.com'; | ||
Use the `getPolicy` function to fetch the MTA-STS policy for a domain. | ||
##### Syntax | ||
```javascript | ||
const { getPolicy } = require('mailauth/lib/mta-sts'); | ||
const { policy, status } = await getPolicy(domain [, knownPolicy]); | ||
// Returns an object with the policy and status | ||
``` | ||
##### Parameters | ||
- **domain** (`string`): The domain to retrieve the policy for. | ||
- **knownPolicy** (`object`, optional): Previously cached policy for the domain. | ||
##### Example | ||
```javascript | ||
const { getPolicy } = require('mailauth/lib/mta-sts'); | ||
const knownPolicy = /* Retrieve from your cache if available */; | ||
const { policy, status } = await getPolicy('gmail.com', knownPolicy); | ||
const policyMatch = validateMx(mx, policy); | ||
if (policy.id !== knownPolicy?.id) { | ||
// policy has been updated, update cache | ||
// Update your cache with the new policy | ||
} | ||
if (policy.mode === 'enforce') { | ||
// must use TLS | ||
// TLS must be used when sending to this domain | ||
} | ||
if (policy.mx && !policyMatch.valid) { | ||
// can't connect, unlisted MX | ||
} | ||
``` | ||
### Resolve policy | ||
**Possible Status Values:** | ||
Resolve MTA-STS policy for a domain | ||
- `"not_found"`: No policy was found. | ||
- `"cached"`: Existing policy is still valid. | ||
- `"found"`: New or updated policy found. | ||
- `"renew"`: Existing policy is valid; renew cache. | ||
- `"errored"`: Policy discovery failed due to a temporary error. | ||
``` | ||
async getPolicy(domain [,knownPolicy]) -> {policy, status} | ||
``` | ||
#### MX Validation | ||
Where | ||
Use the `validateMx` function to check if an MX hostname is valid according to the MTA-STS policy. | ||
- **domain** is the domain to check for (e.g. "gmail.com") | ||
- **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. | ||
##### Syntax | ||
The function returns an object with the following properties: | ||
```javascript | ||
const { validateMx } = require('mailauth/lib/mta-sts'); | ||
- **policy** (object) | ||
- **id** (string or `false`) ID of the policy | ||
- **mode** (string) one of _"none"_, _"testing"_ or _"enforce"_ | ||
- **mx** (array, if available) an Array of whitelisted MX hostnames | ||
- **expires** (string, if available) ISO date string for cacheing | ||
- **status** (string) one of the following values: | ||
- _"not_found"_ no policy was found for this domain. You can decide yourself how long you want to cache this response | ||
- _"cached"_ no changes detected, current policy is still valid and can be used | ||
- _"found"_ new or updated policy was found. Cache this in your system until _policy.expires_ | ||
- _"renew"_ existing policy is still valid, renew cached version until _policy.expires_ | ||
- _"errored"_ policy discovery failed for some temporary error (e.g., failing DNS queries). See _policy.error_ for details | ||
### Validate MX hostname | ||
Check if a resolved MX hostname is valid by MTA-STS policy or not. | ||
const validation = validateMx(mx, policy); | ||
// Returns an object indicating if the MX is valid | ||
``` | ||
validateMx(mx, policy) -> Object | ||
``` | ||
Where | ||
##### Parameters | ||
- **mx** is the resolved MX hostname (eg. "gmail-smtp-in.l.google.com") | ||
- **policy** is the policy object returned by `getPolicy()` | ||
- **mx** (`string`): The resolved MX hostname. | ||
- **policy** (`object`): The MTA-STS policy object. | ||
The function returns an object. If `{valid}` is `true`, then MX hostname is allowed to be used. | ||
##### Example | ||
## Testing | ||
```javascript | ||
const { getPolicy, validateMx } = require('mailauth/lib/mta-sts'); | ||
`mailauth` uses the following test suites: | ||
const { policy } = await getPolicy('gmail.com'); | ||
### SPF test suite | ||
const mx = 'alt4.gmail-smtp-in.l.google.com'; | ||
const policyMatch = validateMx(mx, policy); | ||
[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: | ||
if (policy.mx && !policyMatch.valid) { | ||
// The MX host is not listed in the policy; do not connect | ||
} | ||
``` | ||
- Less strict whitespace checks (`mailauth` accepts multiple spaces between tags etc.) | ||
- Some macro tests are skipped (macro expansion is supported _in most parts_) | ||
- 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) | ||
- Other than that, all tests pass | ||
## Testing | ||
### ARC test suite from ValiMail | ||
mailauth uses the following test suites: | ||
ValiMail [arc_test_suite](https://github.com/ValiMail/arc_test_suite) | ||
### SPF Test Suite | ||
- `mailauth` is less strict on header tags and casing. For example, uppercase `S=` for a selector passes in `mailauth` but fails in ValiMail. | ||
- 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 | ||
- Other than that, all tests pass | ||
Based on the [OpenSPF test suite](http://www.openspf.org/Test_Suite), with some differences: | ||
## Setup | ||
- Less strict whitespace checks. | ||
- Some macro tests are skipped. | ||
- Some tests are skipped where the invalid component is after a matching part. | ||
- All other tests pass. | ||
First, install the module from npm: | ||
### ARC Test Suite from ValiMail | ||
``` | ||
$ npm install mailauth | ||
``` | ||
Based on ValiMail's [arc_test_suite](https://github.com/ValiMail/arc_test_suite): | ||
next import any method you want to use from mailauth package into your script: | ||
- mailauth is less strict on header tags and casing. | ||
- Signing test suite is used for input; mailauth validates signatures and checks for the same `cv=` output. | ||
- All tests pass, aside from minor differences. | ||
```js | ||
const { authenticate } = require('mailauth'); | ||
``` | ||
## License | ||
@@ -527,2 +612,2 @@ | ||
Licensed under MIT license | ||
Licensed under the [MIT License](LICENSE). |
Sorry, the diff of this file is not supported yet
607
289934
+ Added@peculiar/asn1-schema@2.3.13(transitive)
+ Added@peculiar/asn1-x509@2.3.13(transitive)
+ Added@peculiar/asn1-x509-logotype@2.3.13(transitive)
+ Added@postalsys/vmc@1.1.0(transitive)
+ Addednodemailer@6.9.16(transitive)
+ Addedtldts@6.1.58(transitive)
- Removed@peculiar/asn1-schema@2.3.8(transitive)
- Removed@peculiar/asn1-x509@2.3.8(transitive)
- Removed@peculiar/asn1-x509-logotype@2.3.8(transitive)
- Removed@postalsys/vmc@1.0.8(transitive)
- Removednodemailer@6.9.15(transitive)
- Removedtldts@6.1.52(transitive)
Updated@postalsys/vmc@1.1.0
Updatednodemailer@6.9.16
Updatedtldts@6.1.58