
Security News
vlt Launches "reproduce": A New Tool Challenging the Limits of Package Provenance
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
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:
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.
First, install mailauth from npm:
npm install mailauth
Then, import the desired methods into your script:
const { authenticate } = require('mailauth');
mailauth includes a command-line utility called mailauth
. For detailed information on how to use it, see the command-line documentation.
Use the authenticate
function to validate DKIM signatures, SPF, DMARC, ARC, and BIMI for an email.
await authenticate(message [, options])
// Returns: { dkim, spf, arc, dmarc, bimi, receivedChain, headers }
String
, Buffer
, or Readable
stream representing the email message.string
): Email address from the MAIL FROM command. Defaults to the Return-Path
header if not set.string
): IP address of the remote client that sent the message.string
): Hostname from the HELO/EHLO command.boolean
): If true
, parses ip
and helo
from the latest Received
header if not provided. Defaults to false
.string
): Hostname of the server performing the authentication. Defaults to os.hostname()
. Included in Authentication headers.number
): Minimum allowed bits for RSA public keys. Defaults to 1024
. Keys with fewer bits will fail validation.boolean
): If true
, skips ARC checks.boolean
): If true
, skips DMARC checks, also disabling dependent checks like BIMI.boolean
): If true
, skips BIMI checks.object
): Options for ARC sealing if the message doesn't have a broken ARC chain.
string
): ARC key domain name.string
): ARC key selector.string
or Buffer
): Private key for signing (RSA or Ed25519).async function
): Custom DNS resolver function. Defaults to dns.promises.resolve
.number
): DNS lookup limit for SPF. Defaults to 10
as per RFC7208.number
): DNS lookup limit for SPF producing empty results. Defaults to 2
as per RFC7208.const { authenticate } = require('mailauth');
const dns = require('dns');
const message = /* Your email message here */;
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
// 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);
Sample Output:
Received-SPF: pass (mx.ethereal.email: domain of andris@ekiri.ee designates 217.146.67.33 as permitted sender) client-ip=217.146.67.33;
Authentication-Results: mx.ethereal.email;
dkim=pass header.i=@ekiri.ee header.s=default header.a=rsa-sha256 header.b=TXuCNlsq;
spf=pass (mx.ethereal.email: domain of andris@ekiri.ee designates 217.146.67.33 as permitted sender) smtp.mailfrom=andris@ekiri.ee
smtp.helo=uvn-67-33.tll01.zonevs.eu;
arc=pass (i=2 spf=neutral dkim=pass dkdomain=ekiri.ee);
dmarc=none header.from=ekiri.ee
From: ...
You can see the full output, including structured data for DKIM, SPF, DMARC, and ARC, from this example.
Note: The receivedChain
property is an array of parsed representations of the Received:
headers.
Use the dkimSign
function to sign an email message with DKIM.
const { dkimSign } = require('mailauth/lib/dkim/sign');
const signResult = await dkimSign(message, options);
// Returns: { signatures: String, errors: Array }
String
, Buffer
, or Readable
stream representing the email message.string
): Canonicalization method. Defaults to 'relaxed/relaxed'
.string
): Signing and hashing algorithm. Defaults to 'rsa-sha256'
.Date
): Signing time. Defaults to current time.Array
): Array of signature objects. Each object may contain:
string
): DKIM key domain name.string
): DKIM key selector.string
or Buffer
): Private key for signing (RSA or Ed25519).string
, optional): Overrides parent algorithm
.string
, optional): Overrides parent canonicalization
.number
, optional): Maximum number of canonicalized body bytes to sign (l=
tag). Not recommended for general use.const { dkimSign } = require('mailauth/lib/dkim/sign');
const fs = require('fs');
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.error('Signing errors:', signResult.errors);
}
// Output signed message
process.stdout.write(signResult.signatures); // Includes terminating line break
process.stdout.write(message);
Sample Output:
DKIM-Signature: a=rsa-sha256; v=1; c=relaxed/relaxed; d=tahvel.info;
s=test.rsa; b=...
From: ...
Use DkimSignStream
to sign messages as part of a stream processing pipeline.
const { DkimSignStream } = require('mailauth/lib/dkim/sign');
const fs = require('fs');
const dkimSignStream = new DkimSignStream({
canonicalization: 'relaxed/relaxed',
algorithm: 'rsa-sha256',
signTime: new Date(),
signatureData: [
{
signingDomain: 'tahvel.info',
selector: 'test.rsa',
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem')
}
]
});
// Read from stdin, write signed message to stdout
process.stdin.pipe(dkimSignStream).pipe(process.stdout);
Use the dkimVerify
function to verify DKIM signatures in an email message.
const { dkimVerify } = require('mailauth/lib/dkim/verify');
const result = await dkimVerify(message);
// Returns an object containing verification results
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);
}
Sample Output:
dkim=neutral (invalid public key) header.i=@tahvel.info header.s=test.invalid header.b="b85yao+1"
dkim=pass header.i=@tahvel.info header.s=test.rsa header.b="BrEgDN4A"
dkim=policy policy.dkim-rules=weak-key header.i=@tahvel.info header.s=test.small header.b="d0jjgPun"
Use the spf
function to verify the SPF record for an email sender.
const { spf } = require('mailauth/lib/spf');
const result = await spf(options);
// Returns an object containing SPF verification results
string
): MAIL FROM address.string
): SMTP client IP.string
): HELO/EHLO hostname.string
): Hostname of the MTA performing the check.const { spf } = require('mailauth/lib/spf');
const result = await spf({
sender: 'andris@wildduck.email',
ip: '217.146.76.20',
helo: 'foo',
mta: 'mx.myhost.com'
});
console.log(result.header);
Sample Output:
Received-SPF: pass (mx.myhost.com: domain of andris@wildduck.email
designates 217.146.76.20 as permitted sender) client-ip=217.146.76.20;
envelope-from="andris@wildduck.email";
ARC seals are validated automatically during the authentication step.
const { authenticate } = require('mailauth');
const message = /* Your email message here */;
const { arc } = await authenticate(message, {
trustReceived: true,
});
console.log(arc);
Sample Output:
{
"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...
}
You can seal messages with ARC either during authentication or after modifications.
Provide the sealing key in the options to seal messages automatically during authentication.
const { authenticate } = require('mailauth');
const fs = require('fs');
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);
If you need to modify the message before sealing, first authenticate it, modify as needed, then seal using the authentication results.
const { authenticate, sealMessage } = require('mailauth');
const fs = require('fs');
const message = /* Your email message here */;
// 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',
});
// 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'),
authResults: arc.authResults,
cv: arc.status.result,
});
// Output the sealed message
process.stdout.write(sealHeaders); // ARC headers
process.stdout.write(headers); // Authentication results
process.stdout.write(message);
DMARC is verified during the authentication process. Although the dmarc
handler is exported, it requires input from previous steps like SPF and DKIM.
getDmarcRecord(domain [, resolver])
Fetches and parses the DMARC DNS record for a domain or subdomain. Returns false
if no record exists.
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
string
): The domain to check for a DMARC record.function
, optional): Custom DNS resolver function. Defaults to dns.resolve
.const getDmarcRecord = require('mailauth/lib/dmarc/get-dmarc-record');
const dmarcRecord = await getDmarcRecord('ethereal.email');
console.log(dmarcRecord);
Sample Output:
{
"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
}
Brand Indicators for Message Identification (BIMI) support is based on 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".
const { authenticate } = require('mailauth');
const message = /* Your email message here */;
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
});
if (bimi?.location) {
console.log(`BIMI location: ${bimi.location}`);
}
Note:
BIMI-Location
header is ignored by mailauth.BIMI-Selector
header can be used for selector selection if available.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
.
Example Authority Evidence Documents:
mailauth provides functions to fetch and validate MTA-STS policies for a domain.
Use the getPolicy
function to fetch the MTA-STS policy for a domain.
const { getPolicy } = require('mailauth/lib/mta-sts');
const { policy, status } = await getPolicy(domain [, knownPolicy]);
// Returns an object with the policy and status
string
): The domain to retrieve the policy for.object
, optional): Previously cached policy for the domain.const { getPolicy } = require('mailauth/lib/mta-sts');
const knownPolicy = /* Retrieve from your cache if available */;
const { policy, status } = await getPolicy('gmail.com', knownPolicy);
if (policy.id !== knownPolicy?.id) {
// Update your cache with the new policy
}
if (policy.mode === 'enforce') {
// TLS must be used when sending to this domain
}
Possible Status Values:
"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.Use the validateMx
function to check if an MX hostname is valid according to the MTA-STS policy.
const { validateMx } = require('mailauth/lib/mta-sts');
const validation = validateMx(mx, policy);
// Returns an object indicating if the MX is valid
string
): The resolved MX hostname.object
): The MTA-STS policy object.const { getPolicy, validateMx } = require('mailauth/lib/mta-sts');
const { policy } = await getPolicy('gmail.com');
const mx = 'alt4.gmail-smtp-in.l.google.com';
const policyMatch = validateMx(mx, policy);
if (policy.mx && !policyMatch.valid) {
// The MX host is not listed in the policy; do not connect
}
mailauth uses the following test suites:
Based on the OpenSPF test suite, with some differences:
Based on ValiMail's arc_test_suite:
cv=
output.© 2020-2024 Postal Systems OÜ
Licensed under the MIT License.
FAQs
Email authentication library for Node.js
The npm package mailauth receives a total of 3,533 weekly downloads. As such, mailauth popularity was classified as popular.
We found that mailauth demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
vlt's new "reproduce" tool verifies npm packages against their source code, outperforming traditional provenance adoption in the JavaScript ecosystem.
Research
Security News
Socket researchers uncovered a malicious PyPI package exploiting Deezer’s API to enable coordinated music piracy through API abuse and C2 server control.
Research
The Socket Research Team discovered a malicious npm package, '@ton-wallet/create', stealing cryptocurrency wallet keys from developers and users in the TON ecosystem.