SSL/TLS
SSL/TLS
are two cryptographic protocols (TLS is SSL's successor) that allow
encryption over otherwise unsecure communication channels. The most common
usage of SSL/TLS
is for sure HTTPS
: Hyper Text Transfer Protocol Secure.
These cryptographic protocols ensure that the following properties apply to the communication channel:
- Privacy
- Authentication
- Data Integrity
SSL/TLS protocols are available through Node.js' tls module.
In this section, we will focus on the Node.js implementation and its usage. Although the theoretical part of the protocol design and its cryptographic practices are beyond the scope of this article, additional information is available in the Cryptography Practices section of this document.
As a reminder, TLS protocol should be used to protect sensitive information manipulated by your application such as credentials, Personally Identifiable Information (PII) and credit card numbers.
In general, it is strongly recommended to use only HTTPS instead of switching back and forth between HTTP and HTTPS.
TLS or HTTPS
In Node.js, you have two distinct modules to create a server using SSL/TLS
:
In fact, https module literally implements "HTTP over TLS" as it depends
on both the http
and tls
modules to provide an HTTP server capable of
handling secure connections1.
The following is a simple example of an HTTP server using the tls
module
capable of handling secure connections
const tls = require('tls');
const fs = require('fs');
const options = {
key: fs.readFileSync('/my/certs/safe/location/key.pem'),
cert: fs.readFileSync('/my/certs/safe/location/cert.pem'),
};
const server = tls.createServer(options, (res) => {
console.log('server connected');
res.write('Hello JS-SCP!\n');
res.setEncoding('utf8');
res.pipe(res);
});
server.listen(443, () => {
console.log('server bound');
});
If you're looking to serve your application over HTTPS you're better to go with the https module. Bellow, you will find a sample using the Express - Node.js web application framework.
const express = require('express');
const https = require('https');
const fs = require('fs');
const app = express();
const certOptions = {
key: fs.readFileSync('/my/certs/safe/location/key.pem'),
cert: fs.readFileSync('/my/certs/safe/location/cert.pem')
};
app.get('/', (req, res) => {
res.send('Hello World!')
});
https.createServer(certOptions, app)
.listen(443);
SSL/TLS ciphers
It is important to highlight that Node.js by default accepts only a subset of strong ciphers.
Quoting the Node.js documentation about the Default TLS Cipher Suite:
The default cipher suite included within Node.js has been carefully selected to reflect current security best practices and risk mitigation. Changing the default cipher suite can have a significant impact on the security of an application.
If, for a very strong reason, the default configuration has to be changed, it is recommended to follow the best practices given by Mozilla on its Server Side TLS Guide.
SSL/TLS Protocols
In order to protect against some well known attacks such as POODLE (CVE-2014-3566) and BEAST (CVE-2011-3389), it is recommended to disable some protocols on the server side.
By default, the tls
and https
modules negotiate a protocol from the highest
level down to whatever the client supports. Nowadays, current browsers are not
allowing SSLv2, however, it is important to disable SSLv3 to protect against
POODLE and TLSv1.0 to protect against BEAST.
The secureOptions
parameter on the createServer
function from tls
and
https
modules allows to specify the allowed SSL/TLS protocols as demonstrated
below
const https = require('https');
const httpsServerOptions = {
// This is the default secureProtocol used by Node.js, but it might be
// sane to specify this by default as it's required if you want to
// remove supported protocols from the list. This protocol supports:
//
// - SSLv2, SSLv3, TLSv1, TLSv1.1 and TLSv1.2
secureProtocol: 'SSLv23_method',
// Supply `SSL_OP_NO_SSLv3` constant as secureOption to disable SSLv3
// from the list of supported protocols that SSLv23_method supports.
secureOptions: constants.SSL_OP_NO_SSLv3,
cert: fs.readFileSync('/my/certs/safe/location/cert.pem')),
key: fs.readFileSync('/my/certs/safe/location/key.pem'))
};
https.createServer(httpsServerOptions, (req, res) => {
res.end('works');
}).listen(443);
HTTP Strict Transport Security header
To further improve the communication security, the Strict Transport Security HTTP header (HSTS) can be added as follows
res.SetHeader("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
The helmet package can be also used to add not only the
Strict-Transport-Security
header but also other security HTTP headers
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet.hsts({maxage: 63072000, includeSubDomains: true}));
TLS certificate: client-side validation
Invalid TLS certificates should always be rejected.
Make sure that the NODE_TLS_REJECT_UNAUTHORIZED
environment variable is
not set to 0
in a production environment.
TLS compression
The CRIME attack exploits a flaw with data compression.
By default, Node.js disables all compression.
UTF-8 encoding
All requests should also be encoded to a pre-determined character encoding such as UTF-8. This can be set in the header:
res.SetHeader("Content-Type", "Desired Content Type; charset=utf-8")
HTTP referer
Another important aspect when handling HTTP connections is to verify that the HTTP referer does not contain any sensitive information when accessing external sites. Since the connection could be insecure, the HTTP referer may leak information.
1. Actualy you can see it on source code: lines 26 and 28 of https module source code. ↩