Skip to content

Commit

Permalink
tls: add --tls-cipher-list command line switch
Browse files Browse the repository at this point in the history
This adds a new `--tls-cipher-list` command line switch
that can be used to override the built-in default cipher
list. The intent of this is to make it possible to enforce
an alternative default cipher list at the process level.
Overriding the default cipher list is still permitted at
the application level by changing the value of
`require('tls').DEFAULT_CIPHERS`.

As part of the change, the built in default list is moved
out of tls.js and into node_constants.h and node_constants.cc.
Two new constants are added to require('constants'):

  * defaultCipherList (the active default cipher list)
  * defaultCoreCipherList (the built-in default cipher list)

A test case and doc changes are included.

A new NODE_DEFINE_STRING_CONSTANT macro is also created in
node_internals.h

When node_constants is initialized, it will pick up either
the passed in command line switch or fallback to the default
built-in suite.

Within joyent/node, this change had originaly been wrapped
up with a number of other related commits involving the
removal of the RC4 cipher. This breaks out this isolated
change.

/cc @mhdawson, @misterdjules, @trevnorris, @indutny, @rvagg

Reviewed By: Ben Noordhuis <ben@strongloop.com>
PR-URL: #2412
  • Loading branch information
jasnell authored and rvagg committed Aug 24, 2015
1 parent c9867fe commit 5fef5c6
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 24 deletions.
40 changes: 39 additions & 1 deletion doc/api/tls.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,44 @@ handshake extensions allowing you:
* SNI - to use one TLS server for multiple hostnames with different SSL
certificates.

## Modifying the Default TLS Cipher suite

Node.js is built with a default suite of enabled and disabled TLS ciphers.
Currently, the default cipher suite is:

ECDHE-RSA-AES128-GCM-SHA256:
ECDHE-ECDSA-AES128-GCM-SHA256:
ECDHE-RSA-AES256-GCM-SHA384:
ECDHE-ECDSA-AES256-GCM-SHA384:
DHE-RSA-AES128-GCM-SHA256:
ECDHE-RSA-AES128-SHA256:
DHE-RSA-AES128-SHA256:
ECDHE-RSA-AES256-SHA384:
DHE-RSA-AES256-SHA384:
ECDHE-RSA-AES256-SHA256:
DHE-RSA-AES256-SHA256:
HIGH:
!aNULL:
!eNULL:
!EXPORT:
!DES:
!RC4:
!MD5:
!PSK:
!SRP:
!CAMELLIA

This default can be overriden entirely using the `--tls-cipher-list` command
line switch. For instance, the following makes
`ECDHE-RSA-AES128-GCM-SHA256:!RC4` the default TLS cipher suite:

node --tls-cipher-list="ECDHE-RSA-AES128-GCM-SHA256:!RC4"

Note that 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. The `--tls-cipher-list` switch should by used only if
absolutely necessary.

## Perfect Forward Secrecy

Expand Down Expand Up @@ -138,7 +176,7 @@ automatically set as a listener for the [secureConnection][] event. The
- `crl` : Either a string or list of strings of PEM encoded CRLs (Certificate
Revocation List)

- `ciphers`: A string describing the ciphers to use or exclude, seperated by
- `ciphers`: A string describing the ciphers to use or exclude, separated by
`:`. The default cipher suite is:

ECDHE-RSA-AES128-GCM-SHA256:
Expand Down
3 changes: 3 additions & 0 deletions doc/iojs.1
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ and servers.

--v8-options print v8 command line options

--tls-cipher-list=list use an alternative default TLS cipher list
(available only when Node.js is built with
OpenSSL and crypto support enabled)

.SH ENVIRONMENT VARIABLES

Expand Down
25 changes: 2 additions & 23 deletions lib/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const url = require('url');
const util = require('util');
const binding = process.binding('crypto');
const Buffer = require('buffer').Buffer;
const constants = require('constants');

// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
Expand All @@ -15,29 +16,7 @@ exports.CLIENT_RENEG_WINDOW = 600;

exports.SLAB_BUFFER_SIZE = 10 * 1024 * 1024;

exports.DEFAULT_CIPHERS = [
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-AES256-GCM-SHA384',
'DHE-RSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES128-SHA256',
'DHE-RSA-AES128-SHA256',
'ECDHE-RSA-AES256-SHA384',
'DHE-RSA-AES256-SHA384',
'ECDHE-RSA-AES256-SHA256',
'DHE-RSA-AES256-SHA256',
'HIGH',
'!aNULL',
'!eNULL',
'!EXPORT',
'!DES',
'!RC4',
'!MD5',
'!PSK',
'!SRP',
'!CAMELLIA'
].join(':');
exports.DEFAULT_CIPHERS = constants.defaultCipherList;

exports.DEFAULT_ECDH_CURVE = 'prime256v1';

