Merge branch 'v0.10'
authorFedor Indutny <fedor.indutny@gmail.com>
Tue, 10 Dec 2013 19:06:56 +0000 (23:06 +0400)
committerFedor Indutny <fedor.indutny@gmail.com>
Tue, 10 Dec 2013 19:06:56 +0000 (23:06 +0400)
Conflicts:
lib/tls.js
src/node_crypto.cc
src/node_crypto.h

1  2 
lib/_tls_legacy.js
node.gyp
src/node_crypto.cc
src/node_crypto.h
src/tls_wrap.cc
src/tls_wrap.h
test/simple/test-tls-close-notify.js

index 2f7559d,0000000..be3c8af
mode 100644,000000..100644
--- /dev/null
@@@ -1,882 -1,0 +1,887 @@@
-     if (this._opposite._finished && this._internallyPendingBytes() === 0) {
 +// Copyright Joyent, Inc. and other Node contributors.
 +//
 +// Permission is hereby granted, free of charge, to any person obtaining a
 +// copy of this software and associated documentation files (the
 +// "Software"), to deal in the Software without restriction, including
 +// without limitation the rights to use, copy, modify, merge, publish,
 +// distribute, sublicense, and/or sell copies of the Software, and to permit
 +// persons to whom the Software is furnished to do so, subject to the
 +// following conditions:
 +//
 +// The above copyright notice and this permission notice shall be included
 +// in all copies or substantial portions of the Software.
 +//
 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 +// USE OR OTHER DEALINGS IN THE SOFTWARE.
 +
 +var assert = require('assert');
 +var crypto = require('crypto');
 +var events = require('events');
 +var stream = require('stream');
 +var tls = require('tls');
 +var util = require('util');
 +
 +var Timer = process.binding('timer_wrap').Timer;
 +var Connection = null;
 +try {
 +  Connection = process.binding('crypto').Connection;
 +} catch (e) {
 +  throw new Error('node.js not compiled with openssl crypto support.');
 +}
 +
 +var debug = util.debuglog('tls-legacy');
 +
 +function SlabBuffer() {
 +  this.create();
 +}
 +
 +
 +SlabBuffer.prototype.create = function create() {
 +  this.isFull = false;
 +  this.pool = new Buffer(tls.SLAB_BUFFER_SIZE);
 +  this.offset = 0;
 +  this.remaining = this.pool.length;
 +};
 +
 +
 +SlabBuffer.prototype.use = function use(context, fn, size) {
 +  if (this.remaining === 0) {
 +    this.isFull = true;
 +    return 0;
 +  }
 +
 +  var actualSize = this.remaining;
 +
 +  if (!util.isNull(size)) actualSize = Math.min(size, actualSize);
 +
 +  var bytes = fn.call(context, this.pool, this.offset, actualSize);
 +  if (bytes > 0) {
 +    this.offset += bytes;
 +    this.remaining -= bytes;
 +  }
 +
 +  assert(this.remaining >= 0);
 +
 +  return bytes;
 +};
 +
 +
 +var slabBuffer = null;
 +
 +
 +// Base class of both CleartextStream and EncryptedStream
 +function CryptoStream(pair, options) {
 +  stream.Duplex.call(this, options);
 +
 +  this.pair = pair;
 +  this._pending = null;
 +  this._pendingEncoding = '';
 +  this._pendingCallback = null;
 +  this._doneFlag = false;
 +  this._retryAfterPartial = false;
 +  this._halfRead = false;
 +  this._sslOutCb = null;
 +  this._resumingSession = false;
 +  this._reading = true;
 +  this._destroyed = false;
 +  this._ended = false;
 +  this._finished = false;
 +  this._opposite = null;
 +
 +  if (util.isNull(slabBuffer)) slabBuffer = new SlabBuffer();
 +  this._buffer = slabBuffer;
 +
 +  this.once('finish', onCryptoStreamFinish);
 +
 +  // net.Socket calls .onend too
 +  this.once('end', onCryptoStreamEnd);
 +}
 +util.inherits(CryptoStream, stream.Duplex);
 +
 +
 +function onCryptoStreamFinish() {
 +  this._finished = true;
 +
 +  if (this === this.pair.cleartext) {
 +    debug('cleartext.onfinish');
 +    if (this.pair.ssl) {
 +      // Generate close notify
 +      // NOTE: first call checks if client has sent us shutdown,
 +      // second call enqueues shutdown into the BIO.
 +      if (this.pair.ssl.shutdown() !== 1) {
 +        if (this.pair.ssl && this.pair.ssl.error)
 +          return this.pair.error();
 +
 +        this.pair.ssl.shutdown();
 +      }
 +
 +      if (this.pair.ssl && this.pair.ssl.error)
 +        return this.pair.error();
 +    }
 +  } else {
 +    debug('encrypted.onfinish');
 +  }
 +
 +  // Try to read just to get sure that we won't miss EOF
 +  if (this._opposite.readable) this._opposite.read(0);
 +
 +  if (this._opposite._ended) {
 +    this._done();
 +
 +    // No half-close, sorry
 +    if (this === this.pair.cleartext) this._opposite._done();
 +  }
 +}
 +
 +
 +function onCryptoStreamEnd() {
 +  this._ended = true;
 +  if (this === this.pair.cleartext) {
 +    debug('cleartext.onend');
 +  } else {
 +    debug('encrypted.onend');
 +  }
 +}
 +
 +
 +// NOTE: Called once `this._opposite` is set.
 +CryptoStream.prototype.init = function init() {
 +  var self = this;
 +  this._opposite.on('sslOutEnd', function() {
 +    if (self._sslOutCb) {
 +      var cb = self._sslOutCb;
 +      self._sslOutCb = null;
 +      cb(null);
 +    }
 +  });
 +};
 +
 +
 +CryptoStream.prototype._write = function write(data, encoding, cb) {
 +  assert(util.isNull(this._pending));
 +
 +  // Black-hole data
 +  if (!this.pair.ssl) return cb(null);
 +
 +  // When resuming session don't accept any new data.
 +  // And do not put too much data into openssl, before writing it from encrypted
 +  // side.
 +  //
 +  // TODO(indutny): Remove magic number, use watermark based limits
 +  if (!this._resumingSession &&
 +      this._opposite._internallyPendingBytes() < 128 * 1024) {
 +    // Write current buffer now
 +    var written;
 +    if (this === this.pair.cleartext) {
 +      debug('cleartext.write called with %d bytes', data.length);
 +      written = this.pair.ssl.clearIn(data, 0, data.length);
 +    } else {
 +      debug('encrypted.write called with %d bytes', data.length);
 +      written = this.pair.ssl.encIn(data, 0, data.length);
 +    }
 +
 +    // Handle and report errors
 +    if (this.pair.ssl && this.pair.ssl.error) {
 +      return cb(this.pair.error(true));
 +    }
 +
 +    // Force SSL_read call to cycle some states/data inside OpenSSL
 +    this.pair.cleartext.read(0);
 +
 +    // Cycle encrypted data
 +    if (this.pair.encrypted._internallyPendingBytes())
 +      this.pair.encrypted.read(0);
 +
 +    // Get NPN and Server name when ready
 +    this.pair.maybeInitFinished();
 +
 +    // Whole buffer was written
 +    if (written === data.length) {
 +      if (this === this.pair.cleartext) {
 +        debug('cleartext.write succeed with ' + written + ' bytes');
 +      } else {
 +        debug('encrypted.write succeed with ' + written + ' bytes');
 +      }
 +
 +      // Invoke callback only when all data read from opposite stream
 +      if (this._opposite._halfRead) {
 +        assert(util.isNull(this._sslOutCb));
 +        this._sslOutCb = cb;
 +      } else {
 +        cb(null);
 +      }
 +      return;
 +    } else if (written !== 0 && written !== -1) {
 +      assert(!this._retryAfterPartial);
 +      this._retryAfterPartial = true;
 +      this._write(data.slice(written), encoding, cb);
 +      this._retryAfterPartial = false;
 +      return;
 +    }
 +  } else {
 +    debug('cleartext.write queue is full');
 +
 +    // Force SSL_read call to cycle some states/data inside OpenSSL
 +    this.pair.cleartext.read(0);
 +  }
 +
 +  // No write has happened
 +  this._pending = data;
 +  this._pendingEncoding = encoding;
 +  this._pendingCallback = cb;
 +
 +  if (this === this.pair.cleartext) {
 +    debug('cleartext.write queued with %d bytes', data.length);
 +  } else {
 +    debug('encrypted.write queued with %d bytes', data.length);
 +  }
 +};
 +
 +
 +CryptoStream.prototype._writePending = function writePending() {
 +  var data = this._pending,
 +      encoding = this._pendingEncoding,
 +      cb = this._pendingCallback;
 +
 +  this._pending = null;
 +  this._pendingEncoding = '';
 +  this._pendingCallback = null;
 +  this._write(data, encoding, cb);
 +};
 +
 +
 +CryptoStream.prototype._read = function read(size) {
 +  // XXX: EOF?!
 +  if (!this.pair.ssl) return this.push(null);
 +
 +  // Wait for session to be resumed
 +  // Mark that we're done reading, but don't provide data or EOF
 +  if (this._resumingSession || !this._reading) return this.push('');
 +
 +  var out;
 +  if (this === this.pair.cleartext) {
 +    debug('cleartext.read called with %d bytes', size);
 +    out = this.pair.ssl.clearOut;
 +  } else {
 +    debug('encrypted.read called with %d bytes', size);
 +    out = this.pair.ssl.encOut;
 +  }
 +
 +  var bytesRead = 0,
 +      start = this._buffer.offset,
 +      last = start;
 +  do {
 +    assert(last === this._buffer.offset);
 +    var read = this._buffer.use(this.pair.ssl, out, size - bytesRead);
 +    if (read > 0) {
 +      bytesRead += read;
 +    }
 +    last = this._buffer.offset;
 +
 +    // Handle and report errors
 +    if (this.pair.ssl && this.pair.ssl.error) {
 +      this.pair.error();
 +      break;
 +    }
 +  } while (read > 0 &&
 +           !this._buffer.isFull &&
 +           bytesRead < size &&
 +           this.pair.ssl !== null);
 +
 +  // Get NPN and Server name when ready
 +  this.pair.maybeInitFinished();
 +
 +  // Create new buffer if previous was filled up
 +  var pool = this._buffer.pool;
 +  if (this._buffer.isFull) this._buffer.create();
 +
 +  assert(bytesRead >= 0);
 +
 +  if (this === this.pair.cleartext) {
 +    debug('cleartext.read succeed with %d bytes', bytesRead);
 +  } else {
 +    debug('encrypted.read succeed with %d bytes', bytesRead);
 +  }
 +
 +  // Try writing pending data
 +  if (!util.isNull(this._pending)) this._writePending();
 +  if (!util.isNull(this._opposite._pending)) this._opposite._writePending();
 +
 +  if (bytesRead === 0) {
 +    // EOF when cleartext has finished and we have nothing to read
-       if (this === this.pair.cleartext)
++    if (this._opposite._finished && this._internallyPendingBytes() === 0 ||
++        this.pair.ssl && this.pair.ssl.receivedShutdown) {
 +      // Perform graceful shutdown
 +      this._done();
 +
 +      // No half-open, sorry!
-       // EOF
-       this.push(null);
++      if (this === this.pair.cleartext) {
 +        this._opposite._done();
 +
++        // EOF
++        this.push(null);
++      } else if (!this.pair.ssl || !this.pair.ssl.receivedShutdown) {
++        // EOF
++        this.push(null);
++      }
 +    } else {
 +      // Bail out
 +      this.push('');
 +    }
 +  } else {
 +    // Give them requested data
 +    this.push(pool.slice(start, start + bytesRead));
 +  }
 +
 +  // Let users know that we've some internal data to read
 +  var halfRead = this._internallyPendingBytes() !== 0;
 +
 +  // Smart check to avoid invoking 'sslOutEnd' in the most of the cases
 +  if (this._halfRead !== halfRead) {
 +    this._halfRead = halfRead;
 +
 +    // Notify listeners about internal data end
 +    if (!halfRead) {
 +      if (this === this.pair.cleartext) {
 +        debug('cleartext.sslOutEnd');
 +      } else {
 +        debug('encrypted.sslOutEnd');
 +      }
 +
 +      this.emit('sslOutEnd');
 +    }
 +  }
 +};
 +
 +
 +CryptoStream.prototype.setTimeout = function(timeout, callback) {
 +  if (this.socket) this.socket.setTimeout(timeout, callback);
 +};
 +
 +
 +CryptoStream.prototype.setNoDelay = function(noDelay) {
 +  if (this.socket) this.socket.setNoDelay(noDelay);
 +};
 +
 +
 +CryptoStream.prototype.setKeepAlive = function(enable, initialDelay) {
 +  if (this.socket) this.socket.setKeepAlive(enable, initialDelay);
 +};
 +
 +CryptoStream.prototype.__defineGetter__('bytesWritten', function() {
 +  return this.socket ? this.socket.bytesWritten : 0;
 +});
 +
 +CryptoStream.prototype.getPeerCertificate = function() {
 +  if (this.pair.ssl) {
 +    var c = this.pair.ssl.getPeerCertificate();
 +
 +    if (c) {
 +      if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
 +      if (c.subject) c.subject = tls.parseCertString(c.subject);
 +      return c;
 +    }
 +  }
 +
 +  return null;
 +};
 +
 +CryptoStream.prototype.getSession = function() {
 +  if (this.pair.ssl) {
 +    return this.pair.ssl.getSession();
 +  }
 +
 +  return null;
 +};
 +
 +CryptoStream.prototype.isSessionReused = function() {
 +  if (this.pair.ssl) {
 +    return this.pair.ssl.isSessionReused();
 +  }
 +
 +  return null;
 +};
 +
 +CryptoStream.prototype.getCipher = function(err) {
 +  if (this.pair.ssl) {
 +    return this.pair.ssl.getCurrentCipher();
 +  } else {
 +    return null;
 +  }
 +};
 +
 +
 +CryptoStream.prototype.end = function(chunk, encoding) {
 +  if (this === this.pair.cleartext) {
 +    debug('cleartext.end');
 +  } else {
 +    debug('encrypted.end');
 +  }
 +
 +  // Write pending data first
 +  if (!util.isNull(this._pending)) this._writePending();
 +
 +  this.writable = false;
 +
 +  stream.Duplex.prototype.end.call(this, chunk, encoding);
 +};
 +
 +
 +CryptoStream.prototype.destroySoon = function(err) {
 +  if (this === this.pair.cleartext) {
 +    debug('cleartext.destroySoon');
 +  } else {
 +    debug('encrypted.destroySoon');
 +  }
 +
 +  if (this.writable)
 +    this.end();
 +
 +  if (this._writableState.finished && this._opposite._ended) {
 +    this.destroy();
 +  } else {
 +    // Wait for both `finish` and `end` events to ensure that all data that
 +    // was written on this side was read from the other side.
 +    var self = this;
 +    var waiting = 1;
 +    function finish() {
 +      if (--waiting === 0) self.destroy();
 +    }
 +    this._opposite.once('end', finish);
 +    if (!this._finished) {
 +      this.once('finish', finish);
 +      ++waiting;
 +    }
 +  }
 +};
 +
 +
 +CryptoStream.prototype.destroy = function(err) {
 +  if (this._destroyed) return;
 +  this._destroyed = true;
 +  this.readable = this.writable = false;
 +
 +  // Destroy both ends
 +  if (this === this.pair.cleartext) {
 +    debug('cleartext.destroy');
 +  } else {
 +    debug('encrypted.destroy');
 +  }
 +  this._opposite.destroy();
 +
 +  var self = this;
 +  process.nextTick(function() {
 +    // Force EOF
 +    self.push(null);
 +
 +    // Emit 'close' event
 +    self.emit('close', err ? true : false);
 +  });
 +};
 +
 +
 +CryptoStream.prototype._done = function() {
 +  this._doneFlag = true;
 +
 +  if (this === this.pair.encrypted && !this.pair._secureEstablished)
 +    return this.pair.error();
 +
 +  if (this.pair.cleartext._doneFlag &&
 +      this.pair.encrypted._doneFlag &&
 +      !this.pair._doneFlag) {
 +    // If both streams are done:
 +    this.pair.destroy();
 +  }
 +};
 +
 +
 +// readyState is deprecated. Don't use it.
 +Object.defineProperty(CryptoStream.prototype, 'readyState', {
 +  get: function() {
 +    if (this._connecting) {
 +      return 'opening';
 +    } else if (this.readable && this.writable) {
 +      return 'open';
 +    } else if (this.readable && !this.writable) {
 +      return 'readOnly';
 +    } else if (!this.readable && this.writable) {
 +      return 'writeOnly';
 +    } else {
 +      return 'closed';
 +    }
 +  }
 +});
 +
 +
 +function CleartextStream(pair, options) {
 +  CryptoStream.call(this, pair, options);
 +
 +  // This is a fake kludge to support how the http impl sits
 +  // on top of net Sockets
 +  var self = this;
 +  this._handle = {
 +    readStop: function() {
 +      self._reading = false;
 +    },
 +    readStart: function() {
 +      if (self._reading && self._readableState.length > 0) return;
 +      self._reading = true;
 +      self.read(0);
 +      if (self._opposite.readable) self._opposite.read(0);
 +    }
 +  };
 +}
 +util.inherits(CleartextStream, CryptoStream);
 +
 +
 +CleartextStream.prototype._internallyPendingBytes = function() {
 +  if (this.pair.ssl) {
 +    return this.pair.ssl.clearPending();
 +  } else {
 +    return 0;
 +  }
 +};
 +
 +
 +CleartextStream.prototype.address = function() {
 +  return this.socket && this.socket.address();
 +};
 +
 +
 +CleartextStream.prototype.__defineGetter__('remoteAddress', function() {
 +  return this.socket && this.socket.remoteAddress;
 +});
 +
 +
 +CleartextStream.prototype.__defineGetter__('remotePort', function() {
 +  return this.socket && this.socket.remotePort;
 +});
 +
 +
 +CleartextStream.prototype.__defineGetter__('localAddress', function() {
 +  return this.socket && this.socket.localAddress;
 +});
 +
 +
 +CleartextStream.prototype.__defineGetter__('localPort', function() {
 +  return this.socket && this.socket.localPort;
 +});
 +
 +
 +function EncryptedStream(pair, options) {
 +  CryptoStream.call(this, pair, options);
 +}
 +util.inherits(EncryptedStream, CryptoStream);
 +
 +
 +EncryptedStream.prototype._internallyPendingBytes = function() {
 +  if (this.pair.ssl) {
 +    return this.pair.ssl.encPending();
 +  } else {
 +    return 0;
 +  }
 +};
 +
 +
 +function onhandshakestart() {
 +  debug('onhandshakestart');
 +
 +  var self = this;
 +  var ssl = self.ssl;
 +  var now = Timer.now();
 +
 +  assert(now >= ssl.lastHandshakeTime);
 +
 +  if ((now - ssl.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) {
 +    ssl.handshakes = 0;
 +  }
 +
 +  var first = (ssl.lastHandshakeTime === 0);
 +  ssl.lastHandshakeTime = now;
 +  if (first) return;
 +
 +  if (++ssl.handshakes > tls.CLIENT_RENEG_LIMIT) {
 +    // Defer the error event to the next tick. We're being called from OpenSSL's
 +    // state machine and OpenSSL is not re-entrant. We cannot allow the user's
 +    // callback to destroy the connection right now, it would crash and burn.
 +    setImmediate(function() {
 +      var err = new Error('TLS session renegotiation attack detected.');
 +      if (self.cleartext) self.cleartext.emit('error', err);
 +    });
 +  }
 +}
 +
 +
 +function onhandshakedone() {
 +  // for future use
 +  debug('onhandshakedone');
 +}
 +
 +
 +function onclienthello(hello) {
 +  var self = this,
 +      once = false;
 +
 +  this._resumingSession = true;
 +  function callback(err, session) {
 +    if (once) return;
 +    once = true;
 +
 +    if (err) return self.socket.destroy(err);
 +
 +    self.ssl.loadSession(session);
 +    self.ssl.endParser();
 +
 +    // Cycle data
 +    self._resumingSession = false;
 +    self.cleartext.read(0);
 +    self.encrypted.read(0);
 +  }
 +
 +  if (hello.sessionId.length <= 0 ||
 +      !this.server ||
 +      !this.server.emit('resumeSession', hello.sessionId, callback)) {
 +    callback(null, null);
 +  }
 +}
 +
 +
 +function onnewsession(key, session) {
 +  if (!this.server) return;
 +  this.server.emit('newSession', key, session);
 +}
 +
 +
 +/**
 + * Provides a pair of streams to do encrypted communication.
 + */
 +
 +function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
 +                    options) {
 +  if (!(this instanceof SecurePair)) {
 +    return new SecurePair(credentials,
 +                          isServer,
 +                          requestCert,
 +                          rejectUnauthorized,
 +                          options);
 +  }
 +
 +  var self = this;
 +
 +  options || (options = {});
 +
 +  events.EventEmitter.call(this);
 +
 +  this.server = options.server;
 +  this._secureEstablished = false;
 +  this._isServer = isServer ? true : false;
 +  this._encWriteState = true;
 +  this._clearWriteState = true;
 +  this._doneFlag = false;
 +  this._destroying = false;
 +
 +  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._rejectUnauthorized = rejectUnauthorized ? true : false;
 +  this._requestCert = requestCert ? true : false;
 +
 +  this.ssl = new Connection(this.credentials.context,
 +                            this._isServer ? true : false,
 +                            this._isServer ? this._requestCert :
 +                                             options.servername,
 +                            this._rejectUnauthorized);
 +
 +  if (this._isServer) {
 +    this.ssl.onhandshakestart = onhandshakestart.bind(this);
 +    this.ssl.onhandshakedone = onhandshakedone.bind(this);
 +    this.ssl.onclienthello = onclienthello.bind(this);
 +    this.ssl.onnewsession = onnewsession.bind(this);
 +    this.ssl.lastHandshakeTime = 0;
 +    this.ssl.handshakes = 0;
 +  }
 +
 +  if (process.features.tls_sni) {
 +    if (this._isServer && options.SNICallback) {
 +      this.ssl.setSNICallback(options.SNICallback);
 +    }
 +    this.servername = null;
 +  }
 +
 +  if (process.features.tls_npn && options.NPNProtocols) {
 +    this.ssl.setNPNProtocols(options.NPNProtocols);
 +    this.npnProtocol = null;
 +  }
 +
 +  /* Acts as a r/w stream to the cleartext side of the stream. */
 +  this.cleartext = new CleartextStream(this, options.cleartext);
 +
 +  /* Acts as a r/w stream to the encrypted side of the stream. */
 +  this.encrypted = new EncryptedStream(this, options.encrypted);
 +
 +  /* Let streams know about each other */
 +  this.cleartext._opposite = this.encrypted;
 +  this.encrypted._opposite = this.cleartext;
 +  this.cleartext.init();
 +  this.encrypted.init();
 +
 +  process.nextTick(function() {
 +    /* The Connection may be destroyed by an abort call */
 +    if (self.ssl) {
 +      self.ssl.start();
 +
 +      /* In case of cipher suite failures - SSL_accept/SSL_connect may fail */
 +      if (self.ssl && self.ssl.error)
 +        self.error();
 +    }
 +  });
 +}
 +
 +util.inherits(SecurePair, events.EventEmitter);
 +
 +
 +exports.createSecurePair = function(credentials,
 +                                    isServer,
 +                                    requestCert,
 +                                    rejectUnauthorized) {
 +  var pair = new SecurePair(credentials,
 +                            isServer,
 +                            requestCert,
 +                            rejectUnauthorized);
 +  return pair;
 +};
 +
 +
 +SecurePair.prototype.maybeInitFinished = function() {
 +  if (this.ssl && !this._secureEstablished && this.ssl.isInitFinished()) {
 +    if (process.features.tls_npn) {
 +      this.npnProtocol = this.ssl.getNegotiatedProtocol();
 +    }
 +
 +    if (process.features.tls_sni) {
 +      this.servername = this.ssl.getServername();
 +    }
 +
 +    this._secureEstablished = true;
 +    debug('secure established');
 +    this.emit('secure');
 +  }
 +};
 +
 +
 +SecurePair.prototype.destroy = function() {
 +  if (this._destroying) return;
 +
 +  if (!this._doneFlag) {
 +    debug('SecurePair.destroy');
 +    this._destroying = true;
 +
 +    // SecurePair should be destroyed only after it's streams
 +    this.cleartext.destroy();
 +    this.encrypted.destroy();
 +
 +    this._doneFlag = true;
 +    this.ssl.error = null;
 +    this.ssl.close();
 +    this.ssl = null;
 +  }
 +};
 +
 +
 +SecurePair.prototype.error = function(returnOnly) {
 +  var err = this.ssl.error;
 +  this.ssl.error = null;
 +
 +  if (!this._secureEstablished) {
 +    // Emit ECONNRESET instead of zero return
 +    if (!err || err.message === 'ZERO_RETURN') {
 +      var connReset = new Error('socket hang up');
 +      connReset.code = 'ECONNRESET';
 +      connReset.sslError = err && err.message;
 +
 +      err = connReset;
 +    }
 +    this.destroy();
 +    if (!returnOnly) this.emit('error', err);
 +  } else if (this._isServer &&
 +             this._rejectUnauthorized &&
 +             /peer did not return a certificate/.test(err.message)) {
 +    // Not really an error.
 +    this.destroy();
 +  } else {
 +    if (!returnOnly) this.cleartext.emit('error', err);
 +  }
 +  return err;
 +};
 +
 +
 +exports.pipe = function pipe(pair, socket) {
 +  pair.encrypted.pipe(socket);
 +  socket.pipe(pair.encrypted);
 +
 +  pair.encrypted.on('close', function() {
 +    process.nextTick(function() {
 +      // Encrypted should be unpiped from socket to prevent possible
 +      // write after destroy.
 +      pair.encrypted.unpipe(socket);
 +      socket.destroySoon();
 +    });
 +  });
 +
 +  pair.fd = socket.fd;
 +  var cleartext = pair.cleartext;
 +  cleartext.socket = socket;
 +  cleartext.encrypted = pair.encrypted;
 +  cleartext.authorized = false;
 +
 +  // cycle the data whenever the socket drains, so that
 +  // we can pull some more into it.  normally this would
 +  // be handled by the fact that pipe() triggers read() calls
 +  // on writable.drain, but CryptoStreams are a bit more
 +  // complicated.  Since the encrypted side actually gets
 +  // its data from the cleartext side, we have to give it a
 +  // light kick to get in motion again.
 +  socket.on('drain', function() {
 +    if (pair.encrypted._pending)
 +      pair.encrypted._writePending();
 +    if (pair.cleartext._pending)
 +      pair.cleartext._writePending();
 +    pair.encrypted.read(0);
 +    pair.cleartext.read(0);
 +  });
 +
 +  function onerror(e) {
 +    if (cleartext._controlReleased) {
 +      cleartext.emit('error', e);
 +    }
 +  }
 +
 +  function onclose() {
 +    socket.removeListener('error', onerror);
 +    socket.removeListener('timeout', ontimeout);
 +  }
 +
 +  function ontimeout() {
 +    cleartext.emit('timeout');
 +  }
 +
 +  socket.on('error', onerror);
 +  socket.on('close', onclose);
 +  socket.on('timeout', ontimeout);
 +
 +  return cleartext;
 +};
diff --cc node.gyp
+++ b/node.gyp
            ],
          }],
          [
-           'OS=="linux"', {
+           'OS=="linux" and node_shared_v8=="false"', {
              'ldflags': [
 -              '-Wl,--whole-archive <(PRODUCT_DIR)/obj.target/deps/v8/tools/gyp/libv8_base.a -Wl,--no-whole-archive',
 +              '-Wl,--whole-archive <(PRODUCT_DIR)/obj.target/deps/v8/tools/gyp/libv8_base.<(target_arch).a -Wl,--no-whole-archive',
              ],
          }],
        ],
