wildduck/lib/dkim-handler.js

291 lines
10 KiB
JavaScript
Raw Normal View History

2018-01-02 19:46:32 +08:00
'use strict';
const ObjectID = require('mongodb').ObjectID;
const fingerprint = require('key-fingerprint').fingerprint;
2018-01-03 19:23:42 +08:00
const forge = require('node-forge');
2018-01-02 19:46:32 +08:00
const crypto = require('crypto');
const tools = require('./tools');
2018-05-11 19:39:23 +08:00
const pem = require('pem');
2018-01-02 19:46:32 +08:00
class DkimHandler {
constructor(options) {
options = options || {};
this.cipher = options.cipher;
this.secret = options.secret;
2018-05-11 19:39:23 +08:00
this.useOpenSSL = !!options.useOpenSSL;
if (options.pathOpenSSL) {
pem.config({
pathOpenSSL: options.pathOpenSSL
});
}
2018-01-02 19:46:32 +08:00
this.database = options.database;
2018-10-18 15:37:32 +08:00
this.loggelf = options.loggelf || (() => false);
2018-01-02 19:46:32 +08:00
}
set(options, callback) {
const domain = tools.normalizeDomain(options.domain);
const selector = options.selector;
const description = options.description;
let privateKeyPem = options.privateKey;
2018-01-03 19:23:42 +08:00
let publicKeyPem;
2018-01-02 19:46:32 +08:00
2018-01-03 19:23:42 +08:00
let getPrivateKey = done => {
if (privateKeyPem) {
return done();
2018-01-02 19:46:32 +08:00
}
2018-05-11 19:39:23 +08:00
2018-01-03 19:23:42 +08:00
// private key not set, generate a new key
2018-05-11 19:39:23 +08:00
if (this.useOpenSSL) {
return pem.createPrivateKey(2048, {}, (err, result) => {
if (err) {
err.code = 'KeyGenereateError';
return callback(err);
}
if (!result || !result.key) {
let err = new Error('Failed to generate private key');
err.code = 'KeyGenereateError';
return callback(err);
}
privateKeyPem = result.key;
return done();
});
}
// Fallback to Forge
2018-01-03 19:23:42 +08:00
forge.rsa.generateKeyPair({ bits: 2048, workers: -1 }, (err, keypair) => {
if (err) {
err.code = 'KeyGenereateError';
return callback(err);
}
privateKeyPem = forge.pki.privateKeyToPem(keypair.privateKey);
publicKeyPem = forge.pki.publicKeyToPem(keypair.publicKey);
return done();
});
};
2018-01-02 19:46:32 +08:00
2018-05-11 19:39:23 +08:00
let getPublicKey = done => {
if (publicKeyPem) {
return done();
}
// extract public key from private key
if (this.useOpenSSL) {
return pem.getPublicKey(privateKeyPem, (err, result) => {
if (err) {
err.code = 'KeyGenereateError';
return callback(err);
2018-01-03 19:23:42 +08:00
}
2018-05-11 19:39:23 +08:00
if (!result || !result.publicKey) {
let err = new Error('Failed to generate public key');
err.code = 'KeyGenereateError';
return callback(err);
}
publicKeyPem = result.publicKey;
return done();
});
}
// Fallback to Forge
let privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
let publicKey = forge.pki.setRsaPublicKey(privateKey.n, privateKey.e);
publicKeyPem = forge.pki.publicKeyToPem(publicKey);
if (!publicKeyPem) {
let err = new Error('Failed to generate public key');
err.code = 'KeyGenereateError';
2018-01-02 19:46:32 +08:00
return callback(err);
}
2018-05-11 19:39:23 +08:00
done();
};
getPrivateKey(() => {
getPublicKey(() => {
let fp;
2018-01-03 19:23:42 +08:00
try {
2018-05-11 19:39:23 +08:00
fp = fingerprint(privateKeyPem, 'sha256', true);
let ciphered = crypto.publicEncrypt(publicKeyPem, Buffer.from('secretvalue'));
let deciphered = crypto.privateDecrypt(privateKeyPem, ciphered);
if (deciphered.toString() !== 'secretvalue') {
throw new Error('Was not able to use key for encryption');
}
2018-01-03 19:23:42 +08:00
} catch (E) {
2018-05-11 19:39:23 +08:00
let err = new Error('Invalid or incompatible private key. ' + E.message);
err.code = 'InputValidationError';
2018-01-02 19:46:32 +08:00
return callback(err);
}
2018-01-03 19:23:42 +08:00
2018-05-11 19:39:23 +08:00
if (this.secret) {
try {
let cipher = crypto.createCipher(this.cipher || 'aes192', this.secret);
privateKeyPem = '$' + cipher.update(privateKeyPem, 'utf8', 'hex');
privateKeyPem += cipher.final('hex');
} catch (E) {
let err = new Error('Failed to encrypt private key. ' + E.message);
err.code = 'InternalConfigError';
2018-01-03 19:23:42 +08:00
return callback(err);
}
2018-05-11 19:39:23 +08:00
}
2018-01-03 19:23:42 +08:00
2018-05-11 19:39:23 +08:00
let dkimData = {
domain,
selector,
privateKey: privateKeyPem,
publicKey: publicKeyPem,
fingerprint: fp,
created: new Date(),
latest: true
};
if (description) {
dkimData.description = description;
}
2018-01-03 19:23:42 +08:00
2018-05-11 19:39:23 +08:00
this.database.collection('dkim').findOneAndReplace(
{
domain
},
dkimData,
{
upsert: true,
returnOriginal: false
},
(err, r) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
2018-01-03 19:23:42 +08:00
}
2018-05-11 19:39:23 +08:00
if (!r.value) {
let err = new Error('Failed to insert DKIM key');
err.code = 'InternalDatabaseError';
return callback(err);
}
return callback(null, {
id: r.value._id,
domain: dkimData.domain,
selector: dkimData.selector,
description: dkimData.description,
fingerprint: dkimData.fingerprint,
publicKey: dkimData.publicKey,
dnsTxt: {
name: dkimData.selector + '._domainkey.' + dkimData.domain,
value: 'v=DKIM1;t=s;p=' + dkimData.publicKey.replace(/^-.*-$/gm, '').replace(/\s/g, '')
}
});
}
);
});
2018-01-03 19:23:42 +08:00
});
2018-01-02 19:46:32 +08:00
}
2018-04-29 03:44:38 +08:00
get(options, includePrivateKey, callback) {
2018-01-02 19:46:32 +08:00
let query = {};
2018-04-29 03:44:38 +08:00
options = options || {};
if (options.domain) {
query.domain = tools.normalizeDomain(options.domain);
} else if (options._id && tools.isId(options._id)) {
query._id = new ObjectID(options._id);
2018-01-02 19:46:32 +08:00
} else {
2018-04-29 03:54:38 +08:00
let err = new Error('Invalid or unknown DKIM key');
2018-09-11 16:13:53 +08:00
err.code = 'DkimNotFound';
2018-04-29 03:54:38 +08:00
return setImmediate(() => callback(err));
2018-01-02 19:46:32 +08:00
}
this.database.collection('dkim').findOne(query, (err, dkimData) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
}
if (!dkimData) {
let err = new Error('Invalid or unknown DKIM key');
2018-09-11 16:13:53 +08:00
err.code = 'DkimNotFound';
2018-01-02 19:46:32 +08:00
return callback(err);
}
let privateKey;
if (includePrivateKey) {
privateKey = dkimData.privateKey;
if (privateKey.charAt(0) === '$') {
if (this.secret) {
try {
let decipher = crypto.createDecipher(this.cipher || 'aes192', this.secret);
privateKey = decipher.update(privateKey.substr(1), 'hex', 'utf-8');
privateKey += decipher.final('utf8');
} catch (E) {
let err = new Error('Failed to decrypt private key. ' + E.message);
err.code = 'InternalConfigError';
return callback(err);
}
} else {
let err = new Error('Can not use decrypted key');
err.code = 'InternalConfigError';
return callback(err);
}
}
}
callback(null, {
id: dkimData._id,
domain: dkimData.domain,
selector: dkimData.selector,
description: dkimData.description,
fingerprint: dkimData.fingerprint,
publicKey: dkimData.publicKey,
privateKey,
dnsTxt: {
name: dkimData.selector + '._domainkey.' + dkimData.domain,
value: 'v=DKIM1;t=s;p=' + dkimData.publicKey.replace(/^-.*-$/gm, '').replace(/\s/g, '')
},
created: dkimData.created
});
});
}
2018-04-29 03:44:38 +08:00
del(options, callback) {
2018-01-02 19:46:32 +08:00
let query = {};
2018-04-29 03:44:38 +08:00
if (options.domain) {
query.domain = tools.normalizeDomain(options.domain);
} else if (options._id && tools.isId(options._id)) {
query._id = new ObjectID(options._id);
2018-01-02 19:46:32 +08:00
} else {
2018-04-29 03:54:38 +08:00
let err = new Error('Invalid or unknown DKIM key');
2018-09-11 16:13:53 +08:00
err.code = 'DkimNotFound';
2018-04-29 03:54:38 +08:00
return setImmediate(() => callback(err));
2018-01-02 19:46:32 +08:00
}
// delete address from email address registry
this.database.collection('dkim').deleteOne(query, (err, r) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
}
if (!r.deletedCount) {
let err = new Error('Invalid or unknown DKIM key');
2018-09-11 16:13:53 +08:00
err.code = 'DkimNotFound';
2018-01-02 19:46:32 +08:00
return callback(err);
}
return callback(null, !!r.deletedCount);
});
}
}
module.exports = DkimHandler;