Expand Down
7 changes: 7 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3111,6 +3111,9 @@ static void PrintHelp() {
" --track-heap-objects track heap object allocations for heap "
"snapshots\n"
" --v8-options print v8 command line options\n"
#if HAVE_OPENSSL
" --tls-cipher-list=val use an alternative default TLS cipher list\n"
#endif
#if defined(NODE_HAVE_I18N_SUPPORT)
" --icu-data-dir=dir set ICU data load path to dir\n"
" (overrides NODE_ICU_DATA)\n"
Expand Down Expand Up @@ -3242,6 +3245,10 @@ static void ParseArgs(int* argc,
} else if (strcmp(arg, "--v8-options") == 0) {
new_v8_argv[new_v8_argc] = "--help";
new_v8_argc += 1;
#if HAVE_OPENSSL
} else if (strncmp(arg, "--tls-cipher-list=", 18) == 0) {
default_cipher_list = arg + 18;
#endif
#if defined(NODE_HAVE_I18N_SUPPORT)
} else if (strncmp(arg, "--icu-data-dir=", 15) == 0) {
icu_data_dir = arg + 15;
Expand Down
16 changes: 16 additions & 0 deletions src/node_constants.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ namespace node {
using v8::Handle;
using v8::Object;

#if HAVE_OPENSSL
const char* default_cipher_list = DEFAULT_CIPHER_LIST_CORE;
#endif

void DefineErrnoConstants(Handle<Object> target) {
#ifdef E2BIG
NODE_DEFINE_CONSTANT(target, E2BIG);
Expand Down Expand Up @@ -1108,13 +1112,25 @@ void DefineUVConstants(Handle<Object> target) {
NODE_DEFINE_CONSTANT(target, UV_UDP_REUSEADDR);
}

void DefineCryptoConstants(Handle<Object> target) {
#if HAVE_OPENSSL
NODE_DEFINE_STRING_CONSTANT(target,
"defaultCoreCipherList",
DEFAULT_CIPHER_LIST_CORE);
NODE_DEFINE_STRING_CONSTANT(target,
"defaultCipherList",
default_cipher_list);
#endif
}

void DefineConstants(Handle<Object> target) {
DefineErrnoConstants(target);
DefineWindowsErrorConstants(target);
DefineSignalConstants(target);
DefineOpenSSLConstants(target);
DefineSystemConstants(target);
DefineUVConstants(target);
DefineCryptoConstants(target);
}

} // namespace node
29 changes: 29 additions & 0 deletions src/node_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,36 @@
#include "node.h"
#include "v8.h"

#if HAVE_OPENSSL
#define DEFAULT_CIPHER_LIST_CORE "ECDHE-RSA-AES128-GCM-SHA256:" \
"ECDHE-ECDSA-AES128-GCM-SHA256:" \
"ECDHE-RSA-AES256-GCM-SHA384:" \
"ECDHE-ECDSA-AES256-GCM-SHA384:" \
"DHE-RSA-AES128-GCM-SHA256:" \
"ECDHE-RSA-AES128-SHA256:" \
"DHE-RSA-AES128-SHA256:" \
"ECDHE-RSA-AES256-SHA384:" \
"DHE-RSA-AES256-SHA384:" \
"ECDHE-RSA-AES256-SHA256:" \
"DHE-RSA-AES256-SHA256:" \
"HIGH:" \
"!aNULL:" \
"!eNULL:" \
"!EXPORT:" \
"!DES:" \
"!RC4:" \
"!MD5:" \
"!PSK:" \
"!SRP:" \
"!CAMELLIA"
#endif

namespace node {

#if HAVE_OPENSSL
extern const char* default_cipher_list;
#endif

void DefineConstants(v8::Handle<v8::Object> target);
} // namespace node

Expand Down
16 changes: 16 additions & 0 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@

struct sockaddr;

// Variation on NODE_DEFINE_CONSTANT that sets a String value.
#define NODE_DEFINE_STRING_CONSTANT(target, name, constant) \
do { \
v8::Isolate* isolate = target->GetIsolate(); \
v8::Local<v8::String> constant_name = \
v8::String::NewFromUtf8(isolate, name); \
v8::Local<v8::String> constant_value = \
v8::String::NewFromUtf8(isolate, constant); \
v8::PropertyAttribute constant_attributes = \
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete); \
target->ForceSet(isolate->GetCurrentContext(), \
constant_name, \
constant_value, \
constant_attributes); \
} while (0)

namespace node {

// Forward declaration
Expand Down
32 changes: 32 additions & 0 deletions test/parallel/test-tls-cipher-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';
const common = require('../common');

if (!common.hasCrypto) {
console.log('1..0 # Skipped: missing crypto');
return;
}

const assert = require('assert');
const spawn = require('child_process').spawn;
const defaultCoreList = require('constants').defaultCoreCipherList;

function doCheck(arg, check) {
var out = '';
var arg = arg.concat([
'-pe',
'require("constants").defaultCipherList'
]);
spawn(process.execPath, arg, {}).
on('error', assert.fail).
stdout.on('data', function(chunk) {
out += chunk;
}).on('end', function() {
assert.equal(out.trim(), check);
}).on('error', assert.fail);
}

// test the default unmodified version
doCheck([], defaultCoreList);

// test the command line switch by itself
doCheck(['--tls-cipher-list=ABC'], 'ABC');

0 comments on commit 5fef5c6

Please sign in to comment.