diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 0ca5f2c9705de8..ba39d092e9c1bb 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -37,211 +37,228 @@ const tls_wrap = process.binding('tls_wrap'); const TCP = process.binding('tcp_wrap').TCP; const Pipe = process.binding('pipe_wrap').Pipe; const errors = require('internal/errors'); +const kConnectOptions = Symbol('connect-options'); const kDisableRenegotiation = Symbol('disable-renegotiation'); +const kErrorEmitted = Symbol('error-emitted'); +const kHandshakeTimeout = Symbol('handshake-timeout'); +const kRes = Symbol('res'); +const kSNICallback = Symbol('snicallback'); + +const noop = () => {}; function onhandshakestart() { debug('onhandshakestart'); - var self = this; - var ssl = self._handle; - var now = Timer.now(); + const owner = this.owner; + const now = Timer.now(); - assert(now >= ssl.lastHandshakeTime); + assert(now >= this.lastHandshakeTime); - if ((now - ssl.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) { - ssl.handshakes = 0; + if ((now - this.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) { + this.handshakes = 0; } - var first = (ssl.lastHandshakeTime === 0); - ssl.lastHandshakeTime = now; + const first = (this.lastHandshakeTime === 0); + this.lastHandshakeTime = now; if (first) return; - if (++ssl.handshakes > tls.CLIENT_RENEG_LIMIT) { + if (++this.handshakes > tls.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. - setImmediate(function() { - var err = new errors.Error('ERR_TLS_SESSION_ATTACK'); - self._emitTLSError(err); - }); + setImmediate(emitSessionAttackError, owner); } - if (this[kDisableRenegotiation] && ssl.handshakes > 0) { + if (owner[kDisableRenegotiation] && this.handshakes > 0) { const err = new Error('TLS session renegotiation disabled for this socket'); - self._emitTLSError(err); + owner._emitTLSError(err); } } +function emitSessionAttackError(socket) { + socket._emitTLSError(new errors.Error('ERR_TLS_SESSION_ATTACK')); +} function onhandshakedone() { - // for future use debug('onhandshakedone'); - this._finishInit(); + + const owner = this.owner; + + // `newSession` callback wasn't called yet + if (owner._newSessionPending) { + owner._securePending = true; + return; + } + + owner._finishInit(); } -function loadSession(self, hello, cb) { +function loadSession(hello) { + const owner = this.owner; + var once = false; function onSession(err, session) { if (once) - return cb(new errors.Error('ERR_MULTIPLE_CALLBACK')); + return owner.destroy(new errors.Error('ERR_MULTIPLE_CALLBACK')); once = true; if (err) - return cb(err); + return owner.destroy(err); - if (!self._handle) - return cb(new errors.Error('ERR_SOCKET_CLOSED')); + if (owner._handle === null) + return owner.destroy(new errors.Error('ERR_SOCKET_CLOSED')); - self._handle.loadSession(session); - cb(null); + owner._handle.loadSession(session); + owner._handle.endParser(); } if (hello.sessionId.length <= 0 || hello.tlsTicket || - self.server && - !self.server.emit('resumeSession', hello.sessionId, onSession)) { - cb(null); + owner.server && + !owner.server.emit('resumeSession', hello.sessionId, onSession)) { + owner._handle.endParser(); } } -function loadSNI(self, servername, cb) { - if (!servername || !self._SNICallback) - return cb(null); +function loadSNI(info) { + const owner = this.owner; + const servername = info.servername; + if (!servername || !owner._SNICallback) + return requestOCSP(owner, info); - var once = false; - self._SNICallback(servername, function(err, context) { + let once = false; + owner._SNICallback(servername, (err, context) => { if (once) - return cb(new errors.Error('ERR_MULTIPLE_CALLBACK')); + return owner.destroy(new errors.Error('ERR_MULTIPLE_CALLBACK')); once = true; if (err) - return cb(err); + return owner.destroy(err); - if (!self._handle) - return cb(new errors.Error('ERR_SOCKET_CLOSED')); + if (owner._handle === null) + return owner.destroy(new errors.Error('ERR_SOCKET_CLOSED')); // TODO(indutny): eventually disallow raw `SecureContext` if (context) - self._handle.sni_context = context.context || context; + owner._handle.sni_context = context.context || context; - cb(null, self._handle.sni_context); + requestOCSP(owner, info); }); } -function requestOCSP(self, hello, ctx, cb) { - if (!hello.OCSPRequest || !self.server) - return cb(null); +function requestOCSP(socket, info) { + if (!info.OCSPRequest || !socket.server) + return requestOCSPDone(socket); - if (!ctx) - ctx = self.server._sharedCreds; + let ctx = socket._handle.sni_context; - // TLS socket is using a `net.Server` instead of a tls.TLSServer. - // Some TLS properties like `server._sharedCreds` will not be present - if (!ctx) - return cb(null); + if (!ctx) { + ctx = socket.server._sharedCreds; + + // TLS socket is using a `net.Server` instead of a tls.TLSServer. + // Some TLS properties like `server._sharedCreds` will not be present + if (!ctx) + return requestOCSPDone(socket); + } // TODO(indutny): eventually disallow raw `SecureContext` if (ctx.context) ctx = ctx.context; - if (self.server.listenerCount('OCSPRequest') === 0) { - return cb(null); - } else { - self.server.emit('OCSPRequest', - ctx.getCertificate(), - ctx.getIssuer(), - onOCSP); + if (socket.server.listenerCount('OCSPRequest') === 0) { + return requestOCSPDone(socket); } - var once = false; - function onOCSP(err, response) { + let once = false; + const onOCSP = (err, response) => { if (once) - return cb(new errors.Error('ERR_MULTIPLE_CALLBACK')); + return socket.destroy(new errors.Error('ERR_MULTIPLE_CALLBACK')); once = true; if (err) - return cb(err); + return socket.destroy(err); - if (!self._handle) - return cb(new errors.Error('ERR_SOCKET_CLOSED')); + if (socket._handle === null) + return socket.destroy(new errors.Error('ERR_SOCKET_CLOSED')); if (response) - self._handle.setOCSPResponse(response); - cb(null); - } -} - - -function onclienthello(hello) { - var self = this; - - loadSession(self, hello, function(err) { - if (err) - return self.destroy(err); + socket._handle.setOCSPResponse(response); + requestOCSPDone(socket); + }; - self._handle.endParser(); - }); + socket.server.emit('OCSPRequest', + ctx.getCertificate(), + ctx.getIssuer(), + onOCSP); } - -function oncertcb(info) { - var self = this; - var servername = info.servername; - - loadSNI(self, servername, function(err, ctx) { - if (err) - return self.destroy(err); - requestOCSP(self, info, ctx, function(err) { - if (err) - return self.destroy(err); - - if (!self._handle) - return self.destroy(new errors.Error('ERR_SOCKET_CLOSED')); - - try { - self._handle.certCbDone(); - } catch (e) { - self.destroy(e); - } - }); - }); +function requestOCSPDone(socket) { + try { + socket._handle.certCbDone(); + } catch (e) { + socket.destroy(e); + } } function onnewsession(key, session) { - if (!this.server) + const owner = this.owner; + + if (!owner.server) return; - var self = this; var once = false; - - this._newSessionPending = true; - if (!this.server.emit('newSession', key, session, done)) - done(); - - function done() { + const done = () => { if (once) return; once = true; - if (!self._handle) - return self.destroy(new errors.Error('ERR_SOCKET_CLOSED')); + if (owner._handle === null) + return owner.destroy(new errors.Error('ERR_SOCKET_CLOSED')); - self._handle.newSessionDone(); + this.newSessionDone(); - self._newSessionPending = false; - if (self._securePending) - self._finishInit(); - self._securePending = false; - } + owner._newSessionPending = false; + if (owner._securePending) + owner._finishInit(); + owner._securePending = false; + }; + + owner._newSessionPending = true; + if (!owner.server.emit('newSession', key, session, done)) + done(); } function onocspresponse(resp) { - this.emit('OCSPResponse', resp); + this.owner.emit('OCSPResponse', resp); +} + +function onerror(err) { + const owner = this.owner; + + if (owner._writableState.errorEmitted) + return; + + // Destroy socket if error happened before handshake's finish + if (!owner._secureEstablished) { + // When handshake fails control is not yet released, + // so self._tlsError will return null instead of actual error + owner.destroy(err); + } else if (owner._tlsOptions.isServer && + owner._rejectUnauthorized && + /peer did not return a certificate/.test(err.message)) { + // Ignore server's authorization errors + owner.destroy(); + } else { + // Throw error + owner._emitTLSError(err); + } + + owner._writableState.errorEmitted = true; } function initRead(tls, wrapped) { @@ -278,6 +295,7 @@ function TLSSocket(socket, options) { this.alpnProtocol = null; this.authorized = false; this.authorizationError = null; + this[kRes] = null; // Wrap plain JS Stream into StreamWrap var wrap; @@ -369,7 +387,6 @@ TLSSocket.prototype.disableRenegotiation = function disableRenegotiation() { }; TLSSocket.prototype._wrapHandle = function(wrap) { - var res; var handle; if (wrap) @@ -382,33 +399,42 @@ TLSSocket.prototype._wrapHandle = function(wrap) { } // Wrap socket's handle - var context = options.secureContext || - options.credentials || - tls.createSecureContext(options); - res = tls_wrap.wrap(handle._externalStream, - context.context, - !!options.isServer); + const context = options.secureContext || + options.credentials || + tls.createSecureContext(options); + const res = tls_wrap.wrap(handle._externalStream, + context.context, + !!options.isServer); res._parent = handle; res._parentWrap = wrap; res._secureContext = context; res.reading = handle.reading; + this[kRes] = res; + defineHandleReading(this, handle); + + this.on('close', onSocketCloseDestroySSL); + + return res; +}; + +// This eliminates a cyclic reference to TLSWrap +// Ref: https://github.com/nodejs/node/commit/f7620fb96d339f704932f9bb9a0dceb9952df2d4 +function defineHandleReading(socket, handle) { Object.defineProperty(handle, 'reading', { - get: function get() { - return res.reading; + get: () => { + return socket[kRes].reading; }, - set: function set(value) { - res.reading = value; + set: (value) => { + socket[kRes].reading = value; } }); +} - this.on('close', function() { - // Make sure we are not doing it on OpenSSL's stack - setImmediate(destroySSL, this); - res = null; - }); - - return res; -}; +function onSocketCloseDestroySSL() { + // Make sure we are not doing it on OpenSSL's stack + setImmediate(destroySSL, this); + this[kRes] = null; +} function destroySSL(self) { self._destroySSL(); @@ -425,7 +451,6 @@ TLSSocket.prototype._destroySSL = function _destroySSL() { }; TLSSocket.prototype._init = function(socket, wrap) { - var self = this; var options = this._tlsOptions; var ssl = this._handle; @@ -448,11 +473,11 @@ TLSSocket.prototype._init = function(socket, wrap) { ssl.setVerifyMode(requestCert, rejectUnauthorized); if (options.isServer) { - ssl.onhandshakestart = () => onhandshakestart.call(this); - ssl.onhandshakedone = () => onhandshakedone.call(this); - ssl.onclienthello = (hello) => onclienthello.call(this, hello); - ssl.oncertcb = (info) => oncertcb.call(this, info); - ssl.onnewsession = (key, session) => onnewsession.call(this, key, session); + ssl.onhandshakestart = onhandshakestart; + ssl.onhandshakedone = onhandshakedone; + ssl.onclienthello = loadSession; + ssl.oncertcb = loadSNI; + ssl.onnewsession = onnewsession; ssl.lastHandshakeTime = 0; ssl.handshakes = 0; @@ -465,35 +490,15 @@ TLSSocket.prototype._init = function(socket, wrap) { ssl.enableCertCb(); } } else { - ssl.onhandshakestart = function() {}; - ssl.onhandshakedone = () => this._finishInit(); - ssl.onocspresponse = (resp) => onocspresponse.call(this, resp); + ssl.onhandshakestart = noop; + ssl.onhandshakedone = this._finishInit.bind(this); + ssl.onocspresponse = onocspresponse; if (options.session) ssl.setSession(options.session); } - ssl.onerror = function(err) { - if (self._writableState.errorEmitted) - return; - - // Destroy socket if error happened before handshake's finish - if (!self._secureEstablished) { - // When handshake fails control is not yet released, - // so self._tlsError will return null instead of actual error - self.destroy(err); - } else if (options.isServer && - rejectUnauthorized && - /peer did not return a certificate/.test(err.message)) { - // Ignore server's authorization errors - self.destroy(); - } else { - // Throw error - self._emitTLSError(err); - } - - self._writableState.errorEmitted = true; - }; + ssl.onerror = onerror; // If custom SNICallback was given, or if // there're SNI contexts to perform match against - @@ -526,17 +531,15 @@ TLSSocket.prototype._init = function(socket, wrap) { // To prevent assertion in afterConnect() and properly kick off readStart this.connecting = socket.connecting || !socket._handle; - socket.once('connect', function() { - self.connecting = false; - self.emit('connect'); + socket.once('connect', () => { + this.connecting = false; + this.emit('connect'); }); } // Assume `tls.connect()` if (wrap) { - wrap.on('error', function(err) { - self._emitTLSError(err); - }); + wrap.on('error', (err) => this._emitTLSError(err)); } else { assert(!socket); this.connecting = true; @@ -544,15 +547,15 @@ TLSSocket.prototype._init = function(socket, wrap) { }; TLSSocket.prototype.renegotiate = function(options, callback) { - var requestCert = this._requestCert; - var rejectUnauthorized = this._rejectUnauthorized; - if (this.destroyed) return; - if (typeof options.requestCert !== 'undefined') + let requestCert = this._requestCert; + let rejectUnauthorized = this._rejectUnauthorized; + + if (options.requestCert !== undefined) requestCert = !!options.requestCert; - if (typeof options.rejectUnauthorized !== 'undefined') + if (options.rejectUnauthorized !== undefined) rejectUnauthorized = !!options.rejectUnauthorized; if (requestCert !== this._requestCert || @@ -572,9 +575,7 @@ TLSSocket.prototype.renegotiate = function(options, callback) { this.write(''); if (callback) { - this.once('secure', function() { - callback(null); - }); + this.once('secure', () => callback(null)); } return true; @@ -614,18 +615,12 @@ TLSSocket.prototype._releaseControl = function() { }; TLSSocket.prototype._finishInit = function() { - // `newSession` callback wasn't called yet - if (this._newSessionPending) { - this._securePending = true; - return; - } - if (process.features.tls_npn) { this.npnProtocol = this._handle.getNegotiatedProtocol(); } if (process.features.tls_alpn) { - this.alpnProtocol = this.ssl.getALPNNegotiatedProtocol(); + this.alpnProtocol = this._handle.getALPNNegotiatedProtocol(); } if (process.features.tls_sni && this._tlsOptions.isServer) { @@ -641,9 +636,7 @@ TLSSocket.prototype._finishInit = function() { TLSSocket.prototype._start = function() { if (this.connecting) { - this.once('connect', function() { - this._start(); - }); + this.once('connect', this._start); return; } @@ -717,6 +710,64 @@ TLSSocket.prototype.getProtocol = function() { // TODO: support anonymous (nocert) and PSK +function onSocketSecure() { + if (this._requestCert) { + const verifyError = this._handle.verifyError(); + if (verifyError) { + this.authorizationError = verifyError.code; + + if (this._rejectUnauthorized) + this.destroy(); + } else { + this.authorized = true; + } + } + + if (!this.destroyed && this._releaseControl()) + this._tlsOptions.server.emit('secureConnection', this); +} + +function onSocketTLSError(err) { + if (!this._controlReleased && !this[kErrorEmitted]) { + this[kErrorEmitted] = true; + this._tlsOptions.server.emit('tlsClientError', err, this); + } +} + +function onSocketClose(err) { + // Closed because of error - no need to emit it twice + if (err) + return; + + // Emit ECONNRESET + if (!this._controlReleased && !this[kErrorEmitted]) { + this[kErrorEmitted] = true; + const connReset = new Error('socket hang up'); + connReset.code = 'ECONNRESET'; + this._tlsOptions.server.emit('tlsClientError', connReset, this); + } +} + +function tlsConnectionListener(rawSocket) { + const socket = new TLSSocket(rawSocket, { + secureContext: this._sharedCreds, + isServer: true, + server: this, + requestCert: this.requestCert, + rejectUnauthorized: this.rejectUnauthorized, + handshakeTimeout: this[kHandshakeTimeout], + NPNProtocols: this.NPNProtocols, + ALPNProtocols: this.ALPNProtocols, + SNICallback: this[kSNICallback] || SNICallback + }); + + socket.on('secure', onSocketSecure); + + socket[kErrorEmitted] = false; + socket.on('close', onSocketClose); + socket.on('_tlsError', onSocketTLSError); +} + // AUTHENTICATION MODES // // There are several levels of authentication that TLS/SSL supports. @@ -797,95 +848,43 @@ function Server(options, listener) { this._contexts = []; - var self = this; - // Handle option defaults: this.setOptions(options); var sharedCreds = tls.createSecureContext({ - pfx: self.pfx, - key: self.key, - passphrase: self.passphrase, - cert: self.cert, - ca: self.ca, - ciphers: self.ciphers, - ecdhCurve: self.ecdhCurve, - dhparam: self.dhparam, - secureProtocol: self.secureProtocol, - secureOptions: self.secureOptions, - honorCipherOrder: self.honorCipherOrder, - crl: self.crl, - sessionIdContext: self.sessionIdContext + pfx: this.pfx, + key: this.key, + passphrase: this.passphrase, + cert: this.cert, + ca: this.ca, + ciphers: this.ciphers, + ecdhCurve: this.ecdhCurve, + dhparam: this.dhparam, + secureProtocol: this.secureProtocol, + secureOptions: this.secureOptions, + honorCipherOrder: this.honorCipherOrder, + crl: this.crl, + sessionIdContext: this.sessionIdContext }); this._sharedCreds = sharedCreds; - var timeout = options.handshakeTimeout || (120 * 1000); + this[kHandshakeTimeout] = options.handshakeTimeout || (120 * 1000); + this[kSNICallback] = options.SNICallback; - if (typeof timeout !== 'number') { + if (typeof this[kHandshakeTimeout] !== 'number') { throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'timeout', 'number'); } - if (self.sessionTimeout) { - sharedCreds.context.setSessionTimeout(self.sessionTimeout); + if (this.sessionTimeout) { + sharedCreds.context.setSessionTimeout(this.sessionTimeout); } - if (self.ticketKeys) { - sharedCreds.context.setTicketKeys(self.ticketKeys); + if (this.ticketKeys) { + sharedCreds.context.setTicketKeys(this.ticketKeys); } // constructor call - net.Server.call(this, function(raw_socket) { - var socket = new TLSSocket(raw_socket, { - secureContext: sharedCreds, - isServer: true, - server: self, - requestCert: self.requestCert, - rejectUnauthorized: self.rejectUnauthorized, - handshakeTimeout: timeout, - NPNProtocols: self.NPNProtocols, - ALPNProtocols: self.ALPNProtocols, - SNICallback: options.SNICallback || SNICallback - }); - - socket.on('secure', function() { - if (socket._requestCert) { - var verifyError = socket._handle.verifyError(); - if (verifyError) { - socket.authorizationError = verifyError.code; - - if (socket._rejectUnauthorized) - socket.destroy(); - } else { - socket.authorized = true; - } - } - - if (!socket.destroyed && socket._releaseControl()) - self.emit('secureConnection', socket); - }); - - var errorEmitted = false; - socket.on('close', function(err) { - // Closed because of error - no need to emit it twice - if (err) - return; - - // Emit ECONNRESET - if (!socket._controlReleased && !errorEmitted) { - errorEmitted = true; - var connReset = new Error('socket hang up'); - connReset.code = 'ECONNRESET'; - self.emit('tlsClientError', connReset, socket); - } - }); - - socket.on('_tlsError', function(err) { - if (!socket._controlReleased && !errorEmitted) { - errorEmitted = true; - self.emit('tlsClientError', err, socket); - } - }); - }); + net.Server.call(this, tlsConnectionListener); if (listener) { this.on('secureConnection', listener); @@ -971,16 +970,17 @@ Server.prototype.addContext = function(servername, context) { }; function SNICallback(servername, callback) { - var ctx; + const contexts = this.server._contexts; - this.server._contexts.some(function(elem) { + for (var i = 0; i < contexts.length; i++) { + const elem = contexts[i]; if (elem[0].test(servername)) { - ctx = elem[1]; - return true; + callback(null, elem[1]); + return; } - }); + } - callback(null, ctx); + callback(null, undefined); } @@ -1017,6 +1017,66 @@ function normalizeConnectArgs(listArgs) { return (cb) ? [options, cb] : [options]; } +function onConnectSecure() { + const options = this[kConnectOptions]; + + // Check the size of DHE parameter above minimum requirement + // specified in options. + const ekeyinfo = this.getEphemeralKeyInfo(); + if (ekeyinfo.type === 'DH' && ekeyinfo.size < options.minDHSize) { + const err = new errors.Error('ERR_TLS_DH_PARAM_SIZE', ekeyinfo.size); + this.emit('error', err); + this.destroy(); + return; + } + + let verifyError = this._handle.verifyError(); + + // Verify that server's identity matches it's certificate's names + // Unless server has resumed our existing session + if (!verifyError && !this.isSessionReused()) { + const hostname = options.servername || + options.host || + (options.socket && options.socket._host) || + 'localhost'; + const cert = this.getPeerCertificate(); + verifyError = options.checkServerIdentity(hostname, cert); + } + + if (verifyError) { + this.authorized = false; + this.authorizationError = verifyError.code || verifyError.message; + + if (options.rejectUnauthorized) { + this.destroy(verifyError); + return; + } else { + this.emit('secureConnect'); + } + } else { + this.authorized = true; + this.emit('secureConnect'); + } + + // Uncork incoming data + this.removeListener('end', onConnectEnd); +} + +function onConnectEnd() { + // NOTE: This logic is shared with _http_client.js + if (!this._hadError) { + const options = this[kConnectOptions]; + this._hadError = true; + const error = new Error('socket hang up'); + error.code = 'ECONNRESET'; + error.path = options.path; + error.host = options.host; + error.port = options.port; + error.localAddress = options.localAddress; + this.destroy(error); + } +} + exports.connect = function(...args /* [port,] [host,] [options,] [cb] */) { args = normalizeConnectArgs(args); var options = args[0]; @@ -1040,10 +1100,6 @@ exports.connect = function(...args /* [port,] [host,] [options,] [cb] */) { 'options.minDHSize is not a positive number: ' + options.minDHSize); - var hostname = options.servername || - options.host || - (options.socket && options.socket._host) || - 'localhost'; const NPN = {}; const ALPN = {}; const context = options.secureContext || tls.createSecureContext(options); @@ -1062,6 +1118,8 @@ exports.connect = function(...args /* [port,] [host,] [options,] [cb] */) { requestOCSP: options.requestOCSP }); + socket[kConnectOptions] = options; + if (cb) socket.once('secureConnect', cb); @@ -1074,9 +1132,7 @@ exports.connect = function(...args /* [port,] [host,] [options,] [cb] */) { localAddress: options.localAddress, lookup: options.lookup }; - socket.connect(connectOpt, function() { - socket._start(); - }); + socket.connect(connectOpt, socket._start); } socket._releaseControl(); @@ -1090,59 +1146,8 @@ exports.connect = function(...args /* [port,] [host,] [options,] [cb] */) { if (options.socket) socket._start(); - socket.on('secure', function() { - // Check the size of DHE parameter above minimum requirement - // specified in options. - var ekeyinfo = socket.getEphemeralKeyInfo(); - if (ekeyinfo.type === 'DH' && ekeyinfo.size < options.minDHSize) { - var err = new errors.Error('ERR_TLS_DH_PARAM_SIZE', ekeyinfo.size); - socket.emit('error', err); - socket.destroy(); - return; - } - - var verifyError = socket._handle.verifyError(); - - // Verify that server's identity matches it's certificate's names - // Unless server has resumed our existing session - if (!verifyError && !socket.isSessionReused()) { - var cert = socket.getPeerCertificate(); - verifyError = options.checkServerIdentity(hostname, cert); - } - - if (verifyError) { - socket.authorized = false; - socket.authorizationError = verifyError.code || verifyError.message; - - if (options.rejectUnauthorized) { - socket.destroy(verifyError); - return; - } else { - socket.emit('secureConnect'); - } - } else { - socket.authorized = true; - socket.emit('secureConnect'); - } - - // Uncork incoming data - socket.removeListener('end', onHangUp); - }); - - function onHangUp() { - // NOTE: This logic is shared with _http_client.js - if (!socket._hadError) { - socket._hadError = true; - var error = new Error('socket hang up'); - error.code = 'ECONNRESET'; - error.path = options.path; - error.host = options.host; - error.port = options.port; - error.localAddress = options.localAddress; - socket.destroy(error); - } - } - socket.once('end', onHangUp); + socket.on('secure', onConnectSecure); + socket.once('end', onConnectEnd); return socket; };