mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-10-03 10:24:33 +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} 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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue