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} 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} selector Selector for the key
* @apiParam {String} [description] Key description * @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 {Boolean} success Indicates successful response
* @apiSuccess {String} id ID of the DKIM * @apiSuccess {String} id ID of the DKIM
@ -258,8 +258,7 @@ module.exports = (db, server) => {
privateKey: Joi.string() privateKey: Joi.string()
.empty('') .empty('')
.trim() .trim()
.regex(/^-----BEGIN RSA PRIVATE KEY-----/, 'DKIM key format') .regex(/^-----BEGIN RSA PRIVATE KEY-----/, 'DKIM key format'),
.required(),
description: Joi.string() description: Joi.string()
.max(255) .max(255)
//.hostname() //.hostname()

View file

@ -2,7 +2,7 @@
const ObjectID = require('mongodb').ObjectID; const ObjectID = require('mongodb').ObjectID;
const fingerprint = require('key-fingerprint').fingerprint; const fingerprint = require('key-fingerprint').fingerprint;
const pki = require('node-forge').pki; const forge = require('node-forge');
const crypto = require('crypto'); const crypto = require('crypto');
const tools = require('./tools'); const tools = require('./tools');
@ -21,89 +21,112 @@ class DkimHandler {
const description = options.description; const description = options.description;
let privateKeyPem = options.privateKey; let privateKeyPem = options.privateKey;
let publicKeyPem;
let fp, publicKeyPem; let getPrivateKey = done => {
try { if (privateKeyPem) {
fp = fingerprint(privateKeyPem, 'sha256', true); return done();
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');
} }
// private key not set, generate a new key
let ciphered = crypto.publicEncrypt(publicKeyPem, Buffer.from('secretvalue')); forge.rsa.generateKeyPair({ bits: 2048, workers: -1 }, (err, keypair) => {
let deciphered = crypto.privateDecrypt(privateKeyPem, ciphered); if (err) {
if (deciphered.toString() !== 'secretvalue') { err.code = 'KeyGenereateError';
throw new Error('Was not able to use key for encryption'); return callback(err);
} }
} catch (E) { privateKeyPem = forge.pki.privateKeyToPem(keypair.privateKey);
let err = new Error('Invalid or incompatible private key. ' + E.message); publicKeyPem = forge.pki.publicKeyToPem(keypair.publicKey);
err.code = 'InputValidationError'; return done();
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) { getPrivateKey(() => {
dkimData.description = description; let fp;
} try {
fp = fingerprint(privateKeyPem, 'sha256', true);
this.database.collection('dkim').findOneAndReplace( if (!publicKeyPem) {
{ // extract public key from private key
domain let privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
}, let publicKey = forge.pki.setRsaPublicKey(privateKey.n, privateKey.e);
dkimData, publicKeyPem = forge.pki.publicKeyToPem(publicKey);
{ if (!publicKeyPem) {
upsert: true, throw new Error('Was not able to extract public key from private key');
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, '')
} }
}); }
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) { get(domain, includePrivateKey, callback) {