Error: unable to verify the first certificate when using SNICallback in node.js express server

52 views Asked by At

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.

0

There are 0 answers