diff options
Diffstat (limited to 'lib/tls.js')
-rw-r--r-- | lib/tls.js | 117 |
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); } |