diff --git a/CHANGELOG.yaml b/CHANGELOG.yaml index 638b0ca82..dcccaa70c 100644 --- a/CHANGELOG.yaml +++ b/CHANGELOG.yaml @@ -1,9 +1,11 @@ master: new features: - GH-786 Added history in request and response callback + - GH-787 Added TLS protocol profile behavior breaking changes: - GH-786 Moved timings out of response instance chores: + - GH-775 Added tests for proxy authentication - Updated dependencies 7.10.0: diff --git a/docs/protocol-profile-behavior.md b/docs/protocol-profile-behavior.md index 2c52f65c0..d4ebf65b8 100644 --- a/docs/protocol-profile-behavior.md +++ b/docs/protocol-profile-behavior.md @@ -24,7 +24,17 @@ Redirect with the original HTTP method, by default redirects with HTTP method GE - `removeRefererHeaderOnRedirect`
Removes the `referer` header when a redirect happens. +- `tlsPreferServerCiphers:Boolean`
+Use the server's cipher suite order instead of the client's during negotiation + +- `tlsDisabledProtocols:Array`
+the SSL and TLS protocol versions to disabled during negotiation + +- `tlsCipherSelection:Array`
+Order of cipher suites that the SSL server profile uses to establish a secure connection + **A collection with protocol profile behaviors:** + ```javascript { "info": { diff --git a/lib/requester/core.js b/lib/requester/core.js index 44a6cbcea..f93801431 100644 --- a/lib/requester/core.js +++ b/lib/requester/core.js @@ -1,9 +1,14 @@ -var _ = require('lodash'), - dns = require('dns'), - Socket = require('net').Socket, +var dns = require('dns'), + constants = require('constants'), + + _ = require('lodash'), uuid = require('uuid/v4'), + sdk = require('postman-collection'), + + Socket = require('net').Socket, requestBodyBuilders = require('./core-body-builder'), + version = require('../../package.json').version, LOCAL_IPV6 = '::1', LOCAL_IPV4 = '127.0.0.1', @@ -22,8 +27,7 @@ var _ = require('lodash'), S_ERROR = 'error', S_TIMEOUT = 'timeout', - sdk = require('postman-collection'), - version = require('../../package.json').version, + SSL_OP_NO = 'SSL_OP_NO_', ERROR_ADDRESS_RESOLVE = 'NETERR: getaddrinfo ENOTFOUND ', @@ -229,6 +233,7 @@ module.exports = { hostname = request.url && request.url.getHost(); !defaultOpts && (defaultOpts = {}); + !protocolProfileBehavior && (protocolProfileBehavior = {}); options.headers = request.getHeaders({enabled: true, sanitizeKeys: true}); url = request.url.toString(); @@ -238,6 +243,7 @@ module.exports = { options.timeout = defaultOpts.timeout; options.gzip = true; options.time = defaultOpts.timings; + options.verbose = defaultOpts.verbose; options.extraCA = defaultOpts.extendedRootCA; // Ensures that "request" creates URL encoded formdata or querystring as @@ -253,6 +259,23 @@ module.exports = { options[reqOption] = resolveWithProtocolProfileBehavior(behaviorName, defaultOpts, protocolProfileBehavior); } + // use the server's cipher suite order instead of the client's during negotiation + if (protocolProfileBehavior.tlsPreferServerCiphers) { + options.honorCipherOrder = true; + } + + // the SSL and TLS protocol versions to disabled during negotiation + if (Array.isArray(protocolProfileBehavior.tlsDisabledProtocols)) { + protocolProfileBehavior.tlsDisabledProtocols.forEach(function (protocol) { + options.secureOptions |= constants[SSL_OP_NO + protocol]; + }); + } + + // order of cipher suites that the SSL server profile uses to establish a secure connection + if (Array.isArray(protocolProfileBehavior.tlsCipherSelection)) { + options.ciphers = protocolProfileBehavior.tlsCipherSelection.join(':'); + } + // Request body may return different options depending on the type of the body. bodyParams = self.getRequestBody(request, protocolProfileBehavior); diff --git a/lib/requester/requester-pool.js b/lib/requester/requester-pool.js index a2d977e41..d01312dc9 100644 --- a/lib/requester/requester-pool.js +++ b/lib/requester/requester-pool.js @@ -18,6 +18,7 @@ RequesterPool = function (options, callback) { _.get(options, 'timeout.global') ]), // validated later inside requester timings: _.get(options, 'requester.timings', true), + verbose: _.get(options, 'requester.verbose', false), keepAlive: _.get(options, 'requester.keepAlive', true), cookieJar: _.get(options, 'requester.cookieJar'), // default set later in this constructor strictSSL: _.get(options, 'requester.strictSSL'), diff --git a/test/fixtures/server.js b/test/fixtures/server.js index c24dc7f6f..771d03e62 100644 --- a/test/fixtures/server.js +++ b/test/fixtures/server.js @@ -64,6 +64,7 @@ function createRawEchoServer () { server.on('listening', function () { server.port = this.address().port; + server.url = 'http://localhost:' + server.port; }); enableServerDestroy(server); @@ -85,29 +86,34 @@ function createRawEchoServer () { * s.listen(3000, 'localhost'); */ function createSSLServer (opts) { - var i, - server, + var server, certDataPath = path.join(__dirname, 'certificates'), options = { 'key': path.join(certDataPath, 'server-key.pem'), 'cert': path.join(certDataPath, 'server-crt.pem'), 'ca': path.join(certDataPath, 'ca.pem') - }; + }, + optionsWithFilePath = ['key', 'cert', 'ca', 'pfx']; if (opts) { options = Object.assign(options, opts); } - for (i in options) { - if (i !== 'requestCert' && i !== 'rejectUnauthorized' && i !== 'ciphers') { - options[i] = fs.readFileSync(options[i]); - } - } + optionsWithFilePath.forEach(function (option) { + if (!options[option]) { return; } + + options[option] = fs.readFileSync(options[option]); + }); server = https.createServer(options, function (req, res) { server.emit(req.url, req, res); }); + server.on('listening', function () { + server.port = this.address().port; + server.url = 'https://localhost:' + server.port; + }); + enableServerDestroy(server); return server; @@ -181,6 +187,11 @@ function createHTTPServer () { server.emit(req.url, req, res); }); + server.on('listening', function () { + server.port = this.address().port; + server.url = 'http://localhost:' + server.port; + }); + enableServerDestroy(server); return server; diff --git a/test/integration/protocol-profile-behavior/tlsProtocols.test.js b/test/integration/protocol-profile-behavior/tlsProtocols.test.js new file mode 100644 index 000000000..b0f155f8f --- /dev/null +++ b/test/integration/protocol-profile-behavior/tlsProtocols.test.js @@ -0,0 +1,1204 @@ +var fs = require('fs'), + path = require('path'), + constants = require('constants'), + expect = require('chai').expect, + server = require('../../fixtures/server'), + + forInAsync = function (obj, fn, cb) { + if (!(obj && fn)) { return; } + !cb && (cb = function () { /* (ಠ_ಠ) */ }); + + var index = 0, + keys = Object.keys(obj), + next = function (err) { + if (err || index >= keys.length) { + return cb(err); + } + + fn.call(obj, keys[index++], next); + }; + + if (!keys.length) { + return cb(); + } + + next(); + }; + + +describe('protocolProfileBehavior: tls options', function () { + var testrun, + servers = { + // SSLv2 and SSLv3 methods are disabled in Node + TLSv1: undefined, + TLSv1_1: undefined, + TLSv1_2: undefined + }, + CACertPath = path.resolve(__dirname, '../../fixtures/certificates/ca.pem'), + requestHandler = function (req, res) { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('okay'); + }; + + before(function (done) { + forInAsync(servers, function (protocol, next) { + servers[protocol] = server.createSSLServer({ + secureProtocol: protocol + '_method' // The TLS protocol version to use + }); + servers[protocol].on('/', requestHandler); + servers[protocol].listen(0, next); + }, done); + }); + + after(function (done) { + forInAsync(servers, function (protocol, next) { + servers[protocol].destroy(next); + }, done); + }); + + describe('tlsDisabledProtocols', function () { + describe('TLSv1 server', function () { + describe('default', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: servers.TLSv1.url, + header: [{ + key: 'Connection', + value: 'close' + }] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should choose TLSv1 protocol by default', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + expect(sessions[executionData.session.id].tls).to.have.property('protocol', 'TLSv1'); + }); + }); + + describe('with TLSv1_1, TLSv1_2 disabled', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: servers.TLSv1.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsDisabledProtocols: ['TLSv1_1', 'TLSv1_2'] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should get the response correctly', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + expect(sessions[executionData.session.id].tls).to.have.property('protocol', 'TLSv1'); + }); + }); + + describe('with TLSv1 disabled', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: servers.TLSv1.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsDisabledProtocols: ['TLSv1'] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should throw error for unsupported protocol', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.false; + expect(testrun.response.getCall(0).args[0]).to.be.ok; + }); + }); + }); + + describe('TLSv1_1 server', function () { + describe('default', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: servers.TLSv1_1.url, + header: [{ + key: 'Connection', + value: 'close' + }] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should choose TLSv1.1 protocol by default', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + expect(sessions[executionData.session.id].tls).to.have.property('protocol', 'TLSv1.1'); + }); + }); + + describe('with TLSv1, TLSv1_2 disabled', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: servers.TLSv1_1.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsDisabledProtocols: ['TLSv1', 'TLSv1_2'] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should get the response correctly', function () { + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + expect(sessions[executionData.session.id].tls).to.have.property('protocol', 'TLSv1.1'); + }); + }); + + describe('with TLSv1_1 disabled', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: servers.TLSv1_1.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsDisabledProtocols: ['TLSv1_1'] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should throw error for unsupported protocol', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.false; + expect(testrun.response.getCall(0).args[0]).to.be.ok; + }); + }); + }); + + describe('TLSv1_2 server', function () { + describe('default', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: servers.TLSv1_2.url, + header: [{ + key: 'Connection', + value: 'close' + }] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should choose TLSv1.2 protocol by default', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + expect(sessions[executionData.session.id].tls).to.have.property('protocol', 'TLSv1.2'); + }); + }); + + describe('with TLSv1, TLSv1_1 disabled', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: servers.TLSv1_2.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsDisabledProtocols: ['TLSv1', 'TLSv1_1'] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should get the response correctly', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + expect(sessions[executionData.session.id].tls).to.have.property('protocol', 'TLSv1.2'); + }); + }); + + describe('with TLSv1_2 disabled', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: servers.TLSv1_2.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsDisabledProtocols: ['TLSv1_2'] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should throw error for unsupported protocol', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.false; + expect(testrun.response.getCall(0).args[0]).to.be.ok; + }); + }); + }); + + describe('TLSv1 & TLSv1_1 server', function () { + var sslServer; + + before(function (done) { + sslServer = server.createSSLServer({ + secureOptions: constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_TLSv1_2 // Disable SSLv3 and TLSv1_2 + }); + sslServer.on('/', requestHandler); + sslServer.listen(0, done); + }); + + describe('default', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should choose TLSv1.1 protocol by default', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + expect(sessions[executionData.session.id].tls).to.have.property('protocol', 'TLSv1.1'); + }); + }); + + describe('with just TLSv1 enabled', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsDisabledProtocols: ['SSLv2', 'SSLv3', 'TLSv1_1', 'TLSv1_2'] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should choose TLSv1 protocol correctly', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + expect(sessions[executionData.session.id].tls).to.have.property('protocol', 'TLSv1'); + }); + }); + + describe('with just TLSv1_1 enabled', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsDisabledProtocols: ['SSLv2', 'SSLv3', 'TLSv1', 'TLSv1_2'] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should choose TLSv1.1 protocol correctly', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + expect(sessions[executionData.session.id].tls).to.have.property('protocol', 'TLSv1.1'); + }); + }); + + describe('with just TLSv1_2 enabled', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsDisabledProtocols: ['SSLv2', 'SSLv3', 'TLSv1', 'TLSv1_1'] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should throw error for unsupported protocol', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.false; + expect(testrun.response.getCall(0).args[0]).to.be.ok; + }); + }); + }); + + describe('default server', function () { + var sslServer; + + before(function (done) { + sslServer = server.createSSLServer(); + sslServer.on('/', requestHandler); + sslServer.listen(0, done); + }); + + describe('default', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsCipherSelection: [] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + // @note This will fail when TLSv1.3 will be supported/made default. + // Refer: https://github.com/nodejs/node/pull/26209 + it('should choose TLSv1.2 protocol by default', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + expect(sessions[executionData.session.id].tls).to.have.property('protocol', 'TLSv1.2'); + }); + }); + + describe('with just SSLv23 enabled', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + }, + protocolProfileBehavior: { + tlsDisabledProtocols: ['SSLv2', 'TLSv1', 'TLSv1_1', 'TLSv1_2'] + } + }] + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should throw error for unsupported protocol', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.false; + expect(testrun.response.getCall(0).args[0]).to.be.ok; + }); + }); + }); + }); + + describe('tlsCipherSelection', function () { + var sslServer; + + before(function (done) { + sslServer = server.createSSLServer(); + sslServer.on('/', requestHandler); + sslServer.listen(0, done); + }); + + after(function (done) { + sslServer.destroy(done); + }); + + describe('default', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{key: 'Connection', value: 'close'}] + } + }], + protocolProfileBehavior: {} + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + // @note Might fail with TLSv1_3 if cipher list oder is changed + it('should choose ECDHE-RSA-AES128-GCM-SHA256 cipher by default', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + sessionData = sessions[executionData.session.id]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + + expect(sessionData.tls).to.have.property('cipher'); + expect(sessionData.tls.cipher).to.have.property('name', 'ECDHE-RSA-AES128-GCM-SHA256'); + }); + }); + + describe('with cipher defined', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + } + }], + protocolProfileBehavior: { + tlsCipherSelection: ['AES128-SHA'] + } + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should choose the specified cipher', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + sessionData = sessions[executionData.session.id]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + + expect(sessionData.tls).to.have.property('cipher'); + expect(sessionData.tls.cipher).to.have.property('name', 'AES128-SHA'); + }); + }); + + describe('with invalid cipher', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + } + }], + protocolProfileBehavior: { + tlsCipherSelection: ['RANDOM'] + } + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should throw error for unsupported protocol', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.false; + expect(testrun.response.getCall(0).args[0]).to.be.ok; + }); + }); + }); + + describe('tlsPreferServerCiphers', function () { + var sslServer; + + before(function (done) { + sslServer = server.createSSLServer({ + ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256' + }); + sslServer.on('/', requestHandler); + sslServer.listen(0, done); + }); + + after(function (done) { + sslServer.destroy(done); + }); + + describe('default', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + } + }], + protocolProfileBehavior: {} + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should choose the server specified cipher', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + sessionData = sessions[executionData.session.id]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + + expect(sessionData.tls).to.have.property('cipher'); + expect(sessionData.tls.cipher).to.have.property('name', 'ECDHE-RSA-AES256-GCM-SHA384'); + }); + }); + + describe('with tlsPreferServerCiphers: true', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + } + }], + protocolProfileBehavior: { + tlsPreferServerCiphers: true + } + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should choose the server specified cipher', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + sessionData = sessions[executionData.session.id]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + + expect(sessionData.tls).to.have.property('cipher'); + expect(sessionData.tls.cipher).to.have.property('name', 'ECDHE-RSA-AES256-GCM-SHA384'); + }); + }); + + describe('with tlsPreferServerCiphers: false', function () { + before(function (done) { + this.run({ + fileResolver: fs, + requester: { + extendedRootCA: CACertPath, + verbose: true + }, + collection: { + item: [{ + request: { + url: sslServer.url, + header: [{ + key: 'Connection', + value: 'close' + }] + } + }], + protocolProfileBehavior: { + tlsPreferServerCiphers: false, + tlsCipherSelection: ['ECDHE-RSA-AES128-GCM-SHA256'] + } + } + }, function (err, results) { + testrun = results; + done(err); + }); + }); + + it('should complete the run', function () { + expect(testrun).to.be.ok; + expect(testrun).to.nested.include({ + 'done.calledOnce': true, + 'start.calledOnce': true, + 'request.calledOnce': true + }); + }); + + it('should choose the client specified cipher', function () { + expect(testrun.response.getCall(0).calledWith(null)).to.be.true; + + var response = testrun.response.getCall(0).args[2], + history = testrun.response.getCall(0).args[6], + executionData, + sessionData, + sessions; + + expect(history).to.have.property('execution').that.include.property('sessions'); + sessions = history.execution.sessions; + executionData = history.execution.data[0]; + sessionData = sessions[executionData.session.id]; + + expect(response.reason()).to.eql('OK'); + expect(response.text()).to.eql('okay'); + + expect(sessionData.tls).to.have.property('cipher'); + expect(sessionData.tls.cipher).to.have.property('name', 'ECDHE-RSA-AES128-GCM-SHA256'); + }); + }); + }); +}); diff --git a/test/unit/requester-core.test.js b/test/unit/requester-core.test.js index 7a42aa716..dc51a2c66 100644 --- a/test/unit/requester-core.test.js +++ b/test/unit/requester-core.test.js @@ -43,7 +43,8 @@ describe('requester util', function () { encoding: null, extraCA: undefined, agentOptions: {keepAlive: undefined}, - time: undefined + time: undefined, + verbose: undefined }); }); @@ -79,7 +80,8 @@ describe('requester util', function () { encoding: null, extraCA: undefined, agentOptions: {keepAlive: undefined}, - time: undefined + time: undefined, + verbose: undefined }); }); @@ -211,7 +213,8 @@ describe('requester util', function () { encoding: null, extraCA: undefined, agentOptions: {keepAlive: undefined}, - time: undefined + time: undefined, + verbose: undefined }); }); });