summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2015-04-02 15:16:40 -0700
committerJames M Snell <jasnell@gmail.com>2015-04-08 12:00:18 -0700
commit67d9a56251c4491beacb666ba5833574d0cf0d12 (patch)
treeb3c2ea3cb29a6153c4c370fa28a4ed0c3b8132c2
parent7edfd5f6d11f7a219408bc1edf265dbfe2b90900 (diff)
downloadnode-67d9a56251c4491beacb666ba5833574d0cf0d12.tar.gz
tls: 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/14413
-rw-r--r--doc/api/tls.markdown66
-rw-r--r--lib/tls.js14
-rw-r--r--src/node.cc41
-rw-r--r--src/node_crypto.cc25
-rw-r--r--src/node_crypto.h20
-rw-r--r--test/simple/test-tls-cipher-list.js70
-rw-r--r--test/simple/test-tls-getcipher.js2
7 files changed, 226 insertions, 12 deletions
diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown
index fbd97e88a..49b37106e 100644
--- a/doc/api/tls.markdown
+++ b/doc/api/tls.markdown
@@ -109,6 +109,60 @@ handshake extensions allowing you:
* SNI - to use one TLS server for multiple hostnames with different SSL
certificates.
+## 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:AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH
+
+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.39 uses a different default than
+v0.10.38. 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
+
+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
+
+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()
@@ -151,13 +205,13 @@ automatically set as a listener for the [secureConnection][] event. The
conjunction with the `honorCipherOrder` option described below to
prioritize the non-CBC cipher.
- Defaults to `AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH`.
+ Defaults to `ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH`.
Consult the [OpenSSL cipher list format documentation] for details on the
format. ECDH (Elliptic Curve Diffie-Hellman) ciphers are not yet supported.
`AES128-GCM-SHA256` is used when node.js is linked against OpenSSL 1.0.1
- or newer and the client speaks TLS 1.2, RC4 is used as a secure fallback.
+ or newer and the client speaks TLS 1.2.
**NOTE**: Previous revisions of this section suggested `AES256-SHA` as an
acceptable cipher. Unfortunately, `AES256-SHA` is a CBC cipher and therefore
@@ -333,7 +387,7 @@ Here is an example of a client of echo server as described previously:
// These are necessary only if using the client certificate authentication
key: fs.readFileSync('client-key.pem'),
cert: fs.readFileSync('client-cert.pem'),
-
+
// This is necessary only if the server uses the self-signed certificate
ca: [ fs.readFileSync('server-cert.pem') ]
};
@@ -525,7 +579,7 @@ A ClearTextStream is the `clear` member of a SecurePair object.
### Event: 'secureConnect'
-This event is emitted after a new connection has been successfully handshaked.
+This event is emitted after a new connection has been successfully handshaked.
The listener will be called no matter if the server's certificate was
authorized or not. It is up to the user to test `cleartextStream.authorized`
to see if the server certificate was signed by one of the specified CAs.
@@ -550,14 +604,14 @@ some properties corresponding to the field of the certificate.
Example:
- { subject:
+ { subject:
{ C: 'UK',
ST: 'Acknack Ltd',
L: 'Rhys Jones',
O: 'node.js',
OU: 'Test TLS Certificate',
CN: 'localhost' },
- issuer:
+ issuer:
{ C: 'UK',
ST: 'Acknack Ltd',
L: 'Rhys Jones',
diff --git a/lib/tls.js b/lib/tls.js
index e3b908322..9f53ad82a 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 crypto = require('crypto');
var util = require('util');
var net = require('net');
@@ -31,8 +33,9 @@ var constants = require('constants');
var Timer = process.binding('timer_wrap').Timer;
-var DEFAULT_CIPHERS = 'ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:' + // TLS 1.2
- 'RC4:HIGH:!MD5:!aNULL:!EDH'; // TLS 1.0
+var DEFAULT_CIPHERS = _crypto.DEFAULT_CIPHER_LIST;
+
+exports.getLegacyCiphers = _crypto.getLegacyCiphers;
// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
@@ -44,7 +47,7 @@ exports.CLIENT_RENEG_WINDOW = 600;
exports.SLAB_BUFFER_SIZE = 10 * 1024 * 1024;
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) {
@@ -65,7 +68,7 @@ if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) {
var Connection = null;
try {
- Connection = process.binding('crypto').Connection;
+ Connection = _crypto.Connection;
} catch (e) {
throw new Error('node.js not compiled with openssl crypto support.');
}
@@ -1335,6 +1338,9 @@ exports.connect = function(/* [port, host], options, cb */) {
var defaults = {
rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED
};
+ if (DEFAULT_CIPHERS != _crypto.getLegacyCiphers('v0.10.38')) {
+ defaults.ciphers = DEFAULT_CIPHERS;
+ }
options = util._extend(defaults, options || {});
options.secureOptions = crypto._getSecureOptions(options.secureProtocol,
diff --git a/src/node.cc b/src/node.cc
index e80c1a573..4ba39b111 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -2566,6 +2566,9 @@ static void PrintHelp() {
" --max-stack-size=val set max v8 stack size (bytes)\n"
" --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"
"\n"
"Environment variables:\n"
#ifdef _WIN32
@@ -2577,6 +2580,9 @@ static void PrintHelp() {
"NODE_MODULE_CONTEXTS Set to 1 to load modules in their own\n"
" global contexts.\n"
"NODE_DISABLE_COLORS Set to 1 to disable colors in the REPL\n"
+ "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"
"\n"
"Documentation can be found at http://nodejs.org/\n");
}
@@ -2584,6 +2590,7 @@ static void PrintHelp() {
// Parse node command line arguments.
static void ParseArgs(int argc, char **argv) {
int i;
+ bool using_legacy_cipher_list = false;
// TODO use parse opts
for (i = 1; i < argc; i++) {
@@ -2652,6 +2659,21 @@ static void ParseArgs(int argc, char **argv) {
} else if (strcmp(arg, "--throw-deprecation") == 0) {
argv[i] = const_cast<char*>("");
throw_deprecation = true;
+ } else if (strncmp(arg, "--cipher-list=", 14) == 0) {
+ if (!using_legacy_cipher_list) {
+ DEFAULT_CIPHER_LIST = arg + 14;
+ }
+ argv[i] = const_cast<char*>("");
+ } else if (strncmp(arg, "--enable-legacy-cipher-list=", 28) == 0) {
+ 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);
+ }
+ argv[i] = const_cast<char*>("");
} else if (argv[i][0] != '-') {
break;
}
@@ -2946,6 +2968,25 @@ char** Init(int argc, char *argv[]) {
v8argv[option_end_index + 1] = const_cast<char*>("v8debug");
}
+ 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
+ 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);
+ }
+ }
+
// For the normal stack which moves from high to low addresses when frames
// are pushed, we can compute the limit as stack_size bytes below the
// the address of a stack variable (e.g. &stack_var) as an approximation
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 7a3922a79..c1e943fef 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -71,6 +71,7 @@ const char* root_certs[] = {
bool SSL2_ENABLE = false;
bool SSL3_ENABLE = false;
+const char * DEFAULT_CIPHER_LIST = DEFAULT_CIPHER_LIST_HEAD;
namespace crypto {
@@ -802,7 +803,7 @@ size_t ClientHelloParser::Write(const uint8_t* data, size_t len) {
HandleScope scope;
assert(state_ != kEnded);
-
+
// Just accumulate data, everything will be pushed to BIO later
if (state_ == kPaused) return 0;
@@ -4190,6 +4191,21 @@ static void array_push_back(const TypeName* md,
arr->Set(arr->Length(), String::New(from));
}
+// borrowed from v8
+// (see http://v8.googlecode.com/svn/trunk/samples/shell.cc)
+const char* ToCString(const node::Utf8Value& value) {
+ return *value ? *value : "<string conversion failed>";
+}
+
+Handle<Value> DefaultCiphers(const Arguments& args) {
+ HandleScope scope;
+ node::Utf8Value key(args[0]);
+ const char * list = legacy_cipher_list(ToCString(key));
+ if (list == NULL) {
+ list = DEFAULT_CIPHER_LIST_HEAD;
+ }
+ return scope.Close(v8::String::New(list));
+}
Handle<Value> GetCiphers(const Arguments& args) {
HandleScope scope;
@@ -4264,6 +4280,13 @@ void InitCrypto(Handle<Object> target) {
NODE_DEFINE_CONSTANT(target, SSL3_ENABLE);
NODE_DEFINE_CONSTANT(target, SSL2_ENABLE);
+
+ (target)->ForceSet(
+ v8::String::New("DEFAULT_CIPHER_LIST"),
+ v8::String::New(DEFAULT_CIPHER_LIST),
+ static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete));
+
+ NODE_SET_METHOD(target, "getLegacyCiphers", DefaultCiphers);
}
} // namespace crypto
diff --git a/src/node_crypto.h b/src/node_crypto.h
index 54b9b88e4..0b360cfa3 100644
--- a/src/node_crypto.h
+++ b/src/node_crypto.h
@@ -27,6 +27,7 @@
#include "node_object_wrap.h"
#include "v8.h"
+#include <string.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/evp.h>
@@ -43,10 +44,29 @@
#define EVP_F_EVP_DECRYPTFINAL 101
+#define DEFAULT_CIPHER_LIST_V10_38 "ECDHE-RSA-AES128-SHA256:" \
+ "AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH"
+
+#define DEFAULT_CIPHER_LIST_HEAD "ECDHE-RSA-AES128-SHA256:" \
+ "AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH"
+
+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 {
+ 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 000000000..ac2169537
--- /dev/null
+++ b/test/simple/test-tls-cipher-list.js
@@ -0,0 +1,70 @@
+// 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');
+var crypto = process.binding('crypto');
+
+function doTest(checklist, env, useswitch) {
+ var options;
+ if (env && useswitch === 1) {
+ options = {env:env};
+ }
+ var args = ['-e', 'console.log(process.binding(\'crypto\').DEFAULT_CIPHER_LIST)'];
+
+ 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(crypto.DEFAULT_CIPHER_LIST); // test the default
+doTest('ABC', {'NODE_CIPHER_LIST':'ABC'}); // test the envar
+doTest('ABC', 'ABC', 1); // test the --cipher-list switch
+
+['v0.10.38'].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 22a280e58..8fb9d5287 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();