671a5e85206faf886e1d03b153250f035b9fa231
[platform/upstream/nodejs.git] / lib / _tls_wrap.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // // Emit `beforeExit` if the loop became alive either after emitting
4 // event, or after running some callbacks.
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a
7 // copy of this software and associated documentation files (the
8 // "Software"), to deal in the Software without restriction, including
9 // without limitation the rights to use, copy, modify, merge, publish,
10 // distribute, sublicense, and/or sell copies of the Software, and to permit
11 // persons to whom the Software is furnished to do so, subject to the
12 // following conditions:
13 //
14 // The above copyright notice and this permission notice shall be included
15 // in all copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
20 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23 // USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25 'use strict';
26
27 var assert = require('assert');
28 var crypto = require('crypto');
29 var net = require('net');
30 var tls = require('tls');
31 var util = require('util');
32 var listenerCount = require('events').listenerCount;
33 var common = require('_tls_common');
34
35 var Timer = process.binding('timer_wrap').Timer;
36 var tls_wrap = process.binding('tls_wrap');
37
38 // Lazy load
39 var tls_legacy;
40
41 var debug = util.debuglog('tls');
42
43 function onhandshakestart() {
44   debug('onhandshakestart');
45
46   var self = this;
47   var ssl = self.ssl;
48   var now = Timer.now();
49
50   assert(now >= ssl.lastHandshakeTime);
51
52   if ((now - ssl.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) {
53     ssl.handshakes = 0;
54   }
55
56   var first = (ssl.lastHandshakeTime === 0);
57   ssl.lastHandshakeTime = now;
58   if (first) return;
59
60   if (++ssl.handshakes > tls.CLIENT_RENEG_LIMIT) {
61     // Defer the error event to the next tick. We're being called from OpenSSL's
62     // state machine and OpenSSL is not re-entrant. We cannot allow the user's
63     // callback to destroy the connection right now, it would crash and burn.
64     setImmediate(function() {
65       var err = new Error('TLS session renegotiation attack detected.');
66       self._tlsError(err);
67     });
68   }
69 }
70
71
72 function onhandshakedone() {
73   // for future use
74   debug('onhandshakedone');
75   this._finishInit();
76 }
77
78
79 function loadSession(self, hello, cb) {
80   var once = false;
81   function onSession(err, session) {
82     if (once)
83       return cb(new Error('TLS session callback was called 2 times'));
84     once = true;
85
86     if (err)
87       return cb(err);
88
89     // NOTE: That we have disabled OpenSSL's internal session storage in
90     // `node_crypto.cc` and hence its safe to rely on getting servername only
91     // from clienthello or this place.
92     var ret = self.ssl.loadSession(session);
93
94     cb(null, ret);
95   }
96
97   if (hello.sessionId.length <= 0 ||
98       hello.tlsTicket ||
99       self.server &&
100       !self.server.emit('resumeSession', hello.sessionId, onSession)) {
101     cb(null);
102   }
103 }
104
105
106 function loadSNI(self, servername, cb) {
107   if (!servername || !self._SNICallback)
108     return cb(null);
109
110   var once = false;
111   self._SNICallback(servername, function(err, context) {
112     if (once)
113       return cb(new Error('TLS SNI callback was called 2 times'));
114     once = true;
115
116     if (err)
117       return cb(err);
118
119     // TODO(indutny): eventually disallow raw `SecureContext`
120     if (context)
121       self.ssl.sni_context = context.context || context;
122
123     cb(null, self.ssl.sni_context);
124   });
125 }
126
127
128 function requestOCSP(self, hello, ctx, cb) {
129   if (!hello.OCSPRequest || !self.server)
130     return cb(null);
131
132   if (!ctx)
133     ctx = self.server._sharedCreds;
134   if (ctx.context)
135     ctx = ctx.context;
136
137   if (listenerCount(self.server, 'OCSPRequest') === 0) {
138     return cb(null);
139   } else {
140     self.server.emit('OCSPRequest',
141                      ctx.getCertificate(),
142                      ctx.getIssuer(),
143                      onOCSP);
144   }
145
146   var once = false;
147   function onOCSP(err, response) {
148     if (once)
149       return cb(new Error('TLS OCSP callback was called 2 times'));
150     once = true;
151
152     if (err)
153       return cb(err);
154
155     if (response)
156       self.ssl.setOCSPResponse(response);
157     cb(null);
158   }
159 }
160
161
162 function onclienthello(hello) {
163   var self = this;
164
165   loadSession(self, hello, function(err, session) {
166     if (err)
167       return self.destroy(err);
168
169     // Servername came from SSL session
170     // NOTE: TLS Session ticket doesn't include servername information
171     //
172     // Another note, From RFC3546:
173     //
174     //   If, on the other hand, the older
175     //   session is resumed, then the server MUST ignore extensions appearing
176     //   in the client hello, and send a server hello containing no
177     //   extensions; in this case the extension functionality negotiated
178     //   during the original session initiation is applied to the resumed
179     //   session.
180     //
181     // Therefore we should account session loading when dealing with servername
182     var servername = session && session.servername || hello.servername;
183     loadSNI(self, servername, function(err, ctx) {
184       if (err)
185         return self.destroy(err);
186       requestOCSP(self, hello, ctx, function(err) {
187         if (err)
188           return self.destroy(err);
189
190         self.ssl.endParser();
191       });
192     });
193   });
194 }
195
196
197 function onnewsession(key, session) {
198   if (!this.server)
199     return;
200
201   var self = this;
202   var once = false;
203
204   this._newSessionPending = true;
205   if (!this.server.emit('newSession', key, session, done))
206     done();
207
208   function done() {
209     if (once)
210       return;
211     once = true;
212
213     self.ssl.newSessionDone();
214
215     self._newSessionPending = false;
216     if (self._securePending)
217       self._finishInit();
218     self._securePending = false;
219   }
220 }
221
222
223 function onocspresponse(resp) {
224   this.emit('OCSPResponse', resp);
225 }
226
227
228 /**
229  * Provides a wrap of socket stream to do encrypted communication.
230  */
231
232 function TLSSocket(socket, options) {
233   // Disallow wrapping TLSSocket in TLSSocket
234   assert(!(socket instanceof TLSSocket));
235
236   net.Socket.call(this, {
237     handle: socket && socket._handle,
238     allowHalfOpen: socket && socket.allowHalfOpen,
239     readable: false,
240     writable: false
241   });
242
243   // To prevent assertion in afterConnect()
244   if (socket)
245     this._connecting = socket._connecting;
246
247   this._tlsOptions = options;
248   this._secureEstablished = false;
249   this._securePending = false;
250   this._newSessionPending = false;
251   this._controlReleased = false;
252   this._SNICallback = null;
253   this.ssl = null;
254   this.servername = null;
255   this.npnProtocol = null;
256   this.authorized = false;
257   this.authorizationError = null;
258
259   // Just a documented property to make secure sockets
260   // distinguishable from regular ones.
261   this.encrypted = true;
262
263   this.on('error', this._tlsError);
264
265   if (!this._handle) {
266     this.once('connect', function() {
267       this._init(null);
268     });
269   } else {
270     this._init(socket);
271   }
272
273   // Make sure to setup all required properties like: `_connecting` before
274   // starting the flow of the data
275   this.readable = true;
276   this.writable = true;
277   this.read(0);
278 }
279 util.inherits(TLSSocket, net.Socket);
280 exports.TLSSocket = TLSSocket;
281
282 TLSSocket.prototype._init = function(socket) {
283   assert(this._handle);
284
285   // lib/net.js expect this value to be non-zero if write hasn't been flushed
286   // immediately
287   // TODO(indutny): rewise this solution, it might be 1 before handshake and
288   // represent real writeQueueSize during regular writes.
289   this._handle.writeQueueSize = 1;
290
291   var self = this;
292   var options = this._tlsOptions;
293
294   // Wrap socket's handle
295   var context = options.secureContext ||
296                 options.credentials ||
297                 tls.createSecureContext();
298   this.ssl = tls_wrap.wrap(this._handle, context.context, options.isServer);
299   this.server = options.server || null;
300
301   // For clients, we will always have either a given ca list or be using
302   // default one
303   var requestCert = !!options.requestCert || !options.isServer,
304       rejectUnauthorized = !!options.rejectUnauthorized;
305
306   this._requestCert = requestCert;
307   this._rejectUnauthorized = rejectUnauthorized;
308   if (requestCert || rejectUnauthorized)
309     this.ssl.setVerifyMode(requestCert, rejectUnauthorized);
310
311   if (options.isServer) {
312     this.ssl.onhandshakestart = onhandshakestart.bind(this);
313     this.ssl.onhandshakedone = onhandshakedone.bind(this);
314     this.ssl.onclienthello = onclienthello.bind(this);
315     this.ssl.onnewsession = onnewsession.bind(this);
316     this.ssl.lastHandshakeTime = 0;
317     this.ssl.handshakes = 0;
318
319     if (this.server &&
320         (listenerCount(this.server, 'resumeSession') > 0 ||
321          listenerCount(this.server, 'newSession') > 0 ||
322          listenerCount(this.server, 'OCSPRequest') > 0)) {
323       this.ssl.enableSessionCallbacks();
324     }
325   } else {
326     this.ssl.onhandshakestart = function() {};
327     this.ssl.onhandshakedone = this._finishInit.bind(this);
328     this.ssl.onocspresponse = onocspresponse.bind(this);
329
330     if (options.session)
331       this.ssl.setSession(options.session);
332   }
333
334   this.ssl.onerror = function(err) {
335     if (self._writableState.errorEmitted)
336       return;
337     self._writableState.errorEmitted = true;
338
339     // Destroy socket if error happened before handshake's finish
340     if (!this._secureEstablished) {
341       self._tlsError(err);
342       self.destroy();
343     } else if (options.isServer &&
344                rejectUnauthorized &&
345                /peer did not return a certificate/.test(err.message)) {
346       // Ignore server's authorization errors
347       self.destroy();
348     } else {
349       // Throw error
350       self._tlsError(err);
351     }
352   };
353
354   // If custom SNICallback was given, or if
355   // there're SNI contexts to perform match against -
356   // set `.onsniselect` callback.
357   if (process.features.tls_sni &&
358       options.isServer &&
359       options.server &&
360       (options.SNICallback !== SNICallback ||
361        options.server._contexts.length)) {
362     assert(typeof options.SNICallback === 'function');
363     this._SNICallback = options.SNICallback;
364     this.ssl.enableHelloParser();
365   }
366
367   if (process.features.tls_npn && options.NPNProtocols)
368     this.ssl.setNPNProtocols(options.NPNProtocols);
369
370   if (options.handshakeTimeout > 0)
371     this.setTimeout(options.handshakeTimeout, this._handleTimeout);
372
373   // Socket already has some buffered data - emulate receiving it
374   if (socket && socket._readableState.length) {
375     var buf;
376     while ((buf = socket.read()) !== null)
377       this.ssl.receive(buf);
378   }
379 };
380
381 TLSSocket.prototype.renegotiate = function(options, callback) {
382   var requestCert = this._requestCert,
383       rejectUnauthorized = this._rejectUnauthorized;
384
385   if (typeof options.requestCert !== 'undefined')
386     requestCert = !!options.requestCert;
387   if (typeof options.rejectUnauthorized !== 'undefined')
388     rejectUnauthorized = !!options.rejectUnauthorized;
389
390   if (requestCert !== this._requestCert ||
391       rejectUnauthorized !== this._rejectUnauthorized) {
392     this.ssl.setVerifyMode(requestCert, rejectUnauthorized);
393     this._requestCert = requestCert;
394     this._rejectUnauthorized = rejectUnauthorized;
395   }
396   if (!this.ssl.renegotiate()) {
397     if (callback) {
398       process.nextTick(function() {
399         callback(new Error('Failed to renegotiate'));
400       });
401     }
402     return false;
403   }
404
405   // Ensure that we'll cycle through internal openssl's state
406   this.write('');
407
408   if (callback) {
409     this.once('secure', function() {
410       callback(null);
411     });
412   }
413
414   return true;
415 };
416
417 TLSSocket.prototype.setMaxSendFragment = function setMaxSendFragment(size) {
418   return this.ssl.setMaxSendFragment(size) == 1;
419 };
420
421 TLSSocket.prototype.getTLSTicket = function getTLSTicket() {
422   return this.ssl.getTLSTicket();
423 };
424
425 TLSSocket.prototype._handleTimeout = function() {
426   this._tlsError(new Error('TLS handshake timeout'));
427 };
428
429 TLSSocket.prototype._tlsError = function(err) {
430   this.emit('_tlsError', err);
431   if (this._controlReleased)
432     this.emit('error', err);
433 };
434
435 TLSSocket.prototype._releaseControl = function() {
436   if (this._controlReleased)
437     return false;
438   this._controlReleased = true;
439   this.removeListener('error', this._tlsError);
440   return true;
441 };
442
443 TLSSocket.prototype._finishInit = function() {
444   // `newSession` callback wasn't called yet
445   if (this._newSessionPending) {
446     this._securePending = true;
447     return;
448   }
449
450   if (process.features.tls_npn) {
451     this.npnProtocol = this.ssl.getNegotiatedProtocol();
452   }
453
454   if (process.features.tls_sni && this._tlsOptions.isServer) {
455     this.servername = this.ssl.getServername();
456   }
457
458   debug('secure established');
459   this._secureEstablished = true;
460   if (this._tlsOptions.handshakeTimeout > 0)
461     this.setTimeout(0, this._handleTimeout);
462   this.emit('secure');
463 };
464
465 TLSSocket.prototype._start = function() {
466   if (this._tlsOptions.requestOCSP)
467     this.ssl.requestOCSP();
468   this.ssl.start();
469 };
470
471 TLSSocket.prototype.setServername = function(name) {
472   this.ssl.setServername(name);
473 };
474
475 TLSSocket.prototype.setSession = function(session) {
476   if (util.isString(session))
477     session = new Buffer(session, 'binary');
478   this.ssl.setSession(session);
479 };
480
481 TLSSocket.prototype.getPeerCertificate = function(detailed) {
482   if (this.ssl) {
483     return common.translatePeerCertificate(
484         this.ssl.getPeerCertificate(detailed));
485   }
486
487   return null;
488 };
489
490 TLSSocket.prototype.getSession = function() {
491   if (this.ssl) {
492     return this.ssl.getSession();
493   }
494
495   return null;
496 };
497
498 TLSSocket.prototype.isSessionReused = function() {
499   if (this.ssl) {
500     return this.ssl.isSessionReused();
501   }
502
503   return null;
504 };
505
506 TLSSocket.prototype.getCipher = function(err) {
507   if (this.ssl) {
508     return this.ssl.getCurrentCipher();
509   } else {
510     return null;
511   }
512 };
513
514 // TODO: support anonymous (nocert) and PSK
515
516
517 // AUTHENTICATION MODES
518 //
519 // There are several levels of authentication that TLS/SSL supports.
520 // Read more about this in "man SSL_set_verify".
521 //
522 // 1. The server sends a certificate to the client but does not request a
523 // cert from the client. This is common for most HTTPS servers. The browser
524 // can verify the identity of the server, but the server does not know who
525 // the client is. Authenticating the client is usually done over HTTP using
526 // login boxes and cookies and stuff.
527 //
528 // 2. The server sends a cert to the client and requests that the client
529 // also send it a cert. The client knows who the server is and the server is
530 // requesting the client also identify themselves. There are several
531 // outcomes:
532 //
533 //   A) verifyError returns null meaning the client's certificate is signed
534 //   by one of the server's CAs. The server know's the client idenity now
535 //   and the client is authorized.
536 //
537 //   B) For some reason the client's certificate is not acceptable -
538 //   verifyError returns a string indicating the problem. The server can
539 //   either (i) reject the client or (ii) allow the client to connect as an
540 //   unauthorized connection.
541 //
542 // The mode is controlled by two boolean variables.
543 //
544 // requestCert
545 //   If true the server requests a certificate from client connections. For
546 //   the common HTTPS case, users will want this to be false, which is what
547 //   it defaults to.
548 //
549 // rejectUnauthorized
550 //   If true clients whose certificates are invalid for any reason will not
551 //   be allowed to make connections. If false, they will simply be marked as
552 //   unauthorized but secure communication will continue. By default this is
553 //   true.
554 //
555 //
556 //
557 // Options:
558 // - requestCert. Send verify request. Default to false.
559 // - rejectUnauthorized. Boolean, default to true.
560 // - key. string.
561 // - cert: string.
562 // - ca: string or array of strings.
563 // - sessionTimeout: integer.
564 //
565 // emit 'secureConnection'
566 //   function (tlsSocket) { }
567 //
568 //   "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_CRL",
569 //   "UNABLE_TO_DECRYPT_CERT_SIGNATURE", "UNABLE_TO_DECRYPT_CRL_SIGNATURE",
570 //   "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", "CERT_SIGNATURE_FAILURE",
571 //   "CRL_SIGNATURE_FAILURE", "CERT_NOT_YET_VALID" "CERT_HAS_EXPIRED",
572 //   "CRL_NOT_YET_VALID", "CRL_HAS_EXPIRED" "ERROR_IN_CERT_NOT_BEFORE_FIELD",
573 //   "ERROR_IN_CERT_NOT_AFTER_FIELD", "ERROR_IN_CRL_LAST_UPDATE_FIELD",
574 //   "ERROR_IN_CRL_NEXT_UPDATE_FIELD", "OUT_OF_MEM",
575 //   "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN",
576 //   "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
577 //   "CERT_CHAIN_TOO_LONG", "CERT_REVOKED" "INVALID_CA",
578 //   "PATH_LENGTH_EXCEEDED", "INVALID_PURPOSE" "CERT_UNTRUSTED",
579 //   "CERT_REJECTED"
580 //
581 function Server(/* [options], listener */) {
582   var options, listener;
583   if (util.isObject(arguments[0])) {
584     options = arguments[0];
585     listener = arguments[1];
586   } else if (util.isFunction(arguments[0])) {
587     options = {};
588     listener = arguments[0];
589   }
590
591   if (!(this instanceof Server)) return new Server(options, listener);
592
593   this._contexts = [];
594
595   var self = this;
596
597   // Handle option defaults:
598   this.setOptions(options);
599
600   var sharedCreds = tls.createSecureContext({
601     pfx: self.pfx,
602     key: self.key,
603     passphrase: self.passphrase,
604     cert: self.cert,
605     ca: self.ca,
606     ciphers: self.ciphers,
607     ecdhCurve: self.ecdhCurve,
608     dhparam: self.dhparam,
609     secureProtocol: self.secureProtocol,
610     secureOptions: self.secureOptions,
611     honorCipherOrder: self.honorCipherOrder,
612     crl: self.crl,
613     sessionIdContext: self.sessionIdContext
614   });
615   this._sharedCreds = sharedCreds;
616
617   var timeout = options.handshakeTimeout || (120 * 1000);
618
619   if (!util.isNumber(timeout)) {
620     throw new TypeError('handshakeTimeout must be a number');
621   }
622
623   if (self.sessionTimeout) {
624     sharedCreds.context.setSessionTimeout(self.sessionTimeout);
625   }
626
627   if (self.ticketKeys) {
628     sharedCreds.context.setTicketKeys(self.ticketKeys);
629   }
630
631   // constructor call
632   net.Server.call(this, function(raw_socket) {
633     var socket = new TLSSocket(raw_socket, {
634       secureContext: sharedCreds,
635       isServer: true,
636       server: self,
637       requestCert: self.requestCert,
638       rejectUnauthorized: self.rejectUnauthorized,
639       handshakeTimeout: timeout,
640       NPNProtocols: self.NPNProtocols,
641       SNICallback: options.SNICallback || SNICallback
642     });
643
644     socket.on('secure', function() {
645       if (socket._requestCert) {
646         var verifyError = socket.ssl.verifyError();
647         if (verifyError) {
648           socket.authorizationError = verifyError.code;
649
650           if (socket._rejectUnauthorized)
651             socket.destroy();
652         } else {
653           socket.authorized = true;
654         }
655       }
656
657       if (!socket.destroyed && socket._releaseControl())
658         self.emit('secureConnection', socket);
659     });
660
661     var errorEmitted = false;
662     socket.on('close', function() {
663       // Emit ECONNRESET
664       if (!socket._controlReleased && !errorEmitted) {
665         errorEmitted = true;
666         var connReset = new Error('socket hang up');
667         connReset.code = 'ECONNRESET';
668         self.emit('clientError', connReset, socket);
669       }
670     });
671
672     socket.on('_tlsError', function(err) {
673       if (!socket._controlReleased && !errorEmitted) {
674         errorEmitted = true;
675         self.emit('clientError', err, socket);
676       }
677     });
678   });
679
680   if (listener) {
681     this.on('secureConnection', listener);
682   }
683 }
684
685 util.inherits(Server, net.Server);
686 exports.Server = Server;
687 exports.createServer = function(options, listener) {
688   return new Server(options, listener);
689 };
690
691
692 Server.prototype._getServerData = function() {
693   return {
694     ticketKeys: this._sharedCreds.context.getTicketKeys().toString('hex')
695   };
696 };
697
698
699 Server.prototype._setServerData = function(data) {
700   this._sharedCreds.context.setTicketKeys(new Buffer(data.ticketKeys, 'hex'));
701 };
702
703
704 Server.prototype.setOptions = function(options) {
705   if (util.isBoolean(options.requestCert)) {
706     this.requestCert = options.requestCert;
707   } else {
708     this.requestCert = false;
709   }
710
711   if (util.isBoolean(options.rejectUnauthorized)) {
712     this.rejectUnauthorized = options.rejectUnauthorized;
713   } else {
714     this.rejectUnauthorized = false;
715   }
716
717   if (options.pfx) this.pfx = options.pfx;
718   if (options.key) this.key = options.key;
719   if (options.passphrase) this.passphrase = options.passphrase;
720   if (options.cert) this.cert = options.cert;
721   if (options.ca) this.ca = options.ca;
722   if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
723   if (options.crl) this.crl = options.crl;
724   if (options.ciphers) this.ciphers = options.ciphers;
725   if (!util.isUndefined(options.ecdhCurve))
726     this.ecdhCurve = options.ecdhCurve;
727   if (options.dhparam) this.dhparam = options.dhparam;
728   if (options.sessionTimeout) this.sessionTimeout = options.sessionTimeout;
729   if (options.ticketKeys) this.ticketKeys = options.ticketKeys;
730   var secureOptions = options.secureOptions || 0;
731   if (options.honorCipherOrder)
732     this.honorCipherOrder = true;
733   else
734     this.honorCipherOrder = false;
735   if (secureOptions) this.secureOptions = secureOptions;
736   if (options.NPNProtocols) tls.convertNPNProtocols(options.NPNProtocols, this);
737   if (options.sessionIdContext) {
738     this.sessionIdContext = options.sessionIdContext;
739   } else {
740     this.sessionIdContext = crypto.createHash('md5')
741                                   .update(process.argv.join(' '))
742                                   .digest('hex');
743   }
744 };
745
746 // SNI Contexts High-Level API
747 Server.prototype.addContext = function(servername, context) {
748   if (!servername) {
749     throw new Error('Servername is required parameter for Server.addContext');
750   }
751
752   var re = new RegExp('^' +
753                       servername.replace(/([\.^$+?\-\\[\]{}])/g, '\\$1')
754                                 .replace(/\*/g, '[^\.]*') +
755                       '$');
756   this._contexts.push([re, tls.createSecureContext(context).context]);
757 };
758
759 function SNICallback(servername, callback) {
760   var ctx;
761
762   this.server._contexts.some(function(elem) {
763     if (!util.isNull(servername.match(elem[0]))) {
764       ctx = elem[1];
765       return true;
766     }
767   });
768
769   callback(null, ctx);
770 }
771
772
773 // Target API:
774 //
775 //  var s = tls.connect({port: 8000, host: "google.com"}, function() {
776 //    if (!s.authorized) {
777 //      s.destroy();
778 //      return;
779 //    }
780 //
781 //    // s.socket;
782 //
783 //    s.end("hello world\n");
784 //  });
785 //
786 //
787 function normalizeConnectArgs(listArgs) {
788   var args = net._normalizeConnectArgs(listArgs);
789   var options = args[0];
790   var cb = args[1];
791
792   if (util.isObject(listArgs[1])) {
793     options = util._extend(options, listArgs[1]);
794   } else if (util.isObject(listArgs[2])) {
795     options = util._extend(options, listArgs[2]);
796   }
797
798   return (cb) ? [options, cb] : [options];
799 }
800
801 function legacyConnect(hostname, options, NPN, context) {
802   assert(options.socket);
803   if (!tls_legacy)
804     tls_legacy = require('_tls_legacy');
805
806   var pair = tls_legacy.createSecurePair(context,
807                                          false,
808                                          true,
809                                          !!options.rejectUnauthorized,
810                                          {
811                                            NPNProtocols: NPN.NPNProtocols,
812                                            servername: hostname
813                                          });
814   tls_legacy.pipe(pair, options.socket);
815   pair.cleartext._controlReleased = true;
816   pair.on('error', function(err) {
817     pair.cleartext.emit('error', err);
818   });
819
820   return pair;
821 }
822
823 exports.connect = function(/* [port, host], options, cb */) {
824   var args = normalizeConnectArgs(arguments);
825   var options = args[0];
826   var cb = args[1];
827
828   var defaults = {
829     rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED,
830     ciphers: tls.DEFAULT_CIPHERS,
831     checkServerIdentity: tls.checkServerIdentity
832   };
833
834   options = util._extend(defaults, options || {});
835
836   assert(typeof options.checkServerIdentity === 'function');
837
838   var hostname = options.servername ||
839                  options.host ||
840                  options.socket && options.socket._host,
841       NPN = {},
842       context = tls.createSecureContext(options);
843   tls.convertNPNProtocols(options.NPNProtocols, NPN);
844
845   // Wrapping TLS socket inside another TLS socket was requested -
846   // create legacy secure pair
847   var socket;
848   var legacy;
849   var result;
850   if (options.socket instanceof TLSSocket) {
851     debug('legacy connect');
852     legacy = true;
853     socket = legacyConnect(hostname, options, NPN, context);
854     result = socket.cleartext;
855   } else {
856     legacy = false;
857     socket = new TLSSocket(options.socket, {
858       secureContext: context,
859       isServer: false,
860       requestCert: true,
861       rejectUnauthorized: options.rejectUnauthorized,
862       session: options.session,
863       NPNProtocols: NPN.NPNProtocols,
864       requestOCSP: options.requestOCSP
865     });
866     result = socket;
867   }
868
869   if (socket._handle && !socket._connecting) {
870     onHandle();
871   } else {
872     // Not even started connecting yet (or probably resolving dns address),
873     // catch socket errors and assign handle.
874     if (!legacy && options.socket) {
875       options.socket.once('connect', function() {
876         assert(options.socket._handle);
877         socket._handle = options.socket._handle;
878         socket._handle.owner = socket;
879         socket.emit('connect');
880       });
881     }
882     socket.once('connect', onHandle);
883   }
884
885   if (cb)
886     result.once('secureConnect', cb);
887
888   if (!options.socket) {
889     assert(!legacy);
890     var connect_opt;
891     if (options.path && !options.port) {
892       connect_opt = { path: options.path };
893     } else {
894       connect_opt = {
895         port: options.port,
896         host: options.host,
897         localAddress: options.localAddress
898       };
899     }
900     socket.connect(connect_opt);
901   }
902
903   return result;
904
905   function onHandle() {
906     if (!legacy)
907       socket._releaseControl();
908
909     if (options.session)
910       socket.setSession(options.session);
911
912     if (!legacy) {
913       if (options.servername)
914         socket.setServername(options.servername);
915
916       socket._start();
917     }
918     socket.on('secure', function() {
919       var verifyError = socket.ssl.verifyError();
920
921       // Verify that server's identity matches it's certificate's names
922       if (!verifyError) {
923         var cert = result.getPeerCertificate();
924         verifyError = options.checkServerIdentity(hostname, cert);
925       }
926
927       if (verifyError) {
928         result.authorized = false;
929         result.authorizationError = verifyError.code || verifyError.message;
930
931         if (options.rejectUnauthorized) {
932           result.emit('error', verifyError);
933           result.destroy();
934           return;
935         } else {
936           result.emit('secureConnect');
937         }
938       } else {
939         result.authorized = true;
940         result.emit('secureConnect');
941       }
942
943       // Uncork incoming data
944       result.removeListener('end', onHangUp);
945     });
946
947     function onHangUp() {
948       // NOTE: This logic is shared with _http_client.js
949       if (!socket._hadError) {
950         socket._hadError = true;
951         var error = new Error('socket hang up');
952         error.code = 'ECONNRESET';
953         socket.destroy();
954         socket.emit('error', error);
955       }
956     }
957     result.once('end', onHangUp);
958   }
959 };