@@@ -799,86 -732,138 +799,86 @@@ void SecureContext::LoadPKCS12(const Fu
  }
  
  
 -size_t ClientHelloParser::Write(const uint8_t* data, size_t len) {
 -  HandleScope scope;
 +void SecureContext::GetTicketKeys(const FunctionCallbackInfo<Value>& args) {
 +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
 +  HandleScope handle_scope(args.GetIsolate());
  
 -  // Just accumulate data, everything will be pushed to BIO later
 -  if (state_ == kPaused) return 0;
 +  SecureContext* wrap = Unwrap<SecureContext>(args.This());
  
 -  // Copy incoming data to the internal buffer
 -  // (which has a size of the biggest possible TLS frame)
 -  size_t available = sizeof(data_) - offset_;
 -  size_t copied = len < available ? len : available;
 -  memcpy(data_ + offset_, data, copied);
 -  offset_ += copied;
 +  Local<Object> buff = Buffer::New(wrap->env(), 48);
 +  if (SSL_CTX_get_tlsext_ticket_keys(wrap->ctx_,
 +                                     Buffer::Data(buff),
 +                                     Buffer::Length(buff)) != 1) {
 +    return ThrowError("Failed to fetch tls ticket keys");
 +  }
  
 -  // Vars for parsing hello
 -  bool is_clienthello = false;
 -  uint8_t session_size = -1;
 -  uint8_t* session_id = NULL;
 -  Local<Object> hello;
 -  Handle<Value> argv[1];
 +  args.GetReturnValue().Set(buff);
 +#endif  // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
 +}
  
 -  switch (state_) {
 -   case kWaiting:
 -    // >= 5 bytes for header parsing
 -    if (offset_ < 5) break;
  
 -    if (data_[0] == kChangeCipherSpec || data_[0] == kAlert ||
 -        data_[0] == kHandshake || data_[0] == kApplicationData) {
 -      frame_len_ = (data_[3] << 8) + data_[4];
 -      state_ = kTLSHeader;
 -      body_offset_ = 5;
 -    } else {
 -      frame_len_ = (data_[0] << 8) + data_[1];
 -      state_ = kSSLHeader;
 -      if (*data_ & 0x40) {
 -        // header with padding
 -        body_offset_ = 3;
 -      } else {
 -        // without padding
 -        body_offset_ = 2;
 -      }
 -    }
 +void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
 +#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
 +  HandleScope scope(node_isolate);
  
 -    // Sanity check (too big frame, or too small)
 -    if (frame_len_ >= sizeof(data_)) {
 -      // Let OpenSSL handle it
 -      Finish();
 -      return copied;
 -    }
 -   case kTLSHeader:
 -   case kSSLHeader:
 -    // >= 5 + frame size bytes for frame parsing
 -    if (offset_ < body_offset_ + frame_len_) break;
 -
 -    // Skip unsupported frames and gather some data from frame
 -
 -    // TODO: Check protocol version
 -    if (data_[body_offset_] == kClientHello) {
 -      is_clienthello = true;
 -      uint8_t* body;
 -      size_t session_offset;
 -
 -      if (state_ == kTLSHeader) {
 -        // Skip frame header, hello header, protocol version and random data
 -        session_offset = body_offset_ + 4 + 2 + 32;
 -
 -        if (session_offset + 1 < offset_) {
 -          body = data_ + session_offset;
 -          session_size = *body;
 -          session_id = body + 1;
 -        }
 -      } else if (state_ == kSSLHeader) {
 -        // Skip header, version
 -        session_offset = body_offset_ + 3;
 +  if (args.Length() < 1 ||
 +      !Buffer::HasInstance(args[0]) ||
 +      Buffer::Length(args[0]) != 48) {
 +    return ThrowTypeError("Bad argument");
 +  }
  
 -        if (session_offset + 4 < offset_) {
 -          body = data_ + session_offset;
 +  SecureContext* wrap = Unwrap<SecureContext>(args.This());
  
 -          int ciphers_size = (body[0] << 8) + body[1];
 +  if (SSL_CTX_set_tlsext_ticket_keys(wrap->ctx_,
 +                                     Buffer::Data(args[0]),
 +                                     Buffer::Length(args[0])) != 1) {
 +    return ThrowError("Failed to fetch tls ticket keys");
 +  }
  
 -          if (body + 4 + ciphers_size < data_ + offset_) {
 -            session_size = (body[2] << 8) + body[3];
 -            session_id = body + 4 + ciphers_size;
 -          }
 -        }
 -      } else {
 -        // Whoa? How did we get here?
 -        abort();
 -      }
 +  args.GetReturnValue().Set(true);
 +#endif  // !def(OPENSSL_NO_TLSEXT) && def(SSL_CTX_get_tlsext_ticket_keys)
 +}
  
 -      // Check if we overflowed (do not reply with any private data)
 -      if (session_id == NULL ||
 -          session_size > 32 ||
 -          session_id + session_size > data_ + offset_) {
 -        Finish();
 -        return copied;
 -      }
  
 -      // TODO: Parse other things?
 -    }
 +template <class Base>
 +void SSLWrap<Base>::AddMethods(Handle<FunctionTemplate> t) {
 +  HandleScope scope(node_isolate);
  
 -    // Not client hello - let OpenSSL handle it
 -    if (!is_clienthello) {
 -      Finish();
 -      return copied;
 -    }
 +  NODE_SET_PROTOTYPE_METHOD(t, "getPeerCertificate", GetPeerCertificate);
 +  NODE_SET_PROTOTYPE_METHOD(t, "getSession", GetSession);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setSession", SetSession);
 +  NODE_SET_PROTOTYPE_METHOD(t, "loadSession", LoadSession);
 +  NODE_SET_PROTOTYPE_METHOD(t, "isSessionReused", IsSessionReused);
 +  NODE_SET_PROTOTYPE_METHOD(t, "isInitFinished", IsInitFinished);
 +  NODE_SET_PROTOTYPE_METHOD(t, "verifyError", VerifyError);
 +  NODE_SET_PROTOTYPE_METHOD(t, "getCurrentCipher", GetCurrentCipher);
-   NODE_SET_PROTOTYPE_METHOD(t, "receivedShutdown", ReceivedShutdown);
 +  NODE_SET_PROTOTYPE_METHOD(t, "endParser", EndParser);
 +  NODE_SET_PROTOTYPE_METHOD(t, "renegotiate", Renegotiate);
++  NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Shutdown);
  
 -    // Parse frame, call javascript handler and
 -    // move parser into the paused state
 -    if (onclienthello_sym.IsEmpty()) {
 -      onclienthello_sym = NODE_PSYMBOL("onclienthello");
 -    }
 -    if (sessionid_sym.IsEmpty()) {
 -      sessionid_sym = NODE_PSYMBOL("sessionId");
 -    }
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +  NODE_SET_PROTOTYPE_METHOD(t, "getNegotiatedProtocol", GetNegotiatedProto);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setNPNProtocols", SetNPNProtocols);
 +#endif  // OPENSSL_NPN_NEGOTIATED
 +}
  
 -    state_ = kPaused;
 -    hello = Object::New();
 -    hello->Set(sessionid_sym,
 -               Buffer::New(reinterpret_cast<char*>(session_id),
 -                           session_size)->handle_);
  
 -    argv[0] = hello;
 -    MakeCallback(conn_->handle_, onclienthello_sym, 1, argv);
 -    break;
 -   case kEnded:
 -   default:
 -    break;
 +template <class Base>
 +void SSLWrap<Base>::InitNPN(SecureContext* sc, Base* base) {
 +  if (base->is_server()) {
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +    // Server should advertise NPN protocols
 +    SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
 +                                          AdvertiseNextProtoCallback,
 +                                          base);
 +#endif  // OPENSSL_NPN_NEGOTIATED
 +  } else {
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +    // Client should select protocol from list of advertised
 +    // If server supports NPN
 +    SSL_CTX_set_next_proto_select_cb(sc->ctx_, SelectNextProtoCallback, base);
 +#endif  // OPENSSL_NPN_NEGOTIATED
    }
 -
 -  return copied;
  }
  
  
