Permalink
Browse files

tls: enable rejectUnautholized option to client

Fiexes #2247.
  • Loading branch information...
1 parent 89e894b commit 163967ca38a9d832ad04e8c3da1ed4822be88926 @koichik committed Dec 6, 2011
Showing with 208 additions and 5 deletions.
  1. +7 −2 doc/api/https.markdown
  2. +4 −0 doc/api/tls.markdown
  3. +10 −3 lib/tls.js
  4. +95 −0 test/simple/test-https-client-reject.js
  5. +92 −0 test/simple/test-tls-client-reject.js
View
@@ -11,8 +11,8 @@ This class is a subclass of `tls.Server` and emits events same as
## https.createServer(options, [requestListener])
Returns a new HTTPS web server object. The `options` is similar to
-`tls.createServer()`. The `requestListener` is a function which is
-automatically added to the `'request'` event.
+[tls.createServer()](tls.html#tls.createServer). The `requestListener` is
+a function which is automatically added to the `'request'` event.
Example:
@@ -94,6 +94,11 @@ specified. However, a [globalAgent](#https.globalAgent) silently ignores these.
- `cert`: Public x509 certificate to use. Default `null`.
- `ca`: An authority certificate or array of authority certificates to check
the remote host against.
+- `rejectUnauthorized`: If `true` the connection will be rejected and emit
+ `'error'` event which is not authorized with the list of supplied CAs.
@bnoordhuis

bnoordhuis Dec 6, 2011

Ambiguous sentence. Maybe something like this?

If true, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if verification fails. Verification happens at the connection level, before the HTTP request is sent.

+ Default: `false`.
+
+
In order to specify these options, use a custom `Agent`.
View
@@ -119,6 +119,10 @@ defaults to `localhost`.) `options` should be an object which specifies
omitted several well known "root" CAs will be used, like VeriSign.
These are used to authorize connections.
+ - `rejectUnauthorized`: If `true` the connection will be rejected and emit
+ `'error'` event which is not authorized with the list of supplied CAs.
+ Default: `false`.
+
- `NPNProtocols`: An array of string or `Buffer` containing supported NPN
protocols. `Buffer` should have following format: `0x05hello0x05world`,
where first byte is next protocol name's length. (Passing array should
View
@@ -1023,7 +1023,8 @@ exports.connect = function(port /* host, options, cb */) {
var sslcontext = crypto.createCredentials(options);
convertNPNProtocols(options.NPNProtocols, this);
- var pair = new SecurePair(sslcontext, false, true, false,
+ var pair = new SecurePair(sslcontext, false, true,
+ options.rejectUnauthorized === true ? true : false,
{
NPNProtocols: this.NPNProtocols,
servername: options.servername || host
@@ -1048,11 +1049,17 @@ exports.connect = function(port /* host, options, cb */) {
if (verifyError) {
cleartext.authorized = false;
cleartext.authorizationError = verifyError;
+
+ if (pair._rejectUnauthorized) {
+ cleartext.emit('error', verifyError);
+ pair.destroy();
+ } else {
+ cleartext.emit('secureConnect');
+ }
} else {
cleartext.authorized = true;
+ cleartext.emit('secureConnect');
}
-
- cleartext.emit('secureConnect');
});
cleartext._controlReleased = true;
@@ -0,0 +1,95 @@
+// 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.
+
+if (!process.versions.openssl) {
+ console.error('Skipping because node compiled without OpenSSL.');
+ process.exit(0);
+}
+
+var common = require('../common');
+var assert = require('assert');
+var https = require('https');
+var fs = require('fs');
+var path = require('path');
+
+var options = {
+ key: fs.readFileSync(path.join(common.fixturesDir, 'test_key.pem')),
+ cert: fs.readFileSync(path.join(common.fixturesDir, 'test_cert.pem'))
+};
+
+var reqCount = 0;
+
+var server = https.createServer(options, function(req, res) {
+ ++reqCount;
+ res.writeHead(200);
+ res.end();
+}).listen(common.PORT, function() {
+ unauthorized();
+});
+
+function unauthorized() {
+ var req = https.request({
+ port: common.PORT
+ }, function(res) {
+ assert(!req.socket.authorized);
+ rejectUnauthorized();
+ });
+ req.on('error', function(err) {
+ assert(false);
+ });
+ req.end();
+}
+
+function rejectUnauthorized() {
+ var options = {
+ port: common.PORT,
+ rejectUnauthorized: true
+ };
+ options.agent = new https.Agent(options);
+ var req = https.request(options, function(res) {
+ assert(false);
+ });
+ req.on('error', function(err) {
+ authorized();
+ });
+ req.end();
+}
+
+function authorized() {
+ var options = {
+ port: common.PORT,
+ rejectUnauthorized: true,
+ ca: [ fs.readFileSync(path.join(common.fixturesDir, 'test_cert.pem')) ]
+ };
+ options.agent = new https.Agent(options);
+ var req = https.request(options, function(res) {
+ assert(req.socket.authorized);
+ server.close();
+ });
+ req.on('error', function(err) {
+ assert(false);
+ });
+ req.end();
+}
+
+process.on('exit', function() {
+ assert.equal(reqCount, 2);
+});
@@ -0,0 +1,92 @@
+// 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.
+
+if (!process.versions.openssl) {
+ console.error('Skipping because node compiled without OpenSSL.');
+ process.exit(0);
+}
+
+var common = require('../common');
+var assert = require('assert');
+var tls = require('tls');
+var fs = require('fs');
+var path = require('path');
+
+var options = {
+ key: fs.readFileSync(path.join(common.fixturesDir, 'test_key.pem')),
+ cert: fs.readFileSync(path.join(common.fixturesDir, 'test_cert.pem'))
+};
+
+var connectCount = 0;
+
+var server = tls.createServer(options, function(socket) {
+ ++connectCount;
+ socket.on('data', function(data) {
+ common.debug(data.toString());
+ assert.equal(data, 'ok');
+ });
+}).listen(common.PORT, function() {
+ unauthorized();
+});
+
+function unauthorized() {
+ var socket = tls.connect(common.PORT, function() {
+ assert(!socket.authorized);
+ socket.end();
+ rejectUnauthorized();
+ });
+ socket.on('error', function(err) {
+ assert(false);
+ });
+ socket.write('ok');
+}
+
+function rejectUnauthorized() {
+ var socket = tls.connect(common.PORT, {
+ rejectUnauthorized: true
+ }, function() {
+ assert(false);
+ });
+ socket.on('error', function(err) {
+ common.debug(err);
+ authorized();
+ });
+ socket.write('ng');
+}
+
+function authorized() {
+ var socket = tls.connect(common.PORT, {
+ rejectUnauthorized: true,
+ ca: [ fs.readFileSync(path.join(common.fixturesDir, 'test_cert.pem')) ]
+ }, function() {
+ assert(socket.authorized);
+ socket.end();
+ server.close();
+ });
+ socket.on('error', function(err) {
+ assert(false);
+ });
+ socket.write('ok');
+}
+
+process.on('exit', function() {
+ assert.equal(connectCount, 3);
+});

2 comments on commit 163967c

LGTM, Koichi.

BTW, there's a typo in the commit log: s/rejectUnautholized /rejectUnauthorized/

Please sign in to comment.