mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
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>
This commit is contained in:
1 parent
fa58c15aee
commit
5723b5dbbc
1 file changed
+333
-328
+333
-328
@@ -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);
|
||||
socket._handle.setOCSPResponse(response);
|
||||
requestOCSPDone(socket);
|
||||
};
|
||||
|
||||
socket.server.emit('OCSPRequest',
|
||||
ctx.getCertificate(),
|
||||
ctx.getIssuer(),
|
||||
onOCSP);
|
||||
}
|
||||
|
||||
function requestOCSPDone(socket) {
|
||||
try {
|
||||
socket._handle.certCbDone();
|
||||
} catch (e) {
|
||||
socket.destroy(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onclienthello(hello) {
|
||||
var self = this;
|
||||
|
||||
loadSession(self, hello, function(err) {
|
||||
if (err)
|
||||
return self.destroy(err);
|
||||
|
||||
self._handle.endParser();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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 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,34 +399,43 @@ 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;
|
||||
Object.defineProperty(handle, 'reading', {
|
||||
get: function get() {
|
||||
return res.reading;
|
||||
},
|
||||
set: function set(value) {
|
||||
res.reading = value;
|
||||
}
|
||||
});
|
||||
this[kRes] = res;
|
||||
defineHandleReading(this, handle);
|
||||
|
||||
this.on('close', function() {
|
||||
// Make sure we are not doing it on OpenSSL's stack
|
||||
setImmediate(destroySSL, this);
|
||||
res = null;
|
||||
});
|
||||
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: () => {
|
||||
return socket[kRes].reading;
|
||||
},
|
||||
set: (value) => {
|
||||
socket[kRes].reading = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
Reference in new issue
Block a user