@@@ -1206,20 -1295,44 +1206,11 @@@ void SSLWrap<Base>::IsSessionReused(con
  }
  
  
 -Handle<Value> Connection::EncIn(const Arguments& args) {
 -  HandleScope scope;
 -
 -  Connection *ss = Connection::Unwrap(args);
 -
 -  if (args.Length() < 3) {
 -    return ThrowException(Exception::TypeError(
 -          String::New("Takes 3 parameters")));
 -  }
 -
 -  if (!Buffer::HasInstance(args[0])) {
 -    return ThrowException(Exception::TypeError(
 -          String::New("Second argument should be a buffer")));
 -  }
 -
 -  char* buffer_data = Buffer::Data(args[0]);
 -  size_t buffer_length = Buffer::Length(args[0]);
 -
 -  size_t off = args[1]->Int32Value();
 -  size_t len = args[2]->Int32Value();
 -  if (off + len > buffer_length) {
 -    return ThrowException(Exception::Error(
 -          String::New("off + len > buffer.length")));
 -  }
 -
 -  int bytes_written;
 -  char* data = buffer_data + off;
 -
 -  if (ss->is_server_ && !ss->hello_parser_.ended()) {
 -    bytes_written = ss->hello_parser_.Write(reinterpret_cast<uint8_t*>(data),
 -                                            len);
 -  } else {
 -    bytes_written = BIO_write(ss->bio_read_, data, len);
 -    ss->HandleBIOError(ss->bio_read_, "BIO_write", bytes_written);
 -    ss->SetShutdownFlags();
 -  }
 -
 -  return scope.Close(Integer::New(bytes_written));
 +template <class Base>
- void SSLWrap<Base>::ReceivedShutdown(const FunctionCallbackInfo<Value>& args) {
-   HandleScope scope(node_isolate);
-   Base* w = Unwrap<Base>(args.This());
-   bool yes = SSL_get_shutdown(w->ssl_) == SSL_RECEIVED_SHUTDOWN;
-   args.GetReturnValue().Set(yes);
- }
- template <class Base>
 +void SSLWrap<Base>::EndParser(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +  Base* w = Unwrap<Base>(args.This());
 +  w->hello_parser_.End();
  }
  
  
@@@ -1237,12 -1392,23 +1228,23 @@@ void SSLWrap<Base>::Renegotiate(const F
  }
  
  
 -Handle<Value> Connection::ClearPending(const Arguments& args) {
 -  HandleScope scope;
 +template <class Base>
++void SSLWrap<Base>::Shutdown(const FunctionCallbackInfo<Value>& args) {
++  HandleScope scope(node_isolate);
 -  Connection *ss = Connection::Unwrap(args);
++  Base* w = Unwrap<Base>(args.This());
 -  int bytes_pending = BIO_pending(ss->bio_read_);
 -  return scope.Close(Integer::New(bytes_pending));
++  int rv = SSL_shutdown(w->ssl_);
++  args.GetReturnValue().Set(rv);
+ }
 -Handle<Value> Connection::EncPending(const Arguments& args) {
 -  HandleScope scope;
 -
 -  Connection *ss = Connection::Unwrap(args);
 -
 -  int bytes_pending = BIO_pending(ss->bio_write_);
 -  return scope.Close(Integer::New(bytes_pending));
++template <class Base>
 +void SSLWrap<Base>::IsInitFinished(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +  Base* w = Unwrap<Base>(args.This());
 +  bool yes = SSL_is_init_finished(w->ssl_);
 +  args.GetReturnValue().Set(yes);
  }
  
  
@@@ -1588,1935 -1923,1878 +1590,1918 @@@ void Connection::ClearError() 
  }
  
  
 -Handle<Value> Connection::GetCurrentCipher(const Arguments& args) {
 -  HandleScope scope;
 +void Connection::SetShutdownFlags() {
 +  HandleScope scope(node_isolate);
  
 -  Connection *ss = Connection::Unwrap(args);
 +  int flags = SSL_get_shutdown(ssl_);
  
 -  OPENSSL_CONST SSL_CIPHER *c;
 +  if (flags & SSL_SENT_SHUTDOWN) {
 +    Local<String> sent_shutdown_key =
 +        FIXED_ONE_BYTE_STRING(node_isolate, "sentShutdown");
 +    object()->Set(sent_shutdown_key, True(node_isolate));
 +  }
  
 -  if ( ss->ssl_ == NULL ) return Undefined();
 -  c = SSL_get_current_cipher(ss->ssl_);
 -  if ( c == NULL ) return Undefined();
 -  Local<Object> info = Object::New();
 -  const char* cipher_name = SSL_CIPHER_get_name(c);
 -  info->Set(name_symbol, String::New(cipher_name));
 -  const char* cipher_version = SSL_CIPHER_get_version(c);
 -  info->Set(version_symbol, String::New(cipher_version));
 -  return scope.Close(info);
 +  if (flags & SSL_RECEIVED_SHUTDOWN) {
 +    Local<String> received_shutdown_key =
 +        FIXED_ONE_BYTE_STRING(node_isolate, "receivedShutdown");
 +    object()->Set(received_shutdown_key, True(node_isolate));
 +  }
  }
  
 -Handle<Value> Connection::Close(const Arguments& args) {
 -  HandleScope scope;
  
 -  Connection *ss = Connection::Unwrap(args);
 +void Connection::Initialize(Environment* env, Handle<Object> target) {
 +  Local<FunctionTemplate> t = FunctionTemplate::New(Connection::New);
 +  t->InstanceTemplate()->SetInternalFieldCount(1);
 +  t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "Connection"));
  
 -  if (ss->ssl_ != NULL) {
 -    SSL_free(ss->ssl_);
 -    ss->ssl_ = NULL;
 -  }
 -  return True();
 -}
 +  NODE_SET_PROTOTYPE_METHOD(t, "encIn", Connection::EncIn);
 +  NODE_SET_PROTOTYPE_METHOD(t, "clearOut", Connection::ClearOut);
 +  NODE_SET_PROTOTYPE_METHOD(t, "clearIn", Connection::ClearIn);
 +  NODE_SET_PROTOTYPE_METHOD(t, "encOut", Connection::EncOut);
 +  NODE_SET_PROTOTYPE_METHOD(t, "clearPending", Connection::ClearPending);
 +  NODE_SET_PROTOTYPE_METHOD(t, "encPending", Connection::EncPending);
 +  NODE_SET_PROTOTYPE_METHOD(t, "start", Connection::Start);
-   NODE_SET_PROTOTYPE_METHOD(t, "shutdown", Connection::Shutdown);
 +  NODE_SET_PROTOTYPE_METHOD(t, "close", Connection::Close);
  
 +  SSLWrap<Connection>::AddMethods(t);
  
 -void Connection::InitNPN(SecureContext* sc, bool is_server) {
  #ifdef OPENSSL_NPN_NEGOTIATED
 -  if (is_server) {
 -    // Server should advertise NPN protocols
 -    SSL_CTX_set_next_protos_advertised_cb(sc->ctx_,
 -                                          AdvertiseNextProtoCallback_,
 -                                          NULL);
 -  } else {
 -    // Client should select protocol from advertised
 -    // If server supports NPN
 -    SSL_CTX_set_next_proto_select_cb(sc->ctx_,
 -                                     SelectNextProtoCallback_,
 -                                     NULL);
 -  }
 +  NODE_SET_PROTOTYPE_METHOD(t,
 +                            "getNegotiatedProtocol",
 +                            Connection::GetNegotiatedProto);
 +  NODE_SET_PROTOTYPE_METHOD(t,
 +                            "setNPNProtocols",
 +                            Connection::SetNPNProtocols);
  #endif
 -}
 -
 -#ifdef OPENSSL_NPN_NEGOTIATED
 -Handle<Value> Connection::GetNegotiatedProto(const Arguments& args) {
 -  HandleScope scope;
  
 -  Connection *ss = Connection::Unwrap(args);
  
 -  if (ss->is_server_) {
 -    const unsigned char* npn_proto;
 -    unsigned int npn_proto_len;
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +  NODE_SET_PROTOTYPE_METHOD(t, "getServername", Connection::GetServername);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setSNICallback",  Connection::SetSNICallback);
 +#endif
  
 -    SSL_get0_next_proto_negotiated(ss->ssl_, &npn_proto, &npn_proto_len);
 +  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Connection"),
 +              t->GetFunction());
 +}
  
 -    if (!npn_proto) {
 -      return False();
 -    }
  
 -    return scope.Close(String::New(reinterpret_cast<const char*>(npn_proto),
 -                                   npn_proto_len));
 -  } else {
 -    return ss->selectedNPNProto_;
 -  }
 +int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
 +  // Quoting SSL_set_verify(3ssl):
 +  //
 +  //   The VerifyCallback function is used to control the behaviour when
 +  //   the SSL_VERIFY_PEER flag is set. It must be supplied by the
 +  //   application and receives two arguments: preverify_ok indicates,
 +  //   whether the verification of the certificate in question was passed
 +  //   (preverify_ok=1) or not (preverify_ok=0). x509_ctx is a pointer to
 +  //   the complete context used for the certificate chain verification.
 +  //
 +  //   The certificate chain is checked starting with the deepest nesting
 +  //   level (the root CA certificate) and worked upward to the peer's
 +  //   certificate.  At each level signatures and issuer attributes are
 +  //   checked.  Whenever a verification error is found, the error number is
 +  //   stored in x509_ctx and VerifyCallback is called with preverify_ok=0.
 +  //   By applying X509_CTX_store_* functions VerifyCallback can locate the
 +  //   certificate in question and perform additional steps (see EXAMPLES).
 +  //   If no error is found for a certificate, VerifyCallback is called
 +  //   with preverify_ok=1 before advancing to the next level.
 +  //
 +  //   The return value of VerifyCallback controls the strategy of the
 +  //   further verification process. If VerifyCallback returns 0, the
 +  //   verification process is immediately stopped with "verification
 +  //   failed" state. If SSL_VERIFY_PEER is set, a verification failure
 +  //   alert is sent to the peer and the TLS/SSL handshake is terminated. If
 +  //   VerifyCallback returns 1, the verification process is continued. If
 +  //   VerifyCallback always returns 1, the TLS/SSL handshake will not be
 +  //   terminated with respect to verification failures and the connection
 +  //   will be established. The calling process can however retrieve the
 +  //   error code of the last verification error using
 +  //   SSL_get_verify_result(3) or by maintaining its own error storage
 +  //   managed by VerifyCallback.
 +  //
 +  //   If no VerifyCallback is specified, the default callback will be
 +  //   used.  Its return value is identical to preverify_ok, so that any
 +  //   verification failure will lead to a termination of the TLS/SSL
 +  //   handshake with an alert message, if SSL_VERIFY_PEER is set.
 +  //
 +  // Since we cannot perform I/O quickly enough in this callback, we ignore
 +  // all preverify_ok errors and let the handshake continue. It is
 +  // imparative that the user use Connection::VerifyError after the
 +  // 'secure' callback has been made.
 +  return 1;
  }
  
 -Handle<Value> Connection::SetNPNProtocols(const Arguments& args) {
 -  HandleScope scope;
  
 -  Connection *ss = Connection::Unwrap(args);
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) {
 +  HandleScope scope(node_isolate);
  
 -  if (args.Length() < 1 || !Buffer::HasInstance(args[0])) {
 -    return ThrowException(Exception::Error(String::New(
 -           "Must give a Buffer as first argument")));
 -  }
 +  Connection* conn = static_cast<Connection*>(SSL_get_app_data(s));
 +  Environment* env = conn->env();
  
 -  // Release old handle
 -  if (!ss->npnProtos_.IsEmpty()) {
 -    ss->npnProtos_.Dispose();
 -  }
 -  ss->npnProtos_ = Persistent<Object>::New(args[0]->ToObject());
 +  const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
  
 -  return True();
 -};
 -#endif
 +  if (servername) {
 +    conn->servername_.Reset(node_isolate,
 +                            OneByteString(node_isolate, servername));
  
 -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 -Handle<Value> Connection::GetServername(const Arguments& args) {
 -  HandleScope scope;
 +    // Call the SNI callback and use its return value as context
 +    if (!conn->sniObject_.IsEmpty()) {
 +      conn->sniContext_.Dispose();
  
 -  Connection *ss = Connection::Unwrap(args);
 +      Local<Value> arg = PersistentToLocal(node_isolate, conn->servername_);
 +      Local<Value> ret = conn->MakeCallback(env->onselect_string(), 1, &arg);
  
 -  if (ss->is_server_ && !ss->servername_.IsEmpty()) {
 -    return ss->servername_;
 -  } else {
 -    return False();
 +      // If ret is SecureContext
 +      Local<FunctionTemplate> secure_context_constructor_template =
 +          env->secure_context_constructor_template();
 +      if (secure_context_constructor_template->HasInstance(ret)) {
 +        conn->sniContext_.Reset(node_isolate, ret);
 +        SecureContext* sc = Unwrap<SecureContext>(ret.As<Object>());
 +        InitNPN(sc, conn);
 +        SSL_set_SSL_CTX(s, sc->ctx_);
 +      } else {
 +        return SSL_TLSEXT_ERR_NOACK;
 +      }
 +    }
    }
 -}
  
 -Handle<Value> Connection::SetSNICallback(const Arguments& args) {
 -  HandleScope scope;
 -
 -  Connection *ss = Connection::Unwrap(args);
 +  return SSL_TLSEXT_ERR_OK;
 +}
 +#endif
  
 -  if (args.Length() < 1 || !args[0]->IsFunction()) {
 -    return ThrowException(Exception::Error(String::New(
 -           "Must give a Function as first argument")));
 -  }
 +void Connection::New(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -  // Release old handle
 -  if (!ss->sniObject_.IsEmpty()) {
 -    ss->sniObject_.Dispose();
 +  if (args.Length() < 1 || !args[0]->IsObject()) {
 +    return ThrowError("First argument must be a crypto module Credentials");
    }
 -  ss->sniObject_ = Persistent<Object>::New(Object::New());
 -  ss->sniObject_->Set(String::New("onselect"), args[0]);
  
 -  return True();
 -}
 -#endif
 +  SecureContext* sc = Unwrap<SecureContext>(args[0]->ToObject());
 +  Environment* env = sc->env();
  
 +  bool is_server = args[1]->BooleanValue();
  
 -class Cipher : public ObjectWrap {
 - public:
 -  static void Initialize (v8::Handle<v8::Object> target) {
 -    HandleScope scope;
 +  SSLWrap<Connection>::Kind kind =
 +      is_server ? SSLWrap<Connection>::kServer : SSLWrap<Connection>::kClient;
 +  Connection* conn = new Connection(env, args.This(), sc, kind);
 +  conn->ssl_ = SSL_new(sc->ctx_);
 +  conn->bio_read_ = NodeBIO::New();
 +  conn->bio_write_ = NodeBIO::New();
  
 -    Local<FunctionTemplate> t = FunctionTemplate::New(New);
 +  SSL_set_app_data(conn->ssl_, conn);
  
 -    t->InstanceTemplate()->SetInternalFieldCount(1);
 +  if (is_server)
 +    SSL_set_info_callback(conn->ssl_, SSLInfoCallback);
  
 -    NODE_SET_PROTOTYPE_METHOD(t, "init", CipherInit);
 -    NODE_SET_PROTOTYPE_METHOD(t, "initiv", CipherInitIv);
 -    NODE_SET_PROTOTYPE_METHOD(t, "update", CipherUpdate);
 -    NODE_SET_PROTOTYPE_METHOD(t, "setAutoPadding", SetAutoPadding);
 -    NODE_SET_PROTOTYPE_METHOD(t, "final", CipherFinal);
 +  InitNPN(sc, conn);
  
 -    target->Set(String::NewSymbol("Cipher"), t->GetFunction());
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +  if (is_server) {
 +    SSL_CTX_set_tlsext_servername_callback(sc->ctx_, SelectSNIContextCallback_);
 +  } else if (args[2]->IsString()) {
 +    const String::Utf8Value servername(args[2]);
 +    SSL_set_tlsext_host_name(conn->ssl_, *servername);
    }
 +#endif
  
 +  SSL_set_bio(conn->ssl_, conn->bio_read_, conn->bio_write_);
  
 -  bool CipherInit(char* cipherType, char* key_buf, int key_buf_len) {
 -    cipher = EVP_get_cipherbyname(cipherType);
 -    if(!cipher) {
 -      fprintf(stderr, "node-crypto : Unknown cipher %s\n", cipherType);
 -      return false;
 -    }
 +#ifdef SSL_MODE_RELEASE_BUFFERS
 +  long mode = SSL_get_mode(conn->ssl_);
 +  SSL_set_mode(conn->ssl_, mode | SSL_MODE_RELEASE_BUFFERS);
 +#endif
  
 -    unsigned char key[EVP_MAX_KEY_LENGTH],iv[EVP_MAX_IV_LENGTH];
 -    int key_len = EVP_BytesToKey(cipher, EVP_md5(), NULL,
 -      (unsigned char*) key_buf, key_buf_len, 1, key, iv);
  
 -    EVP_CIPHER_CTX_init(&ctx);
 -    EVP_CipherInit_ex(&ctx, cipher, NULL, NULL, NULL, true);
 -    if (!EVP_CIPHER_CTX_set_key_length(&ctx, key_len)) {
 -      fprintf(stderr, "node-crypto : Invalid key length %d\n", key_len);
 -      EVP_CIPHER_CTX_cleanup(&ctx);
 -      return false;
 -    }
 -    EVP_CipherInit_ex(&ctx, NULL, NULL,
 -      (unsigned char*)key,
 -      (unsigned char*)iv, true);
 -    initialised_ = true;
 -    return true;
 -  }
 -
 -
 -  bool CipherInitIv(char* cipherType,
 -                    char* key,
 -                    int key_len,
 -                    char* iv,
 -                    int iv_len) {
 -    cipher = EVP_get_cipherbyname(cipherType);
 -    if(!cipher) {
 -      fprintf(stderr, "node-crypto : Unknown cipher %s\n", cipherType);
 -      return false;
 -    }
 -    /* OpenSSL versions up to 0.9.8l failed to return the correct
 -       iv_length (0) for ECB ciphers */
 -    if (EVP_CIPHER_iv_length(cipher) != iv_len &&
 -      !(EVP_CIPHER_mode(cipher) == EVP_CIPH_ECB_MODE && iv_len == 0)) {
 -      fprintf(stderr, "node-crypto : Invalid IV length %d\n", iv_len);
 -      return false;
 -    }
 -    EVP_CIPHER_CTX_init(&ctx);
 -    EVP_CipherInit_ex(&ctx, cipher, NULL, NULL, NULL, true);
 -    if (!EVP_CIPHER_CTX_set_key_length(&ctx, key_len)) {
 -      fprintf(stderr, "node-crypto : Invalid key length %d\n", key_len);
 -      EVP_CIPHER_CTX_cleanup(&ctx);
 -      return false;
 +  int verify_mode;
 +  if (is_server) {
 +    bool request_cert = args[2]->BooleanValue();
 +    if (!request_cert) {
 +      // Note reject_unauthorized ignored.
 +      verify_mode = SSL_VERIFY_NONE;
 +    } else {
 +      bool reject_unauthorized = args[3]->BooleanValue();
 +      verify_mode = SSL_VERIFY_PEER;
 +      if (reject_unauthorized)
 +        verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
      }
 -    EVP_CipherInit_ex(&ctx, NULL, NULL,
 -      (unsigned char*)key,
 -      (unsigned char*)iv, true);
 -    initialised_ = true;
 -    return true;
 +  } else {
 +    // Note request_cert and reject_unauthorized are ignored for clients.
 +    verify_mode = SSL_VERIFY_NONE;
    }
  
 -  int CipherUpdate(char* data, int len, unsigned char** out, int* out_len) {
 -    if (!initialised_) return 0;
 -    *out_len = len+EVP_CIPHER_CTX_block_size(&ctx);
 -    *out = new unsigned char[*out_len];
 -    return EVP_CipherUpdate(&ctx, *out, out_len, (unsigned char*)data, len);
 -  }
  
 -  int SetAutoPadding(bool auto_padding) {
 -    if (!initialised_) return 0;
 -    return EVP_CIPHER_CTX_set_padding(&ctx, auto_padding ? 1 : 0);
 -  }
 +  // Always allow a connection. We'll reject in javascript.
 +  SSL_set_verify(conn->ssl_, verify_mode, VerifyCallback);
  
 -  int CipherFinal(unsigned char** out, int *out_len) {
 -    if (!initialised_) return 0;
 -    *out = new unsigned char[EVP_CIPHER_CTX_block_size(&ctx)];
 -    int r = EVP_CipherFinal_ex(&ctx,*out, out_len);
 -    EVP_CIPHER_CTX_cleanup(&ctx);
 -    initialised_ = false;
 -    return r;
 +  if (is_server) {
 +    SSL_set_accept_state(conn->ssl_);
 +  } else {
 +    SSL_set_connect_state(conn->ssl_);
    }
 +}
  
  
 - protected:
 +void Connection::SSLInfoCallback(const SSL *ssl_, int where, int ret) {
 +  if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE)))
 +    return;
  
 -  static Handle<Value> New(const Arguments& args) {
 -    HandleScope scope;
 +  // Be compatible with older versions of OpenSSL. SSL_get_app_data() wants
 +  // a non-const SSL* in OpenSSL <= 0.9.7e.
 +  SSL* ssl = const_cast<SSL*>(ssl_);
 +  Connection* conn = static_cast<Connection*>(SSL_get_app_data(ssl));
 +  Environment* env = conn->env();
 +  HandleScope handle_scope(env->isolate());
 +  Context::Scope context_scope(env->context());
  
 -    Cipher *cipher = new Cipher();
 -    cipher->Wrap(args.This());
 -    return args.This();
 +  if (where & SSL_CB_HANDSHAKE_START) {
 +    conn->MakeCallback(env->onhandshakestart_string(), 0, NULL);
    }
  
 -  static Handle<Value> CipherInit(const Arguments& args) {
 -    HandleScope scope;
 -
 -    Cipher *cipher = ObjectWrap::Unwrap<Cipher>(args.This());
 -
 -    if (args.Length() <= 1
 -        || !args[0]->IsString()
 -        || !(args[1]->IsString() || Buffer::HasInstance(args[1])))
 -    {
 -      return ThrowException(Exception::Error(String::New(
 -        "Must give cipher-type, key")));
 -    }
 -
 -    ASSERT_IS_BUFFER(args[1]);
 -    ssize_t key_buf_len = Buffer::Length(args[1]);
 -
 -    if (key_buf_len < 0) {
 -      Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
 -      return ThrowException(exception);
 -    }
 -
 -    char* key_buf = new char[key_buf_len];
 -    ssize_t key_written = DecodeWrite(key_buf, key_buf_len, args[1], BINARY);
 -    assert(key_written == key_buf_len);
 -
 -    String::Utf8Value cipherType(args[0]);
 +  if (where & SSL_CB_HANDSHAKE_DONE) {
 +    conn->MakeCallback(env->onhandshakedone_string(), 0, NULL);
 +  }
 +}
  
 -    bool r = cipher->CipherInit(*cipherType, key_buf, key_buf_len);
  
 -    delete [] key_buf;
 +void Connection::EncIn(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    if (!r) return ThrowCryptoError(ERR_get_error());
 +  Connection* conn = Unwrap<Connection>(args.This());
  
 -    return args.This();
 +  if (args.Length() < 3) {
 +    return ThrowTypeError("Takes 3 parameters");
    }
  
 +  if (!Buffer::HasInstance(args[0])) {
 +    return ThrowTypeError("Second argument should be a buffer");
 +  }
  
 -  static Handle<Value> CipherInitIv(const Arguments& args) {
 -    Cipher *cipher = ObjectWrap::Unwrap<Cipher>(args.This());
 +  char* buffer_data = Buffer::Data(args[0]);
 +  size_t buffer_length = Buffer::Length(args[0]);
  
 -    HandleScope scope;
 +  size_t off = args[1]->Int32Value();
 +  size_t len = args[2]->Int32Value();
 +  if (off + len > buffer_length) {
 +    return ThrowError("off + len > buffer.length");
 +  }
  
 +  int bytes_written;
 +  char* data = buffer_data + off;
  
 -    if (args.Length() <= 2
 -        || !args[0]->IsString()
 -        || !(args[1]->IsString() || Buffer::HasInstance(args[1]))
 -        || !(args[2]->IsString() || Buffer::HasInstance(args[2])))
 -    {
 -      return ThrowException(Exception::Error(String::New(
 -        "Must give cipher-type, key, and iv as argument")));
 +  if (conn->is_server() && !conn->hello_parser_.IsEnded()) {
 +    // Just accumulate data, everything will be pushed to BIO later
 +    if (conn->hello_parser_.IsPaused()) {
 +      bytes_written = 0;
 +    } else {
 +      // Copy incoming data to the internal buffer
 +      // (which has a size of the biggest possible TLS frame)
 +      size_t available = sizeof(conn->hello_data_) - conn->hello_offset_;
 +      size_t copied = len < available ? len : available;
 +      memcpy(conn->hello_data_ + conn->hello_offset_, data, copied);
 +      conn->hello_offset_ += copied;
 +
 +      conn->hello_parser_.Parse(conn->hello_data_, conn->hello_offset_);
 +      bytes_written = copied;
      }
 +  } else {
 +    bytes_written = BIO_write(conn->bio_read_, data, len);
 +    conn->HandleBIOError(conn->bio_read_, "BIO_write", bytes_written);
 +    conn->SetShutdownFlags();
 +  }
  
 -    ASSERT_IS_BUFFER(args[1]);
 -    ssize_t key_len = Buffer::Length(args[1]);
 +  args.GetReturnValue().Set(bytes_written);
 +}
  
 -    if (key_len < 0) {
 -      Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
 -      return ThrowException(exception);
 -    }
  
 -    ASSERT_IS_BUFFER(args[2]);
 -    ssize_t iv_len = Buffer::Length(args[2]);
 +void Connection::ClearOut(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    if (iv_len < 0) {
 -      Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
 -      return ThrowException(exception);
 -    }
 +  Connection* conn = Unwrap<Connection>(args.This());
  
 -    char* key_buf = new char[key_len];
 -    ssize_t key_written = DecodeWrite(key_buf, key_len, args[1], BINARY);
 -    assert(key_written == key_len);
 +  if (args.Length() < 3) {
 +    return ThrowTypeError("Takes 3 parameters");
 +  }
  
 -    char* iv_buf = new char[iv_len];
 -    ssize_t iv_written = DecodeWrite(iv_buf, iv_len, args[2], BINARY);
 -    assert(iv_written == iv_len);
 +  if (!Buffer::HasInstance(args[0])) {
 +    return ThrowTypeError("Second argument should be a buffer");
 +  }
  
 -    String::Utf8Value cipherType(args[0]);
 +  char* buffer_data = Buffer::Data(args[0]);
 +  size_t buffer_length = Buffer::Length(args[0]);
  
 -    bool r = cipher->CipherInitIv(*cipherType, key_buf,key_len,iv_buf,iv_len);
 +  size_t off = args[1]->Int32Value();
 +  size_t len = args[2]->Int32Value();
 +  if (off + len > buffer_length) {
 +    return ThrowError("off + len > buffer.length");
 +  }
  
 -    delete [] key_buf;
 -    delete [] iv_buf;
 +  if (!SSL_is_init_finished(conn->ssl_)) {
 +    int rv;
  
 -    if (!r) return ThrowCryptoError(ERR_get_error());
 +    if (conn->is_server()) {
 +      rv = SSL_accept(conn->ssl_);
 +      conn->HandleSSLError("SSL_accept:ClearOut",
 +                           rv,
 +                           kZeroIsAnError,
 +                           kSyscallError);
 +    } else {
 +      rv = SSL_connect(conn->ssl_);
 +      conn->HandleSSLError("SSL_connect:ClearOut",
 +                           rv,
 +                           kZeroIsAnError,
 +                           kSyscallError);
 +    }
  
 -    return args.This();
 +    if (rv < 0) {
 +      return args.GetReturnValue().Set(rv);
 +    }
    }
  
 -  static Handle<Value> CipherUpdate(const Arguments& args) {
 -    Cipher *cipher = ObjectWrap::Unwrap<Cipher>(args.This());
 +  int bytes_read = SSL_read(conn->ssl_, buffer_data + off, len);
 +  conn->HandleSSLError("SSL_read:ClearOut",
 +                       bytes_read,
 +                       kZeroIsNotAnError,
 +                       kSyscallError);
 +  conn->SetShutdownFlags();
  
 -    HandleScope scope;
 +  args.GetReturnValue().Set(bytes_read);
 +}
  
 -    ASSERT_IS_STRING_OR_BUFFER(args[0]);
  
 -    // Only copy the data if we have to, because it's a string
 -    unsigned char* out = 0;
 -    int out_len = 0, r;
 -    if (args[0]->IsString()) {
 -      Local<String> string = args[0].As<String>();
 -      enum encoding encoding = ParseEncoding(args[1], BINARY);
 -      if (!StringBytes::IsValidString(string, encoding))
 -        return ThrowTypeError("Bad input string");
 -      size_t buflen = StringBytes::StorageSize(string, encoding);
 -      char* buf = new char[buflen];
 -      size_t written = StringBytes::Write(buf, buflen, string, encoding);
 -      r = cipher->CipherUpdate(buf, written, &out, &out_len);
 -      delete[] buf;
 -    } else {
 -      char* buf = Buffer::Data(args[0]);
 -      size_t buflen = Buffer::Length(args[0]);
 -      r = cipher->CipherUpdate(buf, buflen, &out, &out_len);
 -    }
 +void Connection::ClearPending(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +  Connection* conn = Unwrap<Connection>(args.This());
 +  int bytes_pending = BIO_pending(conn->bio_read_);
 +  args.GetReturnValue().Set(bytes_pending);
 +}
  
 -    if (r == 0) {
 -      delete[] out;
 -      return ThrowCryptoTypeError(ERR_get_error());
 -    }
  
 -    Local<Value> outString;
 -    outString = Encode(out, out_len, BUFFER);
 +void Connection::EncPending(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +  Connection* conn = Unwrap<Connection>(args.This());
 +  int bytes_pending = BIO_pending(conn->bio_write_);
 +  args.GetReturnValue().Set(bytes_pending);
 +}
 +
  
 -    if (out) delete[] out;
 +void Connection::EncOut(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    return scope.Close(outString);
 +  Connection* conn = Unwrap<Connection>(args.This());
 +
 +  if (args.Length() < 3) {
 +    return ThrowTypeError("Takes 3 parameters");
    }
  
 -  static Handle<Value> SetAutoPadding(const Arguments& args) {
 -    HandleScope scope;
 -    Cipher *cipher = ObjectWrap::Unwrap<Cipher>(args.This());
 +  if (!Buffer::HasInstance(args[0])) {
 +    return ThrowTypeError("Second argument should be a buffer");
 +  }
  
 -    cipher->SetAutoPadding(args.Length() < 1 || args[0]->BooleanValue());
 +  char* buffer_data = Buffer::Data(args[0]);
 +  size_t buffer_length = Buffer::Length(args[0]);
  
 -    return Undefined();
 +  size_t off = args[1]->Int32Value();
 +  size_t len = args[2]->Int32Value();
 +  if (off + len > buffer_length) {
 +    return ThrowError("off + len > buffer.length");
    }
  
 -  static Handle<Value> CipherFinal(const Arguments& args) {
 -    Cipher *cipher = ObjectWrap::Unwrap<Cipher>(args.This());
 +  int bytes_read = BIO_read(conn->bio_write_, buffer_data + off, len);
  
 -    HandleScope scope;
 +  conn->HandleBIOError(conn->bio_write_, "BIO_read:EncOut", bytes_read);
 +  conn->SetShutdownFlags();
  
 -    unsigned char* out_value = NULL;
 -    int out_len = -1;
 -    Local<Value> outString ;
 +  args.GetReturnValue().Set(bytes_read);
 +}
  
 -    int r = cipher->CipherFinal(&out_value, &out_len);
  
 -    if (out_len <= 0 || r == 0) {
 -      delete[] out_value;
 -      out_value = NULL;
 -      if (r == 0) return ThrowCryptoTypeError(ERR_get_error());
 -    }
 +void Connection::ClearIn(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    outString = Encode(out_value, out_len, BUFFER);
 +  Connection* conn = Unwrap<Connection>(args.This());
  
 -    delete [] out_value;
 -    return scope.Close(outString);
 +  if (args.Length() < 3) {
 +    return ThrowTypeError("Takes 3 parameters");
    }
  
 -  Cipher () : ObjectWrap ()
 -  {
 -    initialised_ = false;
 +  if (!Buffer::HasInstance(args[0])) {
 +    return ThrowTypeError("Second argument should be a buffer");
    }
  
 -  ~Cipher () {
 -    if (initialised_) {
 -      EVP_CIPHER_CTX_cleanup(&ctx);
 -    }
 +  char* buffer_data = Buffer::Data(args[0]);
 +  size_t buffer_length = Buffer::Length(args[0]);
 +
 +  size_t off = args[1]->Int32Value();
 +  size_t len = args[2]->Int32Value();
 +  if (off + len > buffer_length) {
 +    return ThrowError("off + len > buffer.length");
    }
  
 - private:
 +  if (!SSL_is_init_finished(conn->ssl_)) {
 +    int rv;
 +    if (conn->is_server()) {
 +      rv = SSL_accept(conn->ssl_);
 +      conn->HandleSSLError("SSL_accept:ClearIn",
 +                           rv,
 +                           kZeroIsAnError,
 +                           kSyscallError);
 +    } else {
 +      rv = SSL_connect(conn->ssl_);
 +      conn->HandleSSLError("SSL_connect:ClearIn",
 +                           rv,
 +                           kZeroIsAnError,
 +                           kSyscallError);
 +    }
  
 -  EVP_CIPHER_CTX ctx; /* coverity[member_decl] */
 -  const EVP_CIPHER *cipher; /* coverity[member_decl] */
 -  bool initialised_;
 -};
 +    if (rv < 0) {
 +      return args.GetReturnValue().Set(rv);
 +    }
 +  }
  
 +  int bytes_written = SSL_write(conn->ssl_, buffer_data + off, len);
  
 +  conn->HandleSSLError("SSL_write:ClearIn",
 +                       bytes_written,
 +                       len == 0 ? kZeroIsNotAnError : kZeroIsAnError,
 +                       kSyscallError);
 +  conn->SetShutdownFlags();
  
 -class Decipher : public ObjectWrap {
 - public:
 -  static void
 -  Initialize (v8::Handle<v8::Object> target)
 -  {
 -    HandleScope scope;
 +  args.GetReturnValue().Set(bytes_written);
 +}
  
 -    Local<FunctionTemplate> t = FunctionTemplate::New(New);
  
 -    t->InstanceTemplate()->SetInternalFieldCount(1);
 +void Connection::Start(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    NODE_SET_PROTOTYPE_METHOD(t, "init", DecipherInit);
 -    NODE_SET_PROTOTYPE_METHOD(t, "initiv", DecipherInitIv);
 -    NODE_SET_PROTOTYPE_METHOD(t, "update", DecipherUpdate);
 -    NODE_SET_PROTOTYPE_METHOD(t, "final", DecipherFinal);
 -    NODE_SET_PROTOTYPE_METHOD(t, "finaltol", DecipherFinal); // remove someday
 -    NODE_SET_PROTOTYPE_METHOD(t, "setAutoPadding", SetAutoPadding);
 +  Connection* conn = Unwrap<Connection>(args.This());
  
 -    target->Set(String::NewSymbol("Decipher"), t->GetFunction());
 +  int rv = 0;
 +  if (!SSL_is_init_finished(conn->ssl_)) {
 +    if (conn->is_server()) {
 +      rv = SSL_accept(conn->ssl_);
 +      conn->HandleSSLError("SSL_accept:Start",
 +                           rv,
 +                           kZeroIsAnError,
 +                           kSyscallError);
 +    } else {
 +      rv = SSL_connect(conn->ssl_);
 +      conn->HandleSSLError("SSL_connect:Start",
 +                           rv,
 +                           kZeroIsAnError,
 +                           kSyscallError);
 +    }
    }
 +  args.GetReturnValue().Set(rv);
 +}
  
 -  bool DecipherInit(char* cipherType, char* key_buf, int key_buf_len) {
 -    cipher_ = EVP_get_cipherbyname(cipherType);
 -
 -    if(!cipher_) {
 -      fprintf(stderr, "node-crypto : Unknown cipher %s\n", cipherType);
 -      return false;
 -    }
  
- void Connection::Shutdown(const FunctionCallbackInfo<Value>& args) {
-   HandleScope scope(node_isolate);
-   Connection* conn = Unwrap<Connection>(args.This());
 -    unsigned char key[EVP_MAX_KEY_LENGTH],iv[EVP_MAX_IV_LENGTH];
 -    int key_len = EVP_BytesToKey(cipher_,
 -                                 EVP_md5(),
 -                                 NULL,
 -                                 (unsigned char*)(key_buf),
 -                                 key_buf_len,
 -                                 1,
 -                                 key,
 -                                 iv);
--
-   if (conn->ssl_ == NULL) {
-     return args.GetReturnValue().Set(false);
 -    EVP_CIPHER_CTX_init(&ctx);
 -    EVP_CipherInit_ex(&ctx, cipher_, NULL, NULL, NULL, false);
 -    if (!EVP_CIPHER_CTX_set_key_length(&ctx, key_len)) {
 -      fprintf(stderr, "node-crypto : Invalid key length %d\n", key_len);
 -      EVP_CIPHER_CTX_cleanup(&ctx);
 -      return false;
 -    }
 -    EVP_CipherInit_ex(&ctx, NULL, NULL,
 -      (unsigned char*)key,
 -      (unsigned char*)iv, false);
 -    initialised_ = true;
 -    return true;
--  }
--
-   int rv = SSL_shutdown(conn->ssl_);
-   conn->HandleSSLError("SSL_shutdown", rv, kZeroIsNotAnError, kIgnoreSyscall);
-   conn->SetShutdownFlags();
-   args.GetReturnValue().Set(rv);
- }
--
 -  bool DecipherInitIv(char* cipherType,
 -                      char* key,
 -                      int key_len,
 -                      char* iv,
 -                      int iv_len) {
 -    cipher_ = EVP_get_cipherbyname(cipherType);
 -    if(!cipher_) {
 -      fprintf(stderr, "node-crypto : Unknown cipher %s\n", cipherType);
 -      return false;
 -    }
 -    /* OpenSSL versions up to 0.9.8l failed to return the correct
 -      iv_length (0) for ECB ciphers */
 -    if (EVP_CIPHER_iv_length(cipher_) != iv_len &&
 -      !(EVP_CIPHER_mode(cipher_) == EVP_CIPH_ECB_MODE && iv_len == 0)) {
 -      fprintf(stderr, "node-crypto : Invalid IV length %d\n", iv_len);
 -      return false;
 -    }
 -    EVP_CIPHER_CTX_init(&ctx);
 -    EVP_CipherInit_ex(&ctx, cipher_, NULL, NULL, NULL, false);
 -    if (!EVP_CIPHER_CTX_set_key_length(&ctx, key_len)) {
 -      fprintf(stderr, "node-crypto : Invalid key length %d\n", key_len);
 -      EVP_CIPHER_CTX_cleanup(&ctx);
 -      return false;
 -    }
 -    EVP_CipherInit_ex(&ctx, NULL, NULL,
 -      (unsigned char*)key,
 -      (unsigned char*)iv, false);
 -    initialised_ = true;
 -    return true;
 -  }
--
 -  int DecipherUpdate(char* data, int len, unsigned char** out, int* out_len) {
 -    if (!initialised_) {
 -      *out_len = 0;
 -      *out = NULL;
 -      return 0;
 -    }
 +void Connection::Close(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    *out_len = len+EVP_CIPHER_CTX_block_size(&ctx);
 -    *out = new unsigned char[*out_len];
 +  Connection* conn = Unwrap<Connection>(args.This());
  
 -    return EVP_CipherUpdate(&ctx, *out, out_len, (unsigned char*)data, len);
 +  if (conn->ssl_ != NULL) {
 +    SSL_free(conn->ssl_);
 +    conn->ssl_ = NULL;
    }
 +}
  
 -  int SetAutoPadding(bool auto_padding) {
 -    if (!initialised_) return 0;
 -    return EVP_CIPHER_CTX_set_padding(&ctx, auto_padding ? 1 : 0);
 -  }
  
 -  // coverity[alloc_arg]
 -  int DecipherFinal(unsigned char** out, int *out_len) {
 -    int r;
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +void Connection::GetServername(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    if (!initialised_) {
 -      *out_len = 0;
 -      *out = NULL;
 -      return 0;
 -    }
 +  Connection* conn = Unwrap<Connection>(args.This());
  
 -    *out = new unsigned char[EVP_CIPHER_CTX_block_size(&ctx)];
 -    r = EVP_CipherFinal_ex(&ctx,*out,out_len);
 -    EVP_CIPHER_CTX_cleanup(&ctx);
 -    initialised_ = false;
 -    return r;
 +  if (conn->is_server() && !conn->servername_.IsEmpty()) {
 +    args.GetReturnValue().Set(conn->servername_);
 +  } else {
 +    args.GetReturnValue().Set(false);
    }
 +}
  
  
 - protected:
 +void Connection::SetSNICallback(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -  static Handle<Value> New (const Arguments& args) {
 -    HandleScope scope;
 +  Connection* conn = Unwrap<Connection>(args.This());
  
 -    Decipher *cipher = new Decipher();
 -    cipher->Wrap(args.This());
 -    return args.This();
 +  if (args.Length() < 1 || !args[0]->IsFunction()) {
 +    return ThrowError("Must give a Function as first argument");
    }
  
 -  static Handle<Value> DecipherInit(const Arguments& args) {
 -    Decipher *cipher = ObjectWrap::Unwrap<Decipher>(args.This());
 +  Local<Object> obj = Object::New();
 +  obj->Set(FIXED_ONE_BYTE_STRING(node_isolate, "onselect"), args[0]);
 +  conn->sniObject_.Reset(node_isolate, obj);
 +}
 +#endif
  
 -    HandleScope scope;
  
 -    if (args.Length() <= 1
 -        || !args[0]->IsString()
 -        || !(args[1]->IsString() || Buffer::HasInstance(args[1])))
 -    {
 -      return ThrowException(Exception::Error(String::New(
 -        "Must give cipher-type, key as argument")));
 -    }
 +void CipherBase::Initialize(Environment* env, Handle<Object> target) {
 +  Local<FunctionTemplate> t = FunctionTemplate::New(New);
  
 -    ASSERT_IS_BUFFER(args[1]);
 -    ssize_t key_len = Buffer::Length(args[1]);
 +  t->InstanceTemplate()->SetInternalFieldCount(1);
  
 -    if (key_len < 0) {
 -      Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
 -      return ThrowException(exception);
 -    }
 +  NODE_SET_PROTOTYPE_METHOD(t, "init", Init);
 +  NODE_SET_PROTOTYPE_METHOD(t, "initiv", InitIv);
 +  NODE_SET_PROTOTYPE_METHOD(t, "update", Update);
 +  NODE_SET_PROTOTYPE_METHOD(t, "final", Final);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setAutoPadding", SetAutoPadding);
 +  NODE_SET_PROTOTYPE_METHOD(t, "getAuthTag", GetAuthTag);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setAuthTag", SetAuthTag);
  
 -    char* key_buf = new char[key_len];
 -    ssize_t key_written = DecodeWrite(key_buf, key_len, args[1], BINARY);
 -    assert(key_written == key_len);
 +  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "CipherBase"),
 +              t->GetFunction());
 +}
  
 -    String::Utf8Value cipherType(args[0]);
  
 -    bool r = cipher->DecipherInit(*cipherType, key_buf,key_len);
 +void CipherBase::New(const FunctionCallbackInfo<Value>& args) {
 +  assert(args.IsConstructCall() == true);
 +  HandleScope handle_scope(args.GetIsolate());
 +  CipherKind kind = args[0]->IsTrue() ? kCipher : kDecipher;
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +  new CipherBase(env, args.This(), kind);
 +}
  
 -    delete [] key_buf;
  
 -    if (!r) {
 -      return ThrowException(Exception::Error(String::New("DecipherInit error")));
 -    }
 +void CipherBase::Init(const char* cipher_type,
 +                      const char* key_buf,
 +                      int key_buf_len) {
 +  HandleScope scope(node_isolate);
  
 -    return args.This();
 +  assert(cipher_ == NULL);
 +  cipher_ = EVP_get_cipherbyname(cipher_type);
 +  if (cipher_ == NULL) {
 +    return ThrowError("Unknown cipher");
    }
  
 -  static Handle<Value> DecipherInitIv(const Arguments& args) {
 -    Decipher *cipher = ObjectWrap::Unwrap<Decipher>(args.This());
 -
 -    HandleScope scope;
 -
 -    if (args.Length() <= 2
 -        || !args[0]->IsString()
 -        || !(args[1]->IsString() || Buffer::HasInstance(args[1]))
 -        || !(args[2]->IsString() || Buffer::HasInstance(args[2])))
 -    {
 -      return ThrowException(Exception::Error(String::New(
 -        "Must give cipher-type, key, and iv as argument")));
 -    }
 +  unsigned char key[EVP_MAX_KEY_LENGTH];
 +  unsigned char iv[EVP_MAX_IV_LENGTH];
  
 -    ASSERT_IS_BUFFER(args[1]);
 -    ssize_t key_len = Buffer::Length(args[1]);
 +  int key_len = EVP_BytesToKey(cipher_,
 +                               EVP_md5(),
 +                               NULL,
 +                               reinterpret_cast<const unsigned char*>(key_buf),
 +                               key_buf_len,
 +                               1,
 +                               key,
 +                               iv);
  
 -    if (key_len < 0) {
 -      Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
 -      return ThrowException(exception);
 -    }
 +  EVP_CIPHER_CTX_init(&ctx_);
 +  EVP_CipherInit_ex(&ctx_, cipher_, NULL, NULL, NULL, kind_ == kCipher);
 +  if (!EVP_CIPHER_CTX_set_key_length(&ctx_, key_len)) {
 +    EVP_CIPHER_CTX_cleanup(&ctx_);
 +    return ThrowError("Invalid key length");
 +  }
  
 -    ASSERT_IS_BUFFER(args[2]);
 -    ssize_t iv_len = Buffer::Length(args[2]);
 +  EVP_CipherInit_ex(&ctx_,
 +                    NULL,
 +                    NULL,
 +                    reinterpret_cast<unsigned char*>(key),
 +                    reinterpret_cast<unsigned char*>(iv),
 +                    kind_ == kCipher);
 +  initialised_ = true;
 +}
  
 -    if (iv_len < 0) {
 -      Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
 -      return ThrowException(exception);
 -    }
  
 -    char* key_buf = new char[key_len];
 -    ssize_t key_written = DecodeWrite(key_buf, key_len, args[1], BINARY);
 -    assert(key_written == key_len);
 +void CipherBase::Init(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    char* iv_buf = new char[iv_len];
 -    ssize_t iv_written = DecodeWrite(iv_buf, iv_len, args[2], BINARY);
 -    assert(iv_written == iv_len);
 +  CipherBase* cipher = Unwrap<CipherBase>(args.This());
  
 -    String::Utf8Value cipherType(args[0]);
 +  if (args.Length() < 2 ||
 +      !(args[0]->IsString() && Buffer::HasInstance(args[1]))) {
 +    return ThrowError("Must give cipher-type, key");
 +  }
  
 -    bool r = cipher->DecipherInitIv(*cipherType, key_buf,key_len,iv_buf,iv_len);
 +  const String::Utf8Value cipher_type(args[0]);
 +  const char* key_buf = Buffer::Data(args[1]);
 +  ssize_t key_buf_len = Buffer::Length(args[1]);
 +  cipher->Init(*cipher_type, key_buf, key_buf_len);
 +}
  
 -    delete [] key_buf;
 -    delete [] iv_buf;
  
 -    if (!r) {
 -      return ThrowException(Exception::Error(String::New("DecipherInitIv error")));
 -    }
 +void CipherBase::InitIv(const char* cipher_type,
 +                        const char* key,
 +                        int key_len,
 +                        const char* iv,
 +                        int iv_len) {
 +  HandleScope scope(node_isolate);
  
 -    return args.This();
 +  cipher_ = EVP_get_cipherbyname(cipher_type);
 +  if (cipher_ == NULL) {
 +    return ThrowError("Unknown cipher");
    }
  
 -  static Handle<Value> DecipherUpdate(const Arguments& args) {
 -    HandleScope scope;
 -
 -    Decipher *cipher = ObjectWrap::Unwrap<Decipher>(args.This());
 -
 -    ASSERT_IS_STRING_OR_BUFFER(args[0]);
 -
 -    // Only copy the data if we have to, because it's a string
 -    unsigned char* out = 0;
 -    int out_len = 0, r;
 -    if (args[0]->IsString()) {
 -      Local<String> string = args[0].As<String>();
 -      enum encoding encoding = ParseEncoding(args[1], BINARY);
 -      if (!StringBytes::IsValidString(string, encoding))
 -        return ThrowTypeError("Bad input string");
 -      size_t buflen = StringBytes::StorageSize(string, encoding);
 -      char* buf = new char[buflen];
 -      size_t written = StringBytes::Write(buf, buflen, string, encoding);
 -      r = cipher->DecipherUpdate(buf, written, &out, &out_len);
 -      delete[] buf;
 -    } else {
 -      char* buf = Buffer::Data(args[0]);
 -      size_t buflen = Buffer::Length(args[0]);
 -      r = cipher->DecipherUpdate(buf, buflen, &out, &out_len);
 -    }
 +  /* OpenSSL versions up to 0.9.8l failed to return the correct
 +     iv_length (0) for ECB ciphers */
 +  if (EVP_CIPHER_iv_length(cipher_) != iv_len &&
 +      !(EVP_CIPHER_mode(cipher_) == EVP_CIPH_ECB_MODE && iv_len == 0)) {
 +    return ThrowError("Invalid IV length");
 +  }
 +  EVP_CIPHER_CTX_init(&ctx_);
 +  EVP_CipherInit_ex(&ctx_, cipher_, NULL, NULL, NULL, kind_ == kCipher);
 +  if (!EVP_CIPHER_CTX_set_key_length(&ctx_, key_len)) {
 +    EVP_CIPHER_CTX_cleanup(&ctx_);
 +    return ThrowError("Invalid key length");
 +  }
 +
 +  EVP_CipherInit_ex(&ctx_,
 +                    NULL,
 +                    NULL,
 +                    reinterpret_cast<const unsigned char*>(key),
 +                    reinterpret_cast<const unsigned char*>(iv),
 +                    kind_ == kCipher);
 +  initialised_ = true;
 +}
  
 -    if (r == 0) {
 -      delete[] out;
 -      return ThrowCryptoTypeError(ERR_get_error());
 -    }
  
 -    Local<Value> outString;
 -    outString = Encode(out, out_len, BUFFER);
 +void CipherBase::InitIv(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    if (out) delete [] out;
 +  CipherBase* cipher = Unwrap<CipherBase>(args.This());
  
 -    return scope.Close(outString);
 +  if (args.Length() < 3 || !args[0]->IsString()) {
 +    return ThrowError("Must give cipher-type, key, and iv as argument");
    }
  
 -  static Handle<Value> SetAutoPadding(const Arguments& args) {
 -    HandleScope scope;
 -    Decipher *cipher = ObjectWrap::Unwrap<Decipher>(args.This());
 +  ASSERT_IS_BUFFER(args[1]);
 +  ASSERT_IS_BUFFER(args[2]);
 +
 +  const String::Utf8Value cipher_type(args[0]);
 +  ssize_t key_len = Buffer::Length(args[1]);
 +  const char* key_buf = Buffer::Data(args[1]);
 +  ssize_t iv_len = Buffer::Length(args[2]);
 +  const char* iv_buf = Buffer::Data(args[2]);
 +  cipher->InitIv(*cipher_type, key_buf, key_len, iv_buf, iv_len);
 +}
  
 -    cipher->SetAutoPadding(args.Length() < 1 || args[0]->BooleanValue());
  
 -    return Undefined();
 -  }
 +bool CipherBase::IsAuthenticatedMode() const {
 +  // check if this cipher operates in an AEAD mode that we support.
 +  if (!cipher_)
 +    return false;
 +  int mode = EVP_CIPHER_mode(cipher_);
 +  return mode == EVP_CIPH_GCM_MODE;
 +}
  
 -  static Handle<Value> DecipherFinal(const Arguments& args) {
 -    HandleScope scope;
  
 -    Decipher *cipher = ObjectWrap::Unwrap<Decipher>(args.This());
 +bool CipherBase::GetAuthTag(char** out, unsigned int* out_len) const {
 +  // only callable after Final and if encrypting.
 +  if (initialised_ || kind_ != kCipher || !auth_tag_)
 +    return false;
 +  *out_len = auth_tag_len_;
 +  *out = new char[auth_tag_len_];
 +  memcpy(*out, auth_tag_, auth_tag_len_);
 +  return true;
 +}
  
 -    unsigned char* out_value = NULL;
 -    int out_len = -1;
 -    Local<Value> outString;
  
 -    int r = cipher->DecipherFinal(&out_value, &out_len);
 +void CipherBase::GetAuthTag(const FunctionCallbackInfo<Value>& args) {
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +  HandleScope handle_scope(args.GetIsolate());
 +  CipherBase* cipher = Unwrap<CipherBase>(args.This());
  
 -    if (out_len <= 0 || r == 0) {
 -      delete [] out_value; // allocated even if out_len == 0
 -      out_value = NULL;
 -      if (r == 0) return ThrowCryptoTypeError(ERR_get_error());
 -    }
 +  char* out = NULL;
 +  unsigned int out_len = 0;
  
 -    outString = Encode(out_value, out_len, BUFFER);
 -    delete [] out_value;
 -    return scope.Close(outString);
 +  if (cipher->GetAuthTag(&out, &out_len)) {
 +    Local<Object> buf = Buffer::Use(env, out, out_len);
 +    args.GetReturnValue().Set(buf);
 +  } else {
 +    ThrowError("Attempting to get auth tag in unsupported state");
    }
 +}
  
 -  Decipher () : ObjectWrap () {
 -    initialised_ = false;
 -  }
  
 -  ~Decipher () {
 -    if (initialised_) {
 -      EVP_CIPHER_CTX_cleanup(&ctx);
 -    }
 -  }
 +bool CipherBase::SetAuthTag(const char* data, unsigned int len) {
 +  if (!initialised_ || !IsAuthenticatedMode() || kind_ != kDecipher)
 +    return false;
 +  delete[] auth_tag_;
 +  auth_tag_len_ = len;
 +  auth_tag_ = new char[len];
 +  memcpy(auth_tag_, data, len);
 +  return true;
 +}
  
 - private:
  
 -  EVP_CIPHER_CTX ctx;
 -  const EVP_CIPHER *cipher_;
 -  bool initialised_;
 -};
 +void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope handle_scope(args.GetIsolate());
  
 +  Local<Object> buf = args[0].As<Object>();
 +  if (!buf->IsObject() || !Buffer::HasInstance(buf))
 +    return ThrowTypeError("Argument must be a Buffer");
  
 +  CipherBase* cipher = Unwrap<CipherBase>(args.This());
  
 +  if (!cipher->SetAuthTag(Buffer::Data(buf), Buffer::Length(buf)))
 +    ThrowError("Attempting to set auth tag in unsupported state");
 +}
  
 -class Hmac : public ObjectWrap {
 - public:
 -  static void Initialize (v8::Handle<v8::Object> target) {
 -    HandleScope scope;
  
 -    Local<FunctionTemplate> t = FunctionTemplate::New(New);
 +bool CipherBase::Update(const char* data,
 +                        int len,
 +                        unsigned char** out,
 +                        int* out_len) {
 +  if (!initialised_)
 +    return 0;
  
 -    t->InstanceTemplate()->SetInternalFieldCount(1);
 +  // on first update:
 +  if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_ != NULL) {
 +    EVP_CIPHER_CTX_ctrl(&ctx_,
 +                        EVP_CTRL_GCM_SET_TAG,
 +                        auth_tag_len_,
 +                        reinterpret_cast<unsigned char*>(auth_tag_));
 +    delete[] auth_tag_;
 +    auth_tag_ = NULL;
 +  }
 +
 +  *out_len = len + EVP_CIPHER_CTX_block_size(&ctx_);
 +  *out = new unsigned char[*out_len];
 +  return EVP_CipherUpdate(&ctx_,
 +                          *out,
 +                          out_len,
 +                          reinterpret_cast<const unsigned char*>(data),
 +                          len);
 +}
  
 -    NODE_SET_PROTOTYPE_METHOD(t, "init", HmacInit);
 -    NODE_SET_PROTOTYPE_METHOD(t, "update", HmacUpdate);
 -    NODE_SET_PROTOTYPE_METHOD(t, "digest", HmacDigest);
  
 -    target->Set(String::NewSymbol("Hmac"), t->GetFunction());
 -  }
 +void CipherBase::Update(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope handle_scope(args.GetIsolate());
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
  
 -  bool HmacInit(char* hashType, char* key, int key_len) {
 -    md = EVP_get_digestbyname(hashType);
 -    if(!md) {
 -      fprintf(stderr, "node-crypto : Unknown message digest %s\n", hashType);
 -      return false;
 -    }
 -    HMAC_CTX_init(&ctx);
 -    if (key_len == 0) {
 -      HMAC_Init(&ctx, "", 0, md);
 -    } else {
 -      HMAC_Init(&ctx, key, key_len, md);
 -    }
 -    initialised_ = true;
 -    return true;
 +  CipherBase* cipher = Unwrap<CipherBase>(args.This());
  
 -  }
 +  ASSERT_IS_STRING_OR_BUFFER(args[0]);
  
 -  int HmacUpdate(char* data, int len) {
 -    if (!initialised_) return 0;
 -    HMAC_Update(&ctx, (unsigned char*)data, len);
 -    return 1;
 -  }
 +  unsigned char* out = NULL;
 +  bool r;
 +  int out_len = 0;
  
 -  int HmacDigest(unsigned char** md_value, unsigned int *md_len) {
 -    if (!initialised_) return 0;
 -    *md_value = new unsigned char[EVP_MAX_MD_SIZE];
 -    HMAC_Final(&ctx, *md_value, md_len);
 -    HMAC_CTX_cleanup(&ctx);
 -    initialised_ = false;
 -    return 1;
 +  // Only copy the data if we have to, because it's a string
 +  if (args[0]->IsString()) {
 +    Local<String> string = args[0].As<String>();
 +    enum encoding encoding = ParseEncoding(args[1], BINARY);
 +    if (!StringBytes::IsValidString(string, encoding))
 +      return ThrowTypeError("Bad input string");
 +    size_t buflen = StringBytes::StorageSize(string, encoding);
 +    char* buf = new char[buflen];
 +    size_t written = StringBytes::Write(buf, buflen, string, encoding);
 +    r = cipher->Update(buf, written, &out, &out_len);
 +    delete[] buf;
 +  } else {
 +    char* buf = Buffer::Data(args[0]);
 +    size_t buflen = Buffer::Length(args[0]);
 +    r = cipher->Update(buf, buflen, &out, &out_len);
    }
  
 +  if (!r) {
 +    delete[] out;
 +    return ThrowCryptoTypeError(ERR_get_error());
 +  }
  
 - protected:
 +  Local<Object> buf = Buffer::New(env, reinterpret_cast<char*>(out), out_len);
 +  if (out)
 +    delete[] out;
  
 -  static Handle<Value> New (const Arguments& args) {
 -    HandleScope scope;
 +  args.GetReturnValue().Set(buf);
 +}
  
 -    Hmac *hmac = new Hmac();
 -    hmac->Wrap(args.This());
 -    return args.This();
 -  }
  
 -  static Handle<Value> HmacInit(const Arguments& args) {
 -    Hmac *hmac = ObjectWrap::Unwrap<Hmac>(args.This());
 +bool CipherBase::SetAutoPadding(bool auto_padding) {
 +  if (!initialised_)
 +    return false;
 +  return EVP_CIPHER_CTX_set_padding(&ctx_, auto_padding);
 +}
  
 -    HandleScope scope;
  
 -    if (args.Length() == 0 || !args[0]->IsString()) {
 -      return ThrowException(Exception::Error(String::New(
 -        "Must give hashtype string as argument")));
 -    }
 +void CipherBase::SetAutoPadding(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +  CipherBase* cipher = Unwrap<CipherBase>(args.This());
 +  cipher->SetAutoPadding(args.Length() < 1 || args[0]->BooleanValue());
 +}
  
 -    ASSERT_IS_BUFFER(args[1]);
 -    ssize_t len = Buffer::Length(args[1]);
  
 -    if (len < 0) {
 -      Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
 -      return ThrowException(exception);
 +bool CipherBase::Final(unsigned char** out, int *out_len) {
 +  if (!initialised_)
 +    return false;
 +
 +  *out = new unsigned char[EVP_CIPHER_CTX_block_size(&ctx_)];
 +  bool r = EVP_CipherFinal_ex(&ctx_, *out, out_len);
 +
 +  if (r && kind_ == kCipher) {
 +    delete[] auth_tag_;
 +    auth_tag_ = NULL;
 +    if (IsAuthenticatedMode()) {
 +      auth_tag_len_ = EVP_GCM_TLS_TAG_LEN;  // use default tag length
 +      auth_tag_ = new char[auth_tag_len_];
 +      memset(auth_tag_, 0, auth_tag_len_);
 +      EVP_CIPHER_CTX_ctrl(&ctx_,
 +                          EVP_CTRL_GCM_GET_TAG,
 +                          auth_tag_len_,
 +                          reinterpret_cast<unsigned char*>(auth_tag_));
      }
 +  }
  
 -    String::Utf8Value hashType(args[0]);
 +  EVP_CIPHER_CTX_cleanup(&ctx_);
 +  initialised_ = false;
  
 -    bool r;
 +  return r;
 +}
  
 -    if( Buffer::HasInstance(args[1])) {
 -      char* buffer_data = Buffer::Data(args[1]);
 -      size_t buffer_length = Buffer::Length(args[1]);
  
 -      r = hmac->HmacInit(*hashType, buffer_data, buffer_length);
 -    } else {
 -      char* buf = new char[len];
 -      ssize_t written = DecodeWrite(buf, len, args[1], BINARY);
 -      assert(written == len);
 +void CipherBase::Final(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope handle_scope(args.GetIsolate());
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
  
 -      r = hmac->HmacInit(*hashType, buf, len);
 +  CipherBase* cipher = Unwrap<CipherBase>(args.This());
  
 -      delete [] buf;
 -    }
 +  unsigned char* out_value = NULL;
 +  int out_len = -1;
 +  Local<Value> outString;
  
 -    if (!r) {
 -      return ThrowException(Exception::Error(String::New("hmac error")));
 -    }
 +  bool r = cipher->Final(&out_value, &out_len);
  
 -    return args.This();
 +  if (out_len <= 0 || !r) {
 +    delete[] out_value;
 +    out_value = NULL;
 +    out_len = 0;
 +    if (!r)
 +      return ThrowCryptoTypeError(ERR_get_error());
    }
  
 -  static Handle<Value> HmacUpdate(const Arguments& args) {
 -    Hmac *hmac = ObjectWrap::Unwrap<Hmac>(args.This());
 +  args.GetReturnValue().Set(
 +      Buffer::New(env, reinterpret_cast<char*>(out_value), out_len));
 +}
  
 -    HandleScope scope;
  
 -    ASSERT_IS_STRING_OR_BUFFER(args[0]);
 +void Hmac::Initialize(Environment* env, v8::Handle<v8::Object> target) {
 +  Local<FunctionTemplate> t = FunctionTemplate::New(New);
  
 -    // Only copy the data if we have to, because it's a string
 -    int r;
 -    if (args[0]->IsString()) {
 -      Local<String> string = args[0].As<String>();
 -      enum encoding encoding = ParseEncoding(args[1], BINARY);
 -      if (!StringBytes::IsValidString(string, encoding))
 -        return ThrowTypeError("Bad input string");
 -      size_t buflen = StringBytes::StorageSize(string, encoding);
 -      char* buf = new char[buflen];
 -      size_t written = StringBytes::Write(buf, buflen, string, encoding);
 -      r = hmac->HmacUpdate(buf, written);
 -      delete[] buf;
 -    } else {
 -      char* buf = Buffer::Data(args[0]);
 -      size_t buflen = Buffer::Length(args[0]);
 -      r = hmac->HmacUpdate(buf, buflen);
 -    }
 +  t->InstanceTemplate()->SetInternalFieldCount(1);
  
 -    if (!r) {
 -      Local<Value> exception = Exception::TypeError(String::New("HmacUpdate fail"));
 -      return ThrowException(exception);
 -    }
 +  NODE_SET_PROTOTYPE_METHOD(t, "init", HmacInit);
 +  NODE_SET_PROTOTYPE_METHOD(t, "update", HmacUpdate);
 +  NODE_SET_PROTOTYPE_METHOD(t, "digest", HmacDigest);
  
 -    return args.This();
 -  }
 +  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Hmac"), t->GetFunction());
 +}
  
 -  static Handle<Value> HmacDigest(const Arguments& args) {
 -    Hmac *hmac = ObjectWrap::Unwrap<Hmac>(args.This());
  
 -    HandleScope scope;
 +void Hmac::New(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope handle_scope(args.GetIsolate());
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +  new Hmac(env, args.This());
 +}
  
 -    enum encoding encoding = BUFFER;
 -    if (args.Length() >= 1) {
 -      encoding = ParseEncoding(args[0]->ToString(), BUFFER);
 -    }
  
 -    unsigned char* md_value = NULL;
 -    unsigned int md_len = 0;
 -    Local<Value> outString;
 +void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) {
 +  HandleScope scope(node_isolate);
  
 -    int r = hmac->HmacDigest(&md_value, &md_len);
 -    if (r == 0) {
 -      md_value = NULL;
 -      md_len = 0;
 -    }
 +  assert(md_ == NULL);
 +  md_ = EVP_get_digestbyname(hash_type);
 +  if (md_ == NULL) {
 +    return ThrowError("Unknown message digest");
 +  }
 +  HMAC_CTX_init(&ctx_);
 +  if (key_len == 0) {
 +    HMAC_Init(&ctx_, "", 0, md_);
 +  } else {
 +    HMAC_Init(&ctx_, key, key_len, md_);
 +  }
 +  initialised_ = true;
 +}
  
 -    outString = StringBytes::Encode(
 -          reinterpret_cast<const char*>(md_value), md_len, encoding);
  
 -    delete[] md_value;
 -    return scope.Close(outString);
 -  }
 +void Hmac::HmacInit(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -  Hmac () : ObjectWrap () {
 -    initialised_ = false;
 -  }
 +  Hmac* hmac = Unwrap<Hmac>(args.This());
  
 -  ~Hmac () {
 -    if (initialised_) {
 -      HMAC_CTX_cleanup(&ctx);
 -    }
 +  if (args.Length() < 2 || !args[0]->IsString()) {
 +    return ThrowError("Must give hashtype string, key as arguments");
    }
  
 - private:
 +  ASSERT_IS_BUFFER(args[1]);
  
 -  HMAC_CTX ctx; /* coverity[member_decl] */
 -  const EVP_MD *md; /* coverity[member_decl] */
 -  bool initialised_;
 -};
 +  const String::Utf8Value hash_type(args[0]);
 +  const char* buffer_data = Buffer::Data(args[1]);
 +  size_t buffer_length = Buffer::Length(args[1]);
 +  hmac->HmacInit(*hash_type, buffer_data, buffer_length);
 +}
  
  
 -class Hash : public ObjectWrap {
 - public:
 -  static void Initialize (v8::Handle<v8::Object> target) {
 -    HandleScope scope;
 +bool Hmac::HmacUpdate(const char* data, int len) {
 +  if (!initialised_)
 +    return false;
 +  HMAC_Update(&ctx_, reinterpret_cast<const unsigned char*>(data), len);
 +  return true;
 +}
  
 -    Local<FunctionTemplate> t = FunctionTemplate::New(New);
  
 -    t->InstanceTemplate()->SetInternalFieldCount(1);
 +void Hmac::HmacUpdate(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    NODE_SET_PROTOTYPE_METHOD(t, "update", HashUpdate);
 -    NODE_SET_PROTOTYPE_METHOD(t, "digest", HashDigest);
 +  Hmac* hmac = Unwrap<Hmac>(args.This());
  
 -    target->Set(String::NewSymbol("Hash"), t->GetFunction());
 -  }
 +  ASSERT_IS_STRING_OR_BUFFER(args[0]);
  
 -  bool HashInit (const char* hashType) {
 -    md = EVP_get_digestbyname(hashType);
 -    if(!md) return false;
 -    EVP_MD_CTX_init(&mdctx);
 -    EVP_DigestInit_ex(&mdctx, md, NULL);
 -    initialised_ = true;
 -    return true;
 +  // Only copy the data if we have to, because it's a string
 +  bool r;
 +  if (args[0]->IsString()) {
 +    Local<String> string = args[0].As<String>();
 +    enum encoding encoding = ParseEncoding(args[1], BINARY);
 +    if (!StringBytes::IsValidString(string, encoding))
 +      return ThrowTypeError("Bad input string");
 +    size_t buflen = StringBytes::StorageSize(string, encoding);
 +    char* buf = new char[buflen];
 +    size_t written = StringBytes::Write(buf, buflen, string, encoding);
 +    r = hmac->HmacUpdate(buf, written);
 +    delete[] buf;
 +  } else {
 +    char* buf = Buffer::Data(args[0]);
 +    size_t buflen = Buffer::Length(args[0]);
 +    r = hmac->HmacUpdate(buf, buflen);
    }
  
 -  int HashUpdate(char* data, int len) {
 -    if (!initialised_) return 0;
 -    EVP_DigestUpdate(&mdctx, data, len);
 -    return 1;
 +  if (!r) {
 +    return ThrowTypeError("HmacUpdate fail");
    }
 +}
  
  
 - protected:
 -
 -  static Handle<Value> New (const Arguments& args) {
 -    HandleScope scope;
 +bool Hmac::HmacDigest(unsigned char** md_value, unsigned int* md_len) {
 +  if (!initialised_)
 +    return false;
 +  *md_value = new unsigned char[EVP_MAX_MD_SIZE];
 +  HMAC_Final(&ctx_, *md_value, md_len);
 +  HMAC_CTX_cleanup(&ctx_);
 +  initialised_ = false;
 +  return true;
 +}
  
 -    if (args.Length() == 0 || !args[0]->IsString()) {
 -      return ThrowException(Exception::Error(String::New(
 -        "Must give hashtype string as argument")));
 -    }
  
 -    String::Utf8Value hashType(args[0]);
 +void Hmac::HmacDigest(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    Hash *hash = new Hash();
 -    if (!hash->HashInit(*hashType)) {
 -      delete hash;
 -      return ThrowException(Exception::Error(String::New(
 -        "Digest method not supported")));
 -    }
 +  Hmac* hmac = Unwrap<Hmac>(args.This());
  
 -    hash->Wrap(args.This());
 -    return args.This();
 +  enum encoding encoding = BUFFER;
 +  if (args.Length() >= 1) {
 +    encoding = ParseEncoding(args[0]->ToString(), BUFFER);
    }
  
 -  static Handle<Value> HashUpdate(const Arguments& args) {
 -    HandleScope scope;
 -
 -    Hash *hash = ObjectWrap::Unwrap<Hash>(args.This());
 -
 -    ASSERT_IS_STRING_OR_BUFFER(args[0]);
 +  unsigned char* md_value = NULL;
 +  unsigned int md_len = 0;
  
 -    // Only copy the data if we have to, because it's a string
 -    int r;
 -    if (args[0]->IsString()) {
 -      Local<String> string = args[0].As<String>();
 -      enum encoding encoding = ParseEncoding(args[1], BINARY);
 -      if (!StringBytes::IsValidString(string, encoding))
 -        return ThrowTypeError("Bad input string");
 -      size_t buflen = StringBytes::StorageSize(string, encoding);
 -      char* buf = new char[buflen];
 -      size_t written = StringBytes::Write(buf, buflen, string, encoding);
 -      r = hash->HashUpdate(buf, written);
 -      delete[] buf;
 -    } else {
 -      char* buf = Buffer::Data(args[0]);
 -      size_t buflen = Buffer::Length(args[0]);
 -      r = hash->HashUpdate(buf, buflen);
 -    }
 +  bool r = hmac->HmacDigest(&md_value, &md_len);
 +  if (!r) {
 +    md_value = NULL;
 +    md_len = 0;
 +  }
  
 -    if (!r) {
 -      Local<Value> exception = Exception::TypeError(String::New("HashUpdate fail"));
 -      return ThrowException(exception);
 -    }
 +  Local<Value> rc = StringBytes::Encode(
 +        reinterpret_cast<const char*>(md_value), md_len, encoding);
 +  delete[] md_value;
 +  args.GetReturnValue().Set(rc);
 +}
  
 -    return args.This();
 -  }
  
 -  static Handle<Value> HashDigest(const Arguments& args) {
 -    HandleScope scope;
 +void Hash::Initialize(Environment* env, v8::Handle<v8::Object> target) {
 +  Local<FunctionTemplate> t = FunctionTemplate::New(New);
  
 -    Hash *hash = ObjectWrap::Unwrap<Hash>(args.This());
 +  t->InstanceTemplate()->SetInternalFieldCount(1);
  
 -    if (!hash->initialised_) {
 -      return ThrowException(Exception::Error(String::New("Not initialized")));
 -    }
 +  NODE_SET_PROTOTYPE_METHOD(t, "update", HashUpdate);
 +  NODE_SET_PROTOTYPE_METHOD(t, "digest", HashDigest);
  
 -    enum encoding encoding = BUFFER;
 -    if (args.Length() >= 1) {
 -      encoding = ParseEncoding(args[0]->ToString(), BUFFER);
 -    }
 +  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Hash"), t->GetFunction());
 +}
  
 -    unsigned char md_value[EVP_MAX_MD_SIZE];
 -    unsigned int md_len;
  
 -    EVP_DigestFinal_ex(&hash->mdctx, md_value, &md_len);
 -    EVP_MD_CTX_cleanup(&hash->mdctx);
 -    hash->initialised_ = false;
 +void Hash::New(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    return scope.Close(StringBytes::Encode(
 -          reinterpret_cast<const char*>(md_value), md_len, encoding));
 +  if (args.Length() == 0 || !args[0]->IsString()) {
 +    return ThrowError("Must give hashtype string as argument");
    }
  
 -  Hash () : ObjectWrap () {
 -    initialised_ = false;
 -  }
 +  const String::Utf8Value hash_type(args[0]);
  
 -  ~Hash () {
 -    if (initialised_) {
 -      EVP_MD_CTX_cleanup(&mdctx);
 -    }
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +  Hash* hash = new Hash(env, args.This());
 +  if (!hash->HashInit(*hash_type)) {
 +    return ThrowError("Digest method not supported");
    }
 +}
  
 - private:
  
 -  EVP_MD_CTX mdctx; /* coverity[member_decl] */
 -  const EVP_MD *md; /* coverity[member_decl] */
 -  bool initialised_;
 -};
 +bool Hash::HashInit(const char* hash_type) {
 +  assert(md_ == NULL);
 +  md_ = EVP_get_digestbyname(hash_type);
 +  if (md_ == NULL)
 +    return false;
 +  EVP_MD_CTX_init(&mdctx_);
 +  EVP_DigestInit_ex(&mdctx_, md_, NULL);
 +  initialised_ = true;
 +  return true;
 +}
  
 -class Sign : public ObjectWrap {
 - public:
 -  static void
 -  Initialize (v8::Handle<v8::Object> target) {
 -    HandleScope scope;
  
 -    Local<FunctionTemplate> t = FunctionTemplate::New(New);
 +bool Hash::HashUpdate(const char* data, int len) {
 +  if (!initialised_)
 +    return false;
 +  EVP_DigestUpdate(&mdctx_, data, len);
 +  return true;
 +}
  
 -    t->InstanceTemplate()->SetInternalFieldCount(1);
  
 -    NODE_SET_PROTOTYPE_METHOD(t, "init", SignInit);
 -    NODE_SET_PROTOTYPE_METHOD(t, "update", SignUpdate);
 -    NODE_SET_PROTOTYPE_METHOD(t, "sign", SignFinal);
 +void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    target->Set(String::NewSymbol("Sign"), t->GetFunction());
 -  }
 +  Hash* hash = Unwrap<Hash>(args.This());
  
 -  bool SignInit (const char* signType) {
 -    md = EVP_get_digestbyname(signType);
 -    if(!md) {
 -      printf("Unknown message digest %s\n", signType);
 -      return false;
 -    }
 -    EVP_MD_CTX_init(&mdctx);
 -    EVP_SignInit_ex(&mdctx, md, NULL);
 -    initialised_ = true;
 -    return true;
 +  ASSERT_IS_STRING_OR_BUFFER(args[0]);
  
 +  // Only copy the data if we have to, because it's a string
 +  bool r;
 +  if (args[0]->IsString()) {
 +    Local<String> string = args[0].As<String>();
 +    enum encoding encoding = ParseEncoding(args[1], BINARY);
 +    if (!StringBytes::IsValidString(string, encoding))
 +      return ThrowTypeError("Bad input string");
 +    size_t buflen = StringBytes::StorageSize(string, encoding);
 +    char* buf = new char[buflen];
 +    size_t written = StringBytes::Write(buf, buflen, string, encoding);
 +    r = hash->HashUpdate(buf, written);
 +    delete[] buf;
 +  } else {
 +    char* buf = Buffer::Data(args[0]);
 +    size_t buflen = Buffer::Length(args[0]);
 +    r = hash->HashUpdate(buf, buflen);
    }
  
 -  int SignUpdate(char* data, int len) {
 -    if (!initialised_) return 0;
 -    EVP_SignUpdate(&mdctx, data, len);
 -    return 1;
 +  if (!r) {
 +    return ThrowTypeError("HashUpdate fail");
    }
 +}
  
 -  int SignFinal(unsigned char** md_value,
 -                unsigned int *md_len,
 -                char* key_pem,
 -                int key_pemLen) {
 -    if (!initialised_) return 0;
  
 -    BIO *bp = NULL;
 -    EVP_PKEY* pkey;
 -    bp = BIO_new(BIO_s_mem());
 -    if(!BIO_write(bp, key_pem, key_pemLen)) return 0;
 +void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    pkey = PEM_read_bio_PrivateKey( bp, NULL, NULL, NULL );
 -    if (pkey == NULL) return 0;
 +  Hash* hash = Unwrap<Hash>(args.This());
  
 -    EVP_SignFinal(&mdctx, *md_value, md_len, pkey);
 -    EVP_MD_CTX_cleanup(&mdctx);
 -    initialised_ = false;
 -    EVP_PKEY_free(pkey);
 -    BIO_free(bp);
 -    return 1;
 +  if (!hash->initialised_) {
 +    return ThrowError("Not initialized");
    }
  
 +  enum encoding encoding = BUFFER;
 +  if (args.Length() >= 1) {
 +    encoding = ParseEncoding(args[0]->ToString(), BUFFER);
 +  }
  
 - protected:
 +  unsigned char md_value[EVP_MAX_MD_SIZE];
 +  unsigned int md_len;
  
 -  static Handle<Value> New (const Arguments& args) {
 -    HandleScope scope;
 +  EVP_DigestFinal_ex(&hash->mdctx_, md_value, &md_len);
 +  EVP_MD_CTX_cleanup(&hash->mdctx_);
 +  hash->initialised_ = false;
  
 -    Sign *sign = new Sign();
 -    sign->Wrap(args.This());
 +  Local<Value> rc = StringBytes::Encode(
 +      reinterpret_cast<const char*>(md_value), md_len, encoding);
 +  args.GetReturnValue().Set(rc);
 +}
  
 -    return args.This();
 -  }
  
 -  static Handle<Value> SignInit(const Arguments& args) {
 -    HandleScope scope;
 +void Sign::Initialize(Environment* env, v8::Handle<v8::Object> target) {
 +  Local<FunctionTemplate> t = FunctionTemplate::New(New);
  
 -    Sign *sign = ObjectWrap::Unwrap<Sign>(args.This());
 +  t->InstanceTemplate()->SetInternalFieldCount(1);
  
 -    if (args.Length() == 0 || !args[0]->IsString()) {
 -      return ThrowException(Exception::Error(String::New(
 -        "Must give signtype string as argument")));
 -    }
 +  NODE_SET_PROTOTYPE_METHOD(t, "init", SignInit);
 +  NODE_SET_PROTOTYPE_METHOD(t, "update", SignUpdate);
 +  NODE_SET_PROTOTYPE_METHOD(t, "sign", SignFinal);
  
 -    String::Utf8Value signType(args[0]);
 +  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Sign"), t->GetFunction());
 +}
  
 -    bool r = sign->SignInit(*signType);
  
 -    if (!r) {
 -      return ThrowException(Exception::Error(String::New("SignInit error")));
 -    }
 +void Sign::New(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope handle_scope(args.GetIsolate());
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +  new Sign(env, args.This());
 +}
  
 -    return args.This();
 -  }
  
 -  static Handle<Value> SignUpdate(const Arguments& args) {
 -    Sign *sign = ObjectWrap::Unwrap<Sign>(args.This());
 +void Sign::SignInit(const char* sign_type) {
 +  HandleScope scope(node_isolate);
  
 -    HandleScope scope;
 +  assert(md_ == NULL);
 +  md_ = EVP_get_digestbyname(sign_type);
 +  if (!md_) {
 +    return ThrowError("Uknown message digest");
 +  }
 +  EVP_MD_CTX_init(&mdctx_);
 +  EVP_SignInit_ex(&mdctx_, md_, NULL);
 +  initialised_ = true;
 +}
  
 -    ASSERT_IS_STRING_OR_BUFFER(args[0]);
  
 -    // Only copy the data if we have to, because it's a string
 -    int r;
 -    if (args[0]->IsString()) {
 -      Local<String> string = args[0].As<String>();
 -      enum encoding encoding = ParseEncoding(args[1], BINARY);
 -      if (!StringBytes::IsValidString(string, encoding))
 -        return ThrowTypeError("Bad input string");
 -      size_t buflen = StringBytes::StorageSize(string, encoding);
 -      char* buf = new char[buflen];
 -      size_t written = StringBytes::Write(buf, buflen, string, encoding);
 -      r = sign->SignUpdate(buf, written);
 -      delete[] buf;
 -    } else {
 -      char* buf = Buffer::Data(args[0]);
 -      size_t buflen = Buffer::Length(args[0]);
 -      r = sign->SignUpdate(buf, buflen);
 -    }
 +void Sign::SignInit(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    if (!r) {
 -      Local<Value> exception = Exception::TypeError(String::New("SignUpdate fail"));
 -      return ThrowException(exception);
 -    }
 +  Sign* sign = Unwrap<Sign>(args.This());
  
 -    return args.This();
 +  if (args.Length() == 0 || !args[0]->IsString()) {
 +    return ThrowError("Must give signtype string as argument");
    }
  
 -  static Handle<Value> SignFinal(const Arguments& args) {
 -    Sign *sign = ObjectWrap::Unwrap<Sign>(args.This());
 +  const String::Utf8Value sign_type(args[0]);
 +  sign->SignInit(*sign_type);
 +}
  
 -    HandleScope scope;
  
 -    unsigned char* md_value;
 -    unsigned int md_len;
 -    Local<Value> outString;
 +bool Sign::SignUpdate(const char* data, int len) {
 +  if (!initialised_)
 +    return false;
 +  EVP_SignUpdate(&mdctx_, data, len);
 +  return true;
 +}
  
 -    ASSERT_IS_BUFFER(args[0]);
 -    ssize_t len = Buffer::Length(args[0]);
  
 -    enum encoding encoding = BUFFER;
 -    if (args.Length() >= 2) {
 -      encoding = ParseEncoding(args[1]->ToString(), BUFFER);
 -    }
 +void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    char* buf = new char[len];
 -    ssize_t written = DecodeWrite(buf, len, args[0], BUFFER);
 -    assert(written == len);
 +  Sign* sign = Unwrap<Sign>(args.This());
  
 -    md_len = 8192; // Maximum key size is 8192 bits
 -    md_value = new unsigned char[md_len];
 +  ASSERT_IS_STRING_OR_BUFFER(args[0]);
  
 -    int r = sign->SignFinal(&md_value, &md_len, buf, len);
 -    if (r == 0) {
 -      md_value = NULL;
 -      md_len = r;
 -    }
 +  // Only copy the data if we have to, because it's a string
 +  int r;
 +  if (args[0]->IsString()) {
 +    Local<String> string = args[0].As<String>();
 +    enum encoding encoding = ParseEncoding(args[1], BINARY);
 +    if (!StringBytes::IsValidString(string, encoding))
 +      return ThrowTypeError("Bad input string");
 +    size_t buflen = StringBytes::StorageSize(string, encoding);
 +    char* buf = new char[buflen];
 +    size_t written = StringBytes::Write(buf, buflen, string, encoding);
 +    r = sign->SignUpdate(buf, written);
 +    delete[] buf;
 +  } else {
 +    char* buf = Buffer::Data(args[0]);
 +    size_t buflen = Buffer::Length(args[0]);
 +    r = sign->SignUpdate(buf, buflen);
 +  }
  
 -    delete [] buf;
 +  if (!r) {
 +    return ThrowTypeError("SignUpdate fail");
 +  }
 +}
  
 -    outString = StringBytes::Encode(
 -        reinterpret_cast<const char*>(md_value), md_len, encoding);
  
 -    delete [] md_value;
 -    return scope.Close(outString);
 +bool Sign::SignFinal(const char* key_pem,
 +                     int key_pem_len,
 +                     const char* passphrase,
 +                     unsigned char** sig,
 +                     unsigned int *sig_len) {
 +  if (!initialised_) {
 +    ThrowError("Sign not initalised");
 +    return false;
    }
  
 -  Sign () : ObjectWrap () {
 -    initialised_ = false;
 -  }
 +  BIO* bp = NULL;
 +  EVP_PKEY* pkey = NULL;
 +  bool fatal = true;
  
 -  ~Sign () {
 -    if (initialised_) {
 -      EVP_MD_CTX_cleanup(&mdctx);
 -    }
 -  }
 +  bp = BIO_new(BIO_s_mem());
 +  if (bp == NULL)
 +    goto exit;
  
 - private:
 +  if (!BIO_write(bp, key_pem, key_pem_len))
 +    goto exit;
  
 -  EVP_MD_CTX mdctx; /* coverity[member_decl] */
 -  const EVP_MD *md; /* coverity[member_decl] */
 -  bool initialised_;
 -};
 +  pkey = PEM_read_bio_PrivateKey(bp,
 +                                 NULL,
 +                                 CryptoPemCallback,
 +                                 const_cast<char*>(passphrase));
 +  if (pkey == NULL)
 +    goto exit;
  
 -class Verify : public ObjectWrap {
 - public:
 -  static void Initialize (v8::Handle<v8::Object> target) {
 -    HandleScope scope;
 +  if (EVP_SignFinal(&mdctx_, *sig, sig_len, pkey))
 +    fatal = false;
  
 -    Local<FunctionTemplate> t = FunctionTemplate::New(New);
 +  initialised_ = false;
  
 -    t->InstanceTemplate()->SetInternalFieldCount(1);
 + exit:
 +  if (pkey != NULL)
 +    EVP_PKEY_free(pkey);
 +  if (bp != NULL)
 +    BIO_free_all(bp);
  
 -    NODE_SET_PROTOTYPE_METHOD(t, "init", VerifyInit);
 -    NODE_SET_PROTOTYPE_METHOD(t, "update", VerifyUpdate);
 -    NODE_SET_PROTOTYPE_METHOD(t, "verify", VerifyFinal);
 +  EVP_MD_CTX_cleanup(&mdctx_);
  
 -    target->Set(String::NewSymbol("Verify"), t->GetFunction());
 +  if (fatal) {
 +    unsigned long err = ERR_get_error();
 +    if (err) {
 +      ThrowCryptoError(err);
 +    } else {
 +      ThrowError("PEM_read_bio_PrivateKey");
 +    }
 +    return false;
    }
  
 +  return true;
 +}
  
 -  bool VerifyInit (const char* verifyType) {
 -    md = EVP_get_digestbyname(verifyType);
 -    if(!md) {
 -      fprintf(stderr, "node-crypto : Unknown message digest %s\n", verifyType);
 -      return false;
 -    }
 -    EVP_MD_CTX_init(&mdctx);
 -    EVP_VerifyInit_ex(&mdctx, md, NULL);
 -    initialised_ = true;
 -    return true;
 +
 +void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  Sign* sign = Unwrap<Sign>(args.This());
 +
 +  unsigned char* md_value;
 +  unsigned int md_len;
 +
 +  unsigned int len = args.Length();
 +  enum encoding encoding = BUFFER;
 +  if (len >= 2 && args[1]->IsString()) {
 +    encoding = ParseEncoding(args[1]->ToString(), BUFFER);
    }
  
 +  String::Utf8Value passphrase(args[2]);
 +
 +  ASSERT_IS_BUFFER(args[0]);
 +  size_t buf_len = Buffer::Length(args[0]);
 +  char* buf = Buffer::Data(args[0]);
 +
 +  md_len = 8192;  // Maximum key size is 8192 bits
 +  md_value = new unsigned char[md_len];
  
 -  int VerifyUpdate(char* data, int len) {
 -    if (!initialised_) return 0;
 -    EVP_VerifyUpdate(&mdctx, data, len);
 -    return 1;
 +  bool r = sign->SignFinal(buf,
 +                           buf_len,
 +                           len >= 3 && !args[2]->IsNull() ? *passphrase : NULL,
 +                           &md_value,
 +                           &md_len);
 +  if (!r) {
 +    delete[] md_value;
 +    md_value = NULL;
 +    md_len = 0;
    }
  
 +  Local<Value> rc = StringBytes::Encode(
 +      reinterpret_cast<const char*>(md_value), md_len, encoding);
 +  delete[] md_value;
 +  args.GetReturnValue().Set(rc);
 +}
  
 -  int VerifyFinal(char* key_pem, int key_pemLen, unsigned char* sig, int siglen) {
 -    if (!initialised_) return 0;
  
 -    ClearErrorOnReturn clear_error_on_return;
 -    (void) &clear_error_on_return;  // Silence compiler warning.
 +void Verify::Initialize(Environment* env, v8::Handle<v8::Object> target) {
 +  Local<FunctionTemplate> t = FunctionTemplate::New(New);
  
 -    EVP_PKEY* pkey = NULL;
 -    BIO *bp = NULL;
 -    X509 *x509 = NULL;
 -    int r = 0;
 +  t->InstanceTemplate()->SetInternalFieldCount(1);
  
 -    bp = BIO_new(BIO_s_mem());
 -    if (bp == NULL) {
 -      ERR_print_errors_fp(stderr);
 -      return 0;
 -    }
 -    if(!BIO_write(bp, key_pem, key_pemLen)) {
 -      ERR_print_errors_fp(stderr);
 -      return 0;
 -    }
 +  NODE_SET_PROTOTYPE_METHOD(t, "init", VerifyInit);
 +  NODE_SET_PROTOTYPE_METHOD(t, "update", VerifyUpdate);
 +  NODE_SET_PROTOTYPE_METHOD(t, "verify", VerifyFinal);
  
 -    // Check if this is a PKCS#8 or RSA public key before trying as X.509.
 -    // Split this out into a separate function once we have more than one
 -    // consumer of public keys.
 -    if (strncmp(key_pem, PUBLIC_KEY_PFX, PUBLIC_KEY_PFX_LEN) == 0) {
 -      pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL);
 -      if (pkey == NULL) {
 -        ERR_print_errors_fp(stderr);
 -        return 0;
 -      }
 -    } else if (strncmp(key_pem, PUBRSA_KEY_PFX, PUBRSA_KEY_PFX_LEN) == 0) {
 -      RSA* rsa = PEM_read_bio_RSAPublicKey(bp, NULL, NULL, NULL);
 -      if (rsa) {
 -        pkey = EVP_PKEY_new();
 -        if (pkey) EVP_PKEY_set1_RSA(pkey, rsa);
 -        RSA_free(rsa);
 -      }
 -      if (pkey == NULL) {
 -        ERR_print_errors_fp(stderr);
 -        return 0;
 -      }
 -    } else {
 -      // X.509 fallback
 -      x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL);
 -      if (x509 == NULL) {
 -        ERR_print_errors_fp(stderr);
 -        return 0;
 -      }
 +  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Verify"), t->GetFunction());
 +}
  
 -      pkey = X509_get_pubkey(x509);
 -      if (pkey == NULL) {
 -        ERR_print_errors_fp(stderr);
 -        return 0;
 -      }
 -    }
  
 -    r = EVP_VerifyFinal(&mdctx, sig, siglen, pkey);
 +void Verify::New(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope handle_scope(args.GetIsolate());
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +  new Verify(env, args.This());
 +}
 +
  
 -    if(pkey != NULL)
 -      EVP_PKEY_free (pkey);
 -    if (x509 != NULL)
 -      X509_free(x509);
 -    if (bp != NULL)
 -      BIO_free(bp);
 -    EVP_MD_CTX_cleanup(&mdctx);
 -    initialised_ = false;
 +void Verify::VerifyInit(const char* verify_type) {
 +  HandleScope scope(node_isolate);
  
 -    return r;
 +  assert(md_ == NULL);
 +  md_ = EVP_get_digestbyname(verify_type);
 +  if (md_ == NULL) {
 +    return ThrowError("Unknown message digest");
    }
  
 +  EVP_MD_CTX_init(&mdctx_);
 +  EVP_VerifyInit_ex(&mdctx_, md_, NULL);
 +  initialised_ = true;
 +}
  
 - protected:
  
 -  static Handle<Value> New (const Arguments& args) {
 -    HandleScope scope;
 +void Verify::VerifyInit(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    Verify *verify = new Verify();
 -    verify->Wrap(args.This());
 +  Verify* verify = Unwrap<Verify>(args.This());
  
 -    return args.This();
 +  if (args.Length() == 0 || !args[0]->IsString()) {
 +    return ThrowError("Must give verifytype string as argument");
    }
  
 +  const String::Utf8Value verify_type(args[0]);
 +  verify->VerifyInit(*verify_type);
 +}
  
 -  static Handle<Value> VerifyInit(const Arguments& args) {
 -    Verify *verify = ObjectWrap::Unwrap<Verify>(args.This());
  
 -    HandleScope scope;
 +bool Verify::VerifyUpdate(const char* data, int len) {
 +  if (!initialised_)
 +    return false;
 +  EVP_VerifyUpdate(&mdctx_, data, len);
 +  return true;
 +}
  
 -    if (args.Length() == 0 || !args[0]->IsString()) {
 -      return ThrowException(Exception::Error(String::New(
 -        "Must give verifytype string as argument")));
 -    }
  
 -    String::Utf8Value verifyType(args[0]);
 +void Verify::VerifyUpdate(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    bool r = verify->VerifyInit(*verifyType);
 +  Verify* verify = Unwrap<Verify>(args.This());
  
 -    if (!r) {
 -      return ThrowException(Exception::Error(String::New("VerifyInit error")));
 -    }
 +  ASSERT_IS_STRING_OR_BUFFER(args[0]);
  
 -    return args.This();
 +  // Only copy the data if we have to, because it's a string
 +  bool r;
 +  if (args[0]->IsString()) {
 +    Local<String> string = args[0].As<String>();
 +    enum encoding encoding = ParseEncoding(args[1], BINARY);
 +    if (!StringBytes::IsValidString(string, encoding))
 +      return ThrowTypeError("Bad input string");
 +    size_t buflen = StringBytes::StorageSize(string, encoding);
 +    char* buf = new char[buflen];
 +    size_t written = StringBytes::Write(buf, buflen, string, encoding);
 +    r = verify->VerifyUpdate(buf, written);
 +    delete[] buf;
 +  } else {
 +    char* buf = Buffer::Data(args[0]);
 +    size_t buflen = Buffer::Length(args[0]);
 +    r = verify->VerifyUpdate(buf, buflen);
    }
  
 +  if (!r) {
 +    return ThrowTypeError("VerifyUpdate fail");
 +  }
 +}
  
 -  static Handle<Value> VerifyUpdate(const Arguments& args) {
 -    HandleScope scope;
  
 -    Verify *verify = ObjectWrap::Unwrap<Verify>(args.This());
 +bool Verify::VerifyFinal(const char* key_pem,
 +                         int key_pem_len,
 +                         const char* sig,
 +                         int siglen) {
 +  HandleScope scope(node_isolate);
  
 -    ASSERT_IS_STRING_OR_BUFFER(args[0]);
 +  if (!initialised_) {
 +    ThrowError("Verify not initalised");
 +    return false;
 +  }
  
 -    // Only copy the data if we have to, because it's a string
 -    int r;
 -    if (args[0]->IsString()) {
 -      Local<String> string = args[0].As<String>();
 -      enum encoding encoding = ParseEncoding(args[1], BINARY);
 -      if (!StringBytes::IsValidString(string, encoding))
 -        return ThrowTypeError("Bad input string");
 -      size_t buflen = StringBytes::StorageSize(string, encoding);
 -      char* buf = new char[buflen];
 -      size_t written = StringBytes::Write(buf, buflen, string, encoding);
 -      r = verify->VerifyUpdate(buf, written);
 -      delete[] buf;
 -    } else {
 -      char* buf = Buffer::Data(args[0]);
 -      size_t buflen = Buffer::Length(args[0]);
 -      r = verify->VerifyUpdate(buf, buflen);
 -    }
 +  ClearErrorOnReturn clear_error_on_return;
 +  (void) &clear_error_on_return;  // Silence compiler warning.
  
 -    if (!r) {
 -      Local<Value> exception = Exception::TypeError(String::New("VerifyUpdate fail"));
 -      return ThrowException(exception);
 +  EVP_PKEY* pkey = NULL;
 +  BIO* bp = NULL;
 +  X509* x509 = NULL;
 +  bool fatal = true;
 +  int r = 0;
 +
 +  bp = BIO_new(BIO_s_mem());
 +  if (bp == NULL)
 +    goto exit;
 +
 +  if (!BIO_write(bp, key_pem, key_pem_len))
 +    goto exit;
 +
 +  // Check if this is a PKCS#8 or RSA public key before trying as X.509.
 +  // Split this out into a separate function once we have more than one
 +  // consumer of public keys.
 +  if (strncmp(key_pem, PUBLIC_KEY_PFX, PUBLIC_KEY_PFX_LEN) == 0) {
 +    pkey = PEM_read_bio_PUBKEY(bp, NULL, CryptoPemCallback, NULL);
 +    if (pkey == NULL)
 +      goto exit;
 +  } else if (strncmp(key_pem, PUBRSA_KEY_PFX, PUBRSA_KEY_PFX_LEN) == 0) {
 +    RSA* rsa = PEM_read_bio_RSAPublicKey(bp, NULL, CryptoPemCallback, NULL);
 +    if (rsa) {
 +      pkey = EVP_PKEY_new();
 +      if (pkey)
 +        EVP_PKEY_set1_RSA(pkey, rsa);
 +      RSA_free(rsa);
      }
 +    if (pkey == NULL)
 +      goto exit;
 +  } else {
 +    // X.509 fallback
 +    x509 = PEM_read_bio_X509(bp, NULL, CryptoPemCallback, NULL);
 +    if (x509 == NULL)
 +      goto exit;
  
 -    return args.This();
 +    pkey = X509_get_pubkey(x509);
 +    if (pkey == NULL)
 +      goto exit;
    }
  
 +  fatal = false;
 +  r = EVP_VerifyFinal(&mdctx_,
 +                      reinterpret_cast<const unsigned char*>(sig),
 +                      siglen,
 +                      pkey);
 +
 + exit:
 +  if (pkey != NULL)
 +    EVP_PKEY_free(pkey);
 +  if (bp != NULL)
 +    BIO_free_all(bp);
 +  if (x509 != NULL)
 +    X509_free(x509);
  
 -  static Handle<Value> VerifyFinal(const Arguments& args) {
 -    HandleScope scope;
 +  EVP_MD_CTX_cleanup(&mdctx_);
 +  initialised_ = false;
  
 -    Verify *verify = ObjectWrap::Unwrap<Verify>(args.This());
 +  if (fatal) {
 +    unsigned long err = ERR_get_error();
 +    ThrowCryptoError(err);
 +    return false;
 +  }
  
 -    ASSERT_IS_BUFFER(args[0]);
 -    ssize_t klen = Buffer::Length(args[0]);
 +  return r == 1;
 +}
  
 -    if (klen < 0) {
 -      Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
 -      return ThrowException(exception);
 -    }
  
 -    char* kbuf = new char[klen];
 -    ssize_t kwritten = DecodeWrite(kbuf, klen, args[0], BINARY);
 -    assert(kwritten == klen);
 +void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    ASSERT_IS_STRING_OR_BUFFER(args[1]);
 +  Verify* verify = Unwrap<Verify>(args.This());
  
 -    // BINARY works for both buffers and binary strings.
 -    enum encoding encoding = BINARY;
 -    if (args.Length() >= 3) {
 -      encoding = ParseEncoding(args[2]->ToString(), BINARY);
 -    }
 +  ASSERT_IS_BUFFER(args[0]);
 +  char* kbuf = Buffer::Data(args[0]);
 +  ssize_t klen = Buffer::Length(args[0]);
  
 -    ssize_t hlen = StringBytes::Size(args[1], encoding);
 +  ASSERT_IS_STRING_OR_BUFFER(args[1]);
 +  // BINARY works for both buffers and binary strings.
 +  enum encoding encoding = BINARY;
 +  if (args.Length() >= 3) {
 +    encoding = ParseEncoding(args[2]->ToString(), BINARY);
 +  }
  
 -    if (hlen < 0) {
 -      delete[] kbuf;
 -      Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
 -      return ThrowException(exception);
 -    }
 +  ssize_t hlen = StringBytes::Size(args[1], encoding);
  
 -    unsigned char* hbuf = new unsigned char[hlen];
 -    ssize_t hwritten = StringBytes::Write(
 -        reinterpret_cast<char*>(hbuf), hlen, args[1], encoding);
 +  // only copy if we need to, because it's a string.
 +  char* hbuf;
 +  if (args[1]->IsString()) {
 +    hbuf = new char[hlen];
 +    ssize_t hwritten = StringBytes::Write(hbuf, hlen, args[1], encoding);
      assert(hwritten == hlen);
 +  } else {
 +    hbuf = Buffer::Data(args[1]);
 +  }
  
 -    int r;
 -    r = verify->VerifyFinal(kbuf, klen, hbuf, hlen);
 -
 -    delete[] kbuf;
 +  bool rc = verify->VerifyFinal(kbuf, klen, hbuf, hlen);
 +  if (args[1]->IsString()) {
      delete[] hbuf;
 -
 -    return Boolean::New(r && r != -1);
    }
 +  args.GetReturnValue().Set(rc);
 +}
  
 -  Verify () : ObjectWrap () {
 -    initialised_ = false;
 -  }
  
 -  ~Verify () {
 -    if (initialised_) {
 -      EVP_MD_CTX_cleanup(&mdctx);
 -    }
 -  }
 +void DiffieHellman::Initialize(Environment* env, Handle<Object> target) {
 +  Local<FunctionTemplate> t = FunctionTemplate::New(New);
  
 - private:
 +  t->InstanceTemplate()->SetInternalFieldCount(1);
  
 -  EVP_MD_CTX mdctx; /* coverity[member_decl] */
 -  const EVP_MD *md; /* coverity[member_decl] */
 -  bool initialised_;
 +  NODE_SET_PROTOTYPE_METHOD(t, "generateKeys", GenerateKeys);
 +  NODE_SET_PROTOTYPE_METHOD(t, "computeSecret", ComputeSecret);
 +  NODE_SET_PROTOTYPE_METHOD(t, "getPrime", GetPrime);
 +  NODE_SET_PROTOTYPE_METHOD(t, "getGenerator", GetGenerator);
 +  NODE_SET_PROTOTYPE_METHOD(t, "getPublicKey", GetPublicKey);
 +  NODE_SET_PROTOTYPE_METHOD(t, "getPrivateKey", GetPrivateKey);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setPublicKey", SetPublicKey);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setPrivateKey", SetPrivateKey);
 +
 +  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "DiffieHellman"),
 +              t->GetFunction());
 +
 +  Local<FunctionTemplate> t2 = FunctionTemplate::New(DiffieHellmanGroup);
 +  t2->InstanceTemplate()->SetInternalFieldCount(1);
 +
 +  NODE_SET_PROTOTYPE_METHOD(t2, "generateKeys", GenerateKeys);
 +  NODE_SET_PROTOTYPE_METHOD(t2, "computeSecret", ComputeSecret);
 +  NODE_SET_PROTOTYPE_METHOD(t2, "getPrime", GetPrime);
 +  NODE_SET_PROTOTYPE_METHOD(t2, "getGenerator", GetGenerator);
 +  NODE_SET_PROTOTYPE_METHOD(t2, "getPublicKey", GetPublicKey);
 +  NODE_SET_PROTOTYPE_METHOD(t2, "getPrivateKey", GetPrivateKey);
 +
 +  target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "DiffieHellmanGroup"),
 +              t2->GetFunction());
 +}
  
 -};
  
 -class DiffieHellman : public ObjectWrap {
 - public:
 -  static void Initialize(v8::Handle<v8::Object> target) {
 -    HandleScope scope;
 +bool DiffieHellman::Init(int primeLength) {
 +  dh = DH_new();
 +  DH_generate_parameters_ex(dh, primeLength, DH_GENERATOR_2, 0);
 +  bool result = VerifyContext();
 +  if (!result)
 +    return false;
 +  initialised_ = true;
 +  return true;
 +}
  
 -    Local<FunctionTemplate> t = FunctionTemplate::New(New);
  
 -    t->InstanceTemplate()->SetInternalFieldCount(1);
 +bool DiffieHellman::Init(const char* p, int p_len) {
 +  dh = DH_new();
 +  dh->p = BN_bin2bn(reinterpret_cast<const unsigned char*>(p), p_len, 0);
 +  dh->g = BN_new();
 +  if (!BN_set_word(dh->g, 2))
 +    return false;
 +  bool result = VerifyContext();
 +  if (!result)
 +    return false;
 +  initialised_ = true;
 +  return true;
 +}
  
 -    NODE_SET_PROTOTYPE_METHOD(t, "generateKeys", GenerateKeys);
 -    NODE_SET_PROTOTYPE_METHOD(t, "computeSecret", ComputeSecret);
 -    NODE_SET_PROTOTYPE_METHOD(t, "getPrime", GetPrime);
 -    NODE_SET_PROTOTYPE_METHOD(t, "getGenerator", GetGenerator);
 -    NODE_SET_PROTOTYPE_METHOD(t, "getPublicKey", GetPublicKey);
 -    NODE_SET_PROTOTYPE_METHOD(t, "getPrivateKey", GetPrivateKey);
 -    NODE_SET_PROTOTYPE_METHOD(t, "setPublicKey", SetPublicKey);
 -    NODE_SET_PROTOTYPE_METHOD(t, "setPrivateKey", SetPrivateKey);
  
 -    target->Set(String::NewSymbol("DiffieHellman"), t->GetFunction());
 +bool DiffieHellman::Init(const char* p, int p_len, const char* g, int g_len) {
 +  dh = DH_new();
 +  dh->p = BN_bin2bn(reinterpret_cast<const unsigned char*>(p), p_len, 0);
 +  dh->g = BN_bin2bn(reinterpret_cast<const unsigned char*>(g), g_len, 0);
 +  initialised_ = true;
 +  return true;
 +}
  
 -    Local<FunctionTemplate> t2 = FunctionTemplate::New(DiffieHellmanGroup);
 -    t2->InstanceTemplate()->SetInternalFieldCount(1);
  
 -    NODE_SET_PROTOTYPE_METHOD(t2, "generateKeys", GenerateKeys);
 -    NODE_SET_PROTOTYPE_METHOD(t2, "computeSecret", ComputeSecret);
 -    NODE_SET_PROTOTYPE_METHOD(t2, "getPrime", GetPrime);
 -    NODE_SET_PROTOTYPE_METHOD(t2, "getGenerator", GetGenerator);
 -    NODE_SET_PROTOTYPE_METHOD(t2, "getPublicKey", GetPublicKey);
 -    NODE_SET_PROTOTYPE_METHOD(t2, "getPrivateKey", GetPrivateKey);
 +void DiffieHellman::DiffieHellmanGroup(
 +    const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    target->Set(String::NewSymbol("DiffieHellmanGroup"), t2->GetFunction());
 -  }
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +  DiffieHellman* diffieHellman = new DiffieHellman(env, args.This());
  
 -  bool Init(int primeLength) {
 -    dh = DH_new();
 -    DH_generate_parameters_ex(dh, primeLength, DH_GENERATOR_2, 0);
 -    bool result = VerifyContext();
 -    if (!result) return false;
 -    initialised_ = true;
 -    return true;
 +  if (args.Length() != 1 || !args[0]->IsString()) {
 +    return ThrowError("No group name given");
    }
  
 -  bool Init(unsigned char* p, int p_len) {
 -    dh = DH_new();
 -    dh->p = BN_bin2bn(p, p_len, 0);
 -    dh->g = BN_new();
 -    if (!BN_set_word(dh->g, 2)) return false;
 -    bool result = VerifyContext();
 -    if (!result) return false;
 -    initialised_ = true;
 -    return true;
 -  }
 +  const String::Utf8Value group_name(args[0]);
 +  for (unsigned int i = 0; i < ARRAY_SIZE(modp_groups); ++i) {
 +    const modp_group* it = modp_groups + i;
  
 -  bool Init(unsigned char* p, int p_len, unsigned char* g, int g_len) {
 -    dh = DH_new();
 -    dh->p = BN_bin2bn(p, p_len, 0);
 -    dh->g = BN_bin2bn(g, g_len, 0);
 -    initialised_ = true;
 -    return true;
 +    if (strcasecmp(*group_name, it->name) != 0)
 +      continue;
 +
 +    diffieHellman->Init(it->prime,
 +                        it->prime_size,
 +                        it->gen,
 +                        it->gen_size);
 +    return;
    }
  
 - protected:
 -  static Handle<Value> DiffieHellmanGroup(const Arguments& args) {
 -    HandleScope scope;
 +  ThrowError("Unknown group");
 +}
 +
 +
 +void DiffieHellman::New(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    DiffieHellman* diffieHellman = new DiffieHellman();
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +  DiffieHellman* diffieHellman =
 +      new DiffieHellman(env, args.This());
 +  bool initialized = false;
  
 -    if (args.Length() != 1 || !args[0]->IsString()) {
 -      return ThrowException(Exception::Error(
 -          String::New("No group name given")));
 +  if (args.Length() > 0) {
 +    if (args[0]->IsInt32()) {
 +      initialized = diffieHellman->Init(args[0]->Int32Value());
 +    } else {
 +      initialized = diffieHellman->Init(Buffer::Data(args[0]),
 +                                        Buffer::Length(args[0]));
      }
 +  }
  
 -    String::Utf8Value group_name(args[0]);
 +  if (!initialized) {
 +    return ThrowError("Initialization failed");
 +  }
 +}
  
 -    modp_group* it = modp_groups;
  
 -    while(it->name != NULL) {
 -      if (!strcasecmp(*group_name, it->name))
 -          break;
 -      it++;
 -    }
 +void DiffieHellman::GenerateKeys(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    if (it->name != NULL) {
 -      diffieHellman->Init(it->prime, it->prime_size,
 -              it->gen, it->gen_size);
 -    } else {
 -      return ThrowException(Exception::Error(
 -          String::New("Unknown group")));
 -    }
 +  DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.This());
  
 -    diffieHellman->Wrap(args.This());
 +  if (!diffieHellman->initialised_) {
 +    return ThrowError("Not initialized");
 +  }
  
 -    return args.This();
 +  if (!DH_generate_key(diffieHellman->dh)) {
 +    return ThrowError("Key generation failed");
    }
  
 -  static Handle<Value> New(const Arguments& args) {
 -    HandleScope scope;
 +  int dataSize = BN_num_bytes(diffieHellman->dh->pub_key);
 +  char* data = new char[dataSize];
 +  BN_bn2bin(diffieHellman->dh->pub_key,
 +            reinterpret_cast<unsigned char*>(data));
  
 -    DiffieHellman* diffieHellman = new DiffieHellman();
 -    bool initialized = false;
 +  args.GetReturnValue().Set(Encode(data, dataSize, BUFFER));
 +  delete[] data;
 +}
  
 -    if (args.Length() > 0) {
 -      if (args[0]->IsInt32()) {
 -        initialized = diffieHellman->Init(args[0]->Int32Value());
 -      } else {
 -        initialized = diffieHellman->Init(
 -                reinterpret_cast<unsigned char*>(Buffer::Data(args[0])),
 -                Buffer::Length(args[0]));
 -      }
 -    }
  
 -    if (!initialized) {
 -      return ThrowException(Exception::Error(
 -            String::New("Initialization failed")));
 -    }
 +void DiffieHellman::GetPrime(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    diffieHellman->Wrap(args.This());
 +  DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.This());
  
 -    return args.This();
 +  if (!diffieHellman->initialised_) {
 +    return ThrowError("Not initialized");
    }
  
 -  static Handle<Value> GenerateKeys(const Arguments& args) {
 -    DiffieHellman* diffieHellman =
 -      ObjectWrap::Unwrap<DiffieHellman>(args.This());
 +  int dataSize = BN_num_bytes(diffieHellman->dh->p);
 +  char* data = new char[dataSize];
 +  BN_bn2bin(diffieHellman->dh->p, reinterpret_cast<unsigned char*>(data));
  
 -    HandleScope scope;
 +  args.GetReturnValue().Set(Encode(data, dataSize, BUFFER));
 +  delete[] data;
 +}
  
 -    if (!diffieHellman->initialised_) {
 -      return ThrowException(Exception::Error(
 -            String::New("Not initialized")));
 -    }
  
 -    if (!DH_generate_key(diffieHellman->dh)) {
 -      return ThrowException(Exception::Error(
 -            String::New("Key generation failed")));
 -    }
 +void DiffieHellman::GetGenerator(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    Local<Value> outString;
 +  DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.This());
  
 -    int dataSize = BN_num_bytes(diffieHellman->dh->pub_key);
 -    char* data = new char[dataSize];
 -    BN_bn2bin(diffieHellman->dh->pub_key,
 -        reinterpret_cast<unsigned char*>(data));
 +  if (!diffieHellman->initialised_) {
 +    return ThrowError("Not initialized");
 +  }
  
 -    outString = Encode(data, dataSize, BUFFER);
 -    delete[] data;
 +  int dataSize = BN_num_bytes(diffieHellman->dh->g);
 +  char* data = new char[dataSize];
 +  BN_bn2bin(diffieHellman->dh->g, reinterpret_cast<unsigned char*>(data));
  
 -    return scope.Close(outString);
 -  }
 +  args.GetReturnValue().Set(Encode(data, dataSize, BUFFER));
 +  delete[] data;
 +}
  
 -  static Handle<Value> GetPrime(const Arguments& args) {
 -    DiffieHellman* diffieHellman =
 -      ObjectWrap::Unwrap<DiffieHellman>(args.This());
  
 -    HandleScope scope;
 +void DiffieHellman::GetPublicKey(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    if (!diffieHellman->initialised_) {
 -      return ThrowException(Exception::Error(String::New("Not initialized")));
 -    }
 +  DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.This());
  
 -    int dataSize = BN_num_bytes(diffieHellman->dh->p);
 -    char* data = new char[dataSize];
 -    BN_bn2bin(diffieHellman->dh->p, reinterpret_cast<unsigned char*>(data));
 +  if (!diffieHellman->initialised_) {
 +    return ThrowError("Not initialized");
 +  }
  
 -    Local<Value> outString;
 +  if (diffieHellman->dh->pub_key == NULL) {
 +    return ThrowError("No public key - did you forget to generate one?");
 +  }
  
 -    outString = Encode(data, dataSize, BUFFER);
 +  int dataSize = BN_num_bytes(diffieHellman->dh->pub_key);
 +  char* data = new char[dataSize];
 +  BN_bn2bin(diffieHellman->dh->pub_key,
 +            reinterpret_cast<unsigned char*>(data));
  
 -    delete[] data;
 +  args.GetReturnValue().Set(Encode(data, dataSize, BUFFER));
 +  delete[] data;
 +}
  
 -    return scope.Close(outString);
 -  }
  
 -  static Handle<Value> GetGenerator(const Arguments& args) {
 -    DiffieHellman* diffieHellman =
 -      ObjectWrap::Unwrap<DiffieHellman>(args.This());
 +void DiffieHellman::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    HandleScope scope;
 +  DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.This());
  
 -    if (!diffieHellman->initialised_) {
 -      return ThrowException(Exception::Error(String::New("Not initialized")));
 -    }
 +  if (!diffieHellman->initialised_) {
 +    return ThrowError("Not initialized");
 +  }
  
 -    int dataSize = BN_num_bytes(diffieHellman->dh->g);
 -    char* data = new char[dataSize];
 -    BN_bn2bin(diffieHellman->dh->g, reinterpret_cast<unsigned char*>(data));
 +  if (diffieHellman->dh->priv_key == NULL) {
 +    return ThrowError("No private key - did you forget to generate one?");
 +  }
  
 -    Local<Value> outString;
 +  int dataSize = BN_num_bytes(diffieHellman->dh->priv_key);
 +  char* data = new char[dataSize];
 +  BN_bn2bin(diffieHellman->dh->priv_key,
 +            reinterpret_cast<unsigned char*>(data));
  
 -    outString = Encode(data, dataSize, BUFFER);
 +  args.GetReturnValue().Set(Encode(data, dataSize, BUFFER));
 +  delete[] data;
 +}
  
 -    delete[] data;
  
 -    return scope.Close(outString);
 -  }
 +void DiffieHellman::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -  static Handle<Value> GetPublicKey(const Arguments& args) {
 -    DiffieHellman* diffieHellman =
 -      ObjectWrap::Unwrap<DiffieHellman>(args.This());
 +  DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.This());
  
 -    HandleScope scope;
 +  if (!diffieHellman->initialised_) {
 +    return ThrowError("Not initialized");
 +  }
  
 -    if (!diffieHellman->initialised_) {
 -      return ThrowException(Exception::Error(String::New("Not initialized")));
 -    }
 +  ClearErrorOnReturn clear_error_on_return;
 +  (void) &clear_error_on_return;  // Silence compiler warning.
 +  BIGNUM* key = NULL;
  
 -    if (diffieHellman->dh->pub_key == NULL) {
 -      return ThrowException(Exception::Error(
 -            String::New("No public key - did you forget to generate one?")));
 -    }
 +  if (args.Length() == 0) {
 +    return ThrowError("First argument must be other party's public key");
 +  } else {
 +    ASSERT_IS_BUFFER(args[0]);
 +    key = BN_bin2bn(
 +        reinterpret_cast<unsigned char*>(Buffer::Data(args[0])),
 +        Buffer::Length(args[0]),
 +        0);
 +  }
  
 -    int dataSize = BN_num_bytes(diffieHellman->dh->pub_key);
 -    char* data = new char[dataSize];
 -    BN_bn2bin(diffieHellman->dh->pub_key,
 -        reinterpret_cast<unsigned char*>(data));
 +  int dataSize = DH_size(diffieHellman->dh);
 +  char* data = new char[dataSize];
  
 -    Local<Value> outString;
 +  int size = DH_compute_key(reinterpret_cast<unsigned char*>(data),
 +                            key,
 +                            diffieHellman->dh);
  
 -    outString = Encode(data, dataSize, BUFFER);
 +  if (size == -1) {
 +    int checkResult;
 +    int checked;
  
 +    checked = DH_check_pub_key(diffieHellman->dh, key, &checkResult);
 +    BN_free(key);
      delete[] data;
  
 -    return scope.Close(outString);
 +    if (!checked) {
 +      return ThrowError("Invalid key");
 +    } else if (checkResult) {
 +      if (checkResult & DH_CHECK_PUBKEY_TOO_SMALL) {
 +        return ThrowError("Supplied key is too small");
 +      } else if (checkResult & DH_CHECK_PUBKEY_TOO_LARGE) {
 +        return ThrowError("Supplied key is too large");
 +      } else {
 +        return ThrowError("Invalid key");
 +      }
 +    } else {
 +      return ThrowError("Invalid key");
 +    }
    }
  
 -  static Handle<Value> GetPrivateKey(const Arguments& args) {
 -    DiffieHellman* diffieHellman =
 -      ObjectWrap::Unwrap<DiffieHellman>(args.This());
 +  BN_free(key);
 +  assert(size >= 0);
  
 -    HandleScope scope;
 -
 -    if (!diffieHellman->initialised_) {
 -      return ThrowException(Exception::Error(String::New("Not initialized")));
 -    }
 +  // DH_size returns number of bytes in a prime number
 +  // DH_compute_key returns number of bytes in a remainder of exponent, which
 +  // may have less bytes than a prime number. Therefore add 0-padding to the
 +  // allocated buffer.
 +  if (size != dataSize) {
 +    assert(dataSize > size);
 +    memmove(data + dataSize - size, data, size);
 +    memset(data, 0, dataSize - size);
 +  }
  
 -    if (diffieHellman->dh->priv_key == NULL) {
 -      return ThrowException(Exception::Error(
 -            String::New("No private key - did you forget to generate one?")));
 -    }
 +  args.GetReturnValue().Set(Encode(data, dataSize, BUFFER));
 +  delete[] data;
 +}
  
 -    int dataSize = BN_num_bytes(diffieHellman->dh->priv_key);
 -    char* data = new char[dataSize];
 -    BN_bn2bin(diffieHellman->dh->priv_key,
 -        reinterpret_cast<unsigned char*>(data));
  
 -    Local<Value> outString;
 +void DiffieHellman::SetPublicKey(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    outString = Encode(data, dataSize, BUFFER);
 +  DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.This());
  
 -    delete[] data;
 +  if (!diffieHellman->initialised_) {
 +    return ThrowError("Not initialized");
 +  }
  
 -    return scope.Close(outString);
 +  if (args.Length() == 0) {
 +    return ThrowError("First argument must be public key");
 +  } else {
 +    ASSERT_IS_BUFFER(args[0]);
 +    diffieHellman->dh->pub_key = BN_bin2bn(
 +        reinterpret_cast<unsigned char*>(Buffer::Data(args[0])),
 +        Buffer::Length(args[0]), 0);
    }
 +}
  
 -  static Handle<Value> ComputeSecret(const Arguments& args) {
 -    HandleScope scope;
  
 -    DiffieHellman* diffieHellman =
 -      ObjectWrap::Unwrap<DiffieHellman>(args.This());
 +void DiffieHellman::SetPrivateKey(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
  
 -    if (!diffieHellman->initialised_) {
 -      return ThrowException(Exception::Error(String::New("Not initialized")));
 -    }
 +  DiffieHellman* diffieHellman = Unwrap<DiffieHellman>(args.This());
  
 -    ClearErrorOnReturn clear_error_on_return;
 -    (void) &clear_error_on_return;  // Silence compiler warning.
 -    BIGNUM* key = 0;
 +  if (!diffieHellman->initialised_) {
 +    return ThrowError("Not initialized");
 +  }
  
 -    if (args.Length() == 0) {
 -      return ThrowException(Exception::Error(
 -            String::New("First argument must be other party's public key")));
 -    } else {
 -      ASSERT_IS_BUFFER(args[0]);
 -      key = BN_bin2bn(
 +  if (args.Length() == 0) {
 +    return ThrowError("First argument must be private key");
 +  } else {
 +    ASSERT_IS_BUFFER(args[0]);
 +    diffieHellman->dh->priv_key = BN_bin2bn(
          reinterpret_cast<unsigned char*>(Buffer::Data(args[0])),
 -        Buffer::Length(args[0]), 0);
 -    }
 -
 -    int dataSize = DH_size(diffieHellman->dh);
 -    char* data = new char[dataSize];
 -
 -    int size = DH_compute_key(reinterpret_cast<unsigned char*>(data),
 -      key, diffieHellman->dh);
 -
 -    if (size == -1) {
 -      int checkResult;
 -      int checked;
 -
 -      checked = DH_check_pub_key(diffieHellman->dh, key, &checkResult);
 -      BN_free(key);
 -      delete[] data;
 -
 -      if (!checked) {
 -        return ThrowException(Exception::Error(String::New("Invalid key")));
 -      } else if (checkResult) {
 -        if (checkResult & DH_CHECK_PUBKEY_TOO_SMALL) {
 -          return ThrowException(Exception::Error(
 -                String::New("Supplied key is too small")));
 -        } else if (checkResult & DH_CHECK_PUBKEY_TOO_LARGE) {
 -          return ThrowException(Exception::Error(
 -                String::New("Supplied key is too large")));
 -        } else {
 -          return ThrowException(Exception::Error(String::New("Invalid key")));
 -        }
 -      } else {
 -        return ThrowException(Exception::Error(String::New("Invalid key")));
 -      }
 -    }
 +        Buffer::Length(args[0]),
 +        0);
 +  }
 +}
  
 -    BN_free(key);
 -    assert(size >= 0);
 -
 -    // DH_size returns number of bytes in a prime number
 -    // DH_compute_key returns number of bytes in a remainder of exponent, which
 -    // may have less bytes than a prime number. Therefore add 0-padding to the
 -    // allocated buffer.
 -    if (size != dataSize) {
 -      assert(dataSize > size);
 -      memmove(data + dataSize - size, data, size);
 -      memset(data, 0, dataSize - size);
 -    }
  
 -    Local<Value> outString;
 +bool DiffieHellman::VerifyContext() {
 +  int codes;
 +  if (!DH_check(dh, &codes))
 +    return false;
 +  if (codes & DH_CHECK_P_NOT_SAFE_PRIME)
 +    return false;
 +  if (codes & DH_CHECK_P_NOT_PRIME)
 +    return false;
 +  if (codes & DH_UNABLE_TO_CHECK_GENERATOR)
 +    return false;
 +  if (codes & DH_NOT_SUITABLE_GENERATOR)
 +    return false;
 +  return true;
 +}
  
 -    outString = Encode(data, dataSize, BUFFER);
  
 -    delete[] data;
 -    return scope.Close(outString);
 +class PBKDF2Request : public AsyncWrap {
 + public:
 +  PBKDF2Request(Environment* env,
 +                Local<Object> object,
 +                ssize_t passlen,
 +                char* pass,
 +                ssize_t saltlen,
 +                char* salt,
 +                ssize_t iter,
 +                ssize_t keylen)
 +      : AsyncWrap(env, object),
 +        error_(0),
 +        passlen_(passlen),
 +        pass_(pass),
 +        saltlen_(saltlen),
 +        salt_(salt),
 +        keylen_(keylen),
 +        key_(static_cast<char*>(malloc(keylen))),
 +        iter_(iter) {
 +    if (key() == NULL)
 +      FatalError("node::PBKDF2Request()", "Out of Memory");
    }
  
 -  static Handle<Value> SetPublicKey(const Arguments& args) {
 -    HandleScope scope;
 -
 -    DiffieHellman* diffieHellman =
 -      ObjectWrap::Unwrap<DiffieHellman>(args.This());
 +  ~PBKDF2Request() {
 +    persistent().Dispose();
 +  }
  
 -    if (!diffieHellman->initialised_) {
 -      return ThrowException(Exception::Error(String::New("Not initialized")));
 -    }
 +  uv_work_t* work_req() {
 +    return &work_req_;
 +  }
  
 -    if (args.Length() == 0) {
 -      return ThrowException(Exception::Error(
 -            String::New("First argument must be public key")));
 -    } else {
 -      ASSERT_IS_BUFFER(args[0]);
 -      diffieHellman->dh->pub_key =
 -        BN_bin2bn(
 -          reinterpret_cast<unsigned char*>(Buffer::Data(args[0])),
 -          Buffer::Length(args[0]), 0);
 -    }
 +  inline ssize_t passlen() const {
 +    return passlen_;
 +  }
  
 -    return args.This();
 +  inline char* pass() const {
 +    return pass_;
    }
  
 -  static Handle<Value> SetPrivateKey(const Arguments& args) {
 -    HandleScope scope;
 +  inline ssize_t saltlen() const {
 +    return saltlen_;
 +  }
  
 -    DiffieHellman* diffieHellman =
 -      ObjectWrap::Unwrap<DiffieHellman>(args.This());
 +  inline char* salt() const {
 +    return salt_;
 +  }
  
 -    if (!diffieHellman->initialised_) {
 -      return ThrowException(Exception::Error(
 -            String::New("Not initialized")));
 -    }
 +  inline ssize_t keylen() const {
 +    return keylen_;
 +  }
  
 -    if (args.Length() == 0) {
 -      return ThrowException(Exception::Error(
 -            String::New("First argument must be private key")));
 -    } else {
 -      ASSERT_IS_BUFFER(args[0]);
 -      diffieHellman->dh->priv_key =
 -        BN_bin2bn(
 -          reinterpret_cast<unsigned char*>(Buffer::Data(args[0])),
 -          Buffer::Length(args[0]), 0);
 -    }
 +  inline char* key() const {
 +    return key_;
 +  }
  
 -    return args.This();
 +  inline ssize_t iter() const {
 +    return iter_;
    }
  
 -  DiffieHellman() : ObjectWrap() {
 -    initialised_ = false;
 -    dh = NULL;
 +  inline void release() {
 +    free(pass_);
 +    passlen_ = 0;
 +    free(salt_);
 +    saltlen_ = 0;
 +    free(key_);
 +    keylen_ = 0;
    }
  
 -  ~DiffieHellman() {
 -    if (dh != NULL) {
 -      DH_free(dh);
 -    }
 +  inline int error() const {
 +    return error_;
    }
  
 - private:
 -  bool VerifyContext() {
 -    int codes;
 -    if (!DH_check(dh, &codes)) return false;
 -    if (codes & DH_CHECK_P_NOT_SAFE_PRIME) return false;
 -    if (codes & DH_CHECK_P_NOT_PRIME) return false;
 -    if (codes & DH_UNABLE_TO_CHECK_GENERATOR) return false;
 -    if (codes & DH_NOT_SUITABLE_GENERATOR) return false;
 -    return true;
 -  }
 -
 -  bool initialised_;
 -  DH* dh;
 -};
 +  inline void set_error(int err) {
 +    error_ = err;
 +  }
  
 +  // TODO(trevnorris): Make private and make work with CONTAINER_OF macro.
 +  uv_work_t work_req_;
  
 -struct pbkdf2_req {
 -  uv_work_t work_req;
 -  int err;
 -  char* pass;
 -  size_t passlen;
 -  char* salt;
 -  size_t saltlen;
 -  size_t iter;
 -  char* key;
 -  size_t keylen;
 -  Persistent<Object> obj;
 + private:
 +  int error_;
 +  ssize_t passlen_;
 +  char* pass_;
 +  ssize_t saltlen_;
 +  char* salt_;
 +  ssize_t keylen_;
 +  char* key_;
 +  ssize_t iter_;
  };
  
  
@@@ -118,122 -103,60 +118,122 @@@ class SecureContext : public BaseObjec
        assert(ca_store_ == NULL);
      }
    }
 -
 -  ~SecureContext() {
 -    FreeCTXMem();
 -  }
 -
 - private:
  };
  
 -class ClientHelloParser {
 +// SSLWrap implicitly depends on the inheriting class' handle having an
 +// internal pointer to the Base class.
 +template <class Base>
 +class SSLWrap {
   public:
 -  enum FrameType {
 -    kChangeCipherSpec = 20,
 -    kAlert = 21,
 -    kHandshake = 22,
 -    kApplicationData = 23,
 -    kOther = 255
 +  enum Kind {
 +    kClient,
 +    kServer
    };
  
 -  enum HandshakeType {
 -    kClientHello = 1
 -  };
 +  SSLWrap(Environment* env, SecureContext* sc, Kind kind)
 +      : env_(env),
 +        kind_(kind),
 +        next_sess_(NULL),
 +        session_callbacks_(false) {
 +    ssl_ = SSL_new(sc->ctx_);
 +    assert(ssl_ != NULL);
 +  }
  
 -  enum ParseState {
 -    kWaiting,
 -    kTLSHeader,
 -    kSSLHeader,
 -    kPaused,
 -    kEnded
 -  };
 +  ~SSLWrap() {
 +    if (ssl_ != NULL) {
 +      SSL_free(ssl_);
 +      ssl_ = NULL;
 +    }
 +    if (next_sess_ != NULL) {
 +      SSL_SESSION_free(next_sess_);
 +      next_sess_ = NULL;
 +    }
  
 -  ClientHelloParser(Connection* c) : conn_(c),
 -                                     state_(kWaiting),
 -                                     offset_(0),
 -                                     body_offset_(0) {
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +    npn_protos_.Dispose();
 +    selected_npn_proto_.Dispose();
 +#endif
    }
  
 -  size_t Write(const uint8_t* data, size_t len);
 -  void Finish();
 +  inline SSL* ssl() const { return ssl_; }
 +  inline void enable_session_callbacks() { session_callbacks_ = true; }
 +  inline bool is_server() const { return kind_ == kServer; }
 +  inline bool is_client() const { return kind_ == kClient; }
  
 -  inline bool ended() { return state_ == kEnded; }
 + protected:
 +  static void InitNPN(SecureContext* sc, Base* base);
 +  static void AddMethods(v8::Handle<v8::FunctionTemplate> t);
  
 - private:
 -  Connection* conn_;
 -  ParseState state_;
 -  size_t frame_len_;
 +  static SSL_SESSION* GetSessionCallback(SSL* s,
 +                                         unsigned char* key,
 +                                         int len,
 +                                         int* copy);
 +  static int NewSessionCallback(SSL* s, SSL_SESSION* sess);
 +  static void OnClientHello(void* arg,
 +                            const ClientHelloParser::ClientHello& hello);
 +
 +  static void GetPeerCertificate(
 +      const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void GetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void SetSession(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void LoadSession(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void IsSessionReused(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void IsInitFinished(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void VerifyError(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void GetCurrentCipher(const v8::FunctionCallbackInfo<v8::Value>& args);
-   static void ReceivedShutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void EndParser(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void Renegotiate(const v8::FunctionCallbackInfo<v8::Value>& args);
++  static void Shutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
 +
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +  static void GetNegotiatedProto(
 +      const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void SetNPNProtocols(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static int AdvertiseNextProtoCallback(SSL* s,
 +                                        const unsigned char** data,
 +                                        unsigned int* len,
 +                                        void* arg);
 +  static int SelectNextProtoCallback(SSL* s,
 +                                     unsigned char** out,
 +                                     unsigned char* outlen,
 +                                     const unsigned char* in,
 +                                     unsigned int inlen,
 +                                     void* arg);
 +#endif  // OPENSSL_NPN_NEGOTIATED
 +
 +  inline Environment* ssl_env() const {
 +    return env_;
 +  }
 +
 +  Environment* const env_;
 +  Kind kind_;
 +  SSL_SESSION* next_sess_;
 +  SSL* ssl_;
 +  bool session_callbacks_;
 +  ClientHelloParser hello_parser_;
  
 -  uint8_t data_[18432];
 -  size_t offset_;
 -  size_t body_offset_;
 +#ifdef OPENSSL_NPN_NEGOTIATED
 +  v8::Persistent<v8::Object> npn_protos_;
 +  v8::Persistent<v8::Value> selected_npn_proto_;
 +#endif  // OPENSSL_NPN_NEGOTIATED
 +
 +  friend class SecureContext;
  };
  
 -class Connection : ObjectWrap {
 +// Connection inherits from AsyncWrap because SSLWrap makes calls to
 +// MakeCallback, but SSLWrap doesn't store the handle itself. Instead it
 +// assumes that any args.This() called will be the handle from Connection.
 +class Connection : public SSLWrap<Connection>, public AsyncWrap {
   public:
 -  static void Initialize(v8::Handle<v8::Object> target);
 +  ~Connection() {
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +    sniObject_.Dispose();
 +    sniContext_.Dispose();
 +    servername_.Dispose();
 +#endif
 +  }
 +
 +  static void Initialize(Environment* env, v8::Handle<v8::Object> target);
  
  #ifdef OPENSSL_NPN_NEGOTIATED
    v8::Persistent<v8::Object> npnProtos_;
  #endif
  
   protected:
 -  static v8::Handle<v8::Value> New(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> EncIn(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> ClearOut(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> ClearPending(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> EncPending(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> EncOut(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> ClearIn(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> GetPeerCertificate(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> GetSession(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> SetSession(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> LoadSession(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> IsSessionReused(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> IsInitFinished(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> VerifyError(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> GetCurrentCipher(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> Shutdown(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> Start(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> Close(const v8::Arguments& args);
 -
 -  static void InitNPN(SecureContext* sc, bool is_server);
 -
 -#ifdef OPENSSL_NPN_NEGOTIATED
 -  // NPN
 -  static v8::Handle<v8::Value> GetNegotiatedProto(const v8::Arguments& args);
 -  static v8::Handle<v8::Value> SetNPNProtocols(const v8::Arguments& args);
 -  static int AdvertiseNextProtoCallback_(SSL *s,
 -                                         const unsigned char **data,
 -                                         unsigned int *len,
 -                                         void *arg);
 -  static int SelectNextProtoCallback_(SSL *s,
 -                                      unsigned char **out, unsigned char *outlen,
 -                                      const unsigned char* in,
 -                                      unsigned int inlen, void *arg);
 -#endif
 +  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void EncIn(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void ClearOut(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void ClearPending(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void EncPending(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void EncOut(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void ClearIn(const v8::FunctionCallbackInfo<v8::Value>& args);
-   static void Shutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
  
  #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
    // SNI
diff --cc src/tls_wrap.cc
index b7bce94,0000000..03ccee4
mode 100644,000000..100644
--- /dev/null
@@@ -1,708 -1,0 +1,724 @@@
-       shutdown_(false) {
 +// Copyright Joyent, Inc. and other Node contributors.
 +//
 +// Permission is hereby granted, free of charge, to any person obtaining a
 +// copy of this software and associated documentation files (the
 +// "Software"), to deal in the Software without restriction, including
 +// without limitation the rights to use, copy, modify, merge, publish,
 +// distribute, sublicense, and/or sell copies of the Software, and to permit
 +// persons to whom the Software is furnished to do so, subject to the
 +// following conditions:
 +//
 +// The above copyright notice and this permission notice shall be included
 +// in all copies or substantial portions of the Software.
 +//
 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 +// USE OR OTHER DEALINGS IN THE SOFTWARE.
 +
 +#include "tls_wrap.h"
 +#include "async-wrap.h"
 +#include "async-wrap-inl.h"
 +#include "node_buffer.h"  // Buffer
 +#include "node_crypto.h"  // SecureContext
 +#include "node_crypto_bio.h"  // NodeBIO
 +#include "node_crypto_clienthello.h"  // ClientHelloParser
 +#include "node_crypto_clienthello-inl.h"
 +#include "node_wrap.h"  // WithGenericStream
 +#include "node_counters.h"
 +#include "node_internals.h"
 +#include "util.h"
 +#include "util-inl.h"
 +
 +namespace node {
 +
 +using crypto::SSLWrap;
 +using crypto::SecureContext;
 +using v8::Boolean;
 +using v8::Context;
 +using v8::Exception;
 +using v8::Function;
 +using v8::FunctionCallbackInfo;
 +using v8::FunctionTemplate;
 +using v8::Handle;
 +using v8::HandleScope;
 +using v8::Integer;
 +using v8::Local;
 +using v8::Null;
 +using v8::Object;
 +using v8::String;
 +using v8::Value;
 +
 +static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL
 +                                 | ASN1_STRFLGS_ESC_MSB
 +                                 | XN_FLAG_SEP_MULTILINE
 +                                 | XN_FLAG_FN_SN;
 +
 +
 +TLSCallbacks::TLSCallbacks(Environment* env,
 +                           Kind kind,
 +                           Handle<Object> sc,
 +                           StreamWrapCallbacks* old)
 +    : SSLWrap<TLSCallbacks>(env, Unwrap<SecureContext>(sc), kind),
 +      StreamWrapCallbacks(old),
 +      AsyncWrap(env, env->tls_wrap_constructor_function()->NewInstance()),
 +      sc_(Unwrap<SecureContext>(sc)),
 +      sc_handle_(env->isolate(), sc),
 +      enc_in_(NULL),
 +      enc_out_(NULL),
 +      clear_in_(NULL),
 +      write_size_(0),
 +      pending_write_item_(NULL),
 +      started_(false),
 +      established_(false),
++      shutdown_(false),
++      eof_(false) {
 +  node::Wrap<TLSCallbacks>(object(), this);
 +
 +  // Initialize queue for clearIn writes
 +  QUEUE_INIT(&write_item_queue_);
 +
 +  // We've our own session callbacks
 +  SSL_CTX_sess_set_get_cb(sc_->ctx_, SSLWrap<TLSCallbacks>::GetSessionCallback);
 +  SSL_CTX_sess_set_new_cb(sc_->ctx_, SSLWrap<TLSCallbacks>::NewSessionCallback);
 +
 +  InitSSL();
 +}
 +
 +
 +TLSCallbacks::~TLSCallbacks() {
 +  enc_in_ = NULL;
 +  enc_out_ = NULL;
 +  delete clear_in_;
 +  clear_in_ = NULL;
 +
 +  sc_ = NULL;
 +  sc_handle_.Dispose();
 +  persistent().Dispose();
 +
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +  sni_context_.Dispose();
 +#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +}
 +
 +
 +void TLSCallbacks::InvokeQueued(int status) {
 +  // Empty queue - ignore call
 +  if (pending_write_item_ == NULL)
 +    return;
 +
 +  QUEUE* q = &pending_write_item_->member_;
 +  pending_write_item_ = NULL;
 +
 +  // Process old queue
 +  while (q != &write_item_queue_) {
 +    QUEUE* next = static_cast<QUEUE*>(QUEUE_NEXT(q));
 +    WriteItem* wi = CONTAINER_OF(q, WriteItem, member_);
 +    wi->cb_(&wi->w_->req_, status);
 +    delete wi;
 +    q = next;
 +  }
 +}
 +
 +
 +void TLSCallbacks::InitSSL() {
 +  // Initialize SSL
 +  enc_in_ = NodeBIO::New();
 +  enc_out_ = NodeBIO::New();
 +
 +  SSL_set_bio(ssl_, enc_in_, enc_out_);
 +
 +  // NOTE: This could be overriden in SetVerifyMode
 +  SSL_set_verify(ssl_, SSL_VERIFY_NONE, crypto::VerifyCallback);
 +
 +#ifdef SSL_MODE_RELEASE_BUFFERS
 +  long mode = SSL_get_mode(ssl_);
 +  SSL_set_mode(ssl_, mode | SSL_MODE_RELEASE_BUFFERS);
 +#endif  // SSL_MODE_RELEASE_BUFFERS
 +
 +  SSL_set_app_data(ssl_, this);
 +  SSL_set_info_callback(ssl_, SSLInfoCallback);
 +
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +  if (is_server()) {
 +    SSL_CTX_set_tlsext_servername_callback(sc_->ctx_, SelectSNIContextCallback);
 +    SSL_CTX_set_tlsext_servername_arg(sc_->ctx_, this);
 +  }
 +#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +
 +  InitNPN(sc_, this);
 +
 +  if (is_server()) {
 +    SSL_set_accept_state(ssl_);
 +  } else if (is_client()) {
 +    SSL_set_connect_state(ssl_);
 +  } else {
 +    // Unexpected
 +    abort();
 +  }
 +
 +  // Initialize ring for queud clear data
 +  clear_in_ = new NodeBIO();
 +}
 +
 +
 +void TLSCallbacks::Wrap(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope handle_scope(args.GetIsolate());
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +
 +  if (args.Length() < 1 || !args[0]->IsObject())
 +    return ThrowTypeError("First argument should be a StreamWrap instance");
 +  if (args.Length() < 2 || !args[1]->IsObject())
 +    return ThrowTypeError("Second argument should be a SecureContext instance");
 +  if (args.Length() < 3 || !args[2]->IsBoolean())
 +    return ThrowTypeError("Third argument should be boolean");
 +
 +  Local<Object> stream = args[0].As<Object>();
 +  Local<Object> sc = args[1].As<Object>();
 +  Kind kind = args[2]->IsTrue() ? SSLWrap<TLSCallbacks>::kServer :
 +                                  SSLWrap<TLSCallbacks>::kClient;
 +
 +  TLSCallbacks* callbacks = NULL;
 +  WITH_GENERIC_STREAM(env, stream, {
 +    callbacks = new TLSCallbacks(env, kind, sc, wrap->callbacks());
 +    wrap->OverrideCallbacks(callbacks);
 +  });
 +
 +  if (callbacks == NULL) {
 +    return args.GetReturnValue().SetNull();
 +  }
 +
 +  args.GetReturnValue().Set(callbacks->persistent());
 +}
 +
 +
 +void TLSCallbacks::Start(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  if (wrap->started_)
 +    return ThrowError("Already started.");
 +  wrap->started_ = true;
 +
 +  // Send ClientHello handshake
 +  assert(wrap->is_client());
 +  wrap->ClearOut();
 +  wrap->EncOut();
 +}
 +
 +
 +void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) {
 +  if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE)))
 +    return;
 +
 +  // Be compatible with older versions of OpenSSL. SSL_get_app_data() wants
 +  // a non-const SSL* in OpenSSL <= 0.9.7e.
 +  SSL* ssl = const_cast<SSL*>(ssl_);
 +  TLSCallbacks* c = static_cast<TLSCallbacks*>(SSL_get_app_data(ssl));
 +  Environment* env = c->env();
 +  HandleScope handle_scope(env->isolate());
 +  Context::Scope context_scope(env->context());
 +  Local<Object> object = c->object();
 +
 +  if (where & SSL_CB_HANDSHAKE_START) {
 +    Local<Value> callback = object->Get(env->onhandshakestart_string());
 +    if (callback->IsFunction()) {
 +      c->MakeCallback(callback.As<Function>(), 0, NULL);
 +    }
 +  }
 +
 +  if (where & SSL_CB_HANDSHAKE_DONE) {
 +    c->established_ = true;
 +    Local<Value> callback = object->Get(env->onhandshakedone_string());
 +    if (callback->IsFunction()) {
 +      c->MakeCallback(callback.As<Function>(), 0, NULL);
 +    }
 +  }
 +}
 +
 +
 +void TLSCallbacks::EncOut() {
 +  // Ignore cycling data if ClientHello wasn't yet parsed
 +  if (!hello_parser_.IsEnded())
 +    return;
 +
 +  // Write in progress
 +  if (write_size_ != 0)
 +    return;
 +
 +  // Split-off queue
 +  if (established_ && !QUEUE_EMPTY(&write_item_queue_)) {
 +    pending_write_item_ = CONTAINER_OF(QUEUE_NEXT(&write_item_queue_),
 +                                       WriteItem,
 +                                       member_);
 +    QUEUE_INIT(&write_item_queue_);
 +  }
 +
 +  // No data to write
 +  if (BIO_pending(enc_out_) == 0) {
 +    InvokeQueued(0);
 +    return;
 +  }
 +
 +  char* data[kSimultaneousBufferCount];
 +  size_t size[ARRAY_SIZE(data)];
 +  size_t count = ARRAY_SIZE(data);
 +  write_size_ = NodeBIO::FromBIO(enc_out_)->PeekMultiple(data, size, &count);
 +  assert(write_size_ != 0 && count != 0);
 +
 +  write_req_.data = this;
 +  uv_buf_t buf[ARRAY_SIZE(data)];
 +  for (size_t i = 0; i < count; i++)
 +    buf[i] = uv_buf_init(data[i], size[i]);
 +  int r = uv_write(&write_req_, wrap()->stream(), buf, count, EncOutCb);
 +
 +  // Ignore errors, this should be already handled in js
 +  if (!r) {
 +    if (wrap()->is_tcp()) {
 +      NODE_COUNT_NET_BYTES_SENT(write_size_);
 +    } else if (wrap()->is_named_pipe()) {
 +      NODE_COUNT_PIPE_BYTES_SENT(write_size_);
 +    }
 +  }
 +}
 +
 +
 +void TLSCallbacks::EncOutCb(uv_write_t* req, int status) {
 +  TLSCallbacks* callbacks = static_cast<TLSCallbacks*>(req->data);
 +  Environment* env = callbacks->env();
 +
 +  // Handle error
 +  if (status) {
 +    // Ignore errors after shutdown
 +    if (callbacks->shutdown_)
 +      return;
 +
 +    // Notify about error
 +    HandleScope handle_scope(env->isolate());
 +    Context::Scope context_scope(env->context());
 +    Local<Value> arg = String::Concat(
 +        FIXED_ONE_BYTE_STRING(node_isolate, "write cb error, status: "),
 +        Integer::New(status, node_isolate)->ToString());
 +    callbacks->MakeCallback(env->onerror_string(), 1, &arg);
 +    callbacks->InvokeQueued(status);
 +    return;
 +  }
 +
 +  // Commit
 +  NodeBIO::FromBIO(callbacks->enc_out_)->Read(NULL, callbacks->write_size_);
 +
 +  // Try writing more data
 +  callbacks->write_size_ = 0;
 +  callbacks->EncOut();
 +}
 +
 +
 +Local<Value> TLSCallbacks::GetSSLError(int status, int* err) {
 +  HandleScope scope(node_isolate);
 +
 +  *err = SSL_get_error(ssl_, status);
 +  switch (*err) {
 +    case SSL_ERROR_NONE:
 +    case SSL_ERROR_WANT_READ:
 +    case SSL_ERROR_WANT_WRITE:
 +      break;
 +    case SSL_ERROR_ZERO_RETURN:
 +      return scope.Close(FIXED_ONE_BYTE_STRING(node_isolate, "ZERO_RETURN"));
 +      break;
 +    default:
 +      {
 +        BUF_MEM* mem;
 +        BIO* bio;
 +
 +        assert(*err == SSL_ERROR_SSL || *err == SSL_ERROR_SYSCALL);
 +
 +        bio = BIO_new(BIO_s_mem());
 +        assert(bio != NULL);
 +        ERR_print_errors(bio);
 +        BIO_get_mem_ptr(bio, &mem);
 +        Local<String> message =
 +            OneByteString(node_isolate, mem->data, mem->length);
 +        Local<Value> exception = Exception::Error(message);
 +        BIO_free_all(bio);
 +
 +        return scope.Close(exception);
 +      }
 +  }
 +  return Local<Value>();
 +}
 +
 +
 +void TLSCallbacks::ClearOut() {
 +  // Ignore cycling data if ClientHello wasn't yet parsed
 +  if (!hello_parser_.IsEnded())
 +    return;
 +
 +  HandleScope handle_scope(env()->isolate());
 +  Context::Scope context_scope(env()->context());
 +
 +  assert(ssl_ != NULL);
 +
 +  char out[kClearOutChunkSize];
 +  int read;
 +  do {
 +    read = SSL_read(ssl_, out, sizeof(out));
 +    if (read > 0) {
 +      Local<Value> argv[] = {
 +        Integer::New(read, node_isolate),
 +        Buffer::New(env(), out, read)
 +      };
 +      wrap()->MakeCallback(env()->onread_string(), ARRAY_SIZE(argv), argv);
 +    }
 +  } while (read > 0);
 +
++  int flags = SSL_get_shutdown(ssl_);
++  if (!eof_ && flags & SSL_RECEIVED_SHUTDOWN) {
++    eof_ = true;
++    Local<Value> arg = Integer::New(UV_EOF, node_isolate);
++    wrap()->MakeCallback(env()->onread_string(), 1, &arg);
++  }
++
 +  if (read == -1) {
 +    int err;
 +    Handle<Value> arg = GetSSLError(read, &err);
 +
 +    if (!arg.IsEmpty()) {
 +      MakeCallback(env()->onerror_string(), 1, &arg);
 +    }
 +  }
 +}
 +
 +
 +bool TLSCallbacks::ClearIn() {
 +  // Ignore cycling data if ClientHello wasn't yet parsed
 +  if (!hello_parser_.IsEnded())
 +    return false;
 +
 +  int written = 0;
 +  while (clear_in_->Length() > 0) {
 +    size_t avail = 0;
 +    char* data = clear_in_->Peek(&avail);
 +    written = SSL_write(ssl_, data, avail);
 +    assert(written == -1 || written == static_cast<int>(avail));
 +    if (written == -1)
 +      break;
 +    clear_in_->Read(NULL, avail);
 +  }
 +
 +  // All written
 +  if (clear_in_->Length() == 0) {
 +    assert(written >= 0);
 +    return true;
 +  }
 +
 +  HandleScope handle_scope(env()->isolate());
 +  Context::Scope context_scope(env()->context());
 +
 +  // Error or partial write
 +  int err;
 +  Handle<Value> arg = GetSSLError(written, &err);
 +  if (!arg.IsEmpty()) {
 +    MakeCallback(env()->onerror_string(), 1, &arg);
 +  }
 +
 +  return false;
 +}
 +
 +
 +int TLSCallbacks::DoWrite(WriteWrap* w,
 +                          uv_buf_t* bufs,
 +                          size_t count,
 +                          uv_stream_t* send_handle,
 +                          uv_write_cb cb) {
 +  assert(send_handle == NULL);
 +
 +  // Queue callback to execute it on next tick
 +  WriteItem* wi = new WriteItem(w, cb);
 +  bool empty = true;
 +
 +  // Empty writes should not go through encryption process
 +  size_t i;
 +  for (i = 0; i < count; i++)
 +    if (bufs[i].len > 0) {
 +      empty = false;
 +      break;
 +    }
 +  if (empty) {
 +    ClearOut();
 +    // However if there any data that should be written to socket,
 +    // callback should not be invoked immediately
 +    if (BIO_pending(enc_out_) == 0)
 +      return uv_write(&w->req_, wrap()->stream(), bufs, count, cb);
 +  }
 +
 +  QUEUE_INSERT_TAIL(&write_item_queue_, &wi->member_);
 +
 +  // Write queued data
 +  if (empty) {
 +    EncOut();
 +    return 0;
 +  }
 +
 +  // Process enqueued data first
 +  if (!ClearIn()) {
 +    // If there're still data to process - enqueue current one
 +    for (i = 0; i < count; i++)
 +      clear_in_->Write(bufs[i].base, bufs[i].len);
 +    return 0;
 +  }
 +
 +  int written = 0;
 +  for (i = 0; i < count; i++) {
 +    written = SSL_write(ssl_, bufs[i].base, bufs[i].len);
 +    assert(written == -1 || written == static_cast<int>(bufs[i].len));
 +    if (written == -1)
 +      break;
 +  }
 +
 +  if (i != count) {
 +    int err;
 +    HandleScope handle_scope(env()->isolate());
 +    Context::Scope context_scope(env()->context());
 +    Handle<Value> arg = GetSSLError(written, &err);
 +    if (!arg.IsEmpty()) {
 +      MakeCallback(env()->onerror_string(), 1, &arg);
 +      return -1;
 +    }
 +
 +    // No errors, queue rest
 +    for (; i < count; i++)
 +      clear_in_->Write(bufs[i].base, bufs[i].len);
 +  }
 +
 +  // Try writing data immediately
 +  EncOut();
 +
 +  return 0;
 +}
 +
 +
 +void TLSCallbacks::AfterWrite(WriteWrap* w) {
 +  // Intentionally empty
 +}
 +
 +
 +void TLSCallbacks::DoAlloc(uv_handle_t* handle,
 +                           size_t suggested_size,
 +                           uv_buf_t* buf) {
 +  buf->base = NodeBIO::FromBIO(enc_in_)->PeekWritable(&suggested_size);
 +  buf->len = suggested_size;
 +}
 +
 +
 +void TLSCallbacks::DoRead(uv_stream_t* handle,
 +                          ssize_t nread,
 +                          const uv_buf_t* buf,
 +                          uv_handle_type pending) {
 +  if (nread < 0)  {
 +    // Error should be emitted only after all data was read
 +    ClearOut();
++
++    // Ignore EOF if received close_notify
++    if (nread == UV_EOF) {
++      if (eof_)
++        return;
++      eof_ = true;
++    }
++
 +    HandleScope handle_scope(env()->isolate());
 +    Context::Scope context_scope(env()->context());
 +    Local<Value> arg = Integer::New(nread, node_isolate);
 +    wrap()->MakeCallback(env()->onread_string(), 1, &arg);
 +    return;
 +  }
 +
 +  // Only client connections can receive data
 +  assert(ssl_ != NULL);
 +
 +  // Commit read data
 +  NodeBIO* enc_in = NodeBIO::FromBIO(enc_in_);
 +  enc_in->Commit(nread);
 +
 +  // Parse ClientHello first
 +  if (!hello_parser_.IsEnded()) {
 +    size_t avail = 0;
 +    uint8_t* data = reinterpret_cast<uint8_t*>(enc_in->Peek(&avail));
 +    assert(avail == 0 || data != NULL);
 +    return hello_parser_.Parse(data, avail);
 +  }
 +
 +  // Cycle OpenSSL's state
 +  Cycle();
 +}
 +
 +
 +int TLSCallbacks::DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb) {
 +  if (SSL_shutdown(ssl_) == 0)
 +    SSL_shutdown(ssl_);
 +  shutdown_ = true;
 +  EncOut();
 +  return StreamWrapCallbacks::DoShutdown(req_wrap, cb);
 +}
 +
 +
 +void TLSCallbacks::SetVerifyMode(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  if (args.Length() < 2 || !args[0]->IsBoolean() || !args[1]->IsBoolean())
 +    return ThrowTypeError("Bad arguments, expected two booleans");
 +
 +  int verify_mode;
 +  if (wrap->is_server()) {
 +    bool request_cert = args[0]->IsTrue();
 +    if (!request_cert) {
 +      // Note reject_unauthorized ignored.
 +      verify_mode = SSL_VERIFY_NONE;
 +    } else {
 +      bool reject_unauthorized = args[1]->IsTrue();
 +      verify_mode = SSL_VERIFY_PEER;
 +      if (reject_unauthorized)
 +        verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
 +    }
 +  } else {
 +    // Note request_cert and reject_unauthorized are ignored for clients.
 +    verify_mode = SSL_VERIFY_NONE;
 +  }
 +
 +  // Always allow a connection. We'll reject in javascript.
 +  SSL_set_verify(wrap->ssl_, verify_mode, crypto::VerifyCallback);
 +}
 +
 +
 +void TLSCallbacks::EnableSessionCallbacks(
 +    const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  wrap->enable_session_callbacks();
 +  EnableHelloParser(args);
 +}
 +
 +
 +void TLSCallbacks::EnableHelloParser(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  wrap->hello_parser_.Start(SSLWrap<TLSCallbacks>::OnClientHello,
 +                            OnClientHelloParseEnd,
 +                            wrap);
 +}
 +
 +
 +void TLSCallbacks::OnClientHelloParseEnd(void* arg) {
 +  TLSCallbacks* c = static_cast<TLSCallbacks*>(arg);
 +  c->Cycle();
 +}
 +
 +
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +void TLSCallbacks::GetServername(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  const char* servername = SSL_get_servername(wrap->ssl_,
 +                                              TLSEXT_NAMETYPE_host_name);
 +  if (servername != NULL) {
 +    args.GetReturnValue().Set(OneByteString(node_isolate, servername));
 +  } else {
 +    args.GetReturnValue().Set(false);
 +  }
 +}
 +
 +
 +void TLSCallbacks::SetServername(const FunctionCallbackInfo<Value>& args) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.This());
 +
 +  if (args.Length() < 1 || !args[0]->IsString())
 +    return ThrowTypeError("First argument should be a string");
 +
 +  if (wrap->started_)
 +    return ThrowError("Already started.");
 +
 +  if (!wrap->is_client())
 +    return;
 +
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +  String::Utf8Value servername(args[0].As<String>());
 +  SSL_set_tlsext_host_name(wrap->ssl_, *servername);
 +#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +}
 +
 +
 +int TLSCallbacks::SelectSNIContextCallback(SSL* s, int* ad, void* arg) {
 +  HandleScope scope(node_isolate);
 +
 +  TLSCallbacks* p = static_cast<TLSCallbacks*>(arg);
 +  Environment* env = p->env();
 +
 +  const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
 +
 +  if (servername != NULL) {
 +    // Call the SNI callback and use its return value as context
 +    Local<Object> object = p->object();
 +    Local<Value> ctx = object->Get(env->sni_context_string());
 +
 +    if (!ctx->IsObject())
 +      return SSL_TLSEXT_ERR_NOACK;
 +
 +    p->sni_context_.Dispose();
 +    p->sni_context_.Reset(node_isolate, ctx);
 +
 +    SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>());
 +    InitNPN(sc, p);
 +    SSL_set_SSL_CTX(s, sc->ctx_);
 +  }
 +
 +  return SSL_TLSEXT_ERR_OK;
 +}
 +#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +
 +
 +void TLSCallbacks::Initialize(Handle<Object> target,
 +                              Handle<Value> unused,
 +                              Handle<Context> context) {
 +  Environment* env = Environment::GetCurrent(context);
 +
 +  NODE_SET_METHOD(target, "wrap", TLSCallbacks::Wrap);
 +
 +  Local<FunctionTemplate> t = FunctionTemplate::New();
 +  t->InstanceTemplate()->SetInternalFieldCount(1);
 +  t->SetClassName(FIXED_ONE_BYTE_STRING(node_isolate, "TLSWrap"));
 +
 +  NODE_SET_PROTOTYPE_METHOD(t, "start", Start);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setVerifyMode", SetVerifyMode);
 +  NODE_SET_PROTOTYPE_METHOD(t,
 +                            "enableSessionCallbacks",
 +                            EnableSessionCallbacks);
 +  NODE_SET_PROTOTYPE_METHOD(t,
 +                            "enableHelloParser",
 +                            EnableHelloParser);
 +
 +  SSLWrap<TLSCallbacks>::AddMethods(t);
 +
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +  NODE_SET_PROTOTYPE_METHOD(t, "getServername", GetServername);
 +  NODE_SET_PROTOTYPE_METHOD(t, "setServername", SetServername);
 +#endif  // SSL_CRT_SET_TLSEXT_SERVERNAME_CB
 +
 +  env->set_tls_wrap_constructor_function(t->GetFunction());
 +}
 +
 +}  // namespace node
 +
 +NODE_MODULE_CONTEXT_AWARE(node_tls_wrap, node::TLSCallbacks::Initialize)
diff --cc src/tls_wrap.h
index 9e42d4b,0000000..2b10e09
mode 100644,000000..100644
--- /dev/null
@@@ -1,147 -1,0 +1,151 @@@
-   static const int kClearOutChunkSize = 16384;  // 16kb
 +// Copyright Joyent, Inc. and other Node contributors.
 +//
 +// Permission is hereby granted, free of charge, to any person obtaining a
 +// copy of this software and associated documentation files (the
 +// "Software"), to deal in the Software without restriction, including
 +// without limitation the rights to use, copy, modify, merge, publish,
 +// distribute, sublicense, and/or sell copies of the Software, and to permit
 +// persons to whom the Software is furnished to do so, subject to the
 +// following conditions:
 +//
 +// The above copyright notice and this permission notice shall be included
 +// in all copies or substantial portions of the Software.
 +//
 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 +// USE OR OTHER DEALINGS IN THE SOFTWARE.
 +
 +#ifndef SRC_TLS_WRAP_H_
 +#define SRC_TLS_WRAP_H_
 +
 +#include "node.h"
 +#include "node_crypto.h"  // SSLWrap
 +
 +#include "async-wrap.h"
 +#include "env.h"
 +#include "queue.h"
 +#include "stream_wrap.h"
 +#include "v8.h"
 +
 +#include <openssl/ssl.h>
 +
 +namespace node {
 +
 +// Forward-declarations
 +class NodeBIO;
 +class WriteWrap;
 +namespace crypto {
 +  class SecureContext;
 +}
 +
 +class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
 +                     public StreamWrapCallbacks,
 +                     public AsyncWrap {
 + public:
 +  static void Initialize(v8::Handle<v8::Object> target,
 +                         v8::Handle<v8::Value> unused,
 +                         v8::Handle<v8::Context> context);
 +
 +  int DoWrite(WriteWrap* w,
 +              uv_buf_t* bufs,
 +              size_t count,
 +              uv_stream_t* send_handle,
 +              uv_write_cb cb);
 +  void AfterWrite(WriteWrap* w);
 +  void DoAlloc(uv_handle_t* handle,
 +               size_t suggested_size,
 +               uv_buf_t* buf);
 +  void DoRead(uv_stream_t* handle,
 +              ssize_t nread,
 +              const uv_buf_t* buf,
 +              uv_handle_type pending);
 +  int DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb);
 +
 + protected:
++  static const int kClearOutChunkSize = 1024;
 +
 +  // Maximum number of buffers passed to uv_write()
 +  static const int kSimultaneousBufferCount = 10;
 +
 +  // Write callback queue's item
 +  class WriteItem {
 +   public:
 +    WriteItem(WriteWrap* w, uv_write_cb cb) : w_(w), cb_(cb) {
 +    }
 +    ~WriteItem() {
 +      w_ = NULL;
 +      cb_ = NULL;
 +    }
 +
 +    WriteWrap* w_;
 +    uv_write_cb cb_;
 +    QUEUE member_;
 +  };
 +
 +  TLSCallbacks(Environment* env,
 +               Kind kind,
 +               v8::Handle<v8::Object> sc,
 +               StreamWrapCallbacks* old);
 +  ~TLSCallbacks();
 +
 +  static void SSLInfoCallback(const SSL* ssl_, int where, int ret);
 +  void InitSSL();
 +  void EncOut();
 +  static void EncOutCb(uv_write_t* req, int status);
 +  bool ClearIn();
 +  void ClearOut();
 +  void InvokeQueued(int status);
 +
 +  inline void Cycle() {
 +    ClearIn();
 +    ClearOut();
 +    EncOut();
 +  }
 +
 +  v8::Local<v8::Value> GetSSLError(int status, int* err);
 +  static void OnClientHelloParseEnd(void* arg);
 +
 +  static void Wrap(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void SetVerifyMode(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void EnableSessionCallbacks(
 +      const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void EnableHelloParser(
 +      const v8::FunctionCallbackInfo<v8::Value>& args);
 +
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +  static void GetServername(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static void SetServername(const v8::FunctionCallbackInfo<v8::Value>& args);
 +  static int SelectSNIContextCallback(SSL* s, int* ad, void* arg);
 +#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +
 +  crypto::SecureContext* sc_;
 +  v8::Persistent<v8::Object> sc_handle_;
 +  BIO* enc_in_;
 +  BIO* enc_out_;
 +  NodeBIO* clear_in_;
 +  uv_write_t write_req_;
 +  size_t write_size_;
 +  size_t write_queue_size_;
 +  QUEUE write_item_queue_;
 +  WriteItem* pending_write_item_;
 +  bool started_;
 +  bool established_;
 +  bool shutdown_;
 +
++  // If true - delivered EOF to the js-land, either after `close_notify`, or
++  // after the `UV_EOF` on socket.
++  bool eof_;
++
 +#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +  v8::Persistent<v8::Value> sni_context_;
 +#endif  // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
 +};
 +
 +}  // namespace node
 +
 +#endif  // SRC_TLS_WRAP_H_
index 0000000,95fa653..3c6bf53
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,58 +1,58 @@@
 -  if (c.pair.ssl.shutdown() !== 1)
 -    c.pair.ssl.shutdown();
+ // Copyright Joyent, Inc. and other Node contributors.
+ //
+ // Permission is hereby granted, free of charge, to any person obtaining a
+ // copy of this software and associated documentation files (the
+ // "Software"), to deal in the Software without restriction, including
+ // without limitation the rights to use, copy, modify, merge, publish,
+ // distribute, sublicense, and/or sell copies of the Software, and to permit
+ // persons to whom the Software is furnished to do so, subject to the
+ // following conditions:
+ //
+ // The above copyright notice and this permission notice shall be included
+ // in all copies or substantial portions of the Software.
+ //
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
+ if (!process.versions.openssl) {
+   console.error('Skipping because node compiled without OpenSSL.');
+   process.exit(0);
+ }
+ var assert = require('assert');
+ var fs = require('fs');
+ var net = require('net');
+ var tls = require('tls');
+ var common = require('../common');
+ var ended = 0;
+ var server = tls.createServer({
+   key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'),
+   cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem')
+ }, function(c) {
+   // Send close-notify without shutting down TCP socket
++  if (c.ssl.shutdown() !== 1)
++    c.ssl.shutdown();
+ }).listen(common.PORT, function() {
+   var c = tls.connect(common.PORT, {
+     rejectUnauthorized: false
+   }, function() {
+     // Ensure that we receive 'end' event anyway
+     c.on('end', function() {
+       ended++;
+       c.destroy();
+       server.close();
+     });
+   });
+ });
+ process.on('exit', function() {
+   assert.equal(ended, 1);
+ });