Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Email authentication library for Node.js
Pure JavaScript implementation, no external applications or compilation needed. Runs on any server/device that has Node 14+ installed.
Validate DKIM signatures, SPF, DMARC, ARC and BIMI for an email.
const { authenticate } = require('mailauth');
const { dkim, spf, arc, dmarc, bimi, headers } = await authenticate(
message, // either a String, a Buffer or a Readable Stream
{
// SMTP transmission options must be provided as
// these are not parsed from the message
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
// 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
process.stdout.write(message);
Example 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 full output (structured data for DKIM, SPF, DMARC and ARC) from this example.
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',
// 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)
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=
}
]
}
); // -> {signatures: String, errors: Array} signature headers using \r\n as the line separator
// show signing errors (if any)
if (signResult.errors.length) {
console.log(signResult.errors);
}
// output signed message
process.stdout.write(signResult.signatures); // includes terminating line break
process.stdout.write(message);
Example output:
DKIM-Signature: a=rsa-sha256; v=1; c=relaxed/relaxed; d=tahvel.info;
s=test.rsa; b=...
From: ...
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);
}
Example 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"
const { spf } = require('mailauth/lib/spf');
let result = await spf({
sender: 'andris@wildduck.email',
ip: '217.146.76.20',
helo: 'foo',
mta: 'mx.myhost.com'
});
console.log(result.header);
Example 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 automatically validated during the authentication step.
const { authenticate } = require('mailauth');
const { arc } = await authenticate(
message, // either a String, a Buffer or a Readable Stream
{
// SMTP transmission options must be provided as
// these are not parsed from the message
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
}
);
console.log(arc);
Output being something like this:
{
"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,
...
}
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 anymore as this would break the seal.
const { authenticate } = require('mailauth');
const { headers } = await authenticate(
message, // either a String, a Buffer or a Readable Stream
{
// SMTP transmission options must be provided as
// these are not parsed from the message
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
// Optional 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
process.stdout.write(message);
If you want to modify the message before sealing then you have to authenticate the message first and then use authentication results as input for the sealing step.
const { authenticate, sealMessage } = require('@postalsys/mailauth');
// 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
}
);
// 2. perform some modifications with the message ...
// 3. seal the modified message using the initial authentication results
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
});
// output authenticated message
process.stdout.write(sealHeaders); // ARC set
process.stdout.write(headers); // authentication results
process.stdout.write(message);
Brand Indicators for Message Identification (BIMI) support is based on draft-blank-ietf-bimi-01.
BIMI information is resolved in the authentication step and the results can be found from the bimi
property. Message must pass DMARC validation in order to be processed for BIMI. DMARC policy can not be "none" for BIMI to pass.
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
}
);
if (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).
Authority Evidence Document location is available from the bimi.authority
property (if set).
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).
Some example authority evidence documents:
You can parse logos from these certificate files by using the parseLogoFromX509
function
const { parseLogoFromX509 } = require('mailauth/lib/tools');
let { altnNames, svg } = await parseLogoFromX509(fs.readFileSync('vmc.pem'));
NB!
parseLogoFromX509
does not verify the validity of the VMC certificate. It could be self signed or expired and still be processed.
mailauth
allows you to fetch MTA-STS information for a domain name.
const { getPolicy, validateMx } = require('mailauth/lib/mta-sts');
let knownPolicy = getCachedPolicy('gmail.com'); // optional
let mx = 'alt4.gmail-smtp-in.l.google.com';
const policy = await getPolicy('gmail.com', knownPolicy);
const policyMatch = validateMx(mx, policy);
if (policy?.id !== knownPolicy?.id) {
// policy has been updated, update cache
}
if (policy?.mode === 'enforce') {
// must use TLS
}
if (policy && !policyMatch) {
// can't connect, unlisted MX
}
mailauth
uses the following test suites:
OpenSPF test suite with the following differences:
mailauth
, all PTR related tests are ignoredmailauth
accepts multiple spaces between tags etc)ValiMail arc_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.mailauth
validates the signatures itself and looks for the same cv= output that the ARC-Seal header in the test suite hasFirst install the module from npm:
$ npm install mailauth
next import any method you want to use from mailauth package into your script:
const { authenticate } = require('mailauth');
MIT-licensed version is available for Postal Systems subscribers.
First install the module from Postal Systems private registry:
$ npm install @postalsys/mailauth
next import any method you want to use from mailauth package into your script:
const { authenticate } = require('@postalsys/mailauth');
If you have already built your application using the free version of "mailauth" and do not want to modify require statements in your code, you can install the MIT-licensed version as an alias for "mailauth".
$ npm install mailauth@npm:@postalsys/mailauth
This way you can keep using the old module name
const { authenticate } = require('mailauth');
© 2020 Andris Reinman
Licensed under GNU Affero General Public License v3.0 or later.
MIT-licensed version of mailauth is available for Postal Systems subscribers.
FAQs
Email authentication library for Node.js
The npm package mailauth receives a total of 1,520 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
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.