diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown
index e1e8d2c66c8..7a9f2eba381 100644
--- a/doc/api/tls.markdown
+++ b/doc/api/tls.markdown
@@ -578,6 +578,18 @@ 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` might contain following
+fields: `rejectUnauthorized`, `requestCert`. `callback` will be executed,
+once the renegotiation will be completed.
+
+NOTE: Could be used to request peer's certificate after secure connection
+establishment.
+
+ANOTHER NOTE: When running it from server's side, 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 7ed787ca47d..53c8b99d62b 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,40 @@ 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 (options) {
+ 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())
+ throw new Error('Failed to renegotiate');
+
+ // Ensure that we'll cycle through internal openssl's state
+ this.write('');
+
+ if (callback)
+ this.once('secure', callback);
+};
+
+TLSSocket.prototype._handleTimeout = function() {
+ this._tlsError(new Error('TLS handshake timeout'));
};
TLSSocket.prototype._tlsError = function(err) {
@@ -256,9 +292,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 +309,7 @@ TLSSocket.prototype._finishInit = function() {
debug('secure established');
this._secureEstablished = true;
+ this.setTimeout(0, this._handleTimeout);
this.emit('secure');
};
@@ -453,37 +491,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) {
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index c50e2ec6a25..88396ea2d88 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);
@@ -1159,6 +1160,17 @@ void SSLWrap::EndParser(const FunctionCallbackInfo& args) {
}
+template
+void SSLWrap::Renegotiate(const FunctionCallbackInfo& args) {
+ HandleScope scope(node_isolate);
+
+ Base* w = ObjectWrap::Unwrap(args.This());
+
+ bool yes = SSL_renegotiate(w->ssl_) == 1;
+ args.GetReturnValue().Set(yes);
+}
+
+
template
void SSLWrap::IsInitFinished(const FunctionCallbackInfo& args) {
HandleScope scope(node_isolate);
diff --git a/src/node_crypto.h b/src/node_crypto.h
index 98e0f58ec07..b1c2c7fb077 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 2b09d821636..2c364753754 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:
@@ -197,7 +215,7 @@ function runClient(options, cb) {
rejected = false;
}
- if (/_authed/g.test(out)) {
+ if (!authed && /_authed/g.test(out)) {
console.error(' * authed');
client.stdin.end('goodbye\n');
authed = true;
@@ -247,7 +265,20 @@ function runTest(testIndex) {
var connections = 0;
- var server = tls.Server(serverOptions, function(c) {
+ var renegotiated = false;
+ var server = tls.Server(serverOptions, function handleConnection(c) {
+ if (tcase.renegotiate && !renegotiated) {
+ renegotiated = true;
+ setTimeout(function() {
+ console.error('- connected, renegotiating');
+ return c.renegotiate({
+ requestCert: true,
+ rejectUnauthorized: true
+ }, handleConnection.bind(null, c));
+ }, 500);
+ return;
+ }
+
connections++;
if (c.authorized) {
console.error('- authed connection: ' +