diff --git a/api.js b/api.js index 08641c6..a47ddb8 100644 --- a/api.js +++ b/api.js @@ -145,10 +145,7 @@ if (config.api.secure && certOptions.key) { let httpsServerOptions = {}; httpsServerOptions.key = certOptions.key; - if (certOptions.ca) { - httpsServerOptions.ca = certOptions.ca; - } - httpsServerOptions.cert = certOptions.cert; + httpsServerOptions.cert = tools.buildCertChain(certOptions.cert, certOptions.ca); let defaultSecureContext = tls.createSecureContext(httpsServerOptions); @@ -534,10 +531,7 @@ module.exports = done => { namespace: 'mail' }); - if (config.acme && config.acme.agent && config.acme.agent.enabled) { - acmeRoutes(db, server, { disableRedirect: true }); - } - + acmeRoutes(db, server, { disableRedirect: true }); usersRoutes(db, server, userHandler, settingsHandler); addressesRoutes(db, server, userHandler, settingsHandler); mailboxesRoutes(db, server, mailboxHandler); diff --git a/imap-core/lib/tls-options.js b/imap-core/lib/tls-options.js index 3e02a98..2a82b06 100644 --- a/imap-core/lib/tls-options.js +++ b/imap-core/lib/tls-options.js @@ -54,7 +54,8 @@ const tlsDefaults = { '-----END CERTIFICATE-----', honorCipherOrder: true, requestOCSP: false, - sessionIdContext: crypto.createHash('sha1').update(process.argv.join(' ')).digest('hex').slice(0, 32) + sessionIdContext: crypto.createHash('sha1').update(process.argv.join(' ')).digest('hex').slice(0, 32), + minVersion: 'TLSv1' }; /** diff --git a/lib/cert-handler.js b/lib/cert-handler.js index b19542b..ccf8c96 100644 --- a/lib/cert-handler.js +++ b/lib/cert-handler.js @@ -295,8 +295,6 @@ class CertHandler { certData.cert = cert; } - certData.ca = [].concat(ca || []); - if (primaryCert) { try { const parsedCert = forge.pki.certificateFromPem(primaryCert); @@ -558,7 +556,11 @@ class CertHandler { // key might be encrypted let privateKey = await decrypt(certData.privateKey, this.secret, this.cipher); - let serviceCtxOpts = { key: privateKey, cert: certData.cert, ca: certData.ca }; + let serviceCtxOpts = { + key: privateKey, + cert: tools.buildCertChain(certData.cert, certData.ca) + }; + for (let key of ['dhparam']) { if (serverOptions[key]) { serviceCtxOpts[key] = serverOptions[key]; diff --git a/lib/certs.js b/lib/certs.js index ca1357a..bfb6454 100644 --- a/lib/certs.js +++ b/lib/certs.js @@ -4,6 +4,7 @@ const config = require('wild-config'); const fs = require('fs'); const db = require('./db'); const CertHandler = require('./cert-handler'); +const { buildCertChain } = require('./tools'); const certs = new Map(); const servers = []; @@ -92,12 +93,7 @@ module.exports.loadTLSOptions = (serverOptions, name) => { if (serverCerts) { serverOptions.key = serverCerts.key; - - if (serverCerts.ca) { - serverOptions.ca = serverCerts.ca; - } - - serverOptions.cert = serverCerts.cert; + serverOptions.cert = buildCertChain(serverCerts.cert, serverCerts.ca); if (serverCerts.dhparam) { serverOptions.dhparam = serverCerts.dhparam; @@ -130,13 +126,12 @@ config.on('reload', () => { let certOptions = {}; if (serverCerts) { certOptions.key = serverCerts.key; - if (serverCerts.ca) { - certOptions.ca = serverCerts.ca; - } - certOptions.cert = serverCerts.cert; + certOptions.cert = buildCertChain(serverCerts.cert, serverCerts.ca); + if (serverCerts.dhparam) { certOptions.dhparam = serverCerts.dhparam; } + entry.server.updateSecureContext(certOptions); } }); diff --git a/lib/tools.js b/lib/tools.js index c5ed6f2..6374c07 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -1,3 +1,4 @@ +/* eslint no-control-regex: 0 */ 'use strict'; const os = require('os'); @@ -586,6 +587,24 @@ function roundTime(seconds) { return `${seconds} ${seconds === 1 ? 'second' : 'seconds'}`; } +function parsePemBundle(bundle) { + bundle = (bundle || '').toString().split(/\r?\n/).join('\x00'); + let matches = bundle.match(/[-]{3,}BEGIN [^-]+[-]{3,}.*?[-]{3,}END [^-]+[-]{3,}/g); + if (matches) { + matches = Array.from(matches).map(cert => cert.replace(/\x00/g, '\n') + '\n'); + } + return matches; +} + +function buildCertChain(cert, ca) { + return [cert] + .concat(ca || []) + .flatMap(ca => ca) + .map(ca => ca.trim() + '\n') + .filter(ca => ca.trim()) + .join('\n'); +} + module.exports = { normalizeAddress, normalizeDomain, @@ -609,6 +628,8 @@ module.exports = { formatFingerprint, getEnabled2fa, roundTime, + parsePemBundle, + buildCertChain, formatMetaData: metaData => { if (typeof metaData === 'string') {