mirror of https://github.com/lukechilds/node.git
Ryan Dahl
14 years ago
5 changed files with 396 additions and 403 deletions
@ -1,390 +0,0 @@ |
|||
var util = require('util'); |
|||
var events = require('events'); |
|||
var stream = require('stream'); |
|||
var assert = process.assert; |
|||
|
|||
|
|||
var debugLevel = parseInt(process.env.NODE_DEBUG, 16); |
|||
var debug; |
|||
if (debugLevel & 0x2) { |
|||
debug = function() { util.error.apply(this, arguments); }; |
|||
} else { |
|||
debug = function() { }; |
|||
} |
|||
|
|||
|
|||
/* Lazy Loaded crypto object */ |
|||
var SecureStream = null; |
|||
|
|||
/** |
|||
* Provides a pair of streams to do encrypted communication. |
|||
*/ |
|||
|
|||
function SecurePair(credentials, isServer, requestCert, rejectUnauthorized) { |
|||
if (!(this instanceof SecurePair)) { |
|||
return new SecurePair(credentials, isServer, requestCert, rejectUnauthorized); |
|||
} |
|||
|
|||
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._isServer = isServer ? true : false; |
|||
this._encWriteState = true; |
|||
this._clearWriteState = true; |
|||
this._done = false; |
|||
|
|||
var crypto = require('crypto'); |
|||
|
|||
if (!credentials) { |
|||
this.credentials = crypto.createCredentials(); |
|||
} else { |
|||
this.credentials = credentials; |
|||
} |
|||
|
|||
if (!this._isServer) { |
|||
// For clients, we will always have either a given ca list or be using
|
|||
// default one
|
|||
requestCert = true; |
|||
} |
|||
|
|||
this._secureEstablished = false; |
|||
this._encInPending = []; |
|||
this._clearInPending = []; |
|||
|
|||
this._rejectUnauthorized = rejectUnauthorized ? true : false; |
|||
this._requestCert = requestCert ? true : false; |
|||
|
|||
this._ssl = new SecureStream(this.credentials.context, |
|||
this._isServer ? true : false, |
|||
this._requestCert, |
|||
this._rejectUnauthorized); |
|||
|
|||
|
|||
/* Acts as a r/w stream to the cleartext side of the stream. */ |
|||
this.cleartext = new stream.Stream(); |
|||
this.cleartext.readable = true; |
|||
this.cleartext.writable = true; |
|||
|
|||
/* Acts as a r/w stream to the encrypted side of the stream. */ |
|||
this.encrypted = new stream.Stream(); |
|||
this.encrypted.readable = true; |
|||
this.encrypted.writable = true; |
|||
|
|||
this.cleartext.write = function(data) { |
|||
if (typeof data == 'string') data = Buffer(data); |
|||
debug('clearIn data'); |
|||
self._clearInPending.push(data); |
|||
self._cycle(); |
|||
return self._cleartextWriteState; |
|||
}; |
|||
|
|||
this.cleartext.pause = function() { |
|||
debug('paused cleartext'); |
|||
self._cleartextWriteState = false; |
|||
}; |
|||
|
|||
this.cleartext.resume = function() { |
|||
debug('resumed cleartext'); |
|||
self._cleartextWriteState = 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._encInPending.push(data); |
|||
self._cycle(); |
|||
return self._encryptedWriteState; |
|||
}; |
|||
|
|||
this.encrypted.pause = function() { |
|||
if (typeof data == 'string') data = Buffer(data); |
|||
debug('pause encrypted'); |
|||
self._encryptedWriteState = false; |
|||
}; |
|||
|
|||
this.encrypted.resume = function() { |
|||
debug('resume encrypted'); |
|||
self._encryptedWriteState = 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.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, |
|||
isServer, |
|||
requestCert, |
|||
rejectUnauthorized) { |
|||
var pair = new SecurePair(credentials, |
|||
isServer, |
|||
requestCert, |
|||
rejectUnauthorized); |
|||
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; |
|||
|
|||
// Pull in incoming encrypted data from the socket.
|
|||
// This arrives via some code like this:
|
|||
//
|
|||
// socket.on('data', function (d) {
|
|||
// pair.encrypted.write(d)
|
|||
// });
|
|||
//
|
|||
while (this._encInPending.length > 0) { |
|||
tmp = this._encInPending.shift(); |
|||
|
|||
try { |
|||
debug('writing from encIn'); |
|||
rv = this._ssl.encIn(tmp, 0, tmp.length); |
|||
} catch (e) { |
|||
return this._error(e); |
|||
} |
|||
|
|||
if (rv === 0) { |
|||
this._encInPending.unshift(tmp); |
|||
break; |
|||
} |
|||
|
|||
assert(rv === tmp.length); |
|||
} |
|||
|
|||
// Pull in any clear data coming from the application.
|
|||
// This arrives via some code like this:
|
|||
//
|
|||
// pair.cleartext.write("hello world");
|
|||
//
|
|||
while (this._clearInPending.length > 0) { |
|||
tmp = this._clearInPending.shift(); |
|||
try { |
|||
debug('writng from clearIn'); |
|||
rv = this._ssl.clearIn(tmp, 0, tmp.length); |
|||
} catch (e) { |
|||
return this._error(e); |
|||
} |
|||
|
|||
if (rv === 0) { |
|||
this._clearInPending.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)); |
|||
} |
|||
|
|||
// Move decryptoed, clear data out into the application.
|
|||
// From the user's perspective this occurs as a 'data' event
|
|||
// on the pair.cleartext.
|
|||
mover( |
|||
function(pool, offset, length) { |
|||
debug('reading from clearOut'); |
|||
return self._ssl.clearOut(pool, offset, length); |
|||
}, |
|||
function(chunk) { |
|||
self.cleartext.emit('data', chunk); |
|||
}, |
|||
function(bytesRead) { |
|||
return bytesRead > 0 && self._cleartextWriteState === true; |
|||
}); |
|||
|
|||
// Move encrypted data to the stream. From the user's perspective this
|
|||
// occurs as a 'data' event on the pair.encrypted. Usually the application
|
|||
// will have some code which pipes the stream to a socket:
|
|||
//
|
|||
// pair.encrypted.on('data', function (d) {
|
|||
// socket.write(d);
|
|||
// });
|
|||
//
|
|||
mover( |
|||
function(pool, offset, length) { |
|||
debug('reading from encOut'); |
|||
if (!self._ssl) return -1; |
|||
return self._ssl.encOut(pool, offset, length); |
|||
}, |
|||
function(chunk) { |
|||
self.encrypted.emit('data', chunk); |
|||
}, |
|||
function(bytesRead) { |
|||
if (!self._ssl) return false; |
|||
return bytesRead > 0 && self._encryptedWriteState === true; |
|||
}); |
|||
|
|||
|
|||
|
|||
if (this._ssl && !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(); |
|||
this._ssl = null; |
|||
this.encrypted.emit('close'); |
|||
this.cleartext.emit('close'); |
|||
this.emit('end', err); |
|||
} |
|||
}; |
|||
|
|||
|
|||
SecurePair.prototype._error = function(err) { |
|||
if (this._isServer && |
|||
this._rejectUnauthorized && |
|||
/peer did not return a certificate/.test(err.message)) { |
|||
// Not really an error.
|
|||
this._destroy(); |
|||
} else { |
|||
this.emit('error', err); |
|||
} |
|||
}; |
|||
|
|||
|
|||
SecurePair.prototype.getPeerCertificate = function(err) { |
|||
if (this._ssl) { |
|||
return this._ssl.getPeerCertificate(); |
|||
} else { |
|||
return null; |
|||
} |
|||
}; |
|||
|
|||
|
|||
SecurePair.prototype.getCipher = function(err) { |
|||
if (this._ssl) { |
|||
return this._ssl.getCurrentCipher(); |
|||
} else { |
|||
return null; |
|||
} |
|||
}; |
Loading…
Reference in new issue