3f6d20784b14ceb2ffe92df683c2ae2cf152d668
[platform/upstream/nodejs.git] / lib / _tls_legacy.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 assert = require('assert');
23 var events = require('events');
24 var stream = require('stream');
25 var tls = require('tls');
26 var util = require('util');
27 var common = require('_tls_common');
28
29 var Timer = process.binding('timer_wrap').Timer;
30 var Connection = null;
31 try {
32   Connection = process.binding('crypto').Connection;
33 } catch (e) {
34   throw new Error('node.js not compiled with openssl crypto support.');
35 }
36
37 var debug = util.debuglog('tls-legacy');
38
39 function SlabBuffer() {
40   this.create();
41 }
42
43
44 SlabBuffer.prototype.create = function create() {
45   this.isFull = false;
46   this.pool = new Buffer(tls.SLAB_BUFFER_SIZE);
47   this.offset = 0;
48   this.remaining = this.pool.length;
49 };
50
51
52 SlabBuffer.prototype.use = function use(context, fn, size) {
53   if (this.remaining === 0) {
54     this.isFull = true;
55     return 0;
56   }
57
58   var actualSize = this.remaining;
59
60   if (!util.isNull(size)) actualSize = Math.min(size, actualSize);
61
62   var bytes = fn.call(context, this.pool, this.offset, actualSize);
63   if (bytes > 0) {
64     this.offset += bytes;
65     this.remaining -= bytes;
66   }
67
68   assert(this.remaining >= 0);
69
70   return bytes;
71 };
72
73
74 var slabBuffer = null;
75
76
77 // Base class of both CleartextStream and EncryptedStream
78 function CryptoStream(pair, options) {
79   stream.Duplex.call(this, options);
80
81   this.pair = pair;
82   this._pending = null;
83   this._pendingEncoding = '';
84   this._pendingCallback = null;
85   this._doneFlag = false;
86   this._retryAfterPartial = false;
87   this._halfRead = false;
88   this._sslOutCb = null;
89   this._resumingSession = false;
90   this._reading = true;
91   this._destroyed = false;
92   this._ended = false;
93   this._finished = false;
94   this._opposite = null;
95
96   if (util.isNull(slabBuffer)) slabBuffer = new SlabBuffer();
97   this._buffer = slabBuffer;
98
99   this.once('finish', onCryptoStreamFinish);
100
101   // net.Socket calls .onend too
102   this.once('end', onCryptoStreamEnd);
103 }
104 util.inherits(CryptoStream, stream.Duplex);
105
106
107 function onCryptoStreamFinish() {
108   this._finished = true;
109
110   if (this === this.pair.cleartext) {
111     debug('cleartext.onfinish');
112     if (this.pair.ssl) {
113       // Generate close notify
114       // NOTE: first call checks if client has sent us shutdown,
115       // second call enqueues shutdown into the BIO.
116       if (this.pair.ssl.shutdown() !== 1) {
117         if (this.pair.ssl && this.pair.ssl.error)
118           return this.pair.error();
119
120         this.pair.ssl.shutdown();
121       }
122
123       if (this.pair.ssl && this.pair.ssl.error)
124         return this.pair.error();
125     }
126   } else {
127     debug('encrypted.onfinish');
128   }
129
130   // Try to read just to get sure that we won't miss EOF
131   if (this._opposite.readable) this._opposite.read(0);
132
133   if (this._opposite._ended) {
134     this._done();
135
136     // No half-close, sorry
137     if (this === this.pair.cleartext) this._opposite._done();
138   }
139 }
140
141
142 function onCryptoStreamEnd() {
143   this._ended = true;
144   if (this === this.pair.cleartext) {
145     debug('cleartext.onend');
146   } else {
147     debug('encrypted.onend');
148   }
149 }
150
151
152 // NOTE: Called once `this._opposite` is set.
153 CryptoStream.prototype.init = function init() {
154   var self = this;
155   this._opposite.on('sslOutEnd', function() {
156     if (self._sslOutCb) {
157       var cb = self._sslOutCb;
158       self._sslOutCb = null;
159       cb(null);
160     }
161   });
162 };
163
164
165 CryptoStream.prototype._write = function write(data, encoding, cb) {
166   assert(util.isNull(this._pending));
167
168   // Black-hole data
169   if (!this.pair.ssl) return cb(null);
170
171   // When resuming session don't accept any new data.
172   // And do not put too much data into openssl, before writing it from encrypted
173   // side.
174   //
175   // TODO(indutny): Remove magic number, use watermark based limits
176   if (!this._resumingSession &&
177       this._opposite._internallyPendingBytes() < 128 * 1024) {
178     // Write current buffer now
179     var written;
180     if (this === this.pair.cleartext) {
181       debug('cleartext.write called with %d bytes', data.length);
182       written = this.pair.ssl.clearIn(data, 0, data.length);
183     } else {
184       debug('encrypted.write called with %d bytes', data.length);
185       written = this.pair.ssl.encIn(data, 0, data.length);
186     }
187
188     // Handle and report errors
189     if (this.pair.ssl && this.pair.ssl.error) {
190       return cb(this.pair.error(true));
191     }
192
193     // Force SSL_read call to cycle some states/data inside OpenSSL
194     this.pair.cleartext.read(0);
195
196     // Cycle encrypted data
197     if (this.pair.encrypted._internallyPendingBytes())
198       this.pair.encrypted.read(0);
199
200     // Get NPN and Server name when ready
201     this.pair.maybeInitFinished();
202
203     // Whole buffer was written
204     if (written === data.length) {
205       if (this === this.pair.cleartext) {
206         debug('cleartext.write succeed with ' + written + ' bytes');
207       } else {
208         debug('encrypted.write succeed with ' + written + ' bytes');
209       }
210
211       // Invoke callback only when all data read from opposite stream
212       if (this._opposite._halfRead) {
213         assert(util.isNull(this._sslOutCb));
214         this._sslOutCb = cb;
215       } else {
216         cb(null);
217       }
218       return;
219     } else if (written !== 0 && written !== -1) {
220       assert(!this._retryAfterPartial);
221       this._retryAfterPartial = true;
222       this._write(data.slice(written), encoding, cb);
223       this._retryAfterPartial = false;
224       return;
225     }
226   } else {
227     debug('cleartext.write queue is full');
228
229     // Force SSL_read call to cycle some states/data inside OpenSSL
230     this.pair.cleartext.read(0);
231   }
232
233   // No write has happened
234   this._pending = data;
235   this._pendingEncoding = encoding;
236   this._pendingCallback = cb;
237
238   if (this === this.pair.cleartext) {
239     debug('cleartext.write queued with %d bytes', data.length);
240   } else {
241     debug('encrypted.write queued with %d bytes', data.length);
242   }
243 };
244
245
246 CryptoStream.prototype._writePending = function writePending() {
247   var data = this._pending,
248       encoding = this._pendingEncoding,
249       cb = this._pendingCallback;
250
251   this._pending = null;
252   this._pendingEncoding = '';
253   this._pendingCallback = null;
254   this._write(data, encoding, cb);
255 };
256
257
258 CryptoStream.prototype._read = function read(size) {
259   // XXX: EOF?!
260   if (!this.pair.ssl) return this.push(null);
261
262   // Wait for session to be resumed
263   // Mark that we're done reading, but don't provide data or EOF
264   if (this._resumingSession || !this._reading) return this.push('');
265
266   var out;
267   if (this === this.pair.cleartext) {
268     debug('cleartext.read called with %d bytes', size);
269     out = this.pair.ssl.clearOut;
270   } else {
271     debug('encrypted.read called with %d bytes', size);
272     out = this.pair.ssl.encOut;
273   }
274
275   var bytesRead = 0,
276       start = this._buffer.offset,
277       last = start;
278   do {
279     assert(last === this._buffer.offset);
280     var read = this._buffer.use(this.pair.ssl, out, size - bytesRead);
281     if (read > 0) {
282       bytesRead += read;
283     }
284     last = this._buffer.offset;
285
286     // Handle and report errors
287     if (this.pair.ssl && this.pair.ssl.error) {
288       this.pair.error();
289       break;
290     }
291   } while (read > 0 &&
292            !this._buffer.isFull &&
293            bytesRead < size &&
294            this.pair.ssl !== null);
295
296   // Get NPN and Server name when ready
297   this.pair.maybeInitFinished();
298
299   // Create new buffer if previous was filled up
300   var pool = this._buffer.pool;
301   if (this._buffer.isFull) this._buffer.create();
302
303   assert(bytesRead >= 0);
304
305   if (this === this.pair.cleartext) {
306     debug('cleartext.read succeed with %d bytes', bytesRead);
307   } else {
308     debug('encrypted.read succeed with %d bytes', bytesRead);
309   }
310
311   // Try writing pending data
312   if (!util.isNull(this._pending)) this._writePending();
313   if (!util.isNull(this._opposite._pending)) this._opposite._writePending();
314
315   if (bytesRead === 0) {
316     // EOF when cleartext has finished and we have nothing to read
317     if (this._opposite._finished && this._internallyPendingBytes() === 0 ||
318         this.pair.ssl && this.pair.ssl.receivedShutdown) {
319       // Perform graceful shutdown
320       this._done();
321
322       // No half-open, sorry!
323       if (this === this.pair.cleartext) {
324         this._opposite._done();
325
326         // EOF
327         this.push(null);
328       } else if (!this.pair.ssl || !this.pair.ssl.receivedShutdown) {
329         // EOF
330         this.push(null);
331       }
332     } else {
333       // Bail out
334       this.push('');
335     }
336   } else {
337     // Give them requested data
338     this.push(pool.slice(start, start + bytesRead));
339   }
340
341   // Let users know that we've some internal data to read
342   var halfRead = this._internallyPendingBytes() !== 0;
343
344   // Smart check to avoid invoking 'sslOutEnd' in the most of the cases
345   if (this._halfRead !== halfRead) {
346     this._halfRead = halfRead;
347
348     // Notify listeners about internal data end
349     if (!halfRead) {
350       if (this === this.pair.cleartext) {
351         debug('cleartext.sslOutEnd');
352       } else {
353         debug('encrypted.sslOutEnd');
354       }
355
356       this.emit('sslOutEnd');
357     }
358   }
359 };
360
361
362 CryptoStream.prototype.setTimeout = function(timeout, callback) {
363   if (this.socket) this.socket.setTimeout(timeout, callback);
364 };
365
366
367 CryptoStream.prototype.setNoDelay = function(noDelay) {
368   if (this.socket) this.socket.setNoDelay(noDelay);
369 };
370
371
372 CryptoStream.prototype.setKeepAlive = function(enable, initialDelay) {
373   if (this.socket) this.socket.setKeepAlive(enable, initialDelay);
374 };
375
376 CryptoStream.prototype.__defineGetter__('bytesWritten', function() {
377   return this.socket ? this.socket.bytesWritten : 0;
378 });
379
380 CryptoStream.prototype.getPeerCertificate = function(detailed) {
381   if (this.pair.ssl) {
382     return common.translatePeerCertificate(
383         this.pair.ssl.getPeerCertificate(detailed));
384   }
385
386   return null;
387 };
388
389 CryptoStream.prototype.getSession = function() {
390   if (this.pair.ssl) {
391     return this.pair.ssl.getSession();
392   }
393
394   return null;
395 };
396
397 CryptoStream.prototype.isSessionReused = function() {
398   if (this.pair.ssl) {
399     return this.pair.ssl.isSessionReused();
400   }
401
402   return null;
403 };
404
405 CryptoStream.prototype.getCipher = function(err) {
406   if (this.pair.ssl) {
407     return this.pair.ssl.getCurrentCipher();
408   } else {
409     return null;
410   }
411 };
412
413
414 CryptoStream.prototype.end = function(chunk, encoding) {
415   if (this === this.pair.cleartext) {
416     debug('cleartext.end');
417   } else {
418     debug('encrypted.end');
419   }
420
421   // Write pending data first
422   if (!util.isNull(this._pending)) this._writePending();
423
424   this.writable = false;
425
426   stream.Duplex.prototype.end.call(this, chunk, encoding);
427 };
428
429
430 CryptoStream.prototype.destroySoon = function(err) {
431   if (this === this.pair.cleartext) {
432     debug('cleartext.destroySoon');
433   } else {
434     debug('encrypted.destroySoon');
435   }
436
437   if (this.writable)
438     this.end();
439
440   if (this._writableState.finished && this._opposite._ended) {
441     this.destroy();
442   } else {
443     // Wait for both `finish` and `end` events to ensure that all data that
444     // was written on this side was read from the other side.
445     var self = this;
446     var waiting = 1;
447     function finish() {
448       if (--waiting === 0) self.destroy();
449     }
450     this._opposite.once('end', finish);
451     if (!this._finished) {
452       this.once('finish', finish);
453       ++waiting;
454     }
455   }
456 };
457
458
459 CryptoStream.prototype.destroy = function(err) {
460   if (this._destroyed) return;
461   this._destroyed = true;
462   this.readable = this.writable = false;
463
464   // Destroy both ends
465   if (this === this.pair.cleartext) {
466     debug('cleartext.destroy');
467   } else {
468     debug('encrypted.destroy');
469   }
470   this._opposite.destroy();
471
472   var self = this;
473   process.nextTick(function() {
474     // Force EOF
475     self.push(null);
476
477     // Emit 'close' event
478     self.emit('close', err ? true : false);
479   });
480 };
481
482
483 CryptoStream.prototype._done = function() {
484   this._doneFlag = true;
485
486   if (this === this.pair.encrypted && !this.pair._secureEstablished)
487     return this.pair.error();
488
489   if (this.pair.cleartext._doneFlag &&
490       this.pair.encrypted._doneFlag &&
491       !this.pair._doneFlag) {
492     // If both streams are done:
493     this.pair.destroy();
494   }
495 };
496
497
498 // readyState is deprecated. Don't use it.
499 Object.defineProperty(CryptoStream.prototype, 'readyState', {
500   get: function() {
501     if (this._connecting) {
502       return 'opening';
503     } else if (this.readable && this.writable) {
504       return 'open';
505     } else if (this.readable && !this.writable) {
506       return 'readOnly';
507     } else if (!this.readable && this.writable) {
508       return 'writeOnly';
509     } else {
510       return 'closed';
511     }
512   }
513 });
514
515
516 function CleartextStream(pair, options) {
517   CryptoStream.call(this, pair, options);
518
519   // This is a fake kludge to support how the http impl sits
520   // on top of net Sockets
521   var self = this;
522   this._handle = {
523     readStop: function() {
524       self._reading = false;
525     },
526     readStart: function() {
527       if (self._reading && self._readableState.length > 0) return;
528       self._reading = true;
529       self.read(0);
530       if (self._opposite.readable) self._opposite.read(0);
531     }
532   };
533 }
534 util.inherits(CleartextStream, CryptoStream);
535
536
537 CleartextStream.prototype._internallyPendingBytes = function() {
538   if (this.pair.ssl) {
539     return this.pair.ssl.clearPending();
540   } else {
541     return 0;
542   }
543 };
544
545
546 CleartextStream.prototype.address = function() {
547   return this.socket && this.socket.address();
548 };
549
550
551 CleartextStream.prototype.__defineGetter__('remoteAddress', function() {
552   return this.socket && this.socket.remoteAddress;
553 });
554
555 CleartextStream.prototype.__defineGetter__('remoteFamily', function() {
556   return this.socket && this.socket.remoteFamily;
557 });
558
559 CleartextStream.prototype.__defineGetter__('remotePort', function() {
560   return this.socket && this.socket.remotePort;
561 });
562
563
564 CleartextStream.prototype.__defineGetter__('localAddress', function() {
565   return this.socket && this.socket.localAddress;
566 });
567
568
569 CleartextStream.prototype.__defineGetter__('localPort', function() {
570   return this.socket && this.socket.localPort;
571 });
572
573
574 function EncryptedStream(pair, options) {
575   CryptoStream.call(this, pair, options);
576 }
577 util.inherits(EncryptedStream, CryptoStream);
578
579
580 EncryptedStream.prototype._internallyPendingBytes = function() {
581   if (this.pair.ssl) {
582     return this.pair.ssl.encPending();
583   } else {
584     return 0;
585   }
586 };
587
588
589 function onhandshakestart() {
590   debug('onhandshakestart');
591
592   var self = this;
593   var ssl = self.ssl;
594   var now = Timer.now();
595
596   assert(now >= ssl.lastHandshakeTime);
597
598   if ((now - ssl.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) {
599     ssl.handshakes = 0;
600   }
601
602   var first = (ssl.lastHandshakeTime === 0);
603   ssl.lastHandshakeTime = now;
604   if (first) return;
605
606   if (++ssl.handshakes > tls.CLIENT_RENEG_LIMIT) {
607     // Defer the error event to the next tick. We're being called from OpenSSL's
608     // state machine and OpenSSL is not re-entrant. We cannot allow the user's
609     // callback to destroy the connection right now, it would crash and burn.
610     setImmediate(function() {
611       var err = new Error('TLS session renegotiation attack detected.');
612       if (self.cleartext) self.cleartext.emit('error', err);
613     });
614   }
615 }
616
617
618 function onhandshakedone() {
619   // for future use
620   debug('onhandshakedone');
621 }
622
623
624 function onclienthello(hello) {
625   var self = this,
626       once = false;
627
628   this._resumingSession = true;
629   function callback(err, session) {
630     if (once) return;
631     once = true;
632
633     if (err) return self.socket.destroy(err);
634
635     self.ssl.loadSession(session);
636     self.ssl.endParser();
637
638     // Cycle data
639     self._resumingSession = false;
640     self.cleartext.read(0);
641     self.encrypted.read(0);
642   }
643
644   if (hello.sessionId.length <= 0 ||
645       !this.server ||
646       !this.server.emit('resumeSession', hello.sessionId, callback)) {
647     callback(null, null);
648   }
649 }
650
651
652 function onnewsession(key, session) {
653   if (!this.server) return;
654
655   var self = this;
656   var once = false;
657
658   if (!self.server.emit('newSession', key, session, done))
659     done();
660
661   function done() {
662     if (once)
663       return;
664     once = true;
665
666     if (self.ssl)
667       self.ssl.newSessionDone();
668   };
669 }
670
671
672 function onocspresponse(resp) {
673   this.emit('OCSPResponse', resp);
674 }
675
676
677 /**
678  * Provides a pair of streams to do encrypted communication.
679  */
680
681 function SecurePair(context, isServer, requestCert, rejectUnauthorized,
682                     options) {
683   if (!(this instanceof SecurePair)) {
684     return new SecurePair(context,
685                           isServer,
686                           requestCert,
687                           rejectUnauthorized,
688                           options);
689   }
690
691   var self = this;
692
693   options || (options = {});
694
695   events.EventEmitter.call(this);
696
697   this.server = options.server;
698   this._secureEstablished = false;
699   this._isServer = isServer ? true : false;
700   this._encWriteState = true;
701   this._clearWriteState = true;
702   this._doneFlag = false;
703   this._destroying = false;
704
705   if (!context) {
706     this.credentials = tls.createSecureContext();
707   } else {
708     this.credentials = context;
709   }
710
711   if (!this._isServer) {
712     // For clients, we will always have either a given ca list or be using
713     // default one
714     requestCert = true;
715   }
716
717   this._rejectUnauthorized = rejectUnauthorized ? true : false;
718   this._requestCert = requestCert ? true : false;
719
720   this.ssl = new Connection(this.credentials.context,
721                             this._isServer ? true : false,
722                             this._isServer ? this._requestCert :
723                                              options.servername,
724                             this._rejectUnauthorized);
725
726   if (this._isServer) {
727     this.ssl.onhandshakestart = onhandshakestart.bind(this);
728     this.ssl.onhandshakedone = onhandshakedone.bind(this);
729     this.ssl.onclienthello = onclienthello.bind(this);
730     this.ssl.onnewsession = onnewsession.bind(this);
731     this.ssl.lastHandshakeTime = 0;
732     this.ssl.handshakes = 0;
733   } else {
734     this.ssl.onocspresponse = onocspresponse.bind(this);
735   }
736
737   if (process.features.tls_sni) {
738     if (this._isServer && options.SNICallback) {
739       this.ssl.setSNICallback(options.SNICallback);
740     }
741     this.servername = null;
742   }
743
744   if (process.features.tls_npn && options.NPNProtocols) {
745     this.ssl.setNPNProtocols(options.NPNProtocols);
746     this.npnProtocol = null;
747   }
748
749   /* Acts as a r/w stream to the cleartext side of the stream. */
750   this.cleartext = new CleartextStream(this, options.cleartext);
751
752   /* Acts as a r/w stream to the encrypted side of the stream. */
753   this.encrypted = new EncryptedStream(this, options.encrypted);
754
755   /* Let streams know about each other */
756   this.cleartext._opposite = this.encrypted;
757   this.encrypted._opposite = this.cleartext;
758   this.cleartext.init();
759   this.encrypted.init();
760
761   process.nextTick(function() {
762     /* The Connection may be destroyed by an abort call */
763     if (self.ssl) {
764       self.ssl.start();
765
766       if (options.requestOCSP)
767         self.ssl.requestOCSP();
768
769       /* In case of cipher suite failures - SSL_accept/SSL_connect may fail */
770       if (self.ssl && self.ssl.error)
771         self.error();
772     }
773   });
774 }
775
776 util.inherits(SecurePair, events.EventEmitter);
777
778
779 exports.createSecurePair = function(context,
780                                     isServer,
781                                     requestCert,
782                                     rejectUnauthorized) {
783   var pair = new SecurePair(context,
784                             isServer,
785                             requestCert,
786                             rejectUnauthorized);
787   return pair;
788 };
789
790
791 SecurePair.prototype.maybeInitFinished = function() {
792   if (this.ssl && !this._secureEstablished && this.ssl.isInitFinished()) {
793     if (process.features.tls_npn) {
794       this.npnProtocol = this.ssl.getNegotiatedProtocol();
795     }
796
797     if (process.features.tls_sni) {
798       this.servername = this.ssl.getServername();
799     }
800
801     this._secureEstablished = true;
802     debug('secure established');
803     this.emit('secure');
804   }
805 };
806
807
808 SecurePair.prototype.destroy = function() {
809   if (this._destroying) return;
810
811   if (!this._doneFlag) {
812     debug('SecurePair.destroy');
813     this._destroying = true;
814
815     // SecurePair should be destroyed only after it's streams
816     this.cleartext.destroy();
817     this.encrypted.destroy();
818
819     this._doneFlag = true;
820     this.ssl.error = null;
821     this.ssl.close();
822     this.ssl = null;
823   }
824 };
825
826
827 SecurePair.prototype.error = function(returnOnly) {
828   var err = this.ssl.error;
829   this.ssl.error = null;
830
831   if (!this._secureEstablished) {
832     // Emit ECONNRESET instead of zero return
833     if (!err || err.message === 'ZERO_RETURN') {
834       var connReset = new Error('socket hang up');
835       connReset.code = 'ECONNRESET';
836       connReset.sslError = err && err.message;
837
838       err = connReset;
839     }
840     this.destroy();
841     if (!returnOnly) this.emit('error', err);
842   } else if (this._isServer &&
843              this._rejectUnauthorized &&
844              /peer did not return a certificate/.test(err.message)) {
845     // Not really an error.
846     this.destroy();
847   } else {
848     if (!returnOnly) this.cleartext.emit('error', err);
849   }
850   return err;
851 };
852
853
854 exports.pipe = function pipe(pair, socket) {
855   pair.encrypted.pipe(socket);
856   socket.pipe(pair.encrypted);
857
858   pair.encrypted.on('close', function() {
859     process.nextTick(function() {
860       // Encrypted should be unpiped from socket to prevent possible
861       // write after destroy.
862       pair.encrypted.unpipe(socket);
863       socket.destroySoon();
864     });
865   });
866
867   pair.fd = socket.fd;
868   var cleartext = pair.cleartext;
869   cleartext.socket = socket;
870   cleartext.encrypted = pair.encrypted;
871   cleartext.authorized = false;
872
873   // cycle the data whenever the socket drains, so that
874   // we can pull some more into it.  normally this would
875   // be handled by the fact that pipe() triggers read() calls
876   // on writable.drain, but CryptoStreams are a bit more
877   // complicated.  Since the encrypted side actually gets
878   // its data from the cleartext side, we have to give it a
879   // light kick to get in motion again.
880   socket.on('drain', function() {
881     if (pair.encrypted._pending)
882       pair.encrypted._writePending();
883     if (pair.cleartext._pending)
884       pair.cleartext._writePending();
885     pair.encrypted.read(0);
886     pair.cleartext.read(0);
887   });
888
889   function onerror(e) {
890     if (cleartext._controlReleased) {
891       cleartext.emit('error', e);
892     }
893   }
894
895   function onclose() {
896     socket.removeListener('error', onerror);
897     socket.removeListener('timeout', ontimeout);
898   }
899
900   function ontimeout() {
901     cleartext.emit('timeout');
902   }
903
904   socket.on('error', onerror);
905   socket.on('close', onclose);
906   socket.on('timeout', ontimeout);
907
908   return cleartext;
909 };