Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 111 additions & 5 deletions doc/api/tls.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,103 @@ the character "E" appended to the traditional abbreviations):
Ephemeral methods may have some performance drawbacks, because key generation
is expensive.

## Modifying the Default Cipher Suite

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

ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:HIGH:
!RC4:!MD5:!aNULL

This default can be overridden entirely using the `--cipher-list` command line
switch or `NODE_CIPHER_LIST` environment variable. For instance:

node --cipher-list=ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384

Setting the environment variable would have the same effect:

NODE_CIPHER_LIST=ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384

CAUTION: The default cipher suite 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
`--cipher-list` and `NODE_CIPHER_LIST` options should only be used if
absolutely necessary.

### Using Legacy Default Cipher Suite ###

It is possible for the built-in default cipher suite to change from one release
of Node.js to another. For instance, v0.10.38 uses a different default than
v0.12.2. Such changes can cause issues with applications written to assume
certain specific defaults. To help buffer applications against such changes,
the `--enable-legacy-cipher-list` command line switch or `NODE_LEGACY_CIPHER_LIST`
environment variable can be set to specify a specific preset default:

# Use the v0.10.38 defaults
node --enable-legacy-cipher-list=v0.10.38
// or
NODE_LEGACY_CIPHER_LIST=v0.10.38

# Use the v0.12.2 defaults
node --enable-legacy-cipher-list=v0.12.2
// or
NODE_LEGACY_CIPHER_LIST=v0.12.2

Currently, the values supported for the `enable-legacy-cipher-list` switch and
`NODE_LEGACY_CIPHER_LIST` environment variable include:

v0.10.38 - To enable the default cipher suite used in v0.10.38

ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH

v0.10.39 - To enable the default cipher suite used in v0.10.39

ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH

v0.12.2 - To enable the default cipher suite used in v0.12.2

ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:
HIGH:!MD5:!aNULL

These legacy cipher suites are also made available for use via the
`getLegacyCiphers()` method:

var tls = require('tls');
console.log(tls.getLegacyCiphers('v0.10.38'));

CAUTION: Changes to the default cipher suite are typically made in order to
strengthen the default security for applications running within Node.js.
Reverting back to the defaults used by older releases can weaken the security
of your applications. The legacy cipher suites should only be used if absolutely
necessary.

NOTE: Due to an error in Node.js v0.10.38, the default cipher list only applied
to servers using TLS. The default cipher list would _not_ be used by clients.
This behavior has been changed in v0.10.39 and v0.12.x and the default cipher
list is now used by both the server and client when using TLS. However, when
using `--enable-legacy-cipher-list=v0.10.38`, Node.js is reverted back to the
v0.10.38 behavior of only using the default cipher list on the server.

### Cipher List Precedence

Note that the `--enable-legacy-cipher-list`, `NODE_LEGACY_CIPHER_LIST`,
`--cipher-list` and `NODE_CIPHER_LIST` options are mutually exclusive.

If the `NODE_CIPHER_LIST` and `NODE_LEGACY_CIPHER_LIST` environment variables
are both specified, the `NODE_LEGACY_CIPHER_LIST` setting will take precedence.

The `--cipher-list` and `--enable-legacy-cipher-list` command line options
will override the environment variables. If both happen to be specified, the
right-most (second one specified) will take precedence. For instance, in the
example:

node --cipher-list=ABC --enable-legacy-cipher-list=v0.10.38

The v0.10.38 default cipher list will be used.

node --enable-legacy-cipher-list=v0.10.38 --cipher-list=ABC

The custom cipher list will be used.

## tls.getCiphers()

Expand All @@ -144,6 +241,18 @@ Example:
var ciphers = tls.getCiphers();
console.log(ciphers); // ['AES128-SHA', 'AES256-SHA', ...]

## tls.getLegacyCiphers(version)

Returns a default cipher list used in a previous version of Node.js. The
version parameter must be a string whose value identifies previous Node.js
release version. The only values currently supported are `v0.10.38`,
`v0.10.39`, and `v0.12.2`.

A TypeError will be thrown if: (a) the `version` is any type other than a
string, (b) the `version` parameter is not specified, or (c) additional
parameters are passed in. An Error will be thrown if the `version` parameter is
passed in as a string but the value does not correlate to any known Node.js
release for which a default cipher list is available.

## tls.createServer(options[, secureConnectionListener])

Expand Down Expand Up @@ -177,7 +286,7 @@ automatically set as a listener for the [secureConnection][] event. The
prioritize the non-CBC cipher.

Defaults to
`ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL`.
`ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL`.
Consult the [OpenSSL cipher list format documentation] for details
on the format.

Expand All @@ -187,10 +296,7 @@ automatically set as a listener for the [secureConnection][] event. The
of OpenSSL. Note that it is still possible for a TLS v1.2 client
to negotiate a weaker cipher unless `honorCipherOrder` is enabled.

