HTTPS Server with multiple Domains on same Port & Instance
In this lesson we will see an example how to manage a HTTPS Server that will serve specific SSL Certificates for different domain names.
My sample represents one file, I will explain it’s parts step-by-step
Libraries Used
var express = require("express");
var app = express();
var _classes = {
fs : require("fs"),
tls : require("tls"),
https : require("https")
};
Certificate ca-bundle.crt usually contains multiple certificates.. we need a function for splitting them.
Sample of content structure of ca-bundle.crt
/**************************
-----BEGIN CERTIFICATE-----
MSDIE..
... base64 encoded Content
..FSDS
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
ASDDIE..
... base64 encoded Content
..FSDS
-----END CERTIFICATE-----
..... othermultiple certificates....
*/
We will need a function for splitting ca-bundle.crt file into an array.
var sslCAdecode = function (source) {
var ca = [];
if (!source || typeof(source) !== "string")
return ca;
ca = source.split(/-----END CERTIFICATE-----[\s\n]+-----BEGIN CERTIFICATE-----/).map(function (v, k, arr) {
if (k) {
v = '-----BEGIN CERTIFICATE-----' + v;
}
if (k !== arr.length - 1) {
v = v + '-----END CERTIFICATE-----';
}
v = v.replace(/^\n+/,'').replace(/\n+$/,'');
return v;
});
return ca;
};
Preparing configuration
We need to prepare secure context for all domains/servernames.
SSL Secure Context will be prepared using native module “tls".
The tls.createSecureContext()
method creates a credentials object.
A key is required for ciphers that make use of certificates. Either key
or pfx
can be used to provide it.
If the ‘ca’ option is not given, then Node.js will use the default
publicly trusted list of CAs as given in
http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt.
var sslConfig = {
privateKey : "/home/nodejs-md/ssl/nodejs-ssl.key",
certificate : "/home/nodejs-md/ssl/nodejs-ssl.crt",
bundleChain : "/home/nodejs-md/ssl/nodejs-ssl-ca-bundle.crt"
};
sslConfig.sslCertsContext = {
"sgapps.io" : _classes.tls.createSecureContext({
key : _classes.fs.readFileSync(
"/home/sgapps-io/ssl/sgapps-ssl.key"
).toString(),
cert : _classes.fs.readFileSync(
"/home/sgapps-io/ssl/sgapps-ssl.crt"
).toString(),
ca : sslCAdecode(
_classes.fs.readFileSync(
"/home/sgapps-io/ssl/sgapps-ssl-ca-bundle.crt",
"utf-8"
)
)
}),
"nodejs.md" : _classes.tls.createSecureContext({
key : _classes.fs.readFileSync(
"/home/nodejs-md/ssl/nodejs-ssl.key"
).toString(),
cert : _classes.fs.readFileSync(
"/home/nodejs-md/ssl/nodejs-ssl.crt"
).toString(),
ca : sslCAdecode(
_classes.fs.readFileSync(
"/home/nodejs-md/ssl/nodejs-ssl-ca-bundle.crt",
"utf-8"
)
)
}),
"js.md" : _classes.tls.createSecureContext({
key : _classes.fs.readFileSync(
"/home/js-md/ssl/js-ssl.key"
).toString(),
cert : _classes.fs.readFileSync(
"/home/js-md/ssl/js-ssl.crt"
).toString(),
ca : sslCAdecode(
_classes.fs.readFileSync(
"/home/js-md/ssl/js-ssl-ca-bundle.crt",
"utf-8"
)
)
})
};
Initializing HTTPS Server
we will send ExpressJS instance into HTTPS Server Instance as Main Request handler.
key
<string> | <string[]> | <Buffer> | <Buffer[]> | <Object[]> Optional private keys in PEM format. PEM allows the option of private keys being encrypted. Encrypted keys will be decrypted withoptions.passphrase
. Multiple keys using different algorithms can be provided either as an array of unencrypted key strings or buffers, or an array of objects in the form{pem: <string|buffer>[, passphrase: <string>]}
. The object form can only occur in an array.object.passphrase
is optional. Encrypted keys will be decrypted withobject.passphrase
if provided, oroptions.passphrase
if it is not.cert
<string> | <string[]> | <Buffer> | <Buffer[]> Optional cert chains in PEM format. One cert chain should be provided per private key. Each cert chain should consist of the PEM formatted certificate for a provided privatekey
, followed by the PEM formatted intermediate certificates (if any), in order, and not including the root CA (the root CA must be pre-known to the peer, seeca
). When providing multiple cert chains, they do not have to be in the same order as their private keys inkey
. If the intermediate certificates are not provided, the peer will not be able to validate the certificate, and the handshake will fail.SNICallback(servername, cb)
A function that will be called if the client supports SNI TLS extension. Two arguments will be passed when called: servername
andcb
.SNICallback
should invokecb(null, ctx)
, wherectx
is a SecureContext instance. (tls.createSecureContext(...)
can be used to get a proper SecureContext.) IfSNICallback
wasn't provided the default callback with high-level API will be used (see below).
_classes.https.createServer({
key : _classes.fs.readFileSync(sslConfig.privateKey),
cert : _classes.fs.readFileSync(sslConfig.certificate),
ca : sslCAdecode(
_classes.fs.readFileSync(sslConfig.bundleChain, "utf-8")
),
SNICallback : function (servername, cb) {
var ctx = sslConfig.sslCertsContext[servername] || sslConfig.sslCertsContext["nodejs.md"];
if (cb) {
cb(null, ctx);
} else {
return ctx;
}
}
}, app).listen(
443, // default HTTPS port
"0.0.0.0", // listen all IPs
function (err) {
if (err)
return console.error(err);
console.log("Listen Server on https://0.0.0.0:443");
}
);
Creating a HTTP Server instance that will redirect traffic to HTTPS
Usually Users are accessing sites over HTTP Protocol by entering http://yoursize.com instead of https://yoursize.com.
So to redirect automatically all users/ all traffic to your HTTPS Server you will need a HTTP Server with a very simple functionality, just one redirect…
require("http").createServer(function (request, response) {
response.statusCode = 302;
response.setHeader("Location", (request.headers || {}).origin || "https://nodejs.md");
response.end();
}).listen(
80, // default HTTP posrt
"0.0.0.0", // listen all IPs
function (err) {
if (err)
return console.error(err);
console.log("HTTP Redirect Server on http://0.0.0.0:80");
}
);
Conclusion
So we obtained a HTTPS Server that is able to offer different certificates depending of server-name.
In this example we have 3 TLS Security Contexts for following sites: sgapps.io, nodejs.md, js.md
Entire File ( all code in one file )
var express = require("express");
var app = express();
var _classes = {
fs : require("fs"),
tls : require("tls"),
https : require("https")
};
/**************************
-----BEGIN CERTIFICATE-----
MSDIE..
... base64 encoded Content
..FSDS
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
ASDDIE..
... base64 encoded Content
..FSDS
-----END CERTIFICATE-----
..... othermultiple certificates....
*/
var sslCAdecode = function (source) {
var ca = [];
if (!source || typeof(source) !== "string")
return ca;
ca = source.split(/-----END CERTIFICATE-----[\s\n]+-----BEGIN CERTIFICATE-----/).map(function (v, k, arr) {
if (k) {
v = '-----BEGIN CERTIFICATE-----' + v;
}
if (k !== arr.length - 1) {
v = v + '-----END CERTIFICATE-----';
}
v = v.replace(/^\n+/,'').replace(/\n+$/,'');
return v;
});
return ca;
};
_classes.https.createServer({
key : _classes.fs.readFileSync(sslConfig.privateKey),
cert : _classes.fs.readFileSync(sslConfig.certificate),
ca : sslCAdecode(
_classes.fs.readFileSync(sslConfig.bundleChain, "utf-8")
),
SNICallback : function (servername, cb) {
var ctx = sslConfig.sslCertsContext[servername] || sslConfig.sslCertsContext["nodejs.md"];
if (cb) {
cb(null, ctx);
} else {
return ctx;
}
}
}, app).listen(
443, // default HTTPS port
"0.0.0.0", // listen all IPs
function (err) {
if (err)
return console.error(err);
console.log("Listen Server on https://0.0.0.0:443");
}
);
require("http").createServer(function (request, response) {
response.statusCode = 302;
response.setHeader("Location", (request.headers || {}).origin || "https://nodejs.md");
response.end();
}).listen(
80, // default HTTP posrt
"0.0.0.0", // listen all IPs
function (err) {
if (err)
return console.error(err);
console.log("HTTP Redirect Server on http://0.0.0.0:80");
}
);
0 Comments