Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple SSL Certificates and HTTP/2 with Express.js

Scenario:

I have an express.js server which serves variations of the same static landing page based on where req.headers.host says the user is coming from - think sort of like A/B testing.

GET tulip.flower.com serves pages/flower.com/tulip.html

GET rose.flower.com serves pages/flower.com/rose.html

At the same time, this one IP is also responsible for:

GET potato.vegetable.com serving pages/vegetable.com/potato.html

It's important that these pages are served FAST, so they are precompiled and optimized in all sorts of ways.

The server now needs to:

  1. Provide separate certificates for *.vegetables.com, *.fruits.com, *.rocks.net
  2. Optionally provide no certificate for *.flowers.com
  3. Offer HTTP2

The problem is that HTTP2 mandates a certificate, and there's now multiple certificates in play.

It appears that it's possible to use multiple certificates on one Node.js (and presumably by extension Express.js) server, but is it possible to combine it with a module like spdy, and if so, how?

Instead of hacking node, would it be smarter to pawn the task of sorting out http2 and SSL to nginx? Should the caching network like Imperva or Akamai handle this?

like image 285
dsp_099 Avatar asked Mar 01 '17 03:03

dsp_099


1 Answers

You can use also tls.createSecureContext, Nginx is not necassary.

MY example here:

const https = require("https");
const tls = require("tls");

const certs = {
    "localhost": {
        key: "./certs/localhost.key",
        cert: "./certs/localhost.crt",
    },
    "example.com": {
        key: "./certs/example.key",
        cert: "./certs/example.cert",
        ca: "./certs/example.ca",
    },
} 

function getSecureContexts(certs) {

    if (!certs || Object.keys(certs).length === 0) {
      throw new Error("Any certificate wasn't found.");
    }

    const certsToReturn = {};

    for (const serverName of Object.keys(certs)) {
      const appCert = certs[serverName];

      certsToReturn[serverName] = tls.createSecureContext({
        key: fs.readFileSync(appCert.key),
        cert: fs.readFileSync(appCert.cert),
        // If the 'ca' option is not given, then node.js will use the default
        ca: appCert.ca ? sslCADecode(
          fs.readFileSync(appCert.ca, "utf8"),
        ) : null,
      });
    }

    return certsToReturn;
}

// if CA contains more certificates it will be parsed to array
function sslCADecode(source) {

    if (!source || typeof (source) !== "string") {
        return [];
    }

    return source.split(/-----END CERTIFICATE-----[\s\n]+-----BEGIN CERTIFICATE-----/)
        .map((value, index: number, array) => {
        if (index) {
            value = "-----BEGIN CERTIFICATE-----" + value;
        }
        if (index !== array.length - 1) {
            value = value + "-----END CERTIFICATE-----";
        }
        value = value.replace(/^\n+/, "").replace(/\n+$/, "");
        return value;
    });
}

const secureContexts = getSecureContexts(certs)

const options = {
    // A function that will be called if the client supports SNI TLS extension.
    SNICallback: (servername, cb) => {

        const ctx = secureContexts[servername];

        if (!ctx) {
            log.debug(`Not found SSL certificate for host: ${servername}`);
        } else {
            log.debug(`SSL certificate has been found and assigned to ${servername}`);
        }

        if (cb) {
            cb(null, ctx);
        } else {
            return ctx;
        }
    },
};


var https = require('https');
var httpsServer = https.createServer(options, (req, res) => { console.log(res, req)});
httpsServer.listen(443, function () {
   console.log("Listening https on port: 443")
});

If you want test it:

  1. edit /etc/hosts and add record 127.0.0.1 example.com

  2. open browser with url https://example.com:443

like image 190
Skraloupak Avatar answered Oct 24 '22 23:10

Skraloupak