'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); } }); });