mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-10-17 17:26:16 +08:00
Allow generating DKIM private keys
This commit is contained in:
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
|
@ -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"
}
});
|
||||||
|
|
|
@ -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"
}
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue