I'm running an express web server with SSL certificate provided by Let's Encrypt as below:
var express = require('express');
var fs = require('fs');
var http = require('http');
let https = require('https');
const app = express();
let privateKey = fs.readFileSync(SERVER_KEY, 'utf8');
let certificate = fs.readFileSync(SERVER_CRT, 'utf8');
let certauth = fs.readFileSync(SERVER_CA, 'utf8');
let credentials = {key: privateKey, cert: certificate, ca: certauth};
//HTTPS server
const httpsServer = https.createServer(credentials, app);
httpsServer.listen(443);
//HTTP redirect server
const httpServer = http.createServer(function (req, res) {
res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
res.end();
});
httpServer.listen(80);
The certificate chain parameters are populated as:
SERVER_KEY=/etc/letsencrypt/live/dummy.example.com/privkey.pem SERVER_CRT=/etc/letsencrypt/live/dummy.example.com/cert.pem SERVER_CA=/etc/letsencrypt/live/dummy.example.com/chain.pem
Which works well and I can access the web-server both via a browser and via other applications (node.js, java etc)
However, when the certificates expire and are auto-renewed, the web-server must be restarted for this to take effect. To get around this, I have attempted to implement SNICallback with tls secure context so that the certificate chain is read in real time for each request instead of the cached version:
var express = require('express');
var fs = require('fs');
var http = require('http');
let https = require('https');
var tls = require('tls');
const app = express();
function getCredentials(){
let privateKey = fs.readFileSync(SERVER_KEY, 'utf8');
let certificate = fs.readFileSync(SERVER_CRT, 'utf8');
let certauth = fs.readFileSync(SERVER_CA, 'utf8');
let credentials = {key: privateKey, cert: certificate, ca: certauth};
return credentials;
}
var ctx = function() { return tls.createSecureContext(getCredentials()) };
//HTTPS server
const httpsServer = https.createServer({
SNICallback: (servername, cb) => cb(null, ctx())
}, app);
httpsServer.listen(443);
//HTTP redirect server
const httpServer = http.createServer(function (req, res) {
res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
res.end();
});
httpServer.listen(80);
This appears to work as intended when accessing the server via a browser (when refreshing the certificate with a new expiry, the new certificate is picked up when the page is refreshed).
However, any attempt to connect via other applications is now hitting the following error:
Error: unable to verify the first certificate
suggesting that the intermediate certificate is not being provided by the server in the certificate chain.