Comparing version 1.0.1 to 1.0.2
@@ -14,6 +14,5 @@ 'use strict'; | ||
let algo = process.argv[3] || 'rsa-sha256'; // or 'ed25519-sha256' | ||
let algo = process.argv[3] || false; // allowed: 'rsa-sha256', 'rsa-sha1', 'ed25519-sha256' | ||
dkimSign(eml, { | ||
algorithm: algo, | ||
canonicalization: 'simple/simple', | ||
@@ -23,2 +22,3 @@ signTime: Date.now(), | ||
{ | ||
algorithm: algo, | ||
signingDomain: 'tahvel.info', | ||
@@ -29,2 +29,3 @@ selector: 'test.invalid', | ||
{ | ||
algorithm: algo, | ||
signingDomain: 'tahvel.info', | ||
@@ -35,2 +36,3 @@ selector: 'test.rsa', | ||
{ | ||
algorithm: algo, | ||
signingDomain: 'tahvel.info', | ||
@@ -42,2 +44,3 @@ selector: 'test.small', | ||
{ | ||
algorithm: algo, | ||
signingDomain: 'tahvel.info', | ||
@@ -44,0 +47,0 @@ selector: 'test.ed25519', |
@@ -13,8 +13,4 @@ 'use strict'; | ||
let { algorithm, canonicalization, signTime, headerList, signatureData } = options || {}; | ||
let { canonicalization, signTime, headerList, signatureData } = options || {}; | ||
this.algorithm = algorithm || 'rsa-sha256'; | ||
this.signAlgo = this.algorithm.split('-').shift().toLowerCase().trim(); | ||
this.hashAlgo = this.algorithm.split('-').pop().toLowerCase().trim(); | ||
this.canonicalization = canonicalization || 'relaxed/relaxed'; | ||
@@ -33,20 +29,68 @@ this.headerCanon = this.canonicalization.split('/').shift().toLowerCase().trim(); | ||
this.bodyHash = false; | ||
this.bodyHashes = new Map(); | ||
this.setupHashes(); | ||
} | ||
setupHashes() { | ||
for (let signatureData of this.signatureData || []) { | ||
if (!signatureData.privateKey) { | ||
continue; | ||
} | ||
let algorithm = (signatureData.algorithm || '').toLowerCase().trim(); | ||
let hashAlgo = algorithm.split('-').pop().toLowerCase().trim() || 'sha256'; | ||
if (!this.bodyHashes.has(hashAlgo)) { | ||
this.bodyHashes.set(hashAlgo, { hasher: null, hash: null }); | ||
} | ||
} | ||
} | ||
validateAlgorithm(algorithm) { | ||
try { | ||
if (!algorithm || !/^[^-]+-[^-]+$/.test(algorithm)) { | ||
throw new Error('Invalid algorithm format'); | ||
} | ||
let [signing, hashing] = algorithm.split('-'); | ||
if (!['rsa', 'ed25519'].includes(signing)) { | ||
throw new Error('Unknown signing algorithm: ' + signing); | ||
} | ||
if (!['sha256', 'sha1'].includes(hashing)) { | ||
throw new Error('Unknown hashing algorithm: ' + hashing); | ||
} | ||
} catch (err) { | ||
err.code = 'EINVALIDALGO'; | ||
throw err; | ||
} | ||
} | ||
async messageHeaders(headers) { | ||
this.headers = headers; | ||
this.bodyHash = dkimBody(this.bodyCanon, this.hashAlgo); | ||
for (let hashAlgo of this.bodyHashes.keys()) { | ||
this.bodyHashes.get(hashAlgo).hasher = dkimBody(this.bodyCanon, hashAlgo); | ||
} | ||
} | ||
async nextChunk(chunk) { | ||
this.bodyHash.update(chunk); | ||
for (let hashAlgo of this.bodyHashes.keys()) { | ||
if (this.bodyHashes.get(hashAlgo).hasher) { | ||
this.bodyHashes.get(hashAlgo).hasher.update(chunk); | ||
} | ||
} | ||
} | ||
async finalChunk() { | ||
if (!this.headers || !this.bodyHash) { | ||
if (!this.headers) { | ||
return; | ||
} | ||
this.bodyHash = this.bodyHash.digest('base64'); | ||
for (let hashAlgo of this.bodyHashes.keys()) { | ||
if (this.bodyHashes.get(hashAlgo).hasher) { | ||
this.bodyHashes.get(hashAlgo).hash = this.bodyHashes.get(hashAlgo).hasher.digest('base64'); | ||
} | ||
} | ||
@@ -60,29 +104,29 @@ let signedHeaderLines = getSignedHeaderLines(this.headers.parsed, this.headerList); | ||
let { signingHeaders, dkimHeaderOpts } = dkimHeader( | ||
signedHeaderLines, | ||
Object.assign( | ||
{ | ||
algorithm: this.algorithm, | ||
canonicalization: this.canonicalization, | ||
signTime: this.signTime, | ||
bodyHash: this.bodyHash | ||
}, | ||
signatureData | ||
) | ||
); | ||
let algorithm = (signatureData.algorithm || '').toLowerCase().trim(); | ||
let signAlgo = algorithm.split('-').shift().toLowerCase().trim() || null; | ||
let hashAlgo = algorithm.split('-').pop().toLowerCase().trim() || 'sha256'; | ||
try { | ||
let keyType = crypto.createPrivateKey({ key: signatureData.privateKey, format: 'pem' }).asymmetricKeyType; | ||
if (keyType !== this.signAlgo) { | ||
if (signAlgo && keyType !== signAlgo) { | ||
// invalid key type | ||
let err = new Error(`Invalid key type: ${keyType} (expecting ${this.signAlgo})`); | ||
err.code = 'EKEY'; | ||
let err = new Error(`Invalid key type: "${keyType}" (expecting "${signAlgo}")`); | ||
err.code = 'EINVALIDTYPE'; | ||
throw err; | ||
} | ||
if (!['rsa', 'ed25519'].includes(keyType)) { | ||
let err = new Error(`Unsupported key type: "${keyType}"`); | ||
err.code = 'EINVALIDTYPE'; | ||
throw err; | ||
} | ||
if (!signAlgo) { | ||
signAlgo = keyType; | ||
} | ||
algorithm = `${signAlgo}-${hashAlgo}`; | ||
} catch (err) { | ||
this.errors.push({ | ||
a: dkimHeaderOpts.a, | ||
c: dkimHeaderOpts.c, | ||
s: dkimHeaderOpts.s, | ||
d: dkimHeaderOpts.d, | ||
selector: signatureData.selector, | ||
signingDomain: signatureData.signingDomain, | ||
err | ||
@@ -94,6 +138,29 @@ }); | ||
try { | ||
// throws if invalid | ||
this.validateAlgorithm(algorithm); | ||
} catch (err) { | ||
this.errors.push({ | ||
algorithm, | ||
selector: signatureData.selector, | ||
signingDomain: signatureData.signingDomain, | ||
err | ||
}); | ||
continue; | ||
} | ||
let { signingHeaders, dkimHeaderOpts } = dkimHeader( | ||
signedHeaderLines, | ||
Object.assign({}, signatureData, { | ||
algorithm, | ||
canonicalization: this.canonicalization, | ||
signTime: this.signTime, | ||
bodyHash: this.bodyHashes.has(hashAlgo) ? this.bodyHashes.get(hashAlgo).hash : null | ||
}) | ||
); | ||
try { | ||
let signature = crypto | ||
.sign( | ||
// use `null` as algorithm to detect it from the key file | ||
this.signAlgo === 'rsa' ? this.algorithm : null, | ||
signAlgo === 'rsa' ? algorithm : null, | ||
signingHeaders, | ||
@@ -108,6 +175,5 @@ signatureData.privateKey | ||
this.errors.push({ | ||
a: dkimHeaderOpts.a, | ||
c: dkimHeaderOpts.c, | ||
s: dkimHeaderOpts.s, | ||
d: dkimHeaderOpts.d, | ||
algorithm, | ||
selector: signatureData.selector, | ||
signingDomain: signatureData.signingDomain, | ||
err | ||
@@ -114,0 +180,0 @@ }); |
@@ -18,22 +18,7 @@ 'use strict'; | ||
const validateAlgorithm = algorithm => { | ||
let [signing, hashing] = algorithm.split('-'); | ||
if (!['rsa', 'ed25519'].includes(signing)) { | ||
throw new Error('Unknown signing algorithm: ' + signing); | ||
} | ||
if (!['sha256', 'sha1'].includes(hashing)) { | ||
throw new Error('Unknown hashing algorithm: ' + hashing); | ||
} | ||
}; | ||
const dkimSign = async (input, options) => { | ||
let { algorithm, canonicalization } = options || {}; | ||
let { canonicalization } = options || {}; | ||
canonicalization = (canonicalization || 'relaxed/relaxed').toLowerCase().trim(); | ||
algorithm = (algorithm || 'rsa-sha256').toLowerCase().trim(); | ||
validateCanonicalization(canonicalization); | ||
validateAlgorithm(algorithm); | ||
@@ -40,0 +25,0 @@ let dkimSigner = new DkimSigner(options); |
{ | ||
"name": "mailauth", | ||
"version": "1.0.1", | ||
"version": "1.0.2", | ||
"description": "Email authentication library for Node.js", | ||
@@ -20,3 +20,3 @@ "main": "lib/mailauth.js", | ||
"author": "Andris Reinman", | ||
"license": "MIT", | ||
"license": "AGPL-3.0-or-later", | ||
"bugs": { | ||
@@ -23,0 +23,0 @@ "url": "https://github.com/andris9/mailauth/issues" |
@@ -15,4 +15,6 @@ # mailauth | ||
Install from NPM | ||
### Free, AGPL-licensed version | ||
First install the module from npm: | ||
``` | ||
@@ -22,2 +24,38 @@ $ npm install mailauth | ||
next import any method you want to use from mailauth package into your script: | ||
```js | ||
const { authenticate } = require('mailauth'); | ||
``` | ||
### MIT version | ||
MIT-licensed version is available for [Postal Systems subscribers](https://postalsys.com/). | ||
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: | ||
```js | ||
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 | ||
```js | ||
const { authenticate } = require('mailauth'); | ||
``` | ||
## Usage | ||
## Authentication | ||
@@ -67,6 +105,13 @@ | ||
{ | ||
algorithm: 'rsa-sha256', // a= either "rsa-sha256" (the default) or "ed25519-sha256" if you are using EC keys | ||
// optional canonicalization, default is "relaxed/relaxed" | ||
// this option applies to all signatures, so you can't create multiple signatures | ||
// that use different canonicalization | ||
canonicalization: 'relaxed/relaxed', // c= | ||
// 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: [ | ||
@@ -76,8 +121,12 @@ { | ||
selector: 'test.rsa', // s= | ||
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem') | ||
// supported key types: RSA, Ed25519 | ||
privateKey: fs.readFileSync('./test/fixtures/private-rsa.pem'), | ||
// Optional algorithm, default is derived from the key. | ||
// Mostly useful when you want to use rsa-sha1, otherwise no need to set | ||
algorithm: 'rsa-sha256' | ||
} | ||
] | ||
} | ||
); // -> {String} signature headers using \r\n as the line separator | ||
// show signing errors | ||
); // -> {signatures: String, errors: Array} signature headers using \r\n as the line separator | ||
// show signing errors (if any) | ||
if (signResult.errors.length) { | ||
@@ -144,2 +193,6 @@ console.log(signResult.errors); | ||
**MIT** | ||
© 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](https://postalsys.com/). |
Copyleft License
License(Experimental) Copyleft license information was found.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Non-permissive License
License(Experimental) A license not known to be considered permissive was found.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
375058
2598
194
3
70