Comparing version 1.0.22 to 1.0.23
@@ -489,5 +489,6 @@ 'use strict'; | ||
let hashKey = `${bodyCanon}:${hashAlgo}`; | ||
let hashKey = `${bodyCanon}:${hashAlgo}:`; | ||
bodyHash = dkimSigner.bodyHashes.get(hashKey)?.hash; | ||
arc = arc || dkimSigner.arc; | ||
@@ -494,0 +495,0 @@ seal.i = arc.instance; |
@@ -50,2 +50,4 @@ 'use strict'; | ||
update(chunk) { | ||
this.byteLength += chunk.length; | ||
let bodyStr; | ||
@@ -52,0 +54,0 @@ |
@@ -91,2 +91,3 @@ 'use strict'; | ||
} | ||
return this.bodyHash.digest(encoding); | ||
@@ -93,0 +94,0 @@ } |
@@ -73,6 +73,9 @@ 'use strict'; | ||
signatureData.maxBodyLength = | ||
typeof signatureData.maxBodyLength === 'number' && signatureData.maxBodyLength >= 0 ? signatureData.maxBodyLength : ''; | ||
let { hashAlgo } = this.getAlgorithm(signatureData); | ||
let { bodyCanon } = this.getCanonicalization(signatureData); | ||
let hashKey = `${bodyCanon}:${hashAlgo}`; | ||
let hashKey = `${bodyCanon}:${hashAlgo}:${signatureData.maxBodyLength}`; | ||
@@ -121,4 +124,4 @@ if (!this.bodyHashes.has(hashKey)) { | ||
for (let hashKey of this.bodyHashes.keys()) { | ||
let [bodyCanon, hashAlgo] = hashKey.split(':'); | ||
this.bodyHashes.get(hashKey).hasher = dkimBody(bodyCanon, hashAlgo); | ||
let [bodyCanon, hashAlgo, maxBodyLength] = hashKey.split(':'); | ||
this.bodyHashes.get(hashKey).hasher = dkimBody(bodyCanon, hashAlgo, maxBodyLength ? Number(maxBodyLength) : false); | ||
} | ||
@@ -188,3 +191,3 @@ } | ||
let hashKey = `${bodyCanon}:${hashAlgo}`; | ||
let hashKey = `${bodyCanon}:${hashAlgo}:${signatureData.maxBodyLength}`; | ||
@@ -237,9 +240,20 @@ try { | ||
signingHeaderLines, | ||
Object.assign({}, signatureData, { | ||
instance: this.arc?.instance, // ARC only | ||
algorithm, | ||
canonicalization: this.getCanonicalization(signatureData).canonicalization, | ||
signTime: this.signTime, | ||
bodyHash: this.bodyHashes.has(hashKey) ? this.bodyHashes.get(hashKey).hash : null | ||
}) | ||
Object.assign( | ||
{}, | ||
signatureData, | ||
{ | ||
instance: this.arc?.instance, // ARC only | ||
algorithm, | ||
canonicalization: this.getCanonicalization(signatureData).canonicalization, | ||
signTime: this.signTime, | ||
bodyHash: this.bodyHashes.has(hashKey) ? this.bodyHashes.get(hashKey).hash : null | ||
}, | ||
// value for the l= tag (if needed) | ||
typeof signatureData.maxBodyLength === 'number' | ||
? { | ||
bodyHashedBytes: this.bodyHashes.get(hashKey).hasher.bodyHashedBytes | ||
} | ||
: {} | ||
) | ||
); | ||
@@ -246,0 +260,0 @@ |
@@ -37,3 +37,3 @@ 'use strict'; | ||
let hashAlgo = 'sha256'; | ||
this.sealBodyHashKey = [bodyCanon, hashAlgo].join(':'); | ||
this.sealBodyHashKey = `${bodyCanon}:${hashAlgo}:`; | ||
this.bodyHashes.set(this.sealBodyHashKey, dkimBody(bodyCanon, hashAlgo, false)); | ||
@@ -118,2 +118,5 @@ } | ||
signatureHeader.maxBodyLength = | ||
signatureHeader.parsed?.l?.value && !isNaN(signatureHeader.parsed?.l?.value) ? signatureHeader.parsed?.l?.value : ''; | ||
const validSignAlgo = ['rsa', 'ed25519']; | ||
@@ -136,11 +139,5 @@ const validHeaderAlgo = signatureHeader.type === 'DKIM' ? ['sha256', 'sha1'] : ['sha256']; | ||
signatureHeader.bodyHashKey = [signatureHeader.bodyCanon, signatureHeader.hashAlgo].join(':'); | ||
signatureHeader.bodyHashKey = [signatureHeader.bodyCanon, signatureHeader.hashAlgo, signatureHeader.maxBodyLength].join(':'); | ||
if (!this.bodyHashes.has(signatureHeader.bodyHashKey)) { | ||
let maxLength = false; | ||
if (signatureHeader.parsed?.l?.value) { | ||
maxLength = signatureHeader.parsed?.l?.value; | ||
} | ||
this.bodyHashes.set(signatureHeader.bodyHashKey, dkimBody(signatureHeader.bodyCanon, signatureHeader.hashAlgo, maxLength)); | ||
this.bodyHashes.set(signatureHeader.bodyHashKey, dkimBody(signatureHeader.bodyCanon, signatureHeader.hashAlgo, signatureHeader.maxBodyLength)); | ||
} | ||
@@ -164,3 +161,3 @@ } | ||
for (let [key, bodyHash] of this.bodyHashes.entries()) { | ||
this.bodyHashes.set(key, bodyHash.digest('base64')); | ||
this.bodyHashes.get(key).hash = bodyHash.digest('base64'); | ||
} | ||
@@ -203,3 +200,3 @@ | ||
let bodyHash = this.bodyHashes.get(signatureHeader.bodyHashKey); | ||
let bodyHash = this.bodyHashes.get(signatureHeader.bodyHashKey)?.hash; | ||
if (signatureHeader.parsed?.bh?.value !== bodyHash) { | ||
@@ -278,2 +275,9 @@ status.result = 'neutral'; | ||
signatureHeader.bodyHashedBytes = this.bodyHashes.get(signatureHeader.bodyHashKey)?.bodyHashedBytes; | ||
if (typeof signatureHeader.maxBodyLength === 'number' && signatureHeader.maxBodyLength !== signatureHeader.bodyHashedBytes) { | ||
status.result = 'fail'; | ||
status.comment = `invalid body length ${signatureHeader.bodyHashedBytes}`; | ||
} | ||
let result = { | ||
@@ -290,2 +294,10 @@ signingDomain: signatureHeader.signingDomain, | ||
if (typeof signatureHeader.bodyHashedBytes === 'number') { | ||
result.canonBodyLength = signatureHeader.bodyHashedBytes; | ||
} | ||
if (typeof signatureHeader.maxBodyLength === 'number') { | ||
result.bodyLengthCount = signatureHeader.maxBodyLength; | ||
} | ||
if (publicKey) { | ||
@@ -330,4 +342,4 @@ result.publicKey = publicKey.toString(); | ||
if (this.seal && this.bodyHashes.has(this.sealBodyHashKey) && typeof this.bodyHashes.get(this.sealBodyHashKey) === 'string') { | ||
this.seal.bodyHash = this.bodyHashes.get(this.sealBodyHashKey); | ||
if (this.seal && this.bodyHashes.has(this.sealBodyHashKey) && typeof this.bodyHashes.get(this.sealBodyHashKey)?.hash === 'string') { | ||
this.seal.bodyHash = this.bodyHashes.get(this.sealBodyHashKey).hash; | ||
} | ||
@@ -334,0 +346,0 @@ } |
@@ -7,3 +7,3 @@ 'use strict'; | ||
const relaxedHeaders = (type, signingHeaderLines, options) => { | ||
let { signatureHeaderLine, signingDomain, selector, algorithm, canonicalization, bodyHash, signTime, signature, instance } = options || {}; | ||
let { signatureHeaderLine, signingDomain, selector, algorithm, canonicalization, bodyHash, signTime, signature, instance, bodyHashedBytes } = options || {}; | ||
let chunks = []; | ||
@@ -27,2 +27,6 @@ | ||
if (typeof bodyHashedBytes === 'number') { | ||
opts.l = bodyHashedBytes; | ||
} | ||
if (instance) { | ||
@@ -29,0 +33,0 @@ // ARC only |
@@ -11,3 +11,3 @@ 'use strict'; | ||
const simpleHeaders = (type, signingHeaderLines, options) => { | ||
let { signatureHeaderLine, signingDomain, selector, algorithm, canonicalization, bodyHash, signTime, signature, instance } = options || {}; | ||
let { signatureHeaderLine, signingDomain, selector, algorithm, canonicalization, bodyHash, signTime, signature, instance, bodyHashedBytes } = options || {}; | ||
let chunks = []; | ||
@@ -31,2 +31,6 @@ | ||
if (typeof bodyHashedBytes === 'number') { | ||
opts.l = bodyHashedBytes; | ||
} | ||
if (instance) { | ||
@@ -33,0 +37,0 @@ // ARC only (should never happen thoug as simple algo is not allowed) |
{ | ||
"name": "mailauth", | ||
"version": "1.0.22", | ||
"version": "1.0.23", | ||
"description": "Email authentication library for Node.js", | ||
@@ -5,0 +5,0 @@ "main": "lib/mailauth.js", |
@@ -5,12 +5,12 @@ ![](https://github.com/andris9/mailauth/raw/master/assets/mailauth.png) | ||
- [x] SPF verification | ||
- [x] DKIM signing | ||
- [x] DKIM verification | ||
- [x] DMARC verification | ||
- [x] ARC verification | ||
- [x] ARC sealing | ||
- [x] Sealing on authentication | ||
- [x] Sealing after modifications | ||
- [x] BIMI resolving | ||
- [x] MTA-STS helpers | ||
- **SPF** verification | ||
- **DKIM** signing | ||
- DKIM verification | ||
- **DMARC** verification | ||
- **ARC** verification | ||
- ARC sealing | ||
- Sealing on authentication | ||
- Sealing after modifications | ||
- **BIMI** resolving | ||
- **MTA-STS** helpers | ||
@@ -25,3 +25,3 @@ Pure JavaScript implementation, no external applications or compilation needed. Runs on any server/device that has Node 14+ installed. | ||
``` | ||
```js | ||
await authenticate(message [,options]) -> | ||
@@ -35,8 +35,8 @@ { dkim, spf, arc, dmarc, bimi, receivedChain, headers } | ||
- **options** (_object_) is an optional options object | ||
- **sender** (_string_) is the email address from MAIL FROM command (aka Return-Path address) | ||
- **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 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 latest `Received` header if you have not set these values yourself | ||
- **trustReceived** (_boolean_) if true then parses values for `ip` and `helo` from 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 less bits, then validation is conisdered as failed | ||
- **minBitLength** (_number_) is the minimum allowed bits of RSA public keys (defaults to 1024). If a DKIM or ARC key has less bits, then validation is considered as failed | ||
- **disableArc** (_boolean_) if true then skip ARC checks | ||
@@ -49,3 +49,3 @@ - **disableDmarc** (_boolean_) if true then skip DMARC checks. This also disables checks that are dependent on DMARC (eg. BIMI) | ||
- **privateKey** (_string_ or _buffer_) Private key for signing. Can be a 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) | ||
- **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) | ||
@@ -64,3 +64,3 @@ **Example** | ||
// If you do not want to provide ip/helo/sender manually but parse from the message | ||
// Uncomment if you do not want to provide ip/helo/sender manually but parse from the message | ||
//trustReceived: true, | ||
@@ -134,2 +134,6 @@ | ||
canonicalization: 'relaxed/relaxed' // c= | ||
// Maximum number of canonicalizated body bytes to sign (eg. the "l=" tag). | ||
// Do not use though. This is available only for compatibility testing. | ||
// maxBodyLength: 12345 | ||
} | ||
@@ -210,8 +214,3 @@ ] | ||
{ | ||
// 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 | ||
trustReceived: true | ||
} | ||
@@ -246,10 +245,5 @@ ); | ||
{ | ||
// 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 | ||
trustReceived: true, | ||
// Optional ARC seal settings. If this is set then resulting headers include | ||
// ARC seal settings. If this is set then resulting headers include | ||
// a complete ARC header set (unless the message has a failing ARC chain) | ||
@@ -256,0 +250,0 @@ seal: { |
241752
3883
550