Make sure 'ARCH' get's defined with the CMake build system.
[platform/upstream/nodejs.git] / lib / tls.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 var crypto = require('crypto');
23 var util = require('util');
24 var net = require('net');
25 var events = require('events');
26 var stream = require('stream');
27 var END_OF_FILE = 42;
28 var assert = require('assert').ok;
29
30 var NPN_ENABLED = process.binding('constants').NPN_ENABLED;
31
32 var debug;
33 if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) {
34   debug = function(a) { console.error('TLS:', a); };
35 } else {
36   debug = function() { };
37 }
38
39
40 var Connection = null;
41 try {
42   Connection = process.binding('crypto').Connection;
43   exports.NPN_ENABLED = NPN_ENABLED;
44 } catch (e) {
45   throw new Error('node.js not compiled with openssl crypto support.');
46 }
47
48 // Convert protocols array into valid OpenSSL protocols list
49 // ("\x06spdy/2\x08http/1.1\x08http/1.0")
50 function convertNPNProtocols(NPNProtocols, out) {
51   // If NPNProtocols is Array - translate it into buffer
52   if (Array.isArray(NPNProtocols)) {
53     var buff = new Buffer(NPNProtocols.reduce(function(p, c) {
54       return p + 1 + Buffer.byteLength(c);
55     }, 0));
56
57     NPNProtocols.reduce(function(offset, c) {
58       var clen = Buffer.byteLength(c);
59       buff[offset] = clen;
60       buff.write(c, offset + 1);
61
62       return offset + 1 + clen;
63     }, 0);
64
65     NPNProtocols = buff;
66   }
67
68   // If it's already a Buffer - store it
69   if (Buffer.isBuffer(NPNProtocols)) {
70     out.NPNProtocols = NPNProtocols;
71   }
72 };
73
74 // Base class of both CleartextStream and EncryptedStream
75 function CryptoStream(pair) {
76   stream.Stream.call(this);
77
78   this.pair = pair;
79
80   this.readable = this.writable = true;
81
82   this._writeState = true;
83   this._pending = [];
84   this._pendingCallbacks = [];
85   this._pendingBytes = 0;
86 }
87 util.inherits(CryptoStream, stream.Stream);
88
89
90 CryptoStream.prototype.write = function(data /* , encoding, cb */) {
91   if (this == this.pair.cleartext) {
92     debug('cleartext.write called with ' + data.length + ' bytes');
93   } else {
94     debug('encrypted.write called with ' + data.length + ' bytes');
95   }
96
97   if (!this.writable) {
98     throw new Error('CryptoStream is not writable');
99   }
100
101   var encoding, cb;
102
103   // parse arguments
104   if (typeof arguments[1] == 'string') {
105     encoding = arguments[1];
106     cb = arguments[2];
107   } else {
108     cb = arguments[1];
109   }
110
111
112   // Transform strings into buffers.
113   if (typeof data == 'string') {
114     data = new Buffer(data, encoding);
115   }
116
117   debug('clearIn data');
118
119   this._pending.push(data);
120   this._pendingCallbacks.push(cb);
121
122   this._pendingBytes += data.length;
123
124   this.pair._writeCalled = true;
125   this.pair._cycle();
126
127   return this._pendingBytes < 128 * 1024;
128 };
129
130
131 CryptoStream.prototype.pause = function() {
132   debug('paused ' + (this == this.pair.cleartext ? 'cleartext' : 'encrypted'));
133   this._writeState = false;
134 };
135
136
137 CryptoStream.prototype.resume = function() {
138   debug('resume ' + (this == this.pair.cleartext ? 'cleartext' : 'encrypted'));
139   this._writeState = true;
140   this.pair._cycle();
141 };
142
143
144 CryptoStream.prototype.setTimeout = function(n) {
145   if (this.socket) this.socket.setTimeout(n);
146 };
147
148
149 CryptoStream.prototype.setNoDelay = function() {
150   if (this.socket) this.socket.setNoDelay();
151 };
152
153
154 CryptoStream.prototype.setEncoding = function(encoding) {
155   var StringDecoder = require('string_decoder').StringDecoder; // lazy load
156   this._decoder = new StringDecoder(encoding);
157 };
158
159
160 // EG '/C=US/ST=CA/L=SF/O=Joyent/OU=Node.js/CN=ca1/emailAddress=ry@clouds.org'
161 function parseCertString(s) {
162   var out = {};
163   var parts = s.split('/');
164   // Note: can always skip the first one.
165   for (var i = 1; i < parts.length; i++) {
166     var sepIndex = parts[i].indexOf('=');
167     if (sepIndex > 0) {
168       var key = parts[i].slice(0, sepIndex);
169       var value = parts[i].slice(sepIndex + 1);
170       out[key] = value;
171     }
172   }
173   return out;
174 }
175
176
177 CryptoStream.prototype.getPeerCertificate = function() {
178   if (this.pair._ssl) {
179     var c = this.pair._ssl.getPeerCertificate();
180
181     if (c) {
182       if (c.issuer) c.issuer = parseCertString(c.issuer);
183       if (c.subject) c.subject = parseCertString(c.subject);
184       return c;
185     }
186   }
187
188   return null;
189 };
190
191
192 CryptoStream.prototype.getCipher = function(err) {
193   if (this.pair._ssl) {
194     return this.pair._ssl.getCurrentCipher();
195   } else {
196     return null;
197   }
198 };
199
200
201 CryptoStream.prototype.end = function(d) {
202   if (this.writable) {
203     if (this.pair._done) return;
204
205     if (d) {
206       this.write(d);
207     }
208
209     this._pending.push(END_OF_FILE);
210     this._pendingCallbacks.push(null);
211
212     // If this is an encrypted stream then we need to disable further 'data'
213     // events.
214
215     this.writable = false;
216
217     this.pair._cycle();
218   }
219 };
220
221
222 CryptoStream.prototype.destroySoon = function(err) {
223   if (this.writable) {
224     this.end();
225   } else {
226     this.destroy();
227   }
228 };
229
230
231 CryptoStream.prototype.destroy = function(err) {
232   if (this.pair._done) return;
233   this.pair._destroy();
234 };
235
236
237 CryptoStream.prototype._done = function() {
238   this._doneFlag = true;
239
240   if (this.pair.cleartext._doneFlag &&
241       this.pair.encrypted._doneFlag &&
242       !this.pair._done) {
243     // If both streams are done:
244     this.pair._destroy();
245   }
246 };
247
248
249 CryptoStream.prototype.fd = -1;
250 CryptoStream.prototype.__defineGetter__('readyState',
251     net.Socket.prototype.__lookupGetter__('readyState'));
252
253 // Move decrypted, clear data out into the application.
254 // From the user's perspective this occurs as a 'data' event
255 // on the pair.cleartext.
256 // also
257 // Move encrypted data to the stream. From the user's perspective this
258 // occurs as a 'data' event on the pair.encrypted. Usually the application
259 // will have some code which pipes the stream to a socket:
260 //
261 //   pair.encrypted.on('data', function (d) {
262 //     socket.write(d);
263 //   });
264 //
265 CryptoStream.prototype._push = function() {
266   if (this == this.pair.encrypted && !this.writable) {
267     // If the encrypted side got EOF, we do not attempt
268     // to write out data anymore.
269     return;
270   }
271
272   while (this._writeState == true) {
273     var bytesRead = 0;
274     var chunkBytes = 0;
275     var pool = new Buffer(16 * 4096); // alloc every time?
276
277     do {
278       chunkBytes = this._pusher(pool, bytesRead, pool.length - bytesRead);
279
280       if (this.pair._ssl && this.pair._ssl.error) {
281         this.pair._error();
282         return;
283       }
284
285       this.pair._maybeInitFinished();
286
287       if (chunkBytes >= 0) {
288         bytesRead += chunkBytes;
289       }
290
291     } while ((chunkBytes > 0) && (bytesRead < pool.length));
292
293     assert(bytesRead >= 0);
294
295     // Bail out if we didn't read any data.
296     if (bytesRead == 0) {
297       if (this._internallyPendingBytes() == 0 && this._destroyAfterPush) {
298         this._done();
299       }
300       return;
301     }
302
303     var chunk = pool.slice(0, bytesRead);
304
305     if (this === this.pair.cleartext) {
306       debug('cleartext emit "data" with ' + bytesRead + ' bytes');
307     } else {
308       debug('encrypted emit "data" with ' + bytesRead + ' bytes');
309     }
310
311     if (this._decoder) {
312       var string = this._decoder.write(chunk);
313       if (string.length) this.emit('data', string);
314     } else {
315       this.emit('data', chunk);
316     }
317
318     // Optimization: emit the original buffer with end points
319     if (this.ondata) this.ondata(pool, 0, bytesRead);
320   }
321 };
322
323
324 // Push in any clear data coming from the application.
325 // This arrives via some code like this:
326 //
327 //   pair.cleartext.write("hello world");
328 //
329 // also
330 //
331 // Push in incoming encrypted data from the socket.
332 // This arrives via some code like this:
333 //
334 //   socket.on('data', function (d) {
335 //     pair.encrypted.write(d)
336 //   });
337 //
338 CryptoStream.prototype._pull = function() {
339   var havePending = this._pending.length > 0;
340
341   assert(havePending || this._pendingBytes == 0);
342
343   while (this._pending.length > 0) {
344     if (!this.pair._ssl) break;
345
346     var tmp = this._pending.shift();
347     var cb = this._pendingCallbacks.shift();
348
349     assert(this._pending.length === this._pendingCallbacks.length);
350
351     if (tmp === END_OF_FILE) {
352       // Sending EOF
353       if (this === this.pair.encrypted) {
354         debug('end encrypted ' + this.pair.fd);
355         this.pair.cleartext._destroyAfterPush = true;
356       } else {
357         // CleartextStream
358         assert(this === this.pair.cleartext);
359         debug('end cleartext');
360
361         this.pair._ssl.shutdown();
362
363         // TODO check if we get EAGAIN From shutdown, would have to do it
364         // again. should unshift END_OF_FILE back onto pending and wait for
365         // next cycle.
366
367         this.pair.encrypted._destroyAfterPush = true;
368       }
369       this.pair._cycle();
370       this._done()
371       return;
372     }
373
374     if (tmp.length == 0) continue;
375
376     var rv = this._puller(tmp);
377
378     if (this.pair._ssl && this.pair._ssl.error) {
379       this.pair._error();
380       return;
381     }
382
383     this.pair._maybeInitFinished();
384
385     if (rv === 0 || rv < 0) {
386       this._pending.unshift(tmp);
387       this._pendingCallbacks.unshift(cb);
388       break;
389     }
390
391     this._pendingBytes -= tmp.length;
392     assert(this._pendingBytes >= 0);
393
394     if (cb) cb();
395
396     assert(rv === tmp.length);
397   }
398
399   // If we've cleared all of incoming encrypted data, emit drain.
400   if (havePending && this._pending.length === 0) {
401     debug('drain');
402     this.emit('drain');
403     if (this.__destroyOnDrain) this.end();
404   }
405 };
406
407
408 function CleartextStream(pair) {
409   CryptoStream.call(this, pair);
410 }
411 util.inherits(CleartextStream, CryptoStream);
412
413
414 CleartextStream.prototype._internallyPendingBytes = function() {
415   if (this.pair._ssl) {
416     return this.pair._ssl.clearPending();
417   } else {
418     return 0;
419   }
420 };
421
422
423 CleartextStream.prototype._puller = function(b) {
424   debug('clearIn ' + b.length + ' bytes');
425   return this.pair._ssl.clearIn(b, 0, b.length);
426 };
427
428
429 CleartextStream.prototype._pusher = function(pool, offset, length) {
430   debug('reading from clearOut');
431   if (!this.pair._ssl) return -1;
432   return this.pair._ssl.clearOut(pool, offset, length);
433 };
434
435
436 function EncryptedStream(pair) {
437   CryptoStream.call(this, pair);
438 }
439 util.inherits(EncryptedStream, CryptoStream);
440
441
442 EncryptedStream.prototype._internallyPendingBytes = function() {
443   if (this.pair._ssl) {
444     return this.pair._ssl.encPending();
445   } else {
446     return 0;
447   }
448 };
449
450
451 EncryptedStream.prototype._puller = function(b) {
452   debug('writing from encIn');
453   return this.pair._ssl.encIn(b, 0, b.length);
454 };
455
456
457 EncryptedStream.prototype._pusher = function(pool, offset, length) {
458   debug('reading from encOut');
459   if (!this.pair._ssl) return -1;
460   return this.pair._ssl.encOut(pool, offset, length);
461 };
462
463
464 /**
465  * Provides a pair of streams to do encrypted communication.
466  */
467
468 function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
469                     NPNProtocols) {
470   if (!(this instanceof SecurePair)) {
471     return new SecurePair(credentials,
472                           isServer,
473                           requestCert,
474                           rejectUnauthorized,
475                           NPNProtocols);
476   }
477
478   var self = this;
479
480   events.EventEmitter.call(this);
481
482   this._secureEstablished = false;
483   this._isServer = isServer ? true : false;
484   this._encWriteState = true;
485   this._clearWriteState = true;
486   this._done = false;
487
488   var crypto = require('crypto');
489
490   if (!credentials) {
491     this.credentials = crypto.createCredentials();
492   } else {
493     this.credentials = credentials;
494   }
495
496   if (!this._isServer) {
497     // For clients, we will always have either a given ca list or be using
498     // default one
499     requestCert = true;
500   }
501
502   this._secureEstablished = false;
503   this._rejectUnauthorized = rejectUnauthorized ? true : false;
504   this._requestCert = requestCert ? true : false;
505
506   this._ssl = new Connection(this.credentials.context,
507                              this._isServer ? true : false,
508                              this._requestCert,
509                              this._rejectUnauthorized);
510
511   if (NPN_ENABLED && NPNProtocols) {
512     this._ssl.setNPNProtocols(NPNProtocols);
513     this.npnProtocol = null;
514   }
515
516   /* Acts as a r/w stream to the cleartext side of the stream. */
517   this.cleartext = new CleartextStream(this);
518
519   /* Acts as a r/w stream to the encrypted side of the stream. */
520   this.encrypted = new EncryptedStream(this);
521
522   process.nextTick(function() {
523     self._ssl.start();
524     self._cycle();
525   });
526 }
527
528 util.inherits(SecurePair, events.EventEmitter);
529
530
531 exports.createSecurePair = function(credentials,
532                                     isServer,
533                                     requestCert,
534                                     rejectUnauthorized) {
535   var pair = new SecurePair(credentials,
536                             isServer,
537                             requestCert,
538                             rejectUnauthorized);
539   return pair;
540 };
541
542
543
544
545 /* Attempt to cycle OpenSSLs buffers in various directions.
546  *
547  * An SSL Connection can be viewed as four separate piplines,
548  * interacting with one has no connection to the behavoir of
549  * any of the other 3 -- This might not sound reasonable,
550  * but consider things like mid-stream renegotiation of
551  * the ciphers.
552  *
553  * The four pipelines, using terminology of the client (server is just
554  * reversed):
555  *  (1) Encrypted Output stream (Writing encrypted data to peer)
556  *  (2) Encrypted Input stream (Reading encrypted data from peer)
557  *  (3) Cleartext Output stream (Decrypted content from the peer)
558  *  (4) Cleartext Input stream (Cleartext content to send to the peer)
559  *
560  * This function attempts to pull any available data out of the Cleartext
561  * input stream (4), and the Encrypted input stream (2).  Then it pushes any
562  * data available from the cleartext output stream (3), and finally from the
563  * Encrypted output stream (1)
564  *
565  * It is called whenever we do something with OpenSSL -- post reciving
566  * content, trying to flush, trying to change ciphers, or shutting down the
567  * connection.
568  *
569  * Because it is also called everywhere, we also check if the connection has
570  * completed negotiation and emit 'secure' from here if it has.
571  */
572 SecurePair.prototype._cycle = function(depth) {
573   depth = depth ? depth : 0;
574   if (this._done) {
575     return;
576   }
577
578   if(depth == 0) this._writeCalled = false;
579
580   var established = this._secureEstablished;
581
582   if (!this._cycleEncryptedPullLock) {
583     this._cycleEncryptedPullLock = true;
584     debug("encrypted._pull");
585     this.encrypted._pull();
586     this._cycleEncryptedPullLock = false;
587   }
588
589   if (!this._cycleCleartextPullLock) {
590     this._cycleCleartextPullLock = true;
591     debug("cleartext._pull");
592     this.cleartext._pull();
593     this._cycleCleartextPullLock = false;
594   }
595
596   if (!this._cycleCleartextPushLock) {
597     this._cycleCleartextPushLock = true;
598     debug("cleartext._push");
599     this.cleartext._push();
600     this._cycleCleartextPushLock = false;
601   }
602
603   if (!this._cycleEncryptedPushLock) {
604     this._cycleEncryptedPushLock = true;
605     debug("encrypted._push");
606     this.encrypted._push();
607     this._cycleEncryptedPushLock = false;
608   }
609
610   if (this._done) {
611     return;
612   }
613
614   if ((!established && this._secureEstablished) ||
615       (depth == 0 && this._writeCalled)) {
616     // If we were not established but now we are, let's cycle again.
617     // Or if there is some data to write...
618     this._cycle(depth + 1);
619   }
620 };
621
622
623 SecurePair.prototype._maybeInitFinished = function() {
624   if (this._ssl && !this._secureEstablished && this._ssl.isInitFinished()) {
625     if (NPN_ENABLED) {
626       this.npnProtocol = this._ssl.getNegotiatedProtocol();
627     }
628
629     this._secureEstablished = true;
630     debug('secure established');
631     this.emit('secure');
632   }
633 };
634
635
636 SecurePair.prototype._destroy = function() {
637   var self = this;
638
639   if (!this._done) {
640     this._done = true;
641     this._ssl.error = null;
642     this._ssl.close();
643     this._ssl = null;
644
645     self.encrypted.writable = self.encrypted.readable = false;
646     self.cleartext.writable = self.cleartext.readable = false;
647
648     process.nextTick(function() {
649       self.encrypted.emit('end');
650       if (self.encrypted.onend) self.encrypted.onend();
651       self.encrypted.emit('close');
652
653       self.cleartext.emit('end');
654       if (self.cleartext.onend) self.cleartext.onend();
655       self.cleartext.emit('close');
656     });
657   }
658
659   this._cycle();
660 };
661
662
663 SecurePair.prototype._error = function() {
664   if (!this._secureEstablished) {
665     this._destroy();
666   } else {
667     var err = this._ssl.error;
668     this._ssl.error = null;
669
670     if (this._isServer &&
671         this._rejectUnauthorized &&
672         /peer did not return a certificate/.test(err.message)) {
673       // Not really an error.
674       this._destroy();
675     } else {
676       this.cleartext.emit('error', err);
677     }
678   }
679 };
680
681 // TODO: support anonymous (nocert) and PSK
682
683
684 // AUTHENTICATION MODES
685 //
686 // There are several levels of authentication that TLS/SSL supports.
687 // Read more about this in "man SSL_set_verify".
688 //
689 // 1. The server sends a certificate to the client but does not request a
690 // cert from the client. This is common for most HTTPS servers. The browser
691 // can verify the identity of the server, but the server does not know who
692 // the client is. Authenticating the client is usually done over HTTP using
693 // login boxes and cookies and stuff.
694 //
695 // 2. The server sends a cert to the client and requests that the client
696 // also send it a cert. The client knows who the server is and the server is
697 // requesting the client also identify themselves. There are several
698 // outcomes:
699 //
700 //   A) verifyError returns null meaning the client's certificate is signed
701 //   by one of the server's CAs. The server know's the client idenity now
702 //   and the client is authorized.
703 //
704 //   B) For some reason the client's certificate is not acceptable -
705 //   verifyError returns a string indicating the problem. The server can
706 //   either (i) reject the client or (ii) allow the client to connect as an
707 //   unauthorized connection.
708 //
709 // The mode is controlled by two boolean variables.
710 //
711 // requestCert
712 //   If true the server requests a certificate from client connections. For
713 //   the common HTTPS case, users will want this to be false, which is what
714 //   it defaults to.
715 //
716 // rejectUnauthorized
717 //   If true clients whose certificates are invalid for any reason will not
718 //   be allowed to make connections. If false, they will simply be marked as
719 //   unauthorized but secure communication will continue. By default this is
720 //   false.
721 //
722 //
723 //
724 // Options:
725 // - requestCert. Send verify request. Default to false.
726 // - rejectUnauthorized. Boolean, default to false.
727 // - key. string.
728 // - cert: string.
729 // - ca: string or array of strings.
730 //
731 // emit 'secureConnection'
732 //   function (cleartextStream, encryptedStream) { }
733 //
734 //   'cleartextStream' has the boolean property 'authorized' to determine if
735 //   it was verified by the CA. If 'authorized' is false, a property
736 //   'authorizationError' is set on cleartextStream and has the possible
737 //   values:
738 //
739 //   "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_CRL",
740 //   "UNABLE_TO_DECRYPT_CERT_SIGNATURE", "UNABLE_TO_DECRYPT_CRL_SIGNATURE",
741 //   "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", "CERT_SIGNATURE_FAILURE",
742 //   "CRL_SIGNATURE_FAILURE", "CERT_NOT_YET_VALID" "CERT_HAS_EXPIRED",
743 //   "CRL_NOT_YET_VALID", "CRL_HAS_EXPIRED" "ERROR_IN_CERT_NOT_BEFORE_FIELD",
744 //   "ERROR_IN_CERT_NOT_AFTER_FIELD", "ERROR_IN_CRL_LAST_UPDATE_FIELD",
745 //   "ERROR_IN_CRL_NEXT_UPDATE_FIELD", "OUT_OF_MEM",
746 //   "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN",
747 //   "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
748 //   "CERT_CHAIN_TOO_LONG", "CERT_REVOKED" "INVALID_CA",
749 //   "PATH_LENGTH_EXCEEDED", "INVALID_PURPOSE" "CERT_UNTRUSTED",
750 //   "CERT_REJECTED"
751 //
752 //
753 // TODO:
754 // cleartext.credentials (by mirroring from pair object)
755 // cleartext.getCertificate() (by mirroring from pair.credentials.context)
756 function Server(/* [options], listener */) {
757   var options, listener;
758   if (typeof arguments[0] == 'object') {
759     options = arguments[0];
760     listener = arguments[1];
761   } else if (typeof arguments[0] == 'function') {
762     options = {};
763     listener = arguments[0];
764   }
765
766   if (!(this instanceof Server)) return new Server(options, listener);
767
768   var self = this;
769
770   // constructor call
771   net.Server.call(this, function(socket) {
772     var creds = crypto.createCredentials({
773       key: self.key,
774       cert: self.cert,
775       ca: self.ca,
776       ciphers: self.ciphers,
777       secureProtocol: self.secureProtocol,
778       secureOptions: self.secureOptions,
779       crl: self.crl
780     });
781     creds.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');
782
783     var pair = new SecurePair(creds,
784                               true,
785                               self.requestCert,
786                               self.rejectUnauthorized,
787                               self.NPNProtocols);
788
789     var cleartext = pipe(pair, socket);
790     cleartext._controlReleased = false;
791
792     pair.on('secure', function() {
793       pair.cleartext.authorized = false;
794       pair.cleartext.npnProtocol = pair.npnProtocol;
795       if (!self.requestCert) {
796         cleartext._controlReleased = true;
797         self.emit('secureConnection', pair.cleartext, pair.encrypted);
798       } else {
799         var verifyError = pair._ssl.verifyError();
800         if (verifyError) {
801           pair.cleartext.authorizationError = verifyError;
802
803           if (self.rejectUnauthorized) {
804             socket.destroy();
805             pair._destroy();
806           } else {
807             cleartext._controlReleased = true;
808             self.emit('secureConnection', pair.cleartext, pair.encrypted);
809           }
810         } else {
811           pair.cleartext.authorized = true;
812           cleartext._controlReleased = true;
813           self.emit('secureConnection', pair.cleartext, pair.encrypted);
814         }
815       }
816     });
817   });
818
819   if (listener) {
820     this.on('secureConnection', listener);
821   }
822
823   // Handle option defaults:
824   this.setOptions(options);
825 }
826
827 util.inherits(Server, net.Server);
828 exports.Server = Server;
829 exports.createServer = function(options, listener) {
830   return new Server(options, listener);
831 };
832
833
834 Server.prototype.setOptions = function(options) {
835   if (typeof options.requestCert == 'boolean') {
836     this.requestCert = options.requestCert;
837   } else {
838     this.requestCert = false;
839   }
840
841   if (typeof options.rejectUnauthorized == 'boolean') {
842     this.rejectUnauthorized = options.rejectUnauthorized;
843   } else {
844     this.rejectUnauthorized = false;
845   }
846
847   if (options.key) this.key = options.key;
848   if (options.cert) this.cert = options.cert;
849   if (options.ca) this.ca = options.ca;
850   if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
851   if (options.crl) this.crl = options.crl;
852   if (options.ciphers) this.ciphers = options.ciphers;
853   if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
854   if (options.secureOptions) this.secureOptions = options.secureOptions;
855   if (options.NPNProtocols) convertNPNProtocols(options.NPNProtocols, this);
856 };
857
858
859 // Target API:
860 //
861 //  var s = tls.connect(8000, "google.com", options, function() {
862 //    if (!s.authorized) {
863 //      s.destroy();
864 //      return;
865 //    }
866 //
867 //    // s.socket;
868 //
869 //    s.end("hello world\n");
870 //  });
871 //
872 //
873 // TODO:  make port, host part of options!
874 exports.connect = function(port /* host, options, cb */) {
875   // parse args
876   var host, options = {}, cb;
877   for (var i = 1; i < arguments.length; i++) {
878     switch (typeof arguments[i]) {
879       case 'string':
880         host = arguments[i];
881         break;
882
883       case 'object':
884         options = arguments[i];
885         break;
886
887       case 'function':
888         cb = arguments[i];
889         break;
890     }
891   }
892
893   var socket = new net.Stream();
894
895   var sslcontext = crypto.createCredentials(options);
896   //sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');
897
898   convertNPNProtocols(options.NPNProtocols, this);
899   var pair = new SecurePair(sslcontext, false, true, false,
900                             this.NPNProtocols);
901
902   var cleartext = pipe(pair, socket);
903
904   socket.connect(port, host);
905
906   pair.on('secure', function() {
907     var verifyError = pair._ssl.verifyError();
908
909     cleartext.npnProtocol = pair.npnProtocol;
910
911     if (verifyError) {
912       cleartext.authorized = false;
913       cleartext.authorizationError = verifyError;
914     } else {
915       cleartext.authorized = true;
916     }
917
918     if (cb) cb();
919   });
920
921   cleartext._controlReleased = true;
922   return cleartext;
923 };
924
925
926 function pipe(pair, socket) {
927   pair.encrypted.pipe(socket);
928   socket.pipe(pair.encrypted);
929
930   pair.fd = socket.fd;
931   var cleartext = pair.cleartext;
932   cleartext.socket = socket;
933   cleartext.encrypted = pair.encrypted;
934   cleartext.authorized = false;
935
936   function onerror(e) {
937     if (cleartext._controlReleased) {
938       cleartext.emit('error', e);
939     }
940   }
941
942   function onclose() {
943     socket.removeListener('error', onerror);
944     socket.removeListener('close', onclose);
945   }
946
947   socket.on('error', onerror);
948   socket.on('close', onclose);
949
950   return cleartext;
951 }