From: Fedor Indutny Date: Fri, 23 Aug 2013 13:53:16 +0000 (+0400) Subject: tls: socket.renegotiate(options, callback) X-Git-Tag: v0.11.8~90 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=af76b08666d4ee04d3574efa977b6c14c3b32c90;p=platform%2Fupstream%2Fnodejs.git tls: socket.renegotiate(options, callback) This utility function allows renegotiaion of secure connection after establishing it. fix #2496 --- diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index e1e8d2c..de50a79 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -578,6 +578,19 @@ See SSL_CIPHER_get_name() and SSL_CIPHER_get_version() in http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_CIPHERS for more information. +### tlsSocket.renegotiate(options, callback) + +Initiate TLS renegotiation process. The `options` may contain the following +fields: `rejectUnauthorized`, `requestCert` (See [tls.createServer][] +for details). `callback(err)` will be executed with `null` as `err`, +once the renegotiation is successfully completed. + +NOTE: Can be used to request peer's certificate after the secure connection +has been established. + +ANOTHER NOTE: When running as the server, socket will be destroyed +with an error after `handshakeTimeout` timeout. + ### tlsSocket.address() Returns the bound address, the address family name and port of the diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 7ed787c..32e9a25 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -194,6 +194,8 @@ TLSSocket.prototype._init = function() { var requestCert = !!options.requestCert || !options.isServer, rejectUnauthorized = !!options.rejectUnauthorized; + this._requestCert = requestCert; + this._rejectUnauthorized = rejectUnauthorized; if (requestCert || rejectUnauthorized) this.ssl.setVerifyMode(requestCert, rejectUnauthorized); @@ -246,6 +248,49 @@ TLSSocket.prototype._init = function() { if (process.features.tls_npn && options.NPNProtocols) this.ssl.setNPNProtocols(options.NPNProtocols); + + if (options.handshakeTimeout > 0) + this.setTimeout(options.handshakeTimeout, this._handleTimeout); +}; + +TLSSocket.prototype.renegotiate = function(options, callback) { + var requestCert = this._requestCert, + rejectUnauthorized = this._rejectUnauthorized; + + if (typeof options.requestCert !== 'undefined') + requestCert = !!options.requestCert; + if (typeof options.rejectUnauthorized !== 'undefined') + rejectUnauthorized = !!options.rejectUnauthorized; + + if (requestCert !== this._requestCert || + rejectUnauthorized !== this._rejectUnauthorized) { + this.ssl.setVerifyMode(requestCert, rejectUnauthorized); + this._requestCert = requestCert; + this._rejectUnauthorized = rejectUnauthorized; + } + if (!this.ssl.renegotiate()) { + if (callback) { + process.nextTick(function() { + callback(new Error('Failed to renegotiate')); + }); + } + return false; + } + + // Ensure that we'll cycle through internal openssl's state + this.write(''); + + if (callback) { + this.once('secure', function() { + callback(null); + }); + } + + return true; +}; + +TLSSocket.prototype._handleTimeout = function() { + this._tlsError(new Error('TLS handshake timeout')); }; TLSSocket.prototype._tlsError = function(err) { @@ -256,9 +301,10 @@ TLSSocket.prototype._tlsError = function(err) { TLSSocket.prototype._releaseControl = function() { if (this._controlReleased) - return; + return false; this._controlReleased = true; this.removeListener('error', this._tlsError); + return true; }; TLSSocket.prototype._finishInit = function() { @@ -272,6 +318,8 @@ TLSSocket.prototype._finishInit = function() { debug('secure established'); this._secureEstablished = true; + if (this._tlsOptions.handshakeTimeout > 0) + this.setTimeout(0, this._handleTimeout); this.emit('secure'); }; @@ -453,37 +501,26 @@ function Server(/* [options], listener */) { server: self, requestCert: self.requestCert, rejectUnauthorized: self.rejectUnauthorized, + handshakeTimeout: timeout, NPNProtocols: self.NPNProtocols, SNICallback: options.SNICallback || SNICallback }); - function listener() { - socket._tlsError(new Error('TLS handshake timeout')); - } - - if (timeout > 0) { - socket.setTimeout(timeout, listener); - } - - socket.once('secure', function() { - socket.setTimeout(0, listener); - - if (self.requestCert) { + socket.on('secure', function() { + if (socket._requestCert) { var verifyError = socket.ssl.verifyError(); if (verifyError) { socket.authorizationError = verifyError.message; - if (self.rejectUnauthorized) + if (socket._rejectUnauthorized) socket.destroy(); } else { socket.authorized = true; } } - if (!socket.destroyed) { - socket._releaseControl(); + if (!socket.destroyed && socket._releaseControl()) self.emit('secureConnection', socket); - } }); socket.on('_tlsError', function(err) { @@ -546,7 +583,7 @@ Server.prototype.setOptions = function(options) { if (options.NPNProtocols) tls.convertNPNProtocols(options.NPNProtocols, this); if (options.sessionIdContext) { this.sessionIdContext = options.sessionIdContext; - } else if (this.requestCert) { + } else { this.sessionIdContext = crypto.createHash('md5') .update(process.argv.join(' ')) .digest('hex'); diff --git a/src/node_crypto.cc b/src/node_crypto.cc index c50e2ec..ff90e91 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -805,6 +805,7 @@ void SSLWrap::AddMethods(Handle t) { NODE_SET_PROTOTYPE_METHOD(t, "getCurrentCipher", GetCurrentCipher); NODE_SET_PROTOTYPE_METHOD(t, "receivedShutdown", ReceivedShutdown); NODE_SET_PROTOTYPE_METHOD(t, "endParser", EndParser); + NODE_SET_PROTOTYPE_METHOD(t, "renegotiate", Renegotiate); #ifdef OPENSSL_NPN_NEGOTIATED NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", GetNegotiatedProto); @@ -1160,6 +1161,20 @@ void SSLWrap::EndParser(const FunctionCallbackInfo& args) { template +void SSLWrap::Renegotiate(const FunctionCallbackInfo& args) { + HandleScope scope(node_isolate); + + Base* w = ObjectWrap::Unwrap(args.This()); + + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence unused variable warning. + + bool yes = SSL_renegotiate(w->ssl_) == 1; + args.GetReturnValue().Set(yes); +} + + +template void SSLWrap::IsInitFinished(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Base* w = ObjectWrap::Unwrap(args.This()); diff --git a/src/node_crypto.h b/src/node_crypto.h index 98e0f58..b1c2c7f 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -172,6 +172,7 @@ class SSLWrap { static void GetCurrentCipher(const v8::FunctionCallbackInfo& args); static void ReceivedShutdown(const v8::FunctionCallbackInfo& args); static void EndParser(const v8::FunctionCallbackInfo& args); + static void Renegotiate(const v8::FunctionCallbackInfo& args); #ifdef OPENSSL_NPN_NEGOTIATED static void GetNegotiatedProto( diff --git a/test/simple/test-tls-server-verify.js b/test/simple/test-tls-server-verify.js index 2b09d82..03f598d 100644 --- a/test/simple/test-tls-server-verify.js +++ b/test/simple/test-tls-server-verify.js @@ -39,6 +39,7 @@ var testCases = [{ title: 'Do not request certs. Everyone is unauthorized.', requestCert: false, rejectUnauthorized: false, + renegotiate: false, CAs: ['ca1-cert'], clients: [{ name: 'agent1', shouldReject: false, shouldAuth: false }, @@ -51,6 +52,20 @@ var testCases = { title: 'Allow both authed and unauthed connections with CA1', requestCert: true, rejectUnauthorized: false, + renegotiate: false, + CAs: ['ca1-cert'], + clients: + [{ name: 'agent1', shouldReject: false, shouldAuth: true }, + { name: 'agent2', shouldReject: false, shouldAuth: false }, + { name: 'agent3', shouldReject: false, shouldAuth: false }, + { name: 'nocert', shouldReject: false, shouldAuth: false } + ] + }, + + { title: 'Do not request certs at connection. Do that later', + requestCert: false, + rejectUnauthorized: false, + renegotiate: true, CAs: ['ca1-cert'], clients: [{ name: 'agent1', shouldReject: false, shouldAuth: true }, @@ -63,6 +78,7 @@ var testCases = { title: 'Allow only authed connections with CA1', requestCert: true, rejectUnauthorized: true, + renegotiate: false, CAs: ['ca1-cert'], clients: [{ name: 'agent1', shouldReject: false, shouldAuth: true }, @@ -75,6 +91,7 @@ var testCases = { title: 'Allow only authed connections with CA1 and CA2', requestCert: true, rejectUnauthorized: true, + renegotiate: false, CAs: ['ca1-cert', 'ca2-cert'], clients: [{ name: 'agent1', shouldReject: false, shouldAuth: true }, @@ -88,6 +105,7 @@ var testCases = { title: 'Allow only certs signed by CA2 but not in the CRL', requestCert: true, rejectUnauthorized: true, + renegotiate: false, CAs: ['ca2-cert'], crl: 'ca2-crl', clients: @@ -104,6 +122,7 @@ var testCases = var common = require('../common'); +var constants = require('constants'); var assert = require('assert'); var fs = require('fs'); var tls = require('tls'); @@ -185,20 +204,23 @@ function runClient(options, cb) { var rejected = true; var authed = false; + var goodbye = false; client.stdout.setEncoding('utf8'); client.stdout.on('data', function(d) { out += d; - if (/_unauthed/g.test(out)) { + if (!goodbye && /_unauthed/g.test(out)) { console.error(' * unauthed'); + goodbye = true; client.stdin.end('goodbye\n'); authed = false; rejected = false; } - if (/_authed/g.test(out)) { + if (!goodbye && /_authed/g.test(out)) { console.error(' * authed'); + goodbye = true; client.stdin.end('goodbye\n'); authed = true; rejected = false; @@ -247,7 +269,34 @@ function runTest(testIndex) { var connections = 0; - var server = tls.Server(serverOptions, function(c) { + /* + * If renegotiating - session might be resumed and openssl won't request + * client's certificate (probably because of bug in the openssl) + */ + if (tcase.renegotiate) { + serverOptions.secureOptions = + constants.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; + } + + var renegotiated = false; + var server = tls.Server(serverOptions, function handleConnection(c) { + if (tcase.renegotiate && !renegotiated) { + renegotiated = true; + setTimeout(function() { + console.error('- connected, renegotiating'); + c.write('\n_renegotiating\n'); + return c.renegotiate({ + requestCert: true, + rejectUnauthorized: false + }, function(err) { + assert(!err); + c.write('\n_renegotiated\n'); + handleConnection(c); + }); + }, 200); + return; + } + connections++; if (c.authorized) { console.error('- authed connection: ' +