diff --git a/AUTHORS b/AUTHORS index 3af7fc63a2..b51cd7160b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -515,3 +515,6 @@ Kevin Simper Jackson Tian Tristan Berger Mathias Schreck +Calvin Metcalf +Matthew Fitzsimmons +Swaagie diff --git a/ChangeLog b/ChangeLog index 73f42e0dd0..185b0d30eb 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 f88c55d0a2..597d196f2f 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 392f7ad2ba..77a7089218 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 3971158985..5d1c54ae7b 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 0000000000..c2658d7d1b --- /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 0000000000..114dce6afb --- /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 0000000000..f7e06c93df --- /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 0000000000..e70cfb1ef4 --- /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); +}