summaryrefslogtreecommitdiff
path: root/lib/_tls_wrap.js
diff options
context:
space:
mode:
authorAnatoli Papirovski <apapirovski@mac.com>2017-09-23 14:18:28 -0400
committerRuben Bridgewater <ruben@bridgewater.de>2017-10-01 23:34:48 -0300
commit5723b5dbbc98511ab3bc56ab639fcbfcc4e69776 (patch)
tree21cb451614a6eadeefa26f2e06c9184b406b8dfd /lib/_tls_wrap.js
parentfa58c15aee056b8cf85e8f1fcc30c476686211bf (diff)
downloadandroid-node-v8-5723b5dbbc98511ab3bc56ab639fcbfcc4e69776.tar.gz
android-node-v8-5723b5dbbc98511ab3bc56ab639fcbfcc4e69776.tar.bz2
android-node-v8-5723b5dbbc98511ab3bc56ab639fcbfcc4e69776.zip
tls: improve TLSSocket & Server performance
Limit the number of closures created and functions called throughout the lifecycle of TLSSocket, Server & connect. PR-URL: https://github.com/nodejs/node/pull/15575 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Diffstat (limited to 'lib/_tls_wrap.js')
-rw-r--r--lib/_tls_wrap.js655
1 files changed, 330 insertions, 325 deletions
diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js
index 0ca5f2c970..ba39d092e9 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;
};