summaryrefslogtreecommitdiff
path: root/lib/tls.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tls.js')
-rw-r--r--lib/tls.js117
1 files changed, 92 insertions, 25 deletions
diff --git a/lib/tls.js b/lib/tls.js
index c62d40f8f..36109469f 100644
--- a/lib/tls.js
+++ b/lib/tls.js
@@ -176,7 +176,6 @@ function checkServerIdentity(host, cert) {
exports.checkServerIdentity = checkServerIdentity;
-
function SlabBuffer() {
this.create();
}
@@ -701,19 +700,25 @@ EncryptedStream.prototype._pusher = function(pool, offset, length) {
function onhandshakestart() {
debug('onhandshakestart');
- var self = this, ssl = this.ssl;
+ var self = this;
+ var ssl = self.ssl;
+ var now = Date.now();
+
+ assert(now >= ssl.lastHandshakeTime);
- if (ssl.timer === null) {
- ssl.timer = setTimeout(function timeout() {
- ssl.handshakes = 0;
- ssl.timer = null;
- }, exports.CLIENT_RENEG_WINDOW * 1000);
+ if ((now - ssl.lastHandshakeTime) >= exports.CLIENT_RENEG_WINDOW * 1000) {
+ ssl.handshakes = 0;
}
- else if (++ssl.handshakes > exports.CLIENT_RENEG_LIMIT) {
+
+ var first = (ssl.lastHandshakeTime === 0);
+ ssl.lastHandshakeTime = now;
+ if (first) return;
+
+ if (++ssl.handshakes > exports.CLIENT_RENEG_LIMIT) {
// Defer the error event to the next tick. We're being called from OpenSSL's
// state machine and OpenSSL is not re-entrant. We cannot allow the user's
// callback to destroy the connection right now, it would crash and burn.
- process.nextTick(function() {
+ setImmediate(function() {
var err = new Error('TLS session renegotiation attack detected.');
if (self.cleartext) self.cleartext.emit('error', err);
});
@@ -726,6 +731,37 @@ function onhandshakedone() {
debug('onhandshakedone');
}
+function onclienthello(hello) {
+ var self = this,
+ once = false;
+
+ this.encrypted.pause();
+ this.cleartext.pause();
+ function callback(err, session) {
+ if (once) return;
+ once = true;
+
+ if (err) return self.socket.destroy(err);
+
+ self.ssl.loadSession(session);
+
+ self.encrypted.resume();
+ self.cleartext.resume();
+ }
+
+ if (hello.sessionId.length <= 0 ||
+ !this.server ||
+ !this.server.emit('resumeSession', hello.sessionId, callback)) {
+ callback(null, null);
+ }
+}
+
+
+function onnewsession(key, session) {
+ if (!this.server) return;
+ this.server.emit('newSession', key, session);
+}
+
/**
* Provides a pair of streams to do encrypted communication.
@@ -747,6 +783,7 @@ function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
events.EventEmitter.call(this);
+ this.server = options.server;
this._secureEstablished = false;
this._isServer = isServer ? true : false;
this._encWriteState = true;
@@ -769,15 +806,18 @@ function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
this._requestCert = requestCert ? true : false;
this.ssl = new Connection(this.credentials.context,
- this._isServer ? true : false,
- this._isServer ? this._requestCert : options.servername,
- this._rejectUnauthorized);
+ this._isServer ? true : false,
+ this._isServer ? this._requestCert :
+ options.servername,
+ this._rejectUnauthorized);
if (this._isServer) {
this.ssl.onhandshakestart = onhandshakestart.bind(this);
this.ssl.onhandshakedone = onhandshakedone.bind(this);
+ this.ssl.onclienthello = onclienthello.bind(this);
+ this.ssl.onnewsession = onnewsession.bind(this);
+ this.ssl.lastHandshakeTime = 0;
this.ssl.handshakes = 0;
- this.ssl.timer = null;
}
if (process.features.tls_sni) {
@@ -919,12 +959,6 @@ SecurePair.prototype.destroy = function() {
if (!this._doneFlag) {
this._doneFlag = true;
-
- if (this.ssl.timer) {
- clearTimeout(this.ssl.timer);
- this.ssl.timer = null;
- }
-
this.ssl.error = null;
this.ssl.close();
this.ssl = null;
@@ -933,6 +967,11 @@ SecurePair.prototype.destroy = function() {
self.cleartext.writable = self.cleartext.readable = false;
process.nextTick(function() {
+ if (self.cleartext._decoder) {
+ var ret = self.cleartext._decoder.end();
+ if (ret)
+ self.cleartext.emit('data', ret);
+ }
self.cleartext.emit('end');
self.encrypted.emit('close');
self.cleartext.emit('close');
@@ -1004,13 +1043,13 @@ SecurePair.prototype.error = function() {
// If true clients whose certificates are invalid for any reason will not
// be allowed to make connections. If false, they will simply be marked as
// unauthorized but secure communication will continue. By default this is
-// false.
+// true.
//
//
//
// Options:
// - requestCert. Send verify request. Default to false.
-// - rejectUnauthorized. Boolean, default to false.
+// - rejectUnauthorized. Boolean, default to true.
// - key. string.
// - cert: string.
// - ca: string or array of strings.
@@ -1059,6 +1098,10 @@ function Server(/* [options], listener */) {
// Handle option defaults:
this.setOptions(options);
+ if (!self.pfx && (!self.cert || !self.key)) {
+ throw new Error('Missing PFX or certificate + private key.');
+ }
+
var sharedCreds = crypto.createCredentials({
pfx: self.pfx,
key: self.key,
@@ -1072,6 +1115,12 @@ function Server(/* [options], listener */) {
sessionIdContext: self.sessionIdContext
});
+ var timeout = options.handshakeTimeout || (120 * 1000);
+
+ if (typeof timeout !== 'number') {
+ throw new TypeError('handshakeTimeout must be a number');
+ }
+
// constructor call
net.Server.call(this, function(socket) {
var creds = crypto.createCredentials(null, sharedCreds.context);
@@ -1081,6 +1130,7 @@ function Server(/* [options], listener */) {
self.requestCert,
self.rejectUnauthorized,
{
+ server: self,
NPNProtocols: self.NPNProtocols,
SNICallback: self.SNICallback
});
@@ -1088,7 +1138,17 @@ function Server(/* [options], listener */) {
var cleartext = pipe(pair, socket);
cleartext._controlReleased = false;
- pair.on('secure', function() {
+ function listener() {
+ pair.emit('error', new Error('TLS handshake timeout'));
+ }
+
+ if (timeout > 0) {
+ socket.setTimeout(timeout, listener);
+ }
+
+ pair.once('secure', function() {
+ socket.setTimeout(0, listener);
+
pair.cleartext.authorized = false;
pair.cleartext.npnProtocol = pair.npnProtocol;
pair.cleartext.servername = pair.servername;
@@ -1116,7 +1176,7 @@ function Server(/* [options], listener */) {
}
});
pair.on('error', function(err) {
- self.emit('clientError', err);
+ self.emit('clientError', err, this);
});
});
@@ -1233,6 +1293,11 @@ exports.connect = function(/* [port, host], options, cb */) {
var options = args[0];
var cb = args[1];
+ var defaults = {
+ rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED
+ };
+ options = util._extend(defaults, options || {});
+
var socket = options.socket ? options.socket : new net.Stream();
var sslcontext = crypto.createCredentials(options);
@@ -1247,7 +1312,10 @@ exports.connect = function(/* [port, host], options, cb */) {
});
if (options.session) {
- pair.ssl.setSession(options.session);
+ var session = options.session;
+ if (typeof session === 'string')
+ session = new Buffer(session, 'binary');
+ pair.ssl.setSession(session);
}
var cleartext = pipe(pair, socket);
@@ -1321,7 +1389,6 @@ function pipe(pair, socket) {
function onclose() {
socket.removeListener('error', onerror);
- socket.removeListener('end', onclose);
socket.removeListener('timeout', ontimeout);
}