summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy J Fontaine <tjfontaine@gmail.com>2014-10-23 12:12:52 -0700
committerTimothy J Fontaine <tjfontaine@gmail.com>2014-10-23 12:12:52 -0700
commite59eca58ad62bb3166787de524233854fc6a6545 (patch)
treeab6b388502d152bfd4cd2aca0b5d607783cbfad6
parent35443862a2319c31db50ea540d682f7614d9d959 (diff)
parent8d045a30e95602b443eb259a5021d33feb4df079 (diff)
downloadnode-e59eca58ad62bb3166787de524233854fc6a6545.tar.gz
Merge branch 'v0.10.33-release' into v0.10
-rw-r--r--AUTHORS3
-rw-r--r--ChangeLog23
-rw-r--r--lib/crypto.js44
-rw-r--r--lib/tls.js19
-rw-r--r--src/node_version.h2
-rw-r--r--test/external/ssl-options/.gitignore1
-rw-r--r--test/external/ssl-options/package.json15
-rw-r--r--test/external/ssl-options/test.js729
-rw-r--r--test/simple/test-tls-honorcipherorder-secureOptions.js131
9 files changed, 944 insertions, 23 deletions
diff --git a/AUTHORS b/AUTHORS
index 3af7fc63a..b51cd7160 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -515,3 +515,6 @@ Kevin Simper <kevin.simper@gmail.com>
Jackson Tian <shyvo1987@gmail.com>
Tristan Berger <tristan.berger@gmail.com>
Mathias Schreck <schreck.mathias@googlemail.com>
+Calvin Metcalf <cmetcalf@appgeo.com>
+Matthew Fitzsimmons <matt@fitzage.com>
+Swaagie <info@martijnswaagman.nl>
diff --git a/ChangeLog b/ChangeLog
index 73f42e0dd..185b0d30e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,25 @@
-2014.09.16, Version 0.10.32 (Stable)
+2014.10.20, Version 0.10.33 (Stable)
+
+* openssl: Update to 1.0.1j (Addressing multiple CVEs)
+
+* uv: Update to v0.10.29
+
+* child_process: properly support optional args (cjihrig)
+
+* crypto: Disable autonegotiation for SSLv2/3 by default (Fedor Indutny,
+ Timothy J Fontaine, Alexis Campailla)
+
+ This is a behavior change, by default we will not allow the negotiation to
+ SSLv2 or SSLv3. If you want this behavior, run Node.js with either
+ `--enable-ssl2` or `--enable-ssl3` respectively.
+
+ This does not change the behavior for users specifically requesting
+ `SSLv2_method` or `SSLv3_method`. While this behavior is not advised, it is
+ assumed you know what you're doing since you're specifically asking to use
+ these methods.
+
+
+2014.09.16, Version 0.10.32 (Stable), 0fe0d121551593c23a565db8397f85f17bb0f00e
* npm: Update to 1.4.28
diff --git a/lib/crypto.js b/lib/crypto.js
index f88c55d0a..597d196f2 100644
--- a/lib/crypto.js
+++ b/lib/crypto.js
@@ -61,6 +61,31 @@ var StringDecoder = require('string_decoder').StringDecoder;
var CONTEXT_DEFAULT_OPTIONS = undefined;
+function getSecureOptions(secureProtocol, secureOptions) {
+ if (CONTEXT_DEFAULT_OPTIONS === undefined) {
+ CONTEXT_DEFAULT_OPTIONS = 0;
+
+ if (!binding.SSL3_ENABLE)
+ CONTEXT_DEFAULT_OPTIONS |= constants.SSL_OP_NO_SSLv3;
+
+ if (!binding.SSL2_ENABLE)
+ CONTEXT_DEFAULT_OPTIONS |= constants.SSL_OP_NO_SSLv2;
+ }
+
+ if (secureOptions === undefined) {
+ if (secureProtocol === undefined ||
+ secureProtocol === 'SSLv23_method' ||
+ secureProtocol === 'SSLv23_server_method' ||
+ secureProtocol === 'SSLv23_client_method') {
+ secureOptions |= CONTEXT_DEFAULT_OPTIONS;
+ }
+ }
+
+ return secureOptions;
+}
+exports._getSecureOptions = getSecureOptions;
+
+
function Credentials(secureProtocol, flags, context) {
if (!(this instanceof Credentials)) {
return new Credentials(secureProtocol, flags, context);
@@ -82,24 +107,7 @@ function Credentials(secureProtocol, flags, context) {
}
}
- if (CONTEXT_DEFAULT_OPTIONS === undefined) {
- CONTEXT_DEFAULT_OPTIONS = 0;
-
- if (!binding.SSL3_ENABLE)
- CONTEXT_DEFAULT_OPTIONS |= constants.SSL_OP_NO_SSLv3;
-
- if (!binding.SSL2_ENABLE)
- CONTEXT_DEFAULT_OPTIONS |= constants.SSL_OP_NO_SSLv2;
- }
-
- if (flags === undefined) {
- if (secureProtocol === undefined ||
- secureProtocol === 'SSLv23_method' ||
- secureProtocol === 'SSLv23_server_method' ||
- secureProtocol === 'SSLv23_client_method') {
- flags |= CONTEXT_DEFAULT_OPTIONS;
- }
- }
+ flags = getSecureOptions(secureProtocol, flags);
this.context.setOptions(flags);
}
diff --git a/lib/tls.js b/lib/tls.js
index 392f7ad2b..77a708921 100644
--- a/lib/tls.js
+++ b/lib/tls.js
@@ -1145,7 +1145,12 @@ function Server(/* [options], listener */) {
// constructor call
net.Server.call(this, function(socket) {
- var creds = crypto.createCredentials(null, sharedCreds.context);
+ var connOps = {
+ secureProtocol: self.secureProtocol,
+ secureOptions: self.secureOptions
+ };
+
+ var creds = crypto.createCredentials(connOps, sharedCreds.context);
var pair = new SecurePair(creds,
true,
@@ -1239,11 +1244,16 @@ Server.prototype.setOptions = function(options) {
if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
if (options.crl) this.crl = options.crl;
if (options.ciphers) this.ciphers = options.ciphers;
- var secureOptions = options.secureOptions || 0;
+
+ var secureOptions = crypto._getSecureOptions(options.secureProtocol,
+ options.secureOptions);
+
if (options.honorCipherOrder) {
secureOptions |= constants.SSL_OP_CIPHER_SERVER_PREFERENCE;
}
- if (secureOptions) this.secureOptions = secureOptions;
+
+ this.secureOptions = secureOptions;
+
if (options.NPNProtocols) convertNPNProtocols(options.NPNProtocols, this);
if (options.SNICallback) {
this.SNICallback = options.SNICallback;
@@ -1326,6 +1336,9 @@ exports.connect = function(/* [port, host], options, cb */) {
};
options = util._extend(defaults, options || {});
+ options.secureOptions = crypto._getSecureOptions(options.secureProtocol,
+ options.secureOptions);
+
var socket = options.socket ? options.socket : new net.Stream();
var sslcontext = crypto.createCredentials(options);
diff --git a/src/node_version.h b/src/node_version.h
index 397115898..5d1c54ae7 100644
--- a/src/node_version.h
+++ b/src/node_version.h
@@ -26,7 +26,7 @@
#define NODE_MINOR_VERSION 10
#define NODE_PATCH_VERSION 33
-#define NODE_VERSION_IS_RELEASE 0
+#define NODE_VERSION_IS_RELEASE 1
#ifndef NODE_TAG
# define NODE_TAG ""
diff --git a/test/external/ssl-options/.gitignore b/test/external/ssl-options/.gitignore
new file mode 100644
index 000000000..c2658d7d1
--- /dev/null
+++ b/test/external/ssl-options/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/test/external/ssl-options/package.json b/test/external/ssl-options/package.json
new file mode 100644
index 000000000..114dce6af
--- /dev/null
+++ b/test/external/ssl-options/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "ssl-options-tests",
+ "version": "1.0.0",
+ "description": "",
+ "main": "test.js",
+ "scripts": {
+ "test": "node test.js"
+ },
+ "author": "",
+ "license": "MIT",
+ "dependencies": {
+ "async": "^0.9.0",
+ "debug": "^2.1.0"
+ }
+}
diff --git a/test/external/ssl-options/test.js b/test/external/ssl-options/test.js
new file mode 100644
index 000000000..f7e06c93d
--- /dev/null
+++ b/test/external/ssl-options/test.js
@@ -0,0 +1,729 @@
+var tls = require('tls');
+var fs = require('fs');
+var path = require('path');
+var fork = require('child_process').fork;
+var assert = require('assert');
+var constants = require('constants');
+var os = require('os');
+
+var async = require('async');
+var debug = require('debug')('test-node-ssl');
+
+var common = require('../../common');
+
+var SSL2_COMPATIBLE_CIPHERS = 'RC4-MD5';
+
+var CMD_LINE_OPTIONS = [ null, "--enable-ssl2", "--enable-ssl3" ];
+
+var SERVER_SSL_PROTOCOLS = [
+ null,
+ 'SSLv2_method', 'SSLv2_server_method',
+ 'SSLv3_method', 'SSLv3_server_method',
+ 'TLSv1_method', 'TLSv1_server_method',
+ 'SSLv23_method','SSLv23_server_method'
+];
+
+var CLIENT_SSL_PROTOCOLS = [
+ null,
+ 'SSLv2_method', 'SSLv2_client_method',
+ 'SSLv3_method', 'SSLv3_client_method',
+ 'TLSv1_method', 'TLSv1_client_method',
+ 'SSLv23_method','SSLv23_client_method'
+];
+
+var SECURE_OPTIONS = [
+ null,
+ 0,
+ constants.SSL_OP_NO_SSLv2,
+ constants.SSL_OP_NO_SSLv3,
+ constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3
+];
+
+function xtend(source) {
+ var clone = {};
+
+ for (var property in source) {
+ if (source.hasOwnProperty(property)) {
+ clone[property] = source[property];
+ }
+ }
+
+ return clone;
+}
+
+function isAutoNegotiationProtocol(sslProtocol) {
+ assert(sslProtocol === null || typeof sslProtocol === 'string');
+
+ return sslProtocol == null ||
+ sslProtocol === 'SSLv23_method' ||
+ sslProtocol === 'SSLv23_client_method' ||
+ sslProtocol === 'SSLv23_server_method';
+}
+
+function isSameSslProtocolVersion(serverSecureProtocol, clientSecureProtocol) {
+ assert(serverSecureProtocol === null || typeof serverSecureProtocol === 'string');
+ assert(clientSecureProtocol === null || typeof clientSecureProtocol === 'string');
+
+ if (serverSecureProtocol === clientSecureProtocol) {
+ return true;
+ }
+
+ var serverProtocolPrefix = '';
+ if (serverSecureProtocol)
+ serverProtocolPrefix = serverSecureProtocol.split('_')[0];
+
+ var clientProtocolPrefix = '';
+ if (clientSecureProtocol)
+ clientProtocolPrefix = clientSecureProtocol.split('_')[0];
+
+ if (serverProtocolPrefix === clientProtocolPrefix) {
+ return true;
+ }
+
+ return false;
+}
+
+function secureProtocolsCompatible(serverSecureProtocol, clientSecureProtocol) {
+ if (isAutoNegotiationProtocol(serverSecureProtocol) ||
+ isAutoNegotiationProtocol(clientSecureProtocol)) {
+ return true;
+ }
+
+ if (isSameSslProtocolVersion(serverSecureProtocol,
+ clientSecureProtocol)) {
+ return true;
+ }
+
+ return false;
+}
+
+function isSsl3Protocol(secureProtocol) {
+ assert(secureProtocol === null || typeof secureProtocol === 'string');
+
+ return secureProtocol === 'SSLv3_method' ||
+ secureProtocol === 'SSLv3_client_method' ||
+ secureProtocol === 'SSLv3_server_method';
+}
+
+function isSsl2Protocol(secureProtocol) {
+ assert(secureProtocol === null || typeof secureProtocol === 'string');
+
+ return secureProtocol === 'SSLv2_method' ||
+ secureProtocol === 'SSLv2_client_method' ||
+ secureProtocol === 'SSLv2_server_method';
+}
+
+function secureProtocolCompatibleWithSecureOptions(secureProtocol, secureOptions, cmdLineOption) {
+ if (secureOptions == null) {
+ if (isSsl2Protocol(secureProtocol) &&
+ (!cmdLineOption || cmdLineOption.indexOf('--enable-ssl2') === -1)) {
+ return false;
+ }
+
+ if (isSsl3Protocol(secureProtocol) &&
+ (!cmdLineOption || cmdLineOption.indexOf('--enable-ssl3') === -1)) {
+ return false;
+ }
+ } else {
+ if (secureOptions & constants.SSL_OP_NO_SSLv2 && isSsl2Protocol(secureProtocol)) {
+ return false;
+ }
+
+ if (secureOptions & constants.SSL_OP_NO_SSLv3 && isSsl3Protocol(secureProtocol)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function testSetupsCompatible(serverSetup, clientSetup) {
+ debug('Determing test result for:');
+ debug(serverSetup);
+ debug(clientSetup);
+
+ /*
+ * If the protocols specified by the client and server are
+ * not compatible (e.g SSLv2 vs SSLv3), then the test should fail.
+ */
+ if (!secureProtocolsCompatible(serverSetup.secureProtocol,
+ clientSetup.secureProtocol)) {
+ debug('secureProtocols not compatible! server secureProtocol: ' +
+ serverSetup.secureProtocol + ', client secureProtocol: ' +
+ clientSetup.secureProtocol);
+ return false;
+ }
+
+ /*
+ * If the client's options are not compatible with the server's protocol,
+ * then the test should fail. Same if server's options are not compatible
+ * with the client's protocol.
+ */
+ if (!secureProtocolCompatibleWithSecureOptions(serverSetup.secureProtocol,
+ clientSetup.secureOptions,
+ clientSetup.cmdLine) ||
+ !secureProtocolCompatibleWithSecureOptions(clientSetup.secureProtocol,
+ serverSetup.secureOptions,
+ serverSetup.cmdLine)) {
+ debug('Secure protocol not compatible with secure options!');
+ return false;
+ }
+
+ if (isSsl2Protocol(serverSetup.secureProtocol) ||
+ isSsl2Protocol(clientSetup.secureProtocol)) {
+
+ /*
+ * It seems that in order to be able to use SSLv2, at least the server
+ * *needs* to advertise at least one cipher compatible with it.
+ */
+ if (serverSetup.ciphers !== SSL2_COMPATIBLE_CIPHERS) {
+ return false;
+ }
+
+ /*
+ * If only either one of the client or server specify SSLv2 as their
+ * protocol, then *both* of them *need* to advertise at least one cipher
+ * that is compatible with SSLv2.
+ */
+ if ((!isSsl2Protocol(serverSetup.secureProtocol) || !isSsl2Protocol(clientSetup.secureProtocol)) &&
+ (clientSetup.ciphers !== SSL2_COMPATIBLE_CIPHERS || serverSetup.ciphers !== SSL2_COMPATIBLE_CIPHERS)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function sslSetupMakesSense(cmdLineOption, secureProtocol, secureOption) {
+ if (isSsl2Protocol(secureProtocol)) {
+ if (secureOption & constants.SSL_OP_NO_SSLv2 ||
+ (secureOption == null && (!cmdLineOption || cmdLineOption.indexOf('--enable-ssl2') === -1))) {
+ return false;
+ }
+ }
+
+ if (isSsl3Protocol(secureProtocol)) {
+ if (secureOption & constants.SSL_OP_NO_SSLv3 ||
+ (secureOption == null && (!cmdLineOption || cmdLineOption.indexOf('--enable-ssl3') === -1))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function createTestsSetups() {
+
+ var serversSetup = [];
+ var clientsSetup = [];
+
+ CMD_LINE_OPTIONS.forEach(function (cmdLineOption) {
+ SERVER_SSL_PROTOCOLS.forEach(function (serverSecureProtocol) {
+ SECURE_OPTIONS.forEach(function (secureOption) {
+ if (sslSetupMakesSense(cmdLineOption,
+ serverSecureProtocol,
+ secureOption)) {
+ var serverSetup = {
+ cmdLine: cmdLineOption,
+ secureProtocol: serverSecureProtocol,
+ secureOptions: secureOption
+ };
+
+ serversSetup.push(serverSetup);
+
+ if (isSsl2Protocol(serverSecureProtocol)) {
+ var setupWithSsl2Ciphers = xtend(serverSetup);
+ setupWithSsl2Ciphers.ciphers = SSL2_COMPATIBLE_CIPHERS;
+ serversSetup.push(setupWithSsl2Ciphers);
+ }
+ }
+ });
+ });
+
+ CLIENT_SSL_PROTOCOLS.forEach(function (clientSecureProtocol) {
+ SECURE_OPTIONS.forEach(function (secureOption) {
+ if (sslSetupMakesSense(cmdLineOption,
+ clientSecureProtocol,
+ secureOption)) {
+ var clientSetup = {
+ cmdLine: cmdLineOption,
+ secureProtocol: clientSecureProtocol,
+ secureOptions: secureOption
+ };
+
+ clientsSetup.push(clientSetup);
+
+ if (isSsl2Protocol(clientSecureProtocol)) {
+ var setupWithSsl2Ciphers = xtend(clientSetup);
+ setupWithSsl2Ciphers.ciphers = SSL2_COMPATIBLE_CIPHERS;
+ clientsSetup.push(setupWithSsl2Ciphers);
+ }
+ }
+ });
+ });
+ });
+
+ var testSetups = [];
+ var testId = 0;
+ serversSetup.forEach(function (serverSetup) {
+ clientsSetup.forEach(function (clientSetup) {
+ var testSetup = {
+ server: serverSetup,
+ client: clientSetup,
+ ID: testId++
+ };
+
+ var successExpected = false;
+ if (testSetupsCompatible(serverSetup, clientSetup)) {
+ successExpected = true;
+ }
+ testSetup.successExpected = successExpected;
+
+ testSetups.push(testSetup);
+ });
+ });
+
+ return testSetups;
+}
+
+function runServer(port, secureProtocol, secureOptions, ciphers) {
+ debug('Running server!');
+ debug('port: ' + port);
+ debug('secureProtocol: ' + secureProtocol);
+ debug('secureOptions: ' + secureOptions);
+ debug('ciphers: ' + ciphers);
+
+ var keyPath = path.join(common.fixturesDir, 'agent.key');
+ var certPath = path.join(common.fixturesDir, 'agent.crt');
+
+ var key = fs.readFileSync(keyPath).toString();
+ var cert = fs.readFileSync(certPath).toString();
+
+ var server = new tls.Server({ key: key,
+ cert: cert,
+ ca: [],
+ ciphers: ciphers,
+ secureProtocol: secureProtocol,
+ secureOptions: secureOptions
+ });
+
+ server.listen(port, function() {
+ process.on('message', function onChildMsg(msg) {
+ if (msg === 'close') {
+ server.close();
+ process.exit(0);
+ }
+ });
+
+ process.send('server_listening');
+ });
+
+ server.on('error', function onServerError(err) {
+ debug('Server error: ' + err);
+ process.exit(1);
+ });
+
+ server.on('clientError', function onClientError(err) {
+ debug('Client error on server: ' + err);
+ process.exit(1);
+ });
+}
+
+function runClient(port, secureProtocol, secureOptions, ciphers) {
+ debug('Running client!');
+ debug('port: ' + port);
+ debug('secureProtocol: ' + secureProtocol);
+ debug('secureOptions: ' + secureOptions);
+ debug('ciphers: ' + ciphers);
+
+ var con = tls.connect(port,
+ {
+ rejectUnauthorized: false,
+ secureProtocol: secureProtocol,
+ secureOptions: secureOptions
+ },
+ function() {
+
+ // TODO jgilli: test that sslProtocolUsed is at least as "secure" as
+ // "secureProtocol"
+ /*
+ * var sslProtocolUsed = con.getVersion();
+ * debug('Protocol used: ' + sslProtocolUsed);
+ */
+
+ process.send('client_done');
+ });
+
+ con.on('error', function(err) {
+ debug('Client could not connect:' + err);
+ process.exit(1);
+ });
+}
+
+function stringToSecureOptions(secureOptionsString) {
+ assert(typeof secureOptionsString === 'string');
+
+ var secureOptions;
+
+ var optionStrings = secureOptionsString.split('|');
+ optionStrings.forEach(function (option) {
+ if (option === 'SSL_OP_NO_SSLv2') {
+ secureOptions |= constants.SSL_OP_NO_SSLv2;
+ }
+
+ if (option === 'SSL_OP_NO_SSLv3') {
+ secureOptions |= constants.SSL_OP_NO_SSLv3;
+ }
+
+ if (option === '0') {
+ secureOptions = 0;
+ }
+ });
+
+ return secureOptions;
+}
+
+function processTestCmdLineOptions(argv){
+ var options = {};
+
+ argv.forEach(function (arg) {
+ var key;
+ var value;
+
+ var keyValue = arg.split(':');
+ var key = keyValue[0];
+
+ if (keyValue.length == 2 && keyValue[1].length > 0) {
+ value = keyValue[1];
+
+ if (key === 'secureOptions') {
+ value = stringToSecureOptions(value);
+ }
+
+ if (key === 'port') {
+ value = +value;
+ }
+ }
+
+ options[key] = value;
+ });
+
+ return options;
+}
+
+function checkTestExitCode(testSetup, serverExitCode, clientExitCode) {
+ if (testSetup.successExpected) {
+ if (serverExitCode === 0 && clientExitCode === 0) {
+ debug('Test succeeded as expected!');
+ return true;
+ }
+ } else {
+ if (serverExitCode !== 0 || clientExitCode !== 0) {
+ debug('Test failed as expected!');
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function secureOptionsToString(secureOptions) {
+ var secureOptsString = '';
+
+ if (secureOptions & constants.SSL_OP_NO_SSLv2) {
+ secureOptsString += 'SSL_OP_NO_SSLv2';
+ }
+
+ if (secureOptions & constants.SSL_OP_NO_SSLv3) {
+ secureOptsString += '|SSL_OP_NO_SSLv3';
+ }
+
+ if (secureOptions === 0) {
+ secureOptsString = '0';
+ }
+
+ return secureOptsString;
+}
+
+function forkTestProcess(processType, testSetup, port) {
+ var argv = [ processType ];
+
+ if (testSetup.secureProtocol) {
+ argv.push('secureProtocol:' + testSetup.secureProtocol);
+ } else {
+ argv.push('secureProtocol:');
+ }
+
+ argv.push('secureOptions:' + secureOptionsToString(testSetup.secureOptions));
+
+ if (testSetup.ciphers) {
+ argv.push('ciphers:' + testSetup.ciphers);
+ } else {
+ argv.push('ciphers:');
+ }
+
+ argv.push('port:' + port);
+
+ var forkOptions;
+ if (testSetup.cmdLine) {
+ forkOptions = {
+ execArgv: [ testSetup.cmdLine ]
+ }
+ }
+
+ return fork(process.argv[1],
+ argv,
+ forkOptions);
+}
+
+function runTest(testSetup, testDone) {
+ var clientSetup = testSetup.client;
+ var serverSetup = testSetup.server;
+
+ assert(clientSetup);
+ assert(serverSetup);
+
+ debug('Starting new test on port: ' + testSetup.port);
+
+ debug('client setup:');
+ debug(clientSetup);
+
+ debug('server setup:');
+ debug(serverSetup);
+
+ debug('Success expected:' + testSetup.successExpected);
+
+ var serverExitCode;
+
+ var clientStarted = false;
+ var clientExitCode;
+
+ var serverChild = forkTestProcess('server', serverSetup, testSetup.port);
+ assert(serverChild);
+
+ serverChild.on('message', function onServerMsg(msg) {
+ if (msg === 'server_listening') {
+ debug('Starting client!');
+ clientStarted = true;
+
+ var clientChild = forkTestProcess('client', clientSetup, testSetup.port);
+ assert(clientChild);
+
+ clientChild.on('exit', function onClientExited(exitCode) {
+ debug('Client exited with code:' + exitCode);
+
+ clientExitCode = exitCode;
+ if (serverExitCode != null) {
+ var err;
+ if (!checkTestExitCode(testSetup, serverExitCode, clientExitCode))
+ err = new Error("Test failed!");
+
+ return testDone(err);
+ } else {
+ if (serverChild.connected) {
+ serverChild.send('close');
+ }
+ }
+ });
+
+ clientChild.on('message', function onClientMsg(msg) {
+ if (msg === 'client_done' && serverChild.connected) {
+ serverChild.send('close');
+ }
+ })
+ }
+ });
+
+ serverChild.on('exit', function onServerExited(exitCode) {
+ debug('Server exited with code:' + exitCode);
+
+ serverExitCode = exitCode;
+ if (clientExitCode != null || !clientStarted) {
+ var err;
+ if (!checkTestExitCode(testSetup, serverExitCode, clientExitCode))
+ err = new Error("Test failed!");
+
+ return testDone(err);
+ }
+ });
+}
+
+function usage() {
+ console.log('Usage: test-node-ssl [-j N] [--list-tests] [-s startIndex] ' +
+ '[-e endIndex] [-o outputFile]');
+ process.exit(1);
+}
+
+function processDriverCmdLineOptions(argv) {
+ var options = {
+ parallelTests: 1
+ };
+
+ for (var i = 1; i < argv.length; ++i) {
+ if (argv[i] === '-j') {
+
+ var nbParallelTests = +argv[i + 1];
+ if (!nbParallelTests) {
+ usage();
+ } else {
+ options.parallelTests = argv[++i];
+ }
+ }
+
+ if (argv[i] === '-s') {
+ var start = +argv[i + 1];
+ if (!start) {
+ usage();
+ } else {
+ options.start = argv[++i];
+ }
+ }
+
+ if (argv[i] === '-e') {
+ var end = +argv[i + 1];
+ if (!end) {
+ usage();
+ } else {
+ options.end = argv[++i];
+ }
+ }
+
+ if (argv[i] === '--list-tests') {
+ options.listTests = true;
+ }
+
+ if (argv[i] === '-o') {
+ var outputFile = argv[i + 1];
+ if (!outputFile) {
+ usage();
+ } else {
+ options.outputFile = argv[++i];
+ }
+ }
+ }
+
+ return options;
+}
+
+function outputTestResult(test, err, output) {
+ output.write(os.EOL);
+ output.write('Test:' + os.EOL);
+ output.write(JSON.stringify(test, null, " "));
+ output.write(os.EOL);
+ output.write('Result:');
+ output.write(err ? 'failure' : 'success');
+ output.write(os.EOL);
+}
+
+var agentType = process.argv[2];
+if (agentType === 'client' || agentType === 'server') {
+ var options = processTestCmdLineOptions(process.argv);
+ debug('secureProtocol: ' + options.secureProtocol);
+ debug('secureOptions: ' + options.secureOptions);
+ debug('ciphers:' + options.ciphers);
+ debug('port:' + options.port);
+
+ if (agentType === 'client') {
+ runClient(options.port,
+ options.secureProtocol,
+ options.secureOptions,
+ options.ciphers);
+ } else if (agentType === 'server') {
+ runServer(options.port,
+ options.secureProtocol,
+ options.secureOptions,
+ options.ciphers);
+ }
+} else {
+ var driverOptions = processDriverCmdLineOptions(process.argv);
+ debug('Tests driver options:');
+ debug(driverOptions);
+ /*
+ * This is the tests driver process.
+ *
+ * It forks itself twice for each test. Each of the two forked processees are
+ * respectfully used as an SSL client and an SSL server. The client and
+ * server setup their SSL connection as generated by the "createTestsSetups"
+ * function. Once both processes have exited, the tests driver process
+ * compare both client and server exit codes with the expected test result
+ * of the test setup. If they match, the test is successful, otherwise it
+ * failed.
+ */
+
+ var testSetups = createTestsSetups();
+
+ if (driverOptions.listTests) {
+ console.log(testSetups);
+ process.exit(0);
+ }
+
+ var testOutput = process.stdout;
+ if (driverOptions.outputFile) {
+ testOutput = fs.createWriteStream(driverOptions.outputFile)
+ .on('error', function onError(err) {
+ console.error(err);
+ process.exit(1);
+ });
+ }
+
+ debug('Tests setups:');
+ debug('Number of tests: ' + testSetups.length);
+ debug(JSON.stringify(testSetups, null, " "));
+ debug();
+
+ var nbTestsStarted = 0;
+
+ function runTests(tests, callback) {
+ var nbTests = tests.length;
+ if (nbTests === 0) {
+ return callback();
+ }
+ var error;
+ var nbTestsDone = 0;
+
+ debug('Starting new batch of tests...');
+
+ var port = common.PORT;
+ async.each(tests, function (test, testDone) {
+ test.port = port++;
+
+ ++nbTestsStarted;
+ debug('Starting test nb: ' + nbTestsStarted);
+
+ runTest(test, function onTestDone(err) {
+ ++nbTestsDone;
+ if (err && error === undefined) {
+ error = new Error('Test with ID ' + test.ID + ' failed: ' + err);
+ }
+
+ outputTestResult(test, err, testOutput);
+
+ if (nbTestsDone === nbTests)
+ return testDone(error);
+ return testDone();
+ });
+
+ }, function testsDone(err, results) {
+ if (err) {
+ assert(false,
+ "At least one test in the most recent batch failed: " + err);
+ }
+
+ return callback(err);
+ });
+ }
+
+ function runAllTests(allTests, allTestsDone) {
+ if (allTests.length === 0) {
+ return allTestsDone();
+ }
+
+ return runTests(allTests.splice(0, driverOptions.parallelTests),
+ runAllTests.bind(global, allTests, allTestsDone));
+ }
+
+ runAllTests(testSetups.slice(driverOptions.start, driverOptions.end),
+ function allDone(err) {
+ console.log('All tests done!');
+ });
+}
diff --git a/test/simple/test-tls-honorcipherorder-secureOptions.js b/test/simple/test-tls-honorcipherorder-secureOptions.js
new file mode 100644
index 000000000..e70cfb1ef
--- /dev/null
+++ b/test/simple/test-tls-honorcipherorder-secureOptions.js
@@ -0,0 +1,131 @@
+// 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 common = require('../common');
+var assert = require('assert');
+var tls = require('tls');
+var fs = require('fs');
+var nconns = 0;
+var SSL_Method = 'SSLv23_method';
+var localhost = '127.0.0.1';
+var opCipher = process.binding('constants').SSL_OP_CIPHER_SERVER_PREFERENCE;
+
+/*
+ * This test is to make sure we are preserving secureOptions that are passed
+ * to the server.
+ *
+ * Also that if honorCipherOrder is passed we are preserving that in the
+ * options.
+ *
+ * And that if we are passing in secureOptions no new options (aside from the
+ * honorCipherOrder case) are added to the secureOptions
+ */
+
+
+process.on('exit', function() {
+ assert.equal(nconns, 6);
+});
+
+function test(honorCipherOrder, clientCipher, expectedCipher, secureOptions, cb) {
+ var soptions = {
+ secureProtocol: SSL_Method,
+ key: fs.readFileSync(common.fixturesDir + '/keys/agent2-key.pem'),
+ cert: fs.readFileSync(common.fixturesDir + '/keys/agent2-cert.pem'),
+ ciphers: 'AES256-SHA:RC4-SHA:DES-CBC-SHA',
+ secureOptions: secureOptions,
+ honorCipherOrder: !!honorCipherOrder
+ };
+
+ var server = tls.createServer(soptions, function(cleartextStream) {
+ nconns++;
+ });
+
+ if (!!honorCipherOrder) {
+ assert.strictEqual(server.secureOptions & opCipher, opCipher, 'we should preserve cipher preference');
+ }
+
+ if (secureOptions) {
+ var expectedSecureOpts = secureOptions;
+ if (!!honorCipherOrder) expectedSecureOpts |= opCipher;
+
+ assert.strictEqual(server.secureOptions & expectedSecureOpts,
+ expectedSecureOpts, 'we should preserve secureOptions');
+ assert.strictEqual(server.secureOptions & ~expectedSecureOpts,
+ 0,
+ 'we should not add extra options');
+ }
+
+ server.listen(common.PORT, localhost, function() {
+ var coptions = {
+ rejectUnauthorized: false,
+ secureProtocol: SSL_Method
+ };
+ if (clientCipher) {
+ coptions.ciphers = clientCipher;
+ }
+ var client = tls.connect(common.PORT, localhost, coptions, function() {
+ var cipher = client.getCipher();
+ client.end();
+ server.close();
+ assert.equal(cipher.name, expectedCipher);
+ if (cb) cb();
+ });
+ });
+}
+
+test1();
+
+function test1() {
+ // Client has the preference of cipher suites by default
+ test(false, 'DES-CBC-SHA:RC4-SHA:AES256-SHA','DES-CBC-SHA', 0, test2);
+}
+
+function test2() {
+ // Server has the preference of cipher suites where AES256-SHA is in
+ // the first.
+ test(true, 'DES-CBC-SHA:RC4-SHA:AES256-SHA', 'AES256-SHA', 0, test3);
+}
+
+function test3() {
+ // Server has the preference of cipher suites. RC4-SHA is given
+ // higher priority over DES-CBC-SHA among client cipher suites.
+ test(true, 'DES-CBC-SHA:RC4-SHA', 'RC4-SHA', 0, test4);
+}
+
+function test4() {
+ // As client has only one cipher, server has no choice in regardless
+ // of honorCipherOrder.
+ test(true, 'DES-CBC-SHA', 'DES-CBC-SHA', 0, test5);
+}
+
+function test5() {
+ test(false,
+ 'DES-CBC-SHA',
+ 'DES-CBC-SHA',
+ process.binding('constants').SSL_OP_SINGLE_DH_USE, test6);
+}
+
+function test6() {
+ test(true,
+ 'DES-CBC-SHA',
+ 'DES-CBC-SHA',
+ process.binding('constants').SSL_OP_SINGLE_DH_USE);
+}