summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkoichik <koichik@improvement.jp>2011-12-07 22:47:06 +0900
committerkoichik <koichik@improvement.jp>2011-12-07 22:47:06 +0900
commitf8c335d0caf47f16d31413f89aa28eda3878e3aa (patch)
tree682bf277fc16b73423b1f80990febdd7111abcf6
parente0a207c27a5e366b89133df3b60394e6cac7876b (diff)
downloadnode-new-f8c335d0caf47f16d31413f89aa28eda3878e3aa.tar.gz
tls: enable rejectUnauthorized option to client
Fiexes #2247.
-rw-r--r--doc/api/https.markdown8
-rw-r--r--doc/api/tls.markdown4
-rw-r--r--lib/tls.js13
-rw-r--r--test/simple/test-https-client-reject.js95
-rw-r--r--test/simple/test-tls-client-reject.js92
5 files changed, 207 insertions, 5 deletions
diff --git a/doc/api/https.markdown b/doc/api/https.markdown
index 0c019349b4..5de5b4f41b 100644
--- a/doc/api/https.markdown
+++ b/doc/api/https.markdown
@@ -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,10 @@ 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 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`.
diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown
index ca60c658c1..d7307d4eee 100644
--- a/doc/api/tls.markdown
+++ b/doc/api/tls.markdown
@@ -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 server certificate is verified against
+ the list of supplied CAs. An `'error'` event is emitted if verification
+ fails. 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
diff --git a/lib/tls.js b/lib/tls.js
index 34fe11f058..e4f332d415 100644
--- a/lib/tls.js
+++ b/lib/tls.js
@@ -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;
diff --git a/test/simple/test-https-client-reject.js b/test/simple/test-https-client-reject.js
new file mode 100644
index 0000000000..77820533da
--- /dev/null
+++ b/test/simple/test-https-client-reject.js
@@ -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);
+});
diff --git a/test/simple/test-tls-client-reject.js b/test/simple/test-tls-client-reject.js
new file mode 100644
index 0000000000..e11d790e60
--- /dev/null
+++ b/test/simple/test-tls-client-reject.js
@@ -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);
+});