mirror of https://github.com/lukechilds/node.git
Paul Querna
14 years ago
committed by
Ryan Dahl
2 changed files with 333 additions and 0 deletions
@ -0,0 +1,330 @@ |
|||||
|
var util = require('util'); |
||||
|
var events = require('events'); |
||||
|
var stream = require('stream'); |
||||
|
var assert = process.assert; |
||||
|
|
||||
|
var debugLevel = parseInt(process.env.NODE_DEBUG, 16); |
||||
|
|
||||
|
function debug () { |
||||
|
if (debugLevel & 0x2) { |
||||
|
util.error.apply(this, arguments); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Lazy Loaded crypto object */ |
||||
|
var SecureStream = null; |
||||
|
|
||||
|
/** |
||||
|
* Provides a pair of streams to do encrypted communication. |
||||
|
*/ |
||||
|
|
||||
|
function SecurePair(credentials, is_server) |
||||
|
{ |
||||
|
if (!(this instanceof SecurePair)) { |
||||
|
return new SecurePair(credentials, is_server); |
||||
|
} |
||||
|
|
||||
|
var self = this; |
||||
|
|
||||
|
try { |
||||
|
SecureStream = process.binding('crypto').SecureStream; |
||||
|
} |
||||
|
catch (e) { |
||||
|
throw new Error('node.js not compiled with openssl crypto support.'); |
||||
|
} |
||||
|
|
||||
|
events.EventEmitter.call(this); |
||||
|
|
||||
|
this._secureEstablished = false; |
||||
|
this._is_server = is_server ? true : false; |
||||
|
this._enc_write_state = true; |
||||
|
this._clear_write_state = true; |
||||
|
this._done = false; |
||||
|
|
||||
|
var crypto = require("crypto"); |
||||
|
|
||||
|
if (!credentials) { |
||||
|
this.credentials = crypto.createCredentials(); |
||||
|
} |
||||
|
else { |
||||
|
this.credentials = credentials; |
||||
|
} |
||||
|
|
||||
|
if (!this._is_server) { |
||||
|
/* For clients, we will always have either a given ca list or be using default one */ |
||||
|
this.credentials.shouldVerify = true; |
||||
|
} |
||||
|
|
||||
|
this._secureEstablished = false; |
||||
|
this._encIn_pending = []; |
||||
|
this._clearIn_pending = []; |
||||
|
|
||||
|
this._ssl = new SecureStream(this.credentials.context, |
||||
|
this._is_server ? true : false, |
||||
|
this.credentials.shouldVerify); |
||||
|
|
||||
|
|
||||
|
/* Acts as a r/w stream to the cleartext side of the stream. */ |
||||
|
this.cleartext = new stream.Stream(); |
||||
|
|
||||
|
/* Acts as a r/w stream to the encrypted side of the stream. */ |
||||
|
this.encrypted = new stream.Stream(); |
||||
|
|
||||
|
this.cleartext.write = function(data) { |
||||
|
debug('clearIn data'); |
||||
|
self._clearIn_pending.push(data); |
||||
|
self._cycle(); |
||||
|
return self._cleartext_write_state; |
||||
|
}; |
||||
|
|
||||
|
this.cleartext.pause = function() { |
||||
|
self._cleartext_write_state = false; |
||||
|
}; |
||||
|
|
||||
|
this.cleartext.resume = function() { |
||||
|
self._cleartext_write_state = true; |
||||
|
}; |
||||
|
|
||||
|
this.cleartext.end = function(err) { |
||||
|
debug('cleartext end'); |
||||
|
if (!self._done) { |
||||
|
self._ssl.shutdown(); |
||||
|
self._cycle(); |
||||
|
} |
||||
|
self._destroy(err); |
||||
|
}; |
||||
|
|
||||
|
this.encrypted.write = function(data) { |
||||
|
debug('encIn data'); |
||||
|
self._encIn_pending.push(data); |
||||
|
self._cycle(); |
||||
|
return self._encrypted_write_state; |
||||
|
}; |
||||
|
|
||||
|
this.encrypted.pause = function() { |
||||
|
self._encrypted_write_state = false; |
||||
|
}; |
||||
|
|
||||
|
this.encrypted.resume = function() { |
||||
|
self._encrypted_write_state = true; |
||||
|
}; |
||||
|
|
||||
|
this.encrypted.end = function(err) { |
||||
|
debug('encrypted end'); |
||||
|
if (!self._done) { |
||||
|
self._ssl.shutdown(); |
||||
|
self._cycle(); |
||||
|
} |
||||
|
self._destroy(err); |
||||
|
}; |
||||
|
|
||||
|
this.cleartext.on('end', function(err) { |
||||
|
debug('clearIn end'); |
||||
|
if (!self._done) { |
||||
|
self._ssl.shutdown(); |
||||
|
self._cycle(); |
||||
|
} |
||||
|
self._destroy(err); |
||||
|
}); |
||||
|
|
||||
|
this.cleartext.on('close', function() { |
||||
|
debug('source close'); |
||||
|
self.emit('close'); |
||||
|
self._destroy(); |
||||
|
}); |
||||
|
|
||||
|
this.encrypted.on('end', function() { |
||||
|
if (!self._done) { |
||||
|
self._error(new Error('Encrypted stream ended before secure pair was done')); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
this.encrypted.on('close', function() { |
||||
|
if (!self._done) { |
||||
|
self._error(new Error('Encrypted stream closed before secure pair was done')); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
this.cleartext.on('drain', function() { |
||||
|
debug('source drain'); |
||||
|
self._cycle(); |
||||
|
self.encrypted.resume(); |
||||
|
}); |
||||
|
|
||||
|
this.encrypted.on('drain', function() { |
||||
|
debug('target drain'); |
||||
|
self._cycle(); |
||||
|
self.cleartext.resume(); |
||||
|
}); |
||||
|
|
||||
|
process.nextTick(function() { |
||||
|
self._ssl.start(); |
||||
|
self._cycle(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
util.inherits(SecurePair, events.EventEmitter); |
||||
|
|
||||
|
exports.createSecurePair = function(credentials, is_server) |
||||
|
{ |
||||
|
var pair = new SecurePair(credentials, is_server); |
||||
|
return pair; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Attempt to cycle OpenSSLs buffers in various directions. |
||||
|
* |
||||
|
* An SSL Connection can be viewed as four separate piplines, |
||||
|
* interacting with one has no connection to the behavoir of |
||||
|
* any of the other 3 -- This might not sound reasonable, |
||||
|
* but consider things like mid-stream renegotiation of |
||||
|
* the ciphers. |
||||
|
* |
||||
|
* The four pipelines, using terminology of the client (server is just reversed): |
||||
|
* 1) Encrypted Output stream (Writing encrypted data to peer) |
||||
|
* 2) Encrypted Input stream (Reading encrypted data from peer) |
||||
|
* 3) Cleartext Output stream (Decrypted content from the peer) |
||||
|
* 4) Cleartext Input stream (Cleartext content to send to the peer) |
||||
|
* |
||||
|
* This function attempts to pull any available data out of the Cleartext |
||||
|
* input stream (#4), and the Encrypted input stream (#2). Then it pushes |
||||
|
* any data available from the cleartext output stream (#3), and finally |
||||
|
* from the Encrypted output stream (#1) |
||||
|
* |
||||
|
* It is called whenever we do something with OpenSSL -- post reciving content, |
||||
|
* trying to flush, trying to change ciphers, or shutting down the connection. |
||||
|
* |
||||
|
* Because it is also called everywhere, we also check if the connection |
||||
|
* has completed negotiation and emit 'secure' from here if it has. |
||||
|
*/ |
||||
|
SecurePair.prototype._cycle = function() { |
||||
|
if (this._done) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var self = this; |
||||
|
var rv; |
||||
|
var tmp; |
||||
|
var bytesRead; |
||||
|
var bytesWritten; |
||||
|
var chunkBytes; |
||||
|
var chunk = null; |
||||
|
var pool = null; |
||||
|
|
||||
|
while (this._encIn_pending.length > 0) { |
||||
|
tmp = this._encIn_pending.shift(); |
||||
|
|
||||
|
try { |
||||
|
rv = this._ssl.encIn(tmp, 0, tmp.length); |
||||
|
} catch (e) { |
||||
|
return this._error(e); |
||||
|
} |
||||
|
|
||||
|
if (rv === 0) { |
||||
|
this._encIn_pending.unshift(tmp); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
assert(rv === tmp.length); |
||||
|
} |
||||
|
|
||||
|
while (this._clearIn_pending.length > 0) { |
||||
|
tmp = this._clearIn_pending.shift(); |
||||
|
try { |
||||
|
rv = this._ssl.clearIn(tmp, 0, tmp.length); |
||||
|
} catch (e) { |
||||
|
return this._error(e); |
||||
|
} |
||||
|
|
||||
|
if (rv === 0) { |
||||
|
this._clearIn_pending.unshift(tmp); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
assert(rv === tmp.length); |
||||
|
} |
||||
|
|
||||
|
function mover(reader, writer, checker) { |
||||
|
var bytesRead; |
||||
|
var pool; |
||||
|
var chunkBytes; |
||||
|
do { |
||||
|
bytesRead = 0; |
||||
|
chunkBytes = 0; |
||||
|
pool = new Buffer(4096); |
||||
|
pool.used = 0; |
||||
|
|
||||
|
do { |
||||
|
try { |
||||
|
chunkBytes = reader(pool, |
||||
|
pool.used + bytesRead, |
||||
|
pool.length - pool.used - bytesRead) |
||||
|
} catch (e) { |
||||
|
return self._error(e); |
||||
|
} |
||||
|
if (chunkBytes >= 0) { |
||||
|
bytesRead += chunkBytes; |
||||
|
} |
||||
|
} while ((chunkBytes > 0) && (pool.used + bytesRead < pool.length)); |
||||
|
|
||||
|
if (bytesRead > 0) { |
||||
|
chunk = pool.slice(0, bytesRead); |
||||
|
writer(chunk); |
||||
|
} |
||||
|
} while (checker(bytesRead)); |
||||
|
} |
||||
|
|
||||
|
mover( |
||||
|
function(pool, offset, length) { |
||||
|
return self._ssl.clearOut(pool, offset, length); |
||||
|
}, |
||||
|
function(chunk) { |
||||
|
self.cleartext.emit('data', chunk); |
||||
|
}, |
||||
|
function(bytesRead) { |
||||
|
return bytesRead > 0 && self._cleartext_write_state === true; |
||||
|
}); |
||||
|
|
||||
|
mover( |
||||
|
function(pool, offset, length) { |
||||
|
return self._ssl.encOut(pool, offset, length); |
||||
|
}, |
||||
|
function(chunk) { |
||||
|
self.encrypted.emit('data', chunk); |
||||
|
}, |
||||
|
function(bytesRead) { |
||||
|
return bytesRead > 0 && self._encrypted_write_state === true; |
||||
|
}); |
||||
|
|
||||
|
if (!this._secureEstablished && this._ssl.isInitFinished()) { |
||||
|
this._secureEstablished = true; |
||||
|
debug('secure established'); |
||||
|
this.emit('secure'); |
||||
|
this._cycle(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
SecurePair.prototype._destroy = function(err) |
||||
|
{ |
||||
|
if (!this._done) { |
||||
|
this._done = true; |
||||
|
this._ssl.close(); |
||||
|
delete this._ssl; |
||||
|
this.emit('end', err); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
SecurePair.prototype._error = function (err) |
||||
|
{ |
||||
|
this.emit('error', err); |
||||
|
}; |
||||
|
|
||||
|
SecurePair.prototype.getPeerCertificate = function (err) |
||||
|
{ |
||||
|
return this._ssl.getPeerCertificate(); |
||||
|
}; |
||||
|
|
||||
|
SecurePair.prototype.getCipher = function (err) |
||||
|
{ |
||||
|
return this._ssl.getCurrentCipher(); |
||||
|
}; |
Loading…
Reference in new issue