Move securepair stuff into tls.js
authorRyan Dahl <ry@tinyclouds.org>
Mon, 6 Dec 2010 02:19:18 +0000 (18:19 -0800)
committerRyan Dahl <ry@tinyclouds.org>
Mon, 6 Dec 2010 02:19:18 +0000 (18:19 -0800)
lib/crypto.js
lib/securepair.js [deleted file]
lib/tls.js
test/simple/test-securepair-client.js
test/simple/test-securepair-server.js

index c5edc59..130d17d 100644 (file)
@@ -105,7 +105,3 @@ exports.Verify = Verify;
 exports.createVerify = function(algorithm) {
   return (new Verify).init(algorithm);
 };
-
-
-var securepair = require('securepair');
-exports.createPair = securepair.createSecurePair;
diff --git a/lib/securepair.js b/lib/securepair.js
deleted file mode 100644 (file)
index 2db5a23..0000000
+++ /dev/null
@@ -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;
-  }
-};
index 7d57426..fe05ff5 100644 (file)
 var crypto = require('crypto');
-var securepair = require('securepair');
+var util = require('util');
 var net = require('net');
 var events = require('events');
-var inherits = require('util').inherits;
+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;
+  }
+};
+
 // TODO: support anonymous (nocert) and PSK
 // TODO: how to proxy maxConnections?
 
@@ -100,10 +485,10 @@ function Server(/* [options], listener */) {
         { key: self.key, cert: self.cert, ca: self.ca });
     creds.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');
 
-    var pair = securepair.createSecurePair(creds,
-                                           true,
-                                           self.requestCert,
-                                           self.rejectUnauthorized);
+    var pair = new SecurePair(creds,
+                              true,
+                              self.requestCert,
+                              self.rejectUnauthorized);
     pair.encrypted.pipe(socket);
     socket.pipe(pair.encrypted);
 
@@ -143,7 +528,7 @@ function Server(/* [options], listener */) {
   this.setOptions(options);
 }
 
-inherits(Server, net.Server);
+util.inherits(Server, net.Server);
 exports.Server = Server;
 exports.createServer = function(options, listener) {
   return new Server(options, listener);
index 1e48e23..3ba7fa9 100644 (file)
@@ -4,6 +4,7 @@ var net = require('net');
 var assert = require('assert');
 var fs = require('fs');
 var crypto = require('crypto');
+var tls = require('tls');
 var spawn = require('child_process').spawn;
 
 // FIXME: Avoid the common PORT as this test currently hits a C-level
@@ -71,7 +72,7 @@ function startClient() {
   var sslcontext = crypto.createCredentials({key: key, cert: cert});
   sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');
 
-  var pair = crypto.createPair(sslcontext, false);
+  var pair = tls.createSecurePair(sslcontext, false);
 
   assert.ok(pair.encrypted.writable);
   assert.ok(pair.cleartext.writable);
index fd98419..00798ac 100644 (file)
@@ -5,6 +5,7 @@ var join = require('path').join;
 var net = require('net');
 var fs = require('fs');
 var crypto = require('crypto');
+var tls = require('tls');
 var spawn = require('child_process').spawn;
 
 var connections = 0;
@@ -21,7 +22,7 @@ var server = net.createServer(function(socket) {
   var sslcontext = crypto.createCredentials({key: key, cert: cert});
   sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');
 
-  var pair = crypto.createPair(sslcontext, true);
+  var pair = tls.createSecurePair(sslcontext, true);
 
   assert.ok(pair.encrypted.writable);
   assert.ok(pair.cleartext.writable);