/lib/_tls_wrap.js
JavaScript | 971 lines | 651 code | 160 blank | 160 comment | 148 complexity | c033913688f47700566e4f9ed05920ea MD5 | raw file
Possible License(s): 0BSD, Apache-2.0, MPL-2.0-no-copyleft-exception, JSON, WTFPL, CC-BY-SA-3.0, Unlicense, ISC, BSD-3-Clause, MIT, AGPL-3.0
- // Copyright Joyent, Inc. and other Node contributors.
- //
- // // Emit `beforeExit` if the loop became alive either after emitting
- // event, or after running some callbacks.
- //
- // Permission is hereby granted, free of charge, to any person obtaining a
- // copy of this software and associated documentation files (the
- // "Software"), to deal in the Software without restriction, including
- // without limitation the rights to use, copy, modify, merge, publish,
- // distribute, sublicense, and/or sell copies of the Software, and to permit
- // persons to whom the Software is furnished to do so, subject to the
- // following conditions:
- //
- // The above copyright notice and this permission notice shall be included
- // in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
- // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
- // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
- // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
- // USE OR OTHER DEALINGS IN THE SOFTWARE.
- 'use strict';
- var assert = require('assert');
- var crypto = require('crypto');
- var net = require('net');
- var tls = require('tls');
- var util = require('util');
- var listenerCount = require('events').listenerCount;
- var common = require('_tls_common');
- var constants = require('constants');
- var Timer = process.binding('timer_wrap').Timer;
- var tls_wrap = process.binding('tls_wrap');
- // Lazy load
- var tls_legacy;
- var debug = util.debuglog('tls');
- function onhandshakestart() {
- debug('onhandshakestart');
- var self = this;
- var ssl = self.ssl;
- var now = Timer.now();
- assert(now >= ssl.lastHandshakeTime);
- if ((now - ssl.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) {
- ssl.handshakes = 0;
- }
- var first = (ssl.lastHandshakeTime === 0);
- ssl.lastHandshakeTime = now;
- if (first) return;
- if (++ssl.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 Error('TLS session renegotiation attack detected.');
- self._tlsError(err);
- });
- }
- }
- function onhandshakedone() {
- // for future use
- debug('onhandshakedone');
- this._finishInit();
- }
- function loadSession(self, hello, cb) {
- var once = false;
- function onSession(err, session) {
- if (once)
- return cb(new Error('TLS session callback was called 2 times'));
- once = true;
- if (err)
- return cb(err);
- // NOTE: That we have disabled OpenSSL's internal session storage in
- // `node_crypto.cc` and hence its safe to rely on getting servername only
- // from clienthello or this place.
- var ret = self.ssl.loadSession(session);
- cb(null, ret);
- }
- if (hello.sessionId.length <= 0 ||
- hello.tlsTicket ||
- self.server &&
- !self.server.emit('resumeSession', hello.sessionId, onSession)) {
- cb(null);
- }
- }
- function loadSNI(self, servername, cb) {
- if (!servername || !self._SNICallback)
- return cb(null);
- var once = false;
- self._SNICallback(servername, function(err, context) {
- if (once)
- return cb(new Error('TLS SNI callback was called 2 times'));
- once = true;
- if (err)
- return cb(err);
- // TODO(indutny): eventually disallow raw `SecureContext`
- if (context)
- self.ssl.sni_context = context.context || context;
- cb(null, self.ssl.sni_context);
- });
- }
- function requestOCSP(self, hello, ctx, cb) {
- if (!hello.OCSPRequest || !self.server)
- return cb(null);
- if (!ctx)
- ctx = self.server._sharedCreds;
- if (ctx.context)
- ctx = ctx.context;
- if (listenerCount(self.server, 'OCSPRequest') === 0) {
- return cb(null);
- } else {
- self.server.emit('OCSPRequest',
- ctx.getCertificate(),
- ctx.getIssuer(),
- onOCSP);
- }
- var once = false;
- function onOCSP(err, response) {
- if (once)
- return cb(new Error('TLS OCSP callback was called 2 times'));
- once = true;
- if (err)
- return cb(err);
- if (response)
- self.ssl.setOCSPResponse(response);
- cb(null);
- }
- }
- function onclienthello(hello) {
- var self = this;
- loadSession(self, hello, function(err, session) {
- if (err)
- return self.destroy(err);
- // Servername came from SSL session
- // NOTE: TLS Session ticket doesn't include servername information
- //
- // Another note, From RFC3546:
- //
- // If, on the other hand, the older
- // session is resumed, then the server MUST ignore extensions appearing
- // in the client hello, and send a server hello containing no
- // extensions; in this case the extension functionality negotiated
- // during the original session initiation is applied to the resumed
- // session.
- //
- // Therefore we should account session loading when dealing with servername
- var servername = session && session.servername || hello.servername;
- loadSNI(self, servername, function(err, ctx) {
- if (err)
- return self.destroy(err);
- requestOCSP(self, hello, ctx, function(err) {
- if (err)
- return self.destroy(err);
- self.ssl.endParser();
- });
- });
- });
- }
- function onnewsession(key, session) {
- if (!this.server)
- return;
- var self = this;
- var once = false;
- this._newSessionPending = true;
- this.server.emit('newSession', key, session, function() {
- if (once)
- return;
- once = true;
- self.ssl.newSessionDone();
- self._newSessionPending = false;
- if (self._securePending)
- self._finishInit();
- self._securePending = false;
- });
- }
- function onocspresponse(resp) {
- this.emit('OCSPResponse', resp);
- }
- /**
- * Provides a wrap of socket stream to do encrypted communication.
- */
- function TLSSocket(socket, options) {
- // Disallow wrapping TLSSocket in TLSSocket
- assert(!(socket instanceof TLSSocket));
- net.Socket.call(this, {
- handle: socket && socket._handle,
- allowHalfOpen: socket && socket.allowHalfOpen,
- readable: false,
- writable: false
- });
- if (socket) {
- this._parent = socket;
- // To prevent assertion in afterConnect()
- this._connecting = socket._connecting;
- }
- this._tlsOptions = options;
- this._secureEstablished = false;
- this._securePending = false;
- this._newSessionPending = false;
- this._controlReleased = false;
- this._SNICallback = null;
- this.ssl = null;
- this.servername = null;
- this.npnProtocol = null;
- this.authorized = false;
- this.authorizationError = null;
- // Just a documented property to make secure sockets
- // distinguishable from regular ones.
- this.encrypted = true;
- this.on('error', this._tlsError);
- if (!this._handle) {
- this.once('connect', function() {
- this._init(null);
- });
- } else {
- this._init(socket);
- }
- // Make sure to setup all required properties like: `_connecting` before
- // starting the flow of the data
- this.readable = true;
- this.writable = true;
- this.read(0);
- }
- util.inherits(TLSSocket, net.Socket);
- exports.TLSSocket = TLSSocket;
- TLSSocket.prototype._init = function(socket) {
- assert(this._handle);
- // lib/net.js expect this value to be non-zero if write hasn't been flushed
- // immediately
- // TODO(indutny): rewise this solution, it might be 1 before handshake and
- // represent real writeQueueSize during regular writes.
- this._handle.writeQueueSize = 1;
- var self = this;
- var options = this._tlsOptions;
- // Wrap socket's handle
- var context = options.secureContext ||
- options.credentials ||
- tls.createSecureContext();
- this.ssl = tls_wrap.wrap(this._handle, context.context, options.isServer);
- this.server = options.server || null;
- // For clients, we will always have either a given ca list or be using
- // default one
- var requestCert = !!options.requestCert || !options.isServer,
- rejectUnauthorized = !!options.rejectUnauthorized;
- this._requestCert = requestCert;
- this._rejectUnauthorized = rejectUnauthorized;
- if (requestCert || rejectUnauthorized)
- this.ssl.setVerifyMode(requestCert, rejectUnauthorized);
- if (options.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;
- if (this.server &&
- (listenerCount(this.server, 'resumeSession') > 0 ||
- listenerCount(this.server, 'newSession') > 0 ||
- listenerCount(this.server, 'OCSPRequest') > 0)) {
- this.ssl.enableSessionCallbacks();
- }
- } else {
- this.ssl.onhandshakestart = function() {};
- this.ssl.onhandshakedone = this._finishInit.bind(this);
- this.ssl.onocspresponse = onocspresponse.bind(this);
- if (options.session)
- this.ssl.setSession(options.session);
- }
- this.ssl.onerror = function(err) {
- if (self._writableState.errorEmitted)
- return;
- self._writableState.errorEmitted = true;
- // Destroy socket if error happened before handshake's finish
- if (!this._secureEstablished) {
- self._tlsError(err);
- self.destroy();
- } 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._tlsError(err);
- }
- };
- // If custom SNICallback was given, or if
- // there're SNI contexts to perform match against -
- // set `.onsniselect` callback.
- if (process.features.tls_sni &&
- options.isServer &&
- options.server &&
- (options.SNICallback !== SNICallback ||
- options.server._contexts.length)) {
- assert(typeof options.SNICallback === 'function');
- this._SNICallback = options.SNICallback;
- this.ssl.enableHelloParser();
- }
- if (process.features.tls_npn && options.NPNProtocols)
- this.ssl.setNPNProtocols(options.NPNProtocols);
- if (options.handshakeTimeout > 0)
- this.setTimeout(options.handshakeTimeout, this._handleTimeout);
- // Socket already has some buffered data - emulate receiving it
- if (socket && socket._readableState.length) {
- var buf;
- while ((buf = socket.read()) !== null)
- this.ssl.receive(buf);
- }
- };
- TLSSocket.prototype.renegotiate = function(options, callback) {
- var requestCert = this._requestCert,
- rejectUnauthorized = this._rejectUnauthorized;
- if (typeof options.requestCert !== 'undefined')
- requestCert = !!options.requestCert;
- if (typeof options.rejectUnauthorized !== 'undefined')
- rejectUnauthorized = !!options.rejectUnauthorized;
- if (requestCert !== this._requestCert ||
- rejectUnauthorized !== this._rejectUnauthorized) {
- this.ssl.setVerifyMode(requestCert, rejectUnauthorized);
- this._requestCert = requestCert;
- this._rejectUnauthorized = rejectUnauthorized;
- }
- if (!this.ssl.renegotiate()) {
- if (callback) {
- process.nextTick(function() {
- callback(new Error('Failed to renegotiate'));
- });
- }
- return false;
- }
- // Ensure that we'll cycle through internal openssl's state
- this.write('');
- if (callback) {
- this.once('secure', function() {
- callback(null);
- });
- }
- return true;
- };
- TLSSocket.prototype.setMaxSendFragment = function setMaxSendFragment(size) {
- return this.ssl.setMaxSendFragment(size) == 1;
- };
- TLSSocket.prototype.getTLSTicket = function getTLSTicket() {
- return this.ssl.getTLSTicket();
- };
- TLSSocket.prototype._handleTimeout = function() {
- this._tlsError(new Error('TLS handshake timeout'));
- };
- TLSSocket.prototype._tlsError = function(err) {
- this.emit('_tlsError', err);
- if (this._controlReleased)
- this.emit('error', err);
- };
- TLSSocket.prototype._releaseControl = function() {
- if (this._controlReleased)
- return false;
- this._controlReleased = true;
- this.removeListener('error', this._tlsError);
- return true;
- };
- 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.ssl.getNegotiatedProtocol();
- }
- if (process.features.tls_sni && this._tlsOptions.isServer) {
- this.servername = this.ssl.getServername();
- }
- debug('secure established');
- this._secureEstablished = true;
- if (this._tlsOptions.handshakeTimeout > 0)
- this.setTimeout(0, this._handleTimeout);
- this.emit('secure');
- };
- TLSSocket.prototype._start = function() {
- if (this._tlsOptions.requestOCSP)
- this.ssl.requestOCSP();
- this.ssl.start();
- };
- TLSSocket.prototype.setServername = function(name) {
- this.ssl.setServername(name);
- };
- TLSSocket.prototype.setSession = function(session) {
- if (util.isString(session))
- session = new Buffer(session, 'binary');
- this.ssl.setSession(session);
- };
- TLSSocket.prototype.getPeerCertificate = function(detailed) {
- if (this.ssl) {
- return common.translatePeerCertificate(
- this.ssl.getPeerCertificate(detailed));
- }
- return null;
- };
- TLSSocket.prototype.getSession = function() {
- if (this.ssl) {
- return this.ssl.getSession();
- }
- return null;
- };
- TLSSocket.prototype.isSessionReused = function() {
- if (this.ssl) {
- return this.ssl.isSessionReused();
- }
- return null;
- };
- TLSSocket.prototype.getCipher = function(err) {
- if (this.ssl) {
- return this.ssl.getCurrentCipher();
- } else {
- return null;
- }
- };
- // TODO: support anonymous (nocert) and PSK
- // AUTHENTICATION MODES
- //
- // There are several levels of authentication that TLS/SSL supports.
- // Read more about this in "man SSL_set_verify".
- //
- // 1. The server sends a certificate to the client but does not request a
- // cert from the client. This is common for most HTTPS servers. The browser
- // can verify the identity of the server, but the server does not know who
- // the client is. Authenticating the client is usually done over HTTP using
- // login boxes and cookies and stuff.
- //
- // 2. The server sends a cert to the client and requests that the client
- // also send it a cert. The client knows who the server is and the server is
- // requesting the client also identify themselves. There are several
- // outcomes:
- //
- // A) verifyError returns null meaning the client's certificate is signed
- // by one of the server's CAs. The server know's the client idenity now
- // and the client is authorized.
- //
- // B) For some reason the client's certificate is not acceptable -
- // verifyError returns a string indicating the problem. The server can
- // either (i) reject the client or (ii) allow the client to connect as an
- // unauthorized connection.
- //
- // The mode is controlled by two boolean variables.
- //
- // requestCert
- // If true the server requests a certificate from client connections. For
- // the common HTTPS case, users will want this to be false, which is what
- // it defaults to.
- //
- // rejectUnauthorized
- // 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
- // true.
- //
- //
- //
- // Options:
- // - requestCert. Send verify request. Default to false.
- // - rejectUnauthorized. Boolean, default to true.
- // - key. string.
- // - cert: string.
- // - ca: string or array of strings.
- // - sessionTimeout: integer.
- //
- // emit 'secureConnection'
- // function (tlsSocket) { }
- //
- // "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_CRL",
- // "UNABLE_TO_DECRYPT_CERT_SIGNATURE", "UNABLE_TO_DECRYPT_CRL_SIGNATURE",
- // "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", "CERT_SIGNATURE_FAILURE",
- // "CRL_SIGNATURE_FAILURE", "CERT_NOT_YET_VALID" "CERT_HAS_EXPIRED",
- // "CRL_NOT_YET_VALID", "CRL_HAS_EXPIRED" "ERROR_IN_CERT_NOT_BEFORE_FIELD",
- // "ERROR_IN_CERT_NOT_AFTER_FIELD", "ERROR_IN_CRL_LAST_UPDATE_FIELD",
- // "ERROR_IN_CRL_NEXT_UPDATE_FIELD", "OUT_OF_MEM",
- // "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN",
- // "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
- // "CERT_CHAIN_TOO_LONG", "CERT_REVOKED" "INVALID_CA",
- // "PATH_LENGTH_EXCEEDED", "INVALID_PURPOSE" "CERT_UNTRUSTED",
- // "CERT_REJECTED"
- //
- function Server(/* [options], listener */) {
- var options, listener;
- if (util.isObject(arguments[0])) {
- options = arguments[0];
- listener = arguments[1];
- } else if (util.isFunction(arguments[0])) {
- options = {};
- listener = arguments[0];
- }
- if (!(this instanceof Server)) return new 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
- });
- this._sharedCreds = sharedCreds;
- var timeout = options.handshakeTimeout || (120 * 1000);
- if (!util.isNumber(timeout)) {
- throw new TypeError('handshakeTimeout must be a number');
- }
- if (self.sessionTimeout) {
- sharedCreds.context.setSessionTimeout(self.sessionTimeout);
- }
- if (self.ticketKeys) {
- sharedCreds.context.setTicketKeys(self.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,
- SNICallback: options.SNICallback || SNICallback
- });
- socket.on('secure', function() {
- if (socket._requestCert) {
- var verifyError = socket.ssl.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() {
- // Emit ECONNRESET
- if (!socket._controlReleased && !errorEmitted) {
- errorEmitted = true;
- var connReset = new Error('socket hang up');
- connReset.code = 'ECONNRESET';
- self.emit('clientError', connReset, socket);
- }
- });
- socket.on('_tlsError', function(err) {
- if (!socket._controlReleased && !errorEmitted) {
- errorEmitted = true;
- self.emit('clientError', err, socket);
- }
- });
- });
- if (listener) {
- this.on('secureConnection', listener);
- }
- }
- util.inherits(Server, net.Server);
- exports.Server = Server;
- exports.createServer = function(options, listener) {
- return new Server(options, listener);
- };
- Server.prototype._getServerData = function() {
- return {
- ticketKeys: this._sharedCreds.context.getTicketKeys().toString('hex')
- };
- };
- Server.prototype._setServerData = function(data) {
- this._sharedCreds.context.setTicketKeys(new Buffer(data.ticketKeys, 'hex'));
- };
- Server.prototype.setOptions = function(options) {
- if (util.isBoolean(options.requestCert)) {
- this.requestCert = options.requestCert;
- } else {
- this.requestCert = false;
- }
- if (util.isBoolean(options.rejectUnauthorized)) {
- this.rejectUnauthorized = options.rejectUnauthorized;
- } else {
- this.rejectUnauthorized = false;
- }
- if (options.pfx) this.pfx = options.pfx;
- if (options.key) this.key = options.key;
- if (options.passphrase) this.passphrase = options.passphrase;
- if (options.cert) this.cert = options.cert;
- if (options.ca) this.ca = options.ca;
- if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
- if (options.crl) this.crl = options.crl;
- if (options.ciphers) this.ciphers = options.ciphers;
- if (!util.isUndefined(options.ecdhCurve))
- this.ecdhCurve = options.ecdhCurve;
- if (options.dhparam) this.dhparam = options.dhparam;
- if (options.sessionTimeout) this.sessionTimeout = options.sessionTimeout;
- if (options.ticketKeys) this.ticketKeys = options.ticketKeys;
- var secureOptions = common._getSecureOptions(options.secureProtocol,
- options.secureOptions);
- if (options.honorCipherOrder) {
- secureOptions |= constants.SSL_OP_CIPHER_SERVER_PREFERENCE;
- }
- if (options.honorCipherOrder !== undefined)
- this.honorCipherOrder = !!options.honorCipherOrder;
- else
- this.honorCipherOrder = true;
- this.secureOptions = secureOptions;
- if (options.NPNProtocols) tls.convertNPNProtocols(options.NPNProtocols, this);
- if (options.sessionIdContext) {
- this.sessionIdContext = options.sessionIdContext;
- } else {
- this.sessionIdContext = crypto.createHash('md5')
- .update(process.argv.join(' '))
- .digest('hex');
- }
- };
- // SNI Contexts High-Level API
- Server.prototype.addContext = function(servername, context) {
- if (!servername) {
- throw new Error('Servername is required parameter for Server.addContext');
- }
- var re = new RegExp('^' +
- servername.replace(/([\.^$+?\-\\[\]{}])/g, '\\$1')
- .replace(/\*/g, '[^\.]*') +
- '$');
- this._contexts.push([re, tls.createSecureContext(context).context]);
- };
- function SNICallback(servername, callback) {
- var ctx;
- this.server._contexts.some(function(elem) {
- if (!util.isNull(servername.match(elem[0]))) {
- ctx = elem[1];
- return true;
- }
- });
- callback(null, ctx);
- }
- // Target API:
- //
- // var s = tls.connect({port: 8000, host: "google.com"}, function() {
- // if (!s.authorized) {
- // s.destroy();
- // return;
- // }
- //
- // // s.socket;
- //
- // s.end("hello world\n");
- // });
- //
- //
- function normalizeConnectArgs(listArgs) {
- var args = net._normalizeConnectArgs(listArgs);
- var options = args[0];
- var cb = args[1];
- if (util.isObject(listArgs[1])) {
- options = util._extend(options, listArgs[1]);
- } else if (util.isObject(listArgs[2])) {
- options = util._extend(options, listArgs[2]);
- }
- return (cb) ? [options, cb] : [options];
- }
- function legacyConnect(hostname, options, NPN, context) {
- assert(options.socket);
- if (!tls_legacy)
- tls_legacy = require('_tls_legacy');
- var pair = tls_legacy.createSecurePair(context,
- false,
- true,
- !!options.rejectUnauthorized,
- {
- NPNProtocols: NPN.NPNProtocols,
- servername: hostname
- });
- tls_legacy.pipe(pair, options.socket);
- pair.cleartext._controlReleased = true;
- pair.on('error', function(err) {
- pair.cleartext.emit('error', err);
- });
- return pair;
- }
- exports.connect = function(/* [port, host], options, cb */) {
- var args = normalizeConnectArgs(arguments);
- var options = args[0];
- var cb = args[1];
- var defaults = {
- rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED,
- ciphers: tls.DEFAULT_CIPHERS,
- checkServerIdentity: tls.checkServerIdentity
- };
- options = util._extend(defaults, options || {});
- options.secureOptions = common._getSecureOptions(options.secureProtocol,
- options.secureOptions);
- assert(typeof options.checkServerIdentity === 'function');
- var hostname = options.servername ||
- options.host ||
- options.socket && options.socket._host,
- NPN = {},
- context = tls.createSecureContext(options);
- tls.convertNPNProtocols(options.NPNProtocols, NPN);
- // Wrapping TLS socket inside another TLS socket was requested -
- // create legacy secure pair
- var socket;
- var legacy;
- var result;
- if (options.socket instanceof TLSSocket) {
- debug('legacy connect');
- legacy = true;
- socket = legacyConnect(hostname, options, NPN, context);
- result = socket.cleartext;
- } else {
- legacy = false;
- socket = new TLSSocket(options.socket, {
- secureContext: context,
- isServer: false,
- requestCert: true,
- rejectUnauthorized: options.rejectUnauthorized,
- session: options.session,
- NPNProtocols: NPN.NPNProtocols,
- requestOCSP: options.requestOCSP
- });
- result = socket;
- }
- if (socket._handle && !socket._connecting) {
- onHandle();
- } else {
- // Not even started connecting yet (or probably resolving dns address),
- // catch socket errors and assign handle.
- if (!legacy && options.socket) {
- options.socket.once('connect', function() {
- assert(options.socket._handle);
- socket._handle = options.socket._handle;
- socket._handle.owner = socket;
- socket.emit('connect');
- });
- }
- socket.once('connect', onHandle);
- }
- if (cb)
- result.once('secureConnect', cb);
- if (!options.socket) {
- assert(!legacy);
- var connect_opt;
- if (options.path && !options.port) {
- connect_opt = { path: options.path };
- } else {
- connect_opt = {
- port: options.port,
- host: options.host,
- localAddress: options.localAddress
- };
- }
- socket.connect(connect_opt);
- }
- return result;
- function onHandle() {
- if (!legacy)
- socket._releaseControl();
- if (options.session)
- socket.setSession(options.session);
- if (!legacy) {
- if (options.servername)
- socket.setServername(options.servername);
- socket._start();
- }
- socket.on('secure', function() {
- var verifyError = socket.ssl.verifyError();
- // Verify that server's identity matches it's certificate's names
- if (!verifyError) {
- var cert = result.getPeerCertificate();
- verifyError = options.checkServerIdentity(hostname, cert);
- }
- if (verifyError) {
- result.authorized = false;
- result.authorizationError = verifyError.code || verifyError.message;
- if (options.rejectUnauthorized) {
- result.emit('error', verifyError);
- result.destroy();
- return;
- } else {
- result.emit('secureConnect');
- }
- } else {
- result.authorized = true;
- result.emit('secureConnect');
- }
- // Uncork incoming data
- result.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';
- socket.destroy();
- socket.emit('error', error);
- }
- }
- result.once('end', onHangUp);
- }
- };