Skip to content

Commit

Permalink
tls: socket.renegotiate(options, callback)
Browse files Browse the repository at this point in the history
This utility function allows renegotiaion of secure connection after
establishing it.

fix nodejs#2496
  • Loading branch information
indutny committed Sep 3, 2013
1 parent 906a175 commit 0c59050
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 19 deletions.
12 changes: 12 additions & 0 deletions doc/api/tls.markdown
Expand Up @@ -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
Expand Down
61 changes: 44 additions & 17 deletions lib/_tls_wrap.js
Expand Up @@ -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);

Expand Down Expand Up @@ -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) {
Expand All @@ -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() {
Expand All @@ -272,6 +309,7 @@ TLSSocket.prototype._finishInit = function() {

debug('secure established');
this._secureEstablished = true;
this.setTimeout(0, this._handleTimeout);
this.emit('secure');
};

Expand Down Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions src/node_crypto.cc
Expand Up @@ -805,6 +805,7 @@ void SSLWrap<Base>::AddMethods(Handle<FunctionTemplate> 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);
Expand Down Expand Up @@ -1159,6 +1160,17 @@ void SSLWrap<Base>::EndParser(const FunctionCallbackInfo<Value>& args) {
}


template <class Base>
void SSLWrap<Base>::Renegotiate(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(node_isolate);

Base* w = ObjectWrap::Unwrap<Base>(args.This());

bool yes = SSL_renegotiate(w->ssl_) == 1;
args.GetReturnValue().Set(yes);
}


template <class Base>
void SSLWrap<Base>::IsInitFinished(const FunctionCallbackInfo<Value>& args) {
HandleScope scope(node_isolate);
Expand Down
1 change: 1 addition & 0 deletions src/node_crypto.h
Expand Up @@ -172,6 +172,7 @@ class SSLWrap {
static void GetCurrentCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ReceivedShutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);

#ifdef OPENSSL_NPN_NEGOTIATED
static void GetNegotiatedProto(
Expand Down
35 changes: 33 additions & 2 deletions test/simple/test-tls-server-verify.js
Expand Up @@ -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 },
Expand All @@ -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 },
Expand All @@ -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 },
Expand All @@ -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 },
Expand All @@ -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:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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: ' +
Expand Down

0 comments on commit 0c59050

Please sign in to comment.