diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index 16af6fe74f7c63..422251f1041f70 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -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 @@ -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: diff --git a/doc/iojs.1 b/doc/iojs.1 index 0512d5e75cdd37..c8dc08cb6f42d4 100644 --- a/doc/iojs.1 +++ b/doc/iojs.1 @@ -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 diff --git a/lib/tls.js b/lib/tls.js index 0e22242bc47feb..0d85a948dcc511 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -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 @@ -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'; diff --git a/src/node.cc b/src/node.cc index e1ca866f869d10..c3444000048bcc 100644 --- a/src/node.cc +++ b/src/node.cc @@ -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" @@ -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; diff --git a/src/node_constants.cc b/src/node_constants.cc index ce715a32462655..59dd11113eb70c 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -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 target) { #ifdef E2BIG NODE_DEFINE_CONSTANT(target, E2BIG); @@ -1108,6 +1112,17 @@ void DefineUVConstants(Handle target) { NODE_DEFINE_CONSTANT(target, UV_UDP_REUSEADDR); } +void DefineCryptoConstants(Handle 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 target) { DefineErrnoConstants(target); DefineWindowsErrorConstants(target); @@ -1115,6 +1130,7 @@ void DefineConstants(Handle target) { DefineOpenSSLConstants(target); DefineSystemConstants(target); DefineUVConstants(target); + DefineCryptoConstants(target); } } // namespace node diff --git a/src/node_constants.h b/src/node_constants.h index 8493d4d13b3d2a..45c991022e6bc6 100644 --- a/src/node_constants.h +++ b/src/node_constants.h @@ -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 target); } // namespace node diff --git a/src/node_internals.h b/src/node_internals.h index c68b7155b0cdaf..8f35433b2f85c3 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -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 constant_name = \ + v8::String::NewFromUtf8(isolate, name); \ + v8::Local constant_value = \ + v8::String::NewFromUtf8(isolate, constant); \ + v8::PropertyAttribute constant_attributes = \ + static_cast(v8::ReadOnly | v8::DontDelete); \ + target->ForceSet(isolate->GetCurrentContext(), \ + constant_name, \ + constant_value, \ + constant_attributes); \ + } while (0) + namespace node { // Forward declaration diff --git a/test/parallel/test-tls-cipher-list.js b/test/parallel/test-tls-cipher-list.js new file mode 100644 index 00000000000000..9ae8fefa0f4351 --- /dev/null +++ b/test/parallel/test-tls-cipher-list.js @@ -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');