summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2015-04-02 16:10:15 -0700
committerJames M Snell <jasnell@gmail.com>2015-04-08 12:10:49 -0700
commitf9291a94498c6f9d70a74a1e402f333383286120 (patch)
treeeb385c54eb4c5b03e72b943b30bd57b7f6f11ae6
parent66b243dd87509a7e259bf9bfec1ab31e01620b3e (diff)
downloadnode-new-f9291a94498c6f9d70a74a1e402f333383286120.tar.gz
disable RC4, add --cipher-list command line switch
Disable RC4 in the default cipher list Add the `--cipher-list` command line switch and `NODE_CIPHER_LIST` environment variable to completely override the default cipher list. Add the `--enable-legacy-cipher-list` and `NODE_LEGACY_CIPHER_LIST` environment variable to selectively enable the default cipher list from previous node.js releases. Reviewed-By: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/joyent/node/pull/14414
-rw-r--r--doc/api/tls.markdown82
-rw-r--r--lib/tls.js12
-rw-r--r--src/node.cc46
-rw-r--r--src/node.h13
-rw-r--r--src/node_crypto.cc23
-rw-r--r--src/node_crypto.h31
-rw-r--r--test/simple/test-tls-cipher-list.js69
-rw-r--r--test/simple/test-tls-getcipher.js2
8 files changed, 266 insertions, 12 deletions
diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown
index 410d88300c..fba9a67623 100644
--- a/doc/api/tls.markdown
+++ b/doc/api/tls.markdown
@@ -134,6 +134,75 @@ 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.
## tls.getCiphers()
@@ -144,6 +213,12 @@ Example:
var ciphers = tls.getCiphers();
console.log(ciphers); // ['AES128-SHA', 'AES256-SHA', ...]
+## tls.getLegacyCiphers(version)
+
+Returns the legacy default cipher suite for the specified Node.js release.
+
+Example:
+ var cipher_suite = tls.getLegacyCiphers('v0.10.38');
## tls.createServer(options[, secureConnectionListener])
@@ -177,7 +252,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.
@@ -187,10 +262,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
diff --git a/lib/tls.js b/lib/tls.js
index a00fbb9d72..04a41e8e2b 100644
--- a/lib/tls.js
+++ b/lib/tls.js
@@ -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');
@@ -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) {
diff --git a/src/node.cc b/src/node.cc
index f0590d5a72..303a09196c 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -2936,6 +2936,11 @@ 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"
+ " set to v0.10.38 to use the v0.10.38 list,\n"
+ " set to v0.10.39 to use the v0.10.39 list.\n"
+ " set to v0.12.2 to use the v0.12.2 list.\n"
"\n"
"Environment variables:\n"
#ifdef _WIN32
@@ -2953,6 +2958,11 @@ 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\n"
+ " Set to v0.10.38 to use the v0.10.38 list.\n"
+ " Set to v0.10.39 to use the v0.10.39 list.\n"
+ " Set to v0.12.2 to use the v0.12.2 list.\n"
"\n"
"Documentation can be found at http://nodejs.org/\n");
}
@@ -2992,6 +3002,7 @@ static void ParseArgs(int* argc,
unsigned int new_argc = 1;
new_v8_argv[0] = argv[0];
new_argv[0] = argv[0];
+ bool using_legacy_cipher_list = false;
unsigned int index = 1;
while (index < nargs && argv[index][0] == '-') {
@@ -3047,6 +3058,20 @@ 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) {
+ if (!using_legacy_cipher_list) {
+ 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) {
+ using_legacy_cipher_list = true;
+ 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;
@@ -3414,6 +3439,27 @@ void Init(int* argc,
}
}
+ const char * cipher_list = getenv("NODE_CIPHER_LIST");
+ if (cipher_list != NULL) {
+ DEFAULT_CIPHER_LIST = cipher_list;
+ }
+ // Allow the NODE_LEGACY_CIPHER_LIST envar to override the other
+ // cipher list options. NODE_LEGACY_CIPHER_LIST=v0.10.38 will use
+ // the cipher list from v0.10.38, NODE_LEGACY_CIPHER_LIST=v0.12.2 will
+ // use the cipher list from v0.12.2
+ 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);
+ }
+ }
+
+
#if defined(NODE_HAVE_I18N_SUPPORT)
if (icu_data_dir == NULL) {
// if the parameter isn't given, use the env variable.
diff --git a/src/node.h b/src/node.h
index 38356ac44f..c930928deb 100644
--- a/src/node.h
+++ b/src/node.h
@@ -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,
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 03650a98c6..327bfce131 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -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 {
@@ -4851,6 +4852,26 @@ 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());
+ 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 {
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(args.GetIsolate(),
+ DEFAULT_CIPHER_LIST_HEAD));
+ }
+}
void GetCiphers(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args.GetIsolate());
@@ -5171,6 +5192,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
diff --git a/src/node_crypto.h b/src/node_crypto.h
index 0a4c34a1f3..d6cf9aa218 100644
--- a/src/node_crypto.h
+++ b/src/node_crypto.h
@@ -38,6 +38,7 @@
#include "v8.h"
+#include <string.h>
#include <openssl/ssl.h>
#include <openssl/ec.h>
#include <openssl/ecdh.h>
@@ -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 {
diff --git a/test/simple/test-tls-cipher-list.js b/test/simple/test-tls-cipher-list.js
new file mode 100644
index 0000000000..84e76eb5b5
--- /dev/null
+++ b/test/simple/test-tls-cipher-list.js
@@ -0,0 +1,69 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var spawn = require('child_process').spawn;
+var assert = require('assert');
+var tls = require('tls');
+
+function doTest(checklist, env, useswitch) {
+ var options;
+ if (env && useswitch === 1) {
+ options = {env:env};
+ }
+ var args = ['-e', 'console.log(require(\'tls\').DEFAULT_CIPHERS)'];
+
+ switch(useswitch) {
+ case 1:
+ // Test --cipher-test
+ args.unshift('--cipher-list=' + env);
+ break;
+ case 2:
+ // Test --enable-legacy-cipher-list
+ args.unshift('--enable-legacy-cipher-list=' + env);
+ break;
+ case 3:
+ // Test NODE_LEGACY_CIPHER_LIST
+ if (env) options = {env:{"NODE_LEGACY_CIPHER_LIST": env}};
+ break;
+ default:
+ // Test NODE_CIPHER_LIST
+ if (env) options = {env:env};
+ }
+
+ var out = '';
+ spawn(process.execPath, args, options).
+ stdout.
+ on('data', function(data) {
+ out += data;
+ }).
+ on('end', function() {
+ assert.equal(out.trim(), checklist);
+ });
+}
+
+doTest(tls.DEFAULT_CIPHERS); // test the default
+doTest('ABC', {'NODE_CIPHER_LIST':'ABC'}); // test the envar
+doTest('ABC', 'ABC', 1); // test the --cipher-list switch
+
+['v0.10.38', 'v0.10.39', 'v0.12.2'].forEach(function(ver) {
+ doTest(tls.getLegacyCiphers(ver), ver, 2);
+ doTest(tls.getLegacyCiphers(ver), ver, 3);
+});
diff --git a/test/simple/test-tls-getcipher.js b/test/simple/test-tls-getcipher.js
index 22a280e587..8fb9d52873 100644
--- a/test/simple/test-tls-getcipher.js
+++ b/test/simple/test-tls-getcipher.js
@@ -49,7 +49,7 @@ server.listen(common.PORT, '127.0.0.1', function() {
rejectUnauthorized: false
}, function() {
var cipher = client.getCipher();
- assert.equal(cipher.name, cipher_list[0]);
+ assert.equal(cipher.name, cipher_list[1]);
assert(cipher_version_pattern.test(cipher.version));
client.end();
server.close();