Revision d59beb9f
lib/tls.js | ||
---|---|---|
24 | 24 |
var net = require('net'); |
25 | 25 |
var url = require('url'); |
26 | 26 |
var events = require('events'); |
27 |
var Stream = require('stream'); |
|
28 |
var END_OF_FILE = 42; |
|
27 |
var stream = require('stream'); |
|
29 | 28 |
var assert = require('assert').ok; |
30 | 29 |
var constants = require('constants'); |
31 | 30 |
|
... | ... | |
209 | 208 |
}; |
210 | 209 |
|
211 | 210 |
|
212 |
SlabBuffer.prototype.use = function use(context, fn) { |
|
211 |
SlabBuffer.prototype.use = function use(context, fn, size) {
|
|
213 | 212 |
if (this.remaining === 0) { |
214 | 213 |
this.isFull = true; |
215 | 214 |
return 0; |
216 | 215 |
} |
217 | 216 |
|
218 |
var bytes = fn.call(context, this.pool, this.offset, this.remaining);
|
|
217 |
var actualSize = this.remaining;
|
|
219 | 218 |
|
219 |
if (size !== null) actualSize = Math.min(size, actualSize); |
|
220 |
|
|
221 |
var bytes = fn.call(context, this.pool, this.offset, actualSize); |
|
220 | 222 |
if (bytes > 0) { |
221 | 223 |
this.offset += bytes; |
222 | 224 |
this.remaining -= bytes; |
... | ... | |
232 | 234 |
|
233 | 235 |
|
234 | 236 |
// Base class of both CleartextStream and EncryptedStream |
235 |
function CryptoStream(pair) { |
|
236 |
Stream.call(this);
|
|
237 |
function CryptoStream(pair, options) {
|
|
238 |
stream.Duplex.call(this, options);
|
|
237 | 239 |
|
238 | 240 |
this.pair = pair; |
241 |
this._pending = null; |
|
242 |
this._pendingCallback = null; |
|
243 |
this._doneFlag = false; |
|
244 |
this._resumingSession = false; |
|
245 |
this._destroyed = false; |
|
246 |
this._ended = false; |
|
247 |
this._finished = false; |
|
248 |
this._opposite = null; |
|
239 | 249 |
|
240 |
this.readable = this.writable = true; |
|
241 |
|
|
242 |
this._paused = false; |
|
243 |
this._needDrain = false; |
|
244 |
this._pending = []; |
|
245 |
this._pendingCallbacks = []; |
|
246 |
this._pendingBytes = 0; |
|
247 | 250 |
if (slabBuffer === null) slabBuffer = new SlabBuffer(); |
248 | 251 |
this._buffer = slabBuffer; |
252 |
|
|
253 |
this.once('finish', onCryptoStreamFinish); |
|
254 |
|
|
255 |
// net.Socket calls .onend too |
|
256 |
this.once('end', onCryptoStreamEnd); |
|
249 | 257 |
} |
250 |
util.inherits(CryptoStream, Stream); |
|
258 |
util.inherits(CryptoStream, stream.Duplex); |
|
259 |
|
|
251 | 260 |
|
261 |
function onCryptoStreamFinish() { |
|
262 |
this._finished = true; |
|
252 | 263 |
|
253 |
CryptoStream.prototype.write = function(data /* , encoding, cb */) { |
|
254 |
if (this == this.pair.cleartext) { |
|
255 |
debug('cleartext.write called with ' + data.length + ' bytes'); |
|
264 |
if (this === this.pair.cleartext) { |
|
265 |
debug('cleartext.onfinish'); |
|
266 |
if (this.pair.ssl) { |
|
267 |
// Generate close notify |
|
268 |
// NOTE: first call checks if client has sent us shutdown, |
|
269 |
// second call enqueues shutdown into the BIO. |
|
270 |
if (this.pair.ssl.shutdown() !== 1) { |
|
271 |
this.pair.ssl.shutdown(); |
|
272 |
} |
|
273 |
} |
|
256 | 274 |
} else { |
257 |
debug('encrypted.write called with ' + data.length + ' bytes');
|
|
275 |
debug('encrypted.onfinish');
|
|
258 | 276 |
} |
259 | 277 |
|
260 |
if (!this.writable) { |
|
261 |
throw new Error('CryptoStream is not writable'); |
|
262 |
} |
|
278 |
// Try to read just to get sure that we won't miss EOF |
|
279 |
if (this._opposite.readable) this._opposite.read(0); |
|
263 | 280 |
|
264 |
var encoding, cb; |
|
281 |
if (this._opposite._ended) { |
|
282 |
this._done(); |
|
265 | 283 |
|
266 |
// parse arguments |
|
267 |
if (typeof arguments[1] == 'string') { |
|
268 |
encoding = arguments[1]; |
|
269 |
cb = arguments[2]; |
|
270 |
} else { |
|
271 |
cb = arguments[1]; |
|
284 |
// No half-close, sorry |
|
285 |
if (this === this.pair.cleartext) this._opposite._done(); |
|
272 | 286 |
} |
287 |
} |
|
273 | 288 |
|
274 | 289 |
|
275 |
// Transform strings into buffers. |
|
276 |
if (typeof data == 'string') { |
|
277 |
data = new Buffer(data, encoding); |
|
290 |
function onCryptoStreamEnd() { |
|
291 |
this._ended = true; |
|
292 |
if (this === this.pair.cleartext) { |
|
293 |
debug('cleartext.onend'); |
|
294 |
} else { |
|
295 |
debug('encrypted.onend'); |
|
278 | 296 |
} |
279 | 297 |
|
280 |
debug((this === this.pair.cleartext ? 'clear' : 'encrypted') + 'In data'); |
|
298 |
if (this.onend) this.onend(); |
|
299 |
} |
|
281 | 300 |
|
282 |
this._pending.push(data); |
|
283 |
this._pendingCallbacks.push(cb); |
|
284 |
this._pendingBytes += data.length; |
|
285 | 301 |
|
286 |
this.pair._writeCalled = true;
|
|
287 |
this.pair.cycle();
|
|
302 |
CryptoStream.prototype._write = function write(data, cb) {
|
|
303 |
assert(this._pending === null);
|
|
288 | 304 |
|
289 |
// In the following cases, write() should return a false, |
|
290 |
// then this stream should eventually emit 'drain' event. |
|
291 |
// |
|
292 |
// 1. There are pending data more than 128k bytes. |
|
293 |
// 2. A forward stream shown below is paused. |
|
294 |
// A) EncryptedStream for CleartextStream.write(). |
|
295 |
// B) CleartextStream for EncryptedStream.write(). |
|
305 |
// Black-hole data |
|
306 |
if (!this.pair.ssl) return cb(null); |
|
307 |
|
|
308 |
// When resuming session don't accept any new data. |
|
309 |
// And do not put too much data into openssl, before writing it from encrypted |
|
310 |
// side. |
|
296 | 311 |
// |
297 |
if (!this._needDrain) { |
|
298 |
if (this._pendingBytes >= 128 * 1024) { |
|
299 |
this._needDrain = true; |
|
312 |
// TODO(indutny): Remove magic number, use watermark based limits |
|
313 |
if (!this._resumingSession && |
|
314 |
(this !== this.pair.cleartext || |
|
315 |
this.pair.encrypted._internallyPendingBytes() < 128 * 1024)) { |
|
316 |
// Write current buffer now |
|
317 |
var written; |
|
318 |
if (this === this.pair.cleartext) { |
|
319 |
debug('cleartext.write called with ' + data.length + ' bytes'); |
|
320 |
written = this.pair.ssl.clearIn(data, 0, data.length); |
|
300 | 321 |
} else { |
322 |
debug('encrypted.write called with ' + data.length + ' bytes'); |
|
323 |
written = this.pair.ssl.encIn(data, 0, data.length); |
|
324 |
} |
|
325 |
|
|
326 |
var self = this; |
|
327 |
|
|
328 |
// Force SSL_read call to cycle some states/data inside OpenSSL |
|
329 |
this.pair.cleartext.read(0); |
|
330 |
|
|
331 |
// Cycle encrypted data |
|
332 |
if (this.pair.encrypted._internallyPendingBytes()) { |
|
333 |
this.pair.encrypted.read(0); |
|
334 |
} |
|
335 |
|
|
336 |
// Handle and report errors |
|
337 |
if (this.pair.ssl && this.pair.ssl.error) { |
|
338 |
return cb(this.pair.error()); |
|
339 |
} |
|
340 |
|
|
341 |
// Get NPN and Server name when ready |
|
342 |
this.pair.maybeInitFinished(); |
|
343 |
|
|
344 |
// Whole buffer was written |
|
345 |
if (written === data.length) { |
|
301 | 346 |
if (this === this.pair.cleartext) { |
302 |
this._needDrain = this.pair.encrypted._paused;
|
|
347 |
debug('cleartext.write succeed with ' + data.length + ' bytes');
|
|
303 | 348 |
} else { |
304 |
this._needDrain = this.pair.cleartext._paused;
|
|
349 |
debug('encrypted.write succeed with ' + data.length + ' bytes');
|
|
305 | 350 |
} |
351 |
|
|
352 |
return cb(null); |
|
306 | 353 |
} |
354 |
assert(written === 0 || written === -1); |
|
355 |
} else { |
|
356 |
debug('cleartext.write queue is full'); |
|
357 |
|
|
358 |
// Force SSL_read call to cycle some states/data inside OpenSSL |
|
359 |
this.pair.cleartext.read(0); |
|
360 |
} |
|
361 |
|
|
362 |
// No write has happened |
|
363 |
this._pending = data; |
|
364 |
this._pendingCallback = cb; |
|
365 |
|
|
366 |
if (this === this.pair.cleartext) { |
|
367 |
debug('cleartext.write queued with ' + data.length + ' bytes'); |
|
368 |
} else { |
|
369 |
debug('encrypted.write queued with ' + data.length + ' bytes'); |
|
307 | 370 |
} |
308 |
return !this._needDrain; |
|
309 | 371 |
}; |
310 | 372 |
|
311 | 373 |
|
312 |
CryptoStream.prototype.pause = function() { |
|
313 |
debug('paused ' + (this == this.pair.cleartext ? 'cleartext' : 'encrypted')); |
|
314 |
this._paused = true; |
|
374 |
CryptoStream.prototype._writePending = function writePending() { |
|
375 |
var data = this._pending, |
|
376 |
cb = this._pendingCallback; |
|
377 |
|
|
378 |
this._pending = null; |
|
379 |
this._pendingCallback = null; |
|
380 |
this._write(data, cb); |
|
315 | 381 |
}; |
316 | 382 |
|
317 | 383 |
|
318 |
CryptoStream.prototype.resume = function() { |
|
319 |
debug('resume ' + (this == this.pair.cleartext ? 'cleartext' : 'encrypted')); |
|
320 |
this._paused = false; |
|
321 |
this.pair.cycle(); |
|
384 |
CryptoStream.prototype._read = function read(size, cb) { |
|
385 |
// XXX: EOF?! |
|
386 |
if (!this.pair.ssl) return cb(null, null); |
|
387 |
|
|
388 |
// Wait for session to be resumed |
|
389 |
if (this._resumingSession) return cb(null, ''); |
|
390 |
|
|
391 |
var out; |
|
392 |
if (this === this.pair.cleartext) { |
|
393 |
debug('cleartext.read called with ' + size + ' bytes'); |
|
394 |
out = this.pair.ssl.clearOut; |
|
395 |
} else { |
|
396 |
debug('encrypted.read called with ' + size + ' bytes'); |
|
397 |
out = this.pair.ssl.encOut; |
|
398 |
} |
|
399 |
|
|
400 |
var bytesRead = 0, |
|
401 |
start = this._buffer.offset; |
|
402 |
do { |
|
403 |
var read = this._buffer.use(this.pair.ssl, out, size); |
|
404 |
if (read > 0) { |
|
405 |
bytesRead += read; |
|
406 |
size -= read; |
|
407 |
} |
|
408 |
|
|
409 |
// Handle and report errors |
|
410 |
if (this.pair.ssl && this.pair.ssl.error) { |
|
411 |
this.pair.error(); |
|
412 |
break; |
|
413 |
} |
|
414 |
|
|
415 |
// Get NPN and Server name when ready |
|
416 |
this.pair.maybeInitFinished(); |
|
417 |
} while (read > 0 && !this._buffer.isFull && bytesRead < size); |
|
418 |
|
|
419 |
// Create new buffer if previous was filled up |
|
420 |
var pool = this._buffer.pool; |
|
421 |
if (this._buffer.isFull) this._buffer.create(); |
|
422 |
|
|
423 |
assert(bytesRead >= 0); |
|
424 |
|
|
425 |
if (this === this.pair.cleartext) { |
|
426 |
debug('cleartext.read succeed with ' + bytesRead + ' bytes'); |
|
427 |
} else { |
|
428 |
debug('encrypted.read succeed with ' + bytesRead + ' bytes'); |
|
429 |
} |
|
430 |
|
|
431 |
// Try writing pending data |
|
432 |
if (this._pending !== null) this._writePending(); |
|
433 |
|
|
434 |
if (bytesRead === 0) { |
|
435 |
// EOF when cleartext has finished and we have nothing to read |
|
436 |
if (this._opposite._finished && this._internallyPendingBytes() === 0) { |
|
437 |
// Perform graceful shutdown |
|
438 |
this._done(); |
|
439 |
|
|
440 |
// No half-open, sorry! |
|
441 |
if (this === this.pair.cleartext) |
|
442 |
this._opposite._done(); |
|
443 |
|
|
444 |
return cb(null, null); |
|
445 |
} |
|
446 |
|
|
447 |
// Bail out |
|
448 |
return cb(null, ''); |
|
449 |
} |
|
450 |
|
|
451 |
// Give them requested data |
|
452 |
if (this.ondata) { |
|
453 |
var self = this; |
|
454 |
this.ondata(pool, start, start + bytesRead); |
|
455 |
|
|
456 |
// Consume data automatically |
|
457 |
// simple/test-https-drain fails without it |
|
458 |
process.nextTick(function() { |
|
459 |
self.read(bytesRead); |
|
460 |
}); |
|
461 |
} |
|
462 |
return cb(null, pool.slice(start, start + bytesRead)); |
|
322 | 463 |
}; |
323 | 464 |
|
324 | 465 |
|
... | ... | |
340 | 481 |
return this.socket ? this.socket.bytesWritten : 0; |
341 | 482 |
}); |
342 | 483 |
|
343 |
CryptoStream.prototype.setEncoding = function(encoding) { |
|
344 |
var StringDecoder = require('string_decoder').StringDecoder; // lazy load |
|
345 |
this._decoder = new StringDecoder(encoding); |
|
346 |
}; |
|
347 |
|
|
348 | 484 |
|
349 | 485 |
// Example: |
350 | 486 |
// C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@clouds.org |
... | ... | |
409 | 545 |
}; |
410 | 546 |
|
411 | 547 |
|
412 |
CryptoStream.prototype.end = function(d) { |
|
413 |
if (this.pair._doneFlag) return; |
|
414 |
if (!this.writable) return; |
|
415 |
|
|
416 |
if (d) { |
|
417 |
this.write(d); |
|
548 |
CryptoStream.prototype.end = function(chunk, encoding) { |
|
549 |
if (this === this.pair.cleartext) { |
|
550 |
debug('cleartext.end'); |
|
551 |
} else { |
|
552 |
debug('encrypted.end'); |
|
418 | 553 |
} |
419 | 554 |
|
420 |
this._pending.push(END_OF_FILE); |
|
421 |
this._pendingCallbacks.push(null); |
|
422 |
|
|
423 |
// If this is an encrypted stream then we need to disable further 'data' |
|
424 |
// events. |
|
555 |
// Write pending data first |
|
556 |
if (this._pending !== null) this._writePending(); |
|
425 | 557 |
|
426 | 558 |
this.writable = false; |
427 | 559 |
|
428 |
this.pair.cycle();
|
|
560 |
stream.Duplex.prototype.end.call(this, chunk, encoding);
|
|
429 | 561 |
}; |
430 | 562 |
|
431 | 563 |
|
432 | 564 |
CryptoStream.prototype.destroySoon = function(err) { |
433 |
if (this.writable) {
|
|
434 |
this.end();
|
|
565 |
if (this === this.pair.cleartext) {
|
|
566 |
debug('cleartext.destroySoon');
|
|
435 | 567 |
} else { |
436 |
this.destroy();
|
|
568 |
debug('encrypted.destroySoon');
|
|
437 | 569 |
} |
570 |
|
|
571 |
if (this.writable) |
|
572 |
this.end(); |
|
573 |
|
|
574 |
if (this._writableState.finishing || this._writableState.finished) |
|
575 |
this.destroy(); |
|
576 |
else |
|
577 |
this.once('finish', this.destroy); |
|
438 | 578 |
}; |
439 | 579 |
|
440 | 580 |
|
441 | 581 |
CryptoStream.prototype.destroy = function(err) { |
442 |
if (this.pair._doneFlag) return; |
|
443 |
this.pair.destroy(); |
|
582 |
if (this._destroyed) return; |
|
583 |
this._destroyed = true; |
|
584 |
this.readable = this.writable = false; |
|
585 |
|
|
586 |
// Destroy both ends |
|
587 |
if (this === this.pair.cleartext) { |
|
588 |
debug('cleartext.destroy'); |
|
589 |
} else { |
|
590 |
debug('encrypted.destroy'); |
|
591 |
} |
|
592 |
this._opposite.destroy(); |
|
593 |
|
|
594 |
var self = this; |
|
595 |
process.nextTick(function() { |
|
596 |
// Force EOF |
|
597 |
self.push(null); |
|
598 |
|
|
599 |
// Emit 'close' event |
|
600 |
self.emit('close', err ? true : false); |
|
601 |
}); |
|
444 | 602 |
}; |
445 | 603 |
|
446 | 604 |
|
447 | 605 |
CryptoStream.prototype._done = function() { |
448 | 606 |
this._doneFlag = true; |
449 | 607 |
|
608 |
if (this === this.pair.encrypted && !this.pair._secureEstablished) |
|
609 |
return this.pair.error(); |
|
610 |
|
|
450 | 611 |
if (this.pair.cleartext._doneFlag && |
451 | 612 |
this.pair.encrypted._doneFlag && |
452 | 613 |
!this.pair._doneFlag) { |
453 | 614 |
// If both streams are done: |
454 |
if (!this.pair._secureEstablished) { |
|
455 |
this.pair.error(); |
|
456 |
} else { |
|
457 |
this.pair.destroy(); |
|
458 |
} |
|
615 |
this.pair.destroy(); |
|
459 | 616 |
} |
460 | 617 |
}; |
461 | 618 |
|
... | ... | |
478 | 635 |
}); |
479 | 636 |
|
480 | 637 |
|
481 |
// Move decrypted, clear data out into the application. |
|
482 |
// From the user's perspective this occurs as a 'data' event |
|
483 |
// on the pair.cleartext. |
|
484 |
// also |
|
485 |
// Move encrypted data to the stream. From the user's perspective this |
|
486 |
// occurs as a 'data' event on the pair.encrypted. Usually the application |
|
487 |
// will have some code which pipes the stream to a socket: |
|
488 |
// |
|
489 |
// pair.encrypted.on('data', function (d) { |
|
490 |
// socket.write(d); |
|
491 |
// }); |
|
492 |
// |
|
493 |
CryptoStream.prototype._push = function() { |
|
494 |
if (this == this.pair.encrypted && !this.writable) { |
|
495 |
// If the encrypted side got EOF, we do not attempt |
|
496 |
// to write out data anymore. |
|
497 |
return; |
|
498 |
} |
|
499 |
|
|
500 |
while (!this._paused) { |
|
501 |
var chunkBytes = 0, |
|
502 |
bytesRead = 0, |
|
503 |
start = this._buffer.offset; |
|
504 |
|
|
505 |
do { |
|
506 |
chunkBytes = this._buffer.use(this, this._pusher); |
|
507 |
if (chunkBytes > 0) bytesRead += chunkBytes; |
|
508 |
|
|
509 |
if (this.pair.ssl && this.pair.ssl.error) { |
|
510 |
this.pair.error(); |
|
511 |
return; |
|
512 |
} |
|
513 |
|
|
514 |
this.pair.maybeInitFinished(); |
|
515 |
|
|
516 |
} while (chunkBytes > 0 && !this._buffer.isFull); |
|
517 |
|
|
518 |
var pool = this._buffer.pool; |
|
519 |
|
|
520 |
// Create new buffer if previous was filled up |
|
521 |
if (this._buffer.isFull) this._buffer.create(); |
|
522 |
|
|
523 |
assert(bytesRead >= 0); |
|
524 |
|
|
525 |
// Bail out if we didn't read any data. |
|
526 |
if (bytesRead == 0) { |
|
527 |
if (this._internallyPendingBytes() == 0 && this._destroyAfterPush) { |
|
528 |
this._done(); |
|
529 |
} |
|
530 |
return; |
|
531 |
} |
|
532 |
|
|
533 |
var chunk = pool.slice(start, start + bytesRead); |
|
534 |
|
|
535 |
if (this === this.pair.cleartext) { |
|
536 |
debug('cleartext emit "data" with ' + bytesRead + ' bytes'); |
|
537 |
} else { |
|
538 |
debug('encrypted emit "data" with ' + bytesRead + ' bytes'); |
|
539 |
} |
|
540 |
|
|
541 |
if (this._decoder) { |
|
542 |
var string = this._decoder.write(chunk); |
|
543 |
if (string.length) this.emit('data', string); |
|
544 |
} else { |
|
545 |
this.emit('data', chunk); |
|
546 |
} |
|
547 |
|
|
548 |
// Optimization: emit the original buffer with end points |
|
549 |
if (this.ondata) this.ondata(pool, start, start + bytesRead); |
|
550 |
} |
|
551 |
}; |
|
552 |
|
|
553 |
|
|
554 |
// Push in any clear data coming from the application. |
|
555 |
// This arrives via some code like this: |
|
556 |
// |
|
557 |
// pair.cleartext.write("hello world"); |
|
558 |
// |
|
559 |
// also |
|
560 |
// |
|
561 |
// Push in incoming encrypted data from the socket. |
|
562 |
// This arrives via some code like this: |
|
563 |
// |
|
564 |
// socket.on('data', function (d) { |
|
565 |
// pair.encrypted.write(d) |
|
566 |
// }); |
|
567 |
// |
|
568 |
CryptoStream.prototype._pull = function() { |
|
569 |
var havePending = this._pending.length > 0; |
|
570 |
|
|
571 |
assert(havePending || this._pendingBytes == 0); |
|
572 |
|
|
573 |
while (this._pending.length > 0) { |
|
574 |
if (!this.pair.ssl) break; |
|
575 |
|
|
576 |
var tmp = this._pending.shift(); |
|
577 |
var cb = this._pendingCallbacks.shift(); |
|
578 |
|
|
579 |
assert(this._pending.length === this._pendingCallbacks.length); |
|
580 |
|
|
581 |
if (tmp === END_OF_FILE) { |
|
582 |
// Sending EOF |
|
583 |
if (this === this.pair.encrypted) { |
|
584 |
debug('end encrypted ' + this.pair.fd); |
|
585 |
this.pair.cleartext._destroyAfterPush = true; |
|
586 |
} else { |
|
587 |
// CleartextStream |
|
588 |
assert(this === this.pair.cleartext); |
|
589 |
debug('end cleartext'); |
|
590 |
|
|
591 |
this.pair.ssl.shutdown(); |
|
592 |
|
|
593 |
// TODO check if we get EAGAIN From shutdown, would have to do it |
|
594 |
// again. should unshift END_OF_FILE back onto pending and wait for |
|
595 |
// next cycle. |
|
596 |
|
|
597 |
this.pair.encrypted._destroyAfterPush = true; |
|
598 |
} |
|
599 |
this.pair.cycle(); |
|
600 |
this._done(); |
|
601 |
return; |
|
602 |
} |
|
603 |
|
|
604 |
if (tmp.length == 0) continue; |
|
605 |
|
|
606 |
var rv = this._puller(tmp); |
|
607 |
|
|
608 |
if (this.pair.ssl && this.pair.ssl.error) { |
|
609 |
this.pair.error(); |
|
610 |
return; |
|
611 |
} |
|
612 |
|
|
613 |
this.pair.maybeInitFinished(); |
|
614 |
|
|
615 |
if (rv === 0 || rv < 0) { |
|
616 |
this._pending.unshift(tmp); |
|
617 |
this._pendingCallbacks.unshift(cb); |
|
618 |
break; |
|
619 |
} |
|
620 |
|
|
621 |
this._pendingBytes -= tmp.length; |
|
622 |
assert(this._pendingBytes >= 0); |
|
623 |
|
|
624 |
if (cb) cb(); |
|
625 |
|
|
626 |
assert(rv === tmp.length); |
|
627 |
} |
|
628 |
|
|
629 |
// If pending data has cleared, 'drain' event should be emitted |
|
630 |
// after write() returns a false. |
|
631 |
// Except when a forward stream shown below is paused. |
|
632 |
// A) EncryptedStream for CleartextStream._pull(). |
|
633 |
// B) CleartextStream for EncryptedStream._pull(). |
|
634 |
// |
|
635 |
if (this._needDrain && this._pending.length === 0) { |
|
636 |
var paused; |
|
637 |
if (this === this.pair.cleartext) { |
|
638 |
paused = this.pair.encrypted._paused; |
|
639 |
} else { |
|
640 |
paused = this.pair.cleartext._paused; |
|
641 |
} |
|
642 |
if (!paused) { |
|
643 |
debug('drain ' + (this === this.pair.cleartext ? 'clear' : 'encrypted')); |
|
644 |
var self = this; |
|
645 |
process.nextTick(function() { |
|
646 |
self.emit('drain'); |
|
647 |
}); |
|
648 |
this._needDrain = false; |
|
649 |
if (this.__destroyOnDrain) this.end(); |
|
650 |
} |
|
651 |
} |
|
652 |
}; |
|
653 |
|
|
654 |
|
|
655 |
function CleartextStream(pair) { |
|
656 |
CryptoStream.call(this, pair); |
|
638 |
function CleartextStream(pair, options) { |
|
639 |
CryptoStream.call(this, pair, options); |
|
657 | 640 |
} |
658 | 641 |
util.inherits(CleartextStream, CryptoStream); |
659 | 642 |
|
... | ... | |
667 | 650 |
}; |
668 | 651 |
|
669 | 652 |
|
670 |
CleartextStream.prototype._puller = function(b) { |
|
671 |
debug('clearIn ' + b.length + ' bytes'); |
|
672 |
return this.pair.ssl.clearIn(b, 0, b.length); |
|
673 |
}; |
|
674 |
|
|
675 |
|
|
676 |
CleartextStream.prototype._pusher = function(pool, offset, length) { |
|
677 |
debug('reading from clearOut'); |
|
678 |
if (!this.pair.ssl) return -1; |
|
679 |
return this.pair.ssl.clearOut(pool, offset, length); |
|
680 |
}; |
|
681 |
|
|
682 | 653 |
CleartextStream.prototype.address = function() { |
683 | 654 |
return this.socket && this.socket.address(); |
684 | 655 |
}; |
685 | 656 |
|
657 |
|
|
686 | 658 |
CleartextStream.prototype.__defineGetter__('remoteAddress', function() { |
687 | 659 |
return this.socket && this.socket.remoteAddress; |
688 | 660 |
}); |
... | ... | |
692 | 664 |
return this.socket && this.socket.remotePort; |
693 | 665 |
}); |
694 | 666 |
|
695 |
function EncryptedStream(pair) { |
|
696 |
CryptoStream.call(this, pair); |
|
667 |
function EncryptedStream(pair, options) {
|
|
668 |
CryptoStream.call(this, pair, options);
|
|
697 | 669 |
} |
698 | 670 |
util.inherits(EncryptedStream, CryptoStream); |
699 | 671 |
|
... | ... | |
707 | 679 |
}; |
708 | 680 |
|
709 | 681 |
|
710 |
EncryptedStream.prototype._puller = function(b) { |
|
711 |
debug('writing from encIn'); |
|
712 |
return this.pair.ssl.encIn(b, 0, b.length); |
|
713 |
}; |
|
714 |
|
|
715 |
|
|
716 |
EncryptedStream.prototype._pusher = function(pool, offset, length) { |
|
717 |
debug('reading from encOut'); |
|
718 |
if (!this.pair.ssl) return -1; |
|
719 |
return this.pair.ssl.encOut(pool, offset, length); |
|
720 |
}; |
|
721 |
|
|
722 |
|
|
723 | 682 |
function onhandshakestart() { |
724 | 683 |
debug('onhandshakestart'); |
725 | 684 |
|
... | ... | |
754 | 713 |
debug('onhandshakedone'); |
755 | 714 |
} |
756 | 715 |
|
716 |
|
|
757 | 717 |
function onclienthello(hello) { |
758 | 718 |
var self = this, |
759 | 719 |
once = false; |
760 | 720 |
|
761 |
this.encrypted.pause(); |
|
762 |
this.cleartext.pause(); |
|
721 |
this._resumingSession = true; |
|
763 | 722 |
function callback(err, session) { |
764 | 723 |
if (once) return; |
765 | 724 |
once = true; |
... | ... | |
768 | 727 |
|
769 | 728 |
self.ssl.loadSession(session); |
770 | 729 |
|
771 |
self.encrypted.resume(); |
|
772 |
self.cleartext.resume(); |
|
730 |
// Cycle data |
|
731 |
self._resumingSession = false; |
|
732 |
self.cleartext.read(0); |
|
733 |
self.encrypted.read(0); |
|
773 | 734 |
} |
774 | 735 |
|
775 | 736 |
if (hello.sessionId.length <= 0 || |
... | ... | |
812 | 773 |
this._encWriteState = true; |
813 | 774 |
this._clearWriteState = true; |
814 | 775 |
this._doneFlag = false; |
776 |
this._destroying = false; |
|
815 | 777 |
|
816 | 778 |
if (!credentials) { |
817 | 779 |
this.credentials = crypto.createCredentials(); |
... | ... | |
856 | 818 |
} |
857 | 819 |
|
858 | 820 |
/* Acts as a r/w stream to the cleartext side of the stream. */ |
859 |
this.cleartext = new CleartextStream(this); |
|
821 |
this.cleartext = new CleartextStream(this, options.cleartext);
|
|
860 | 822 |
|
861 | 823 |
/* Acts as a r/w stream to the encrypted side of the stream. */ |
862 |
this.encrypted = new EncryptedStream(this); |
|
824 |
this.encrypted = new EncryptedStream(this, options.encrypted); |
|
825 |
|
|
826 |
/* Let streams know about each other */ |
|
827 |
this.cleartext._opposite = this.encrypted; |
|
828 |
this.encrypted._opposite = this.cleartext; |
|
863 | 829 |
|
864 | 830 |
process.nextTick(function() { |
865 | 831 |
/* The Connection may be destroyed by an abort call */ |
866 | 832 |
if (self.ssl) { |
867 | 833 |
self.ssl.start(); |
868 | 834 |
} |
869 |
self.cycle(); |
|
870 | 835 |
}); |
871 | 836 |
} |
872 | 837 |
|
... | ... | |
885 | 850 |
}; |
886 | 851 |
|
887 | 852 |
|
888 |
|
|
889 |
|
|
890 |
/* Attempt to cycle OpenSSLs buffers in various directions. |
|
891 |
* |
|
892 |
* An SSL Connection can be viewed as four separate piplines, |
|
893 |
* interacting with one has no connection to the behavoir of |
|
894 |
* any of the other 3 -- This might not sound reasonable, |
|
895 |
* but consider things like mid-stream renegotiation of |
|
896 |
* the ciphers. |
|
897 |
* |
|
898 |
* The four pipelines, using terminology of the client (server is just |
|
899 |
* reversed): |
|
900 |
* (1) Encrypted Output stream (Writing encrypted data to peer) |
|
901 |
* (2) Encrypted Input stream (Reading encrypted data from peer) |
|
902 |
* (3) Cleartext Output stream (Decrypted content from the peer) |
|
903 |
* (4) Cleartext Input stream (Cleartext content to send to the peer) |
|
904 |
* |
|
905 |
* This function attempts to pull any available data out of the Cleartext |
|
906 |
* input stream (4), and the Encrypted input stream (2). Then it pushes any |
|
907 |
* data available from the cleartext output stream (3), and finally from the |
|
908 |
* Encrypted output stream (1) |
|
909 |
* |
|
910 |
* It is called whenever we do something with OpenSSL -- post reciving |
|
911 |
* content, trying to flush, trying to change ciphers, or shutting down the |
|
912 |
* connection. |
|
913 |
* |
|
914 |
* Because it is also called everywhere, we also check if the connection has |
|
915 |
* completed negotiation and emit 'secure' from here if it has. |
|
916 |
*/ |
|
917 |
SecurePair.prototype.cycle = function(depth) { |
|
918 |
if (this._doneFlag) return; |
|
919 |
|
|
920 |
depth = depth ? depth : 0; |
|
921 |
|
|
922 |
if (depth == 0) this._writeCalled = false; |
|
923 |
|
|
924 |
var established = this._secureEstablished; |
|
925 |
|
|
926 |
if (!this.cycleEncryptedPullLock) { |
|
927 |
this.cycleEncryptedPullLock = true; |
|
928 |
debug('encrypted._pull'); |
|
929 |
this.encrypted._pull(); |
|
930 |
this.cycleEncryptedPullLock = false; |
|
931 |
} |
|
932 |
|
|
933 |
if (!this.cycleCleartextPullLock) { |
|
934 |
this.cycleCleartextPullLock = true; |
|
935 |
debug('cleartext._pull'); |
|
936 |
this.cleartext._pull(); |
|
937 |
this.cycleCleartextPullLock = false; |
|
938 |
} |
|
939 |
|
|
940 |
if (!this.cycleCleartextPushLock) { |
|
941 |
this.cycleCleartextPushLock = true; |
|
942 |
debug('cleartext._push'); |
|
943 |
this.cleartext._push(); |
|
944 |
this.cycleCleartextPushLock = false; |
|
945 |
} |
|
946 |
|
|
947 |
if (!this.cycleEncryptedPushLock) { |
|
948 |
this.cycleEncryptedPushLock = true; |
|
949 |
debug('encrypted._push'); |
|
950 |
this.encrypted._push(); |
|
951 |
this.cycleEncryptedPushLock = false; |
|
952 |
} |
|
953 |
|
|
954 |
if ((!established && this._secureEstablished) || |
|
955 |
(depth == 0 && this._writeCalled)) { |
|
956 |
// If we were not established but now we are, let's cycle again. |
|
957 |
// Or if there is some data to write... |
|
958 |
this.cycle(depth + 1); |
|
959 |
} |
|
960 |
}; |
|
961 |
|
|
962 |
|
|
963 | 853 |
SecurePair.prototype.maybeInitFinished = function() { |
964 | 854 |
if (this.ssl && !this._secureEstablished && this.ssl.isInitFinished()) { |
965 | 855 |
if (process.features.tls_npn) { |
... | ... | |
978 | 868 |
|
979 | 869 |
|
980 | 870 |
SecurePair.prototype.destroy = function() { |
981 |
var self = this;
|
|
871 |
if (this._destroying) return;
|
|
982 | 872 |
|
983 | 873 |
if (!this._doneFlag) { |
874 |
debug('SecurePair.destroy'); |
|
875 |
this._destroying = true; |
|
876 |
|
|
877 |
// SecurePair should be destroyed only after it's streams |
|
878 |
this.cleartext.destroy(); |
|
879 |
this.encrypted.destroy(); |
|
880 |
|
|
984 | 881 |
this._doneFlag = true; |
985 | 882 |
this.ssl.error = null; |
986 | 883 |
this.ssl.close(); |
987 | 884 |
this.ssl = null; |
988 |
|
|
989 |
self.encrypted.writable = self.encrypted.readable = false; |
|
990 |
self.cleartext.writable = self.cleartext.readable = false; |
|
991 |
|
|
992 |
process.nextTick(function() { |
|
993 |
if (self.cleartext._decoder) { |
|
994 |
var ret = self.cleartext._decoder.end(); |
|
995 |
if (ret) |
|
996 |
self.cleartext.emit('data', ret); |
|
997 |
} |
|
998 |
self.cleartext.emit('end'); |
|
999 |
self.encrypted.emit('close'); |
|
1000 |
self.cleartext.emit('close'); |
|
1001 |
}); |
|
1002 | 885 |
} |
1003 | 886 |
}; |
1004 | 887 |
|
... | ... | |
1012 | 895 |
} |
1013 | 896 |
this.destroy(); |
1014 | 897 |
this.emit('error', error); |
898 |
return error; |
|
1015 | 899 |
} else { |
1016 | 900 |
var err = this.ssl.error; |
1017 | 901 |
this.ssl.error = null; |
... | ... | |
1024 | 908 |
} else { |
1025 | 909 |
this.cleartext.emit('error', err); |
1026 | 910 |
} |
911 |
|
|
912 |
return err; |
|
1027 | 913 |
} |
1028 | 914 |
}; |
1029 | 915 |
|
... | ... | |
1155 | 1041 |
{ |
1156 | 1042 |
server: self, |
1157 | 1043 |
NPNProtocols: self.NPNProtocols, |
1158 |
SNICallback: self.SNICallback |
|
1044 |
SNICallback: self.SNICallback, |
|
1045 |
|
|
1046 |
// Stream options |
|
1047 |
cleartext: self._cleartext, |
|
1048 |
encrypted: self._encrypted |
|
1159 | 1049 |
}); |
1160 | 1050 |
|
1161 | 1051 |
var cleartext = pipe(pair, socket); |
... | ... | |
1254 | 1144 |
.update(process.argv.join(' ')) |
1255 | 1145 |
.digest('hex'); |
1256 | 1146 |
} |
1147 |
if (options.cleartext) this.cleartext = options.cleartext; |
|
1148 |
if (options.encrypted) this.encrypted = options.encrypted; |
|
1257 | 1149 |
}; |
1258 | 1150 |
|
1259 | 1151 |
// SNI Contexts High-Level API |
... | ... | |
1331 | 1223 |
options.rejectUnauthorized === true ? true : false, |
1332 | 1224 |
{ |
1333 | 1225 |
NPNProtocols: this.NPNProtocols, |
1334 |
servername: hostname |
|
1226 |
servername: hostname, |
|
1227 |
cleartext: options.cleartext, |
|
1228 |
encrypted: options.encrypted |
|
1335 | 1229 |
}); |
1336 | 1230 |
|
1337 | 1231 |
if (options.session) { |
Also available in: Unified diff