`RC4` is used as a fallback for clients that speak on older version of
the TLS protocol. `RC4` has in recent years come under suspicion and
should be considered compromised for anything that is truly sensitive.
It is speculated that state-level actors possess the ability to break it.
`RC4` is explicitly switched off by default due to known vulnerabilities.

**NOTE**: Previous revisions of this section suggested `AES256-SHA` as an
acceptable cipher. Unfortunately, `AES256-SHA` is a CBC cipher and therefore
Expand Down
22 changes: 21 additions & 1 deletion lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -827,17 +827,37 @@ function legacyConnect(hostname, options, NPN, context) {
return pair;
}

function usingV1038Ciphers() {
var argv = process.execArgv;
if ((argv.indexOf('--enable-legacy-cipher-list=v0.10.38') > -1 ||
process.env['NODE_LEGACY_CIPHER_LIST'] === 'v0.10.38') &&
tls.DEFAULT_CIPHERS === tls.getLegacyCiphers('v0.10.38')) {
return true;
}
return false;
}

exports.connect = function(/* [port, host], options, cb */) {
var args = normalizeConnectArgs(arguments);
var options = args[0];
var cb = args[1];

var defaults = {
rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED,
ciphers: tls.DEFAULT_CIPHERS,
checkServerIdentity: tls.checkServerIdentity
};

if (!usingV1038Ciphers()) {
// only set the default ciphers if we are _not_ using the
// v0.10.38 legacy cipher list. Node v0.10.38 had a bug
// that failed to set the default ciphers on the default
// options. This has been fixed in v0.10.39 and above.
// However, when the user explicitly tells node to revert
// back to using the v0.10.38 cipher list, node should
// revert back to the original v0.10.38 behavior.
defaults.ciphers = tls.DEFAULT_CIPHERS;
}

options = util._extend(defaults, options || {});

options.secureOptions = common._getSecureOptions(options.secureProtocol,
Expand Down
12 changes: 6 additions & 6 deletions lib/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

var _crypto = process.binding('crypto');

var net = require('net');
var url = require('url');
var util = require('util');
Expand All @@ -33,16 +35,14 @@ exports.CLIENT_RENEG_WINDOW = 600;

exports.SLAB_BUFFER_SIZE = 10 * 1024 * 1024;

exports.DEFAULT_CIPHERS =
// TLS 1.2
'ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:' +
// TLS 1.0
'RC4:HIGH:!MD5:!aNULL';
exports.DEFAULT_CIPHERS = _crypto.DEFAULT_CIPHER_LIST;

exports.getLegacyCiphers = _crypto.getLegacyCiphers;

exports.DEFAULT_ECDH_CURVE = 'prime256v1';

exports.getCiphers = function() {
var names = process.binding('crypto').getSSLCiphers();
var names = _crypto.getSSLCiphers();
// Drop all-caps names in favor of their lowercase aliases,
var ctx = {};
names.forEach(function(name) {
Expand Down
35 changes: 35 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2936,6 +2936,9 @@ static void PrintHelp() {
#endif
" --enable-ssl2 enable ssl2\n"
" --enable-ssl3 enable ssl3\n"
" --cipher-list=val specify the default TLS cipher list\n"
" --enable-legacy-cipher-list=val \n"
" val = v0.10.38, v0.10.39, or v0.12.2\n"
"\n"
"Environment variables:\n"
#ifdef _WIN32
Expand All @@ -2953,6 +2956,9 @@ static void PrintHelp() {
" (will extend linked-in data)\n"
#endif
#endif
"NODE_CIPHER_LIST Override the default TLS cipher list\n"
"NODE_LEGACY_CIPHER_LIST=val\n"
" val = v0.10.38, v0.10.39, or v0.12.2\n"
"\n"
"Documentation can be found at http://nodejs.org/\n");
}
Expand Down Expand Up @@ -3047,6 +3053,17 @@ static void ParseArgs(int* argc,
} else if (strcmp(arg, "--v8-options") == 0) {
new_v8_argv[new_v8_argc] = "--help";
new_v8_argc += 1;
} else if (strncmp(arg, "--cipher-list=", 14) == 0) {
DEFAULT_CIPHER_LIST = arg + 14;
} else if (strncmp(arg, "--enable-legacy-cipher-list=", 28) == 0) {
// use the original v0.10.x/v0.12.x cipher lists
const char * legacy_list = legacy_cipher_list(arg+28);
if (legacy_list != NULL) {
DEFAULT_CIPHER_LIST = legacy_list;
} else {
fprintf(stderr, "Error: An unknown legacy cipher list was specified\n");
exit(9);
}
#if defined(NODE_HAVE_I18N_SUPPORT)
} else if (strncmp(arg, "--icu-data-dir=", 15) == 0) {
icu_data_dir = arg + 15;
Expand Down Expand Up @@ -3398,6 +3415,24 @@ void Init(int* argc,
V8::SetFlagsFromString(NODE_V8_OPTIONS, sizeof(NODE_V8_OPTIONS) - 1);
#endif

// set the cipher list from the environment variable first,
// the command line switch will override if specified.
const char * cipher_list = getenv("NODE_CIPHER_LIST");
if (cipher_list != NULL) {
DEFAULT_CIPHER_LIST = cipher_list;
}
const char * leg_cipher_id = getenv("NODE_LEGACY_CIPHER_LIST");
if (leg_cipher_id != NULL) {
const char * leg_cipher_list =
legacy_cipher_list(leg_cipher_id);
if (leg_cipher_list != NULL) {
DEFAULT_CIPHER_LIST = leg_cipher_list;
} else {
fprintf(stderr, "Error: An unknown legacy cipher list was specified\n");
exit(9);
}
}

// Parse a few arguments which are specific to Node.
int v8_argc;
const char** v8_argv;
Expand Down
13 changes: 13 additions & 0 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,19 @@ NODE_EXTERN void RunAtExit(Environment* env);
} \
while (0)

#define NODE_DEFINE_STRING_CONSTANT(target, constant) \
do { \
v8::Isolate* isolate = v8::Isolate::GetCurrent(); \
v8::Local<v8::String> constant_name = \
v8::String::NewFromUtf8(isolate, #constant); \
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(constant_name, constant_value, constant_attributes); \
} while (0)


// Used to be a macro, hence the uppercase name.
template <typename TypeName>
inline void NODE_SET_METHOD(const TypeName& recv,
Expand Down
25 changes: 25 additions & 0 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ namespace node {

bool SSL2_ENABLE = false;
bool SSL3_ENABLE = false;
const char * DEFAULT_CIPHER_LIST = DEFAULT_CIPHER_LIST_HEAD;

namespace crypto {

Expand Down Expand Up @@ -4851,6 +4852,28 @@ static void array_push_back(const TypeName* md,
ctx->arr->Set(ctx->arr->Length(), OneByteString(ctx->env()->isolate(), from));
}

// borrowed from v8
// (see http://v8.googlecode.com/svn/trunk/samples/shell.cc)
const char* ToCString(const String::Utf8Value& value) {
return *value ? *value : "<string conversion failed>";
}

void DefaultCiphers(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args.GetIsolate());
HandleScope scope(env->isolate());

if (args.Length() != 1 || !args[0]->IsString()) {
return env->ThrowTypeError("A single string parameter is required");
}
v8::String::Utf8Value key(args[0]);
const char * list = legacy_cipher_list(ToCString(key));
if (list != NULL) {
args.GetReturnValue().Set(
v8::String::NewFromUtf8(args.GetIsolate(), list));
} else {
env->ThrowError("Unknown legacy cipher list");
}
}

void GetCiphers(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args.GetIsolate());
Expand Down Expand Up @@ -5171,6 +5194,8 @@ void InitCrypto(Handle<Object> target,

NODE_DEFINE_CONSTANT(target, SSL3_ENABLE);
NODE_DEFINE_CONSTANT(target, SSL2_ENABLE);
NODE_DEFINE_STRING_CONSTANT(target, DEFAULT_CIPHER_LIST);
NODE_SET_METHOD(target, "getLegacyCiphers", DefaultCiphers);
}

} // namespace crypto
Expand Down
31 changes: 31 additions & 0 deletions src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

#include "v8.h"

#include <string.h>
#include <openssl/ssl.h>
#include <openssl/ec.h>
#include <openssl/ecdh.h>
Expand All @@ -59,10 +60,40 @@
# define NODE__HAVE_TLSEXT_STATUS_CB
#endif // !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_set_tlsext_status_cb)

#define DEFAULT_CIPHER_LIST_V10_38 "ECDHE-RSA-AES128-SHA256:" \
"AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH"

#define DEFAULT_CIPHER_LIST_V10_39 "ECDHE-RSA-AES128-SHA256:" \
"AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH"

#define DEFAULT_CIPHER_LIST_V12_2 "ECDHE-RSA-AES128-SHA256:" \
"DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:" \
"HIGH:!MD5:!aNULL"

#define DEFAULT_CIPHER_LIST_HEAD "ECDHE-RSA-AES128-SHA256:" \
"DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:HIGH:"\
"!RC4:!MD5:!aNULL"

static inline const char * legacy_cipher_list(const char * ver) {
if (ver == NULL) {
return NULL;
}
if (strncmp(ver, "v0.10.38", 8) == 0) {
return DEFAULT_CIPHER_LIST_V10_38;
} else if (strncmp(ver, "v0.10.39", 8) == 0) {
return DEFAULT_CIPHER_LIST_V10_39;
} else if (strncmp(ver, "v0.12.2", 7) == 0) {
return DEFAULT_CIPHER_LIST_V12_2;
} else {
return NULL;
}
}

namespace node {

extern bool SSL2_ENABLE;
extern bool SSL3_ENABLE;
extern const char * DEFAULT_CIPHER_LIST;

namespace crypto {

Expand Down
Loading