Allow generating DKIM private keys

This commit is contained in:
Andris Reinman 2018-01-03 13:23:42 +02:00
parent d27a087d10
commit cac8bc9c65
6 changed files with 106 additions and 84 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
define({ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs. Under construction, see old docs here: https://github.com/nodemailer/wildduck/blob/master/docs/api.md", "title": "WildDuck API", "url": "http://localhost:8080", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-01-02T14:09:30.712Z", "url": "http://apidocjs.com", "version": "0.17.6" } });
define({ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs. Under construction, see old docs here: https://github.com/nodemailer/wildduck/blob/master/docs/api.md", "title": "WildDuck API", "url": "http://localhost:8080", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-01-03T11:22:08.500Z", "url": "http://apidocjs.com", "version": "0.17.6" } });

View file

@ -1 +1 @@
{ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs. Under construction, see old docs here: https://github.com/nodemailer/wildduck/blob/master/docs/api.md", "title": "WildDuck API", "url": "http://localhost:8080", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-01-02T14:09:30.712Z", "url": "http://apidocjs.com", "version": "0.17.6" } }
{ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs. Under construction, see old docs here: https://github.com/nodemailer/wildduck/blob/master/docs/api.md", "title": "WildDuck API", "url": "http://localhost:8080", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-01-03T11:22:08.500Z", "url": "http://apidocjs.com", "version": "0.17.6" } }

View file

@ -195,7 +195,7 @@ module.exports = (db, server) => {
* @apiParam {String} domain Domain name this DKIM key applies to. Use <code>"\*"</code> as a special value that will be used for domains that do not have their own DKIM key set
* @apiParam {String} selector Selector for the key
* @apiParam {String} [description] Key description
* @apiParam {String} privateKey Pem formatted DKIM private key
* @apiParam {String} [privateKey] Pem formatted DKIM private key. If not set then a new 2048 bit RSA key is generated, beware though that it can take several seconds to complete.
*
* @apiSuccess {Boolean} success Indicates successful response
* @apiSuccess {String} id ID of the DKIM
@ -258,8 +258,7 @@ module.exports = (db, server) => {
privateKey: Joi.string()
.empty('')
.trim()
.regex(/^-----BEGIN RSA PRIVATE KEY-----/, 'DKIM key format')
.required(),
.regex(/^-----BEGIN RSA PRIVATE KEY-----/, 'DKIM key format'),
description: Joi.string()
.max(255)
//.hostname()

View file

@ -2,7 +2,7 @@
const ObjectID = require('mongodb').ObjectID;
const fingerprint = require('key-fingerprint').fingerprint;
const pki = require('node-forge').pki;
const forge = require('node-forge');
const crypto = require('crypto');
const tools = require('./tools');
@ -21,89 +21,112 @@ class DkimHandler {
const description = options.description;
let privateKeyPem = options.privateKey;
let publicKeyPem;
let fp, publicKeyPem;
try {
fp = fingerprint(privateKeyPem, 'sha256', true);
let privateKey = pki.privateKeyFromPem(privateKeyPem);
let publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e);
publicKeyPem = pki.publicKeyToPem(publicKey);
if (!publicKeyPem) {
throw new Error('Was not able to extract public key from private key');
let getPrivateKey = done => {
if (privateKeyPem) {
return done();
}
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');
}
} catch (E) {
let err = new Error('Invalid or incompatible private key. ' + E.message);
err.code = 'InputValidationError';
return callback(err);
}
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';
return callback(err);
}
}
let dkimData = {
domain,
selector,
privateKey: privateKeyPem,
publicKey: publicKeyPem,
fingerprint: fp,
created: new Date(),
latest: true
// private key not set, generate a new key
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();
});
};
if (description) {
dkimData.description = description;
}
getPrivateKey(() => {
let fp;
try {
fp = fingerprint(privateKeyPem, 'sha256', true);
this.database.collection('dkim').findOneAndReplace(
{
domain
},
dkimData,
{
upsert: true,
returnOriginal: false
},
(err, r) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
}
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, '')
if (!publicKeyPem) {
// extract public key from private key
let privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
let publicKey = forge.pki.setRsaPublicKey(privateKey.n, privateKey.e);
publicKeyPem = forge.pki.publicKeyToPem(publicKey);
if (!publicKeyPem) {
throw new Error('Was not able to extract public key from private key');
}
});
}
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');
}
} catch (E) {
let err = new Error('Invalid or incompatible private key. ' + E.message);
err.code = 'InputValidationError';
return callback(err);
}
);
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';
return callback(err);
}
}
let dkimData = {
domain,
selector,
privateKey: privateKeyPem,
publicKey: publicKeyPem,
fingerprint: fp,
created: new Date(),
latest: true
};
if (description) {
dkimData.description = description;
}
this.database.collection('dkim').findOneAndReplace(
{
domain
},
dkimData,
{
upsert: true,
returnOriginal: false
},
(err, r) => {
if (err) {
err.code = 'InternalDatabaseError';
return callback(err);
}
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, '')
}
});
}
);
});
}
get(domain, includePrivateKey, callback) {