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
});
});
});