wildduck/lib/certs.js
2021-05-13 17:08:14 +03:00

176 lines
4.6 KiB
JavaScript

'use strict';
const config = require('wild-config');
const fs = require('fs');
const tls = require('tls');
const db = require('./db');
const tlsOptions = require('../imap-core/lib/tls-options');
const CertHandler = require('./cert-handler');
const certs = new Map();
const servers = [];
const ctxCache = new Map();
let certHandler;
module.exports.reload = () => {
// load certificate files
[false, 'imap', 'lmtp', 'pop3', 'api', 'api.mobileconfig'].forEach(type => {
let tlsconf = config.tls;
if (type) {
let path = (type + '.tls').split('.');
tlsconf = config;
for (let i = 0; i < path.length; i++) {
let key = path[i];
if (!tlsconf[key]) {
tlsconf = false;
break;
}
tlsconf = tlsconf[key];
}
if (!tlsconf || !tlsconf.key) {
tlsconf = config.tls;
}
}
if (!tlsconf) {
return;
}
let key, cert, ca, dhparam;
if (tlsconf.key) {
key = fs.readFileSync(tlsconf.key, 'ascii');
}
if (!key) {
return;
}
if (tlsconf.cert) {
cert = fs.readFileSync(tlsconf.cert, 'ascii');
}
if (tlsconf.dhparam) {
dhparam = fs.readFileSync(tlsconf.dhparam, 'ascii');
}
if (tlsconf.ca) {
ca = [].concat(tlsconf.ca || []).map(ca => fs.readFileSync(ca, 'ascii'));
if (!ca.length) {
ca = false;
}
}
certs.set(type || 'default', {
key,
cert,
ca,
dhparam
});
});
if (!certs.has('default')) {
certs.set('default', {
key: fs.readFileSync(__dirname + '/../certs/example.key', 'ascii'),
cert: fs.readFileSync(__dirname + '/../certs/example.cert', 'ascii'),
ca: false
});
}
};
module.exports.reload();
module.exports.get = type => (certs.has(type) ? certs.get(type) : certs.get('default')) || false;
module.exports.loadTLSOptions = (serverOptions, name) => {
Object.keys(config[name].tls || {}).forEach(key => {
if (!['key', 'cert', 'ca', 'dhparam'].includes(key)) {
serverOptions[key] = config[name].tls[key];
}
});
let serverCerts = module.exports.get(name);
if (serverCerts) {
serverOptions.key = serverCerts.key;
if (serverCerts.ca) {
serverOptions.ca = serverCerts.ca;
}
serverOptions.cert = serverCerts.cert;
if (serverCerts.dhparam) {
serverOptions.dhparam = serverCerts.dhparam;
}
}
};
module.exports.registerReload = (server, name) => {
servers.push({ server, name });
};
module.exports.getContextForServername = async (servername, serverOptions) => {
const query = { servername };
let cachedContext = false;
if (ctxCache.has(servername)) {
cachedContext = ctxCache.get(servername);
if (cachedContext.entry && cachedContext.entry.v) {
// check for updates
query.v = { $ne: cachedContext.entry.v };
}
}
const certData = await db.database.collection('certs').findOne(query);
if (!certData) {
return (cachedContext && cachedContext.context) || false;
}
if (!certHandler) {
certHandler = new CertHandler({
cipher: config.certs && config.certs.cipher,
secret: config.certs && config.certs.secret,
database: db.database,
redis: db.redis
});
}
// key might be encrypted
let privateKey = certHandler.decodeKey(certData.privateKey);
let serviceCtxOpts = { key: privateKey, cert: certData.cert };
for (let key of ['dhparam']) {
if (serverOptions[key]) {
serviceCtxOpts[key] = serverOptions[key];
}
}
let ctxOptions = tlsOptions(serviceCtxOpts);
let context = tls.createSecureContext(ctxOptions);
ctxCache.set(servername, { entry: certData, context });
return context;
};
config.on('reload', () => {
module.exports.reload();
servers.forEach(entry => {
let serverCerts = certs.get(entry.name);
let certOptions = {};
if (serverCerts) {
certOptions.key = serverCerts.key;
if (serverCerts.ca) {
certOptions.ca = serverCerts.ca;
}
certOptions.cert = serverCerts.cert;
if (serverCerts.dhparam) {
certOptions.dhparam = serverCerts.dhparam;
}
entry.server.updateSecureContext(certOptions);
}
});
});