diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index ef1c7fc32bdd9f..d67a4ffa242e57 100644 --- a/doc/api/tls.markdown +++ b/doc/api/tls.markdown @@ -166,19 +166,6 @@ This is an encrypted stream. A proxy to the underlying socket's bytesWritten accessor, this will return the total bytes written to the socket, *including the TLS overhead*. -## Class: SecurePair - -Returned by tls.createSecurePair. - -### Event: 'secure' - -This event is emitted from the SecurePair once the pair has successfully -established a secure connection. - -As with checking for the server [`secureConnection`](#event-secureconnection) -event, `pair.cleartext.authorized` should be inspected to confirm whether the -certificate used is properly authorized. - ## Class: tls.Server This class is a subclass of `net.Server` and has the same methods on it. @@ -379,9 +366,9 @@ Construct a new TLSSocket object from an existing TCP socket. - `server`: An optional [`net.Server`][] instance - - `requestCert`: Optional, see [`tls.createSecurePair()`][] + - `requestCert`: Optional, see [`tls.createServer()`][] - - `rejectUnauthorized`: Optional, see [`tls.createSecurePair()`][] + - `rejectUnauthorized`: Optional, see [`tls.createServer()`][] - `NPNProtocols`: Optional, see [`tls.createServer()`][] @@ -743,33 +730,6 @@ If no 'CA' details are given, then Node.js will use the default publicly trusted list of CAs as given in . -## tls.createSecurePair([context][, isServer][, requestCert][, rejectUnauthorized][, options]) - -Creates a new secure pair object with two streams, one of which reads and writes -the encrypted data and the other of which reads and writes the cleartext data. -Generally, the encrypted stream is piped to/from an incoming encrypted data -stream and the cleartext one is used as a replacement for the initial encrypted -stream. - - - `credentials`: A secure context object from `tls.createSecureContext( ... )`. - - - `isServer`: A boolean indicating whether this TLS connection should be - opened as a server or a client. - - - `requestCert`: A boolean indicating whether a server should request a - certificate from a connecting client. Only applies to server connections. - - - `rejectUnauthorized`: A boolean indicating whether a server should - automatically reject clients with invalid certificates. Only applies to - servers with `requestCert` enabled. - - - `options`: An object with common SSL options. See [`tls.TLSSocket`][]. - -`tls.createSecurePair()` returns a SecurePair object with `cleartext` and -`encrypted` stream properties. - -NOTE: `cleartext` has the same API as [`tls.TLSSocket`][] - ## tls.createServer(options[, secureConnectionListener]) Creates a new [tls.Server][]. The `connectionListener` argument is @@ -983,7 +943,6 @@ console.log(ciphers); // ['AES128-SHA', 'AES256-SHA', ...] [BEAST attacks]: https://blog.ivanristic.com/2011/10/mitigating-the-beast-attack-on-tls.html [`crypto.getCurves()`]: crypto.html#crypto_crypto_getcurves [`tls.createServer()`]: #tls_tls_createserver_options_secureconnectionlistener -[`tls.createSecurePair()`]: #tls_tls_createsecurepair_context_isserver_requestcert_rejectunauthorized_options [`tls.TLSSocket`]: #tls_class_tls_tlssocket [`net.Server`]: net.html#net_class_net_server [`net.Socket`]: net.html#net_class_net_socket diff --git a/lib/_tls_legacy.js b/lib/_tls_legacy.js deleted file mode 100644 index 456679ff0d2aa4..00000000000000 --- a/lib/_tls_legacy.js +++ /dev/null @@ -1,907 +0,0 @@ -'use strict'; - -require('internal/util').assertCrypto(exports); - -const assert = require('assert'); -const EventEmitter = require('events'); -const stream = require('stream'); -const tls = require('tls'); -const util = require('util'); -const common = require('_tls_common'); -const debug = util.debuglog('tls-legacy'); -const Buffer = require('buffer').Buffer; -const Timer = process.binding('timer_wrap').Timer; -const Connection = process.binding('crypto').Connection; - -function SlabBuffer() { - this.create(); -} - - -SlabBuffer.prototype.create = function create() { - this.isFull = false; - this.pool = Buffer.allocUnsafe(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 (size !== null) 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 (slabBuffer === null) 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.shutdownSSL() !== 1) { - if (this.pair.ssl && this.pair.ssl.error) - return this.pair.error(); - - this.pair.ssl.shutdownSSL(); - } - - 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(this._pending === null); - - // 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 ALPN, 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(this._sslOutCb === null); - 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() { - const data = this._pending; - const encoding = this._pendingEncoding; - const 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; - const start = this._buffer.offset; - var 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 ALPN, 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 (this._pending !== null) this._writePending(); - if (this._opposite._pending !== null) this._opposite._writePending(); - - if (bytesRead === 0) { - // EOF when cleartext has finished and we have nothing to read - if (this._opposite._finished && this._internallyPendingBytes() === 0 || - this.pair.ssl && this.pair.ssl.receivedShutdown) { - // Perform graceful shutdown - this._done(); - - // No half-open, sorry! - 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(detailed) { - if (this.pair.ssl) { - return common.translatePeerCertificate( - this.pair.ssl.getPeerCertificate(detailed)); - } - - 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 (this._pending !== null) 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; - var finish = function() { - 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(); - - process.nextTick(destroyNT, this, err ? true : false); -}; - - -function destroyNT(self, hadErr) { - // Force EOF - self.push(null); - - // Emit 'close' event - self.emit('close', hadErr); -} - - -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__('remoteFamily', function() { - return this.socket && this.socket.remoteFamily; -}); - -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) { - const self = this; - var once = false; - - this._resumingSession = true; - function callback(err, session) { - if (once) return; - once = true; - - if (err) return self.socket.destroy(err); - - setImmediate(function() { - 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; - - var self = this; - var once = false; - - if (!self.server.emit('newSession', key, session, done)) - done(); - - function done() { - if (once) - return; - once = true; - - if (self.ssl) - self.ssl.newSessionDone(); - } -} - - -function onocspresponse(resp) { - this.emit('OCSPResponse', resp); -} - - -/** - * Provides a pair of streams to do encrypted communication. - */ - -function SecurePair(context, isServer, requestCert, rejectUnauthorized, - options) { - if (!(this instanceof SecurePair)) { - return new SecurePair(context, - isServer, - requestCert, - rejectUnauthorized, - options); - } - - options || (options = {}); - - 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 (!context) { - this.credentials = tls.createSecureContext(); - } else { - this.credentials = context; - } - - 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.call(this); - this.ssl.onhandshakedone = () => onhandshakedone.call(this); - this.ssl.onclienthello = (hello) => onclienthello.call(this, hello); - this.ssl.onnewsession = - (key, session) => onnewsession.call(this, key, session); - this.ssl.lastHandshakeTime = 0; - this.ssl.handshakes = 0; - } else { - this.ssl.onocspresponse = (resp) => onocspresponse.call(this, resp); - } - - 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; - } - - if (process.features.tls_alpn && options.ALPNProtocols) { - // keep reference in secureContext not to be GC-ed - this.ssl._secureContext.alpnBuffer = options.ALPNProtocols; - this.ssl.setALPNrotocols(this.ssl._secureContext.alpnBuffer); - this.alpnProtocol = 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(securePairNT, this, options); -} - -util.inherits(SecurePair, EventEmitter); - -function securePairNT(self, options) { - /* The Connection may be destroyed by an abort call */ - if (self.ssl) { - self.ssl.start(); - - if (options.requestOCSP) - self.ssl.requestOCSP(); - - /* In case of cipher suite failures - SSL_accept/SSL_connect may fail */ - if (self.ssl && self.ssl.error) - self.error(); - } -} - - -exports.createSecurePair = function(context, - isServer, - requestCert, - rejectUnauthorized, - options) { - var pair = new SecurePair(context, - isServer, - requestCert, - rejectUnauthorized, - options); - 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_alpn) { - this.alpnProtocol = this.ssl.getALPNNegotiatedProtocol(); - } - - 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(pipeCloseNT, pair, socket); - }); - - 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; -}; - - -function pipeCloseNT(pair, socket) { - // Encrypted should be unpiped from socket to prevent possible - // write after destroy. - pair.encrypted.unpipe(socket); - socket.destroySoon(); -} diff --git a/lib/tls.js b/lib/tls.js index 8699207a24a586..35fc5c14d2a48a 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -239,4 +239,3 @@ exports.TLSSocket = require('_tls_wrap').TLSSocket; exports.Server = require('_tls_wrap').Server; exports.createServer = require('_tls_wrap').createServer; exports.connect = require('_tls_wrap').connect; -exports.createSecurePair = require('_tls_legacy').createSecurePair; diff --git a/node.gyp b/node.gyp index a8747a639e7047..e636a68665a3ad 100644 --- a/node.gyp +++ b/node.gyp @@ -62,7 +62,6 @@ 'lib/timers.js', 'lib/tls.js', 'lib/_tls_common.js', - 'lib/_tls_legacy.js', 'lib/_tls_wrap.js', 'lib/tty.js', 'lib/url.js', diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 3dd3b4424cc8eb..7e4c14f41e9e45 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -13,11 +13,6 @@ #include "util.h" #include "util-inl.h" #include "v8.h" -// CNNIC Hash WhiteList is taken from -// https://hg.mozilla.org/mozilla-central/raw-file/98820360ab66/security/ -// certverifier/CNNICHashWhitelist.inc -#include "CNNICHashWhitelist.inc" - #include #include // INT_MAX #include @@ -97,34 +92,6 @@ using v8::V8; using v8::Value; -// Subject DER of CNNIC ROOT CA and CNNIC EV ROOT CA are taken from -// https://hg.mozilla.org/mozilla-central/file/98820360ab66/security/ -// certverifier/NSSCertDBTrustDomain.cpp#l672 -// C = CN, O = CNNIC, CN = CNNIC ROOT -static const uint8_t CNNIC_ROOT_CA_SUBJECT_DATA[] = - "\x30\x32\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x0E\x30" - "\x0C\x06\x03\x55\x04\x0A\x13\x05\x43\x4E\x4E\x49\x43\x31\x13\x30\x11\x06" - "\x03\x55\x04\x03\x13\x0A\x43\x4E\x4E\x49\x43\x20\x52\x4F\x4F\x54"; -static const uint8_t* cnnic_p = CNNIC_ROOT_CA_SUBJECT_DATA; -static X509_NAME* cnnic_name = - d2i_X509_NAME(nullptr, &cnnic_p, sizeof(CNNIC_ROOT_CA_SUBJECT_DATA)-1); - -// C = CN, O = China Internet Network Information Center, CN = China -// Internet Network Information Center EV Certificates Root -static const uint8_t CNNIC_EV_ROOT_CA_SUBJECT_DATA[] = - "\x30\x81\x8A\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x32" - "\x30\x30\x06\x03\x55\x04\x0A\x0C\x29\x43\x68\x69\x6E\x61\x20\x49\x6E\x74" - "\x65\x72\x6E\x65\x74\x20\x4E\x65\x74\x77\x6F\x72\x6B\x20\x49\x6E\x66\x6F" - "\x72\x6D\x61\x74\x69\x6F\x6E\x20\x43\x65\x6E\x74\x65\x72\x31\x47\x30\x45" - "\x06\x03\x55\x04\x03\x0C\x3E\x43\x68\x69\x6E\x61\x20\x49\x6E\x74\x65\x72" - "\x6E\x65\x74\x20\x4E\x65\x74\x77\x6F\x72\x6B\x20\x49\x6E\x66\x6F\x72\x6D" - "\x61\x74\x69\x6F\x6E\x20\x43\x65\x6E\x74\x65\x72\x20\x45\x56\x20\x43\x65" - "\x72\x74\x69\x66\x69\x63\x61\x74\x65\x73\x20\x52\x6F\x6F\x74"; -static const uint8_t* cnnic_ev_p = CNNIC_EV_ROOT_CA_SUBJECT_DATA; -static X509_NAME *cnnic_ev_name = - d2i_X509_NAME(nullptr, &cnnic_ev_p, - sizeof(CNNIC_EV_ROOT_CA_SUBJECT_DATA)-1); - static uv_mutex_t* locks; const char* const root_certs[] = { @@ -416,8 +383,8 @@ void SecureContext::Init(const FunctionCallbackInfo& args) { SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR); - SSL_CTX_sess_set_get_cb(sc->ctx_, SSLWrap::GetSessionCallback); - SSL_CTX_sess_set_new_cb(sc->ctx_, SSLWrap::NewSessionCallback); + SSL_CTX_sess_set_get_cb(sc->ctx_, SSLWrap::GetSessionCallback); + SSL_CTX_sess_set_new_cb(sc->ctx_, SSLWrap::NewSessionCallback); sc->ca_store_ = nullptr; } @@ -2444,682 +2411,6 @@ int SSLWrap::SetCACerts(SecureContext* sc) { } -void Connection::OnClientHelloParseEnd(void* arg) { - Connection* conn = static_cast(arg); - - // Write all accumulated data - int r = BIO_write(conn->bio_read_, - reinterpret_cast(conn->hello_data_), - conn->hello_offset_); - conn->HandleBIOError(conn->bio_read_, "BIO_write", r); - conn->SetShutdownFlags(); -} - - -#ifdef SSL_PRINT_DEBUG -# define DEBUG_PRINT(...) fprintf (stderr, __VA_ARGS__) -#else -# define DEBUG_PRINT(...) -#endif - - -int Connection::HandleBIOError(BIO *bio, const char* func, int rv) { - if (rv >= 0) - return rv; - - int retry = BIO_should_retry(bio); - (void) retry; // unused if !defined(SSL_PRINT_DEBUG) - - if (BIO_should_write(bio)) { - DEBUG_PRINT("[%p] BIO: %s want write. should retry %d\n", - ssl_, - func, - retry); - return 0; - - } else if (BIO_should_read(bio)) { - DEBUG_PRINT("[%p] BIO: %s want read. should retry %d\n", ssl_, func, retry); - return 0; - - } else { - char ssl_error_buf[512]; - ERR_error_string_n(rv, ssl_error_buf, sizeof(ssl_error_buf)); - - HandleScope scope(ssl_env()->isolate()); - Local exception = - Exception::Error(OneByteString(ssl_env()->isolate(), ssl_error_buf)); - object()->Set(ssl_env()->error_string(), exception); - - DEBUG_PRINT("[%p] BIO: %s failed: (%d) %s\n", - ssl_, - func, - rv, - ssl_error_buf); - - return rv; - } - - return 0; -} - - -int Connection::HandleSSLError(const char* func, - int rv, - ZeroStatus zs, - SyscallStatus ss) { - ClearErrorOnReturn clear_error_on_return; - (void) &clear_error_on_return; // Silence unused variable warning. - - if (rv > 0) - return rv; - if (rv == 0 && zs == kZeroIsNotAnError) - return rv; - - int err = SSL_get_error(ssl_, rv); - - if (err == SSL_ERROR_NONE) { - return 0; - - } else if (err == SSL_ERROR_WANT_WRITE) { - DEBUG_PRINT("[%p] SSL: %s want write\n", ssl_, func); - return 0; - - } else if (err == SSL_ERROR_WANT_READ) { - DEBUG_PRINT("[%p] SSL: %s want read\n", ssl_, func); - return 0; - - } else if (err == SSL_ERROR_WANT_X509_LOOKUP) { - DEBUG_PRINT("[%p] SSL: %s want x509 lookup\n", ssl_, func); - return 0; - - } else if (err == SSL_ERROR_ZERO_RETURN) { - HandleScope scope(ssl_env()->isolate()); - - Local exception = - Exception::Error(ssl_env()->zero_return_string()); - object()->Set(ssl_env()->error_string(), exception); - return rv; - - } else if (err == SSL_ERROR_SYSCALL && ss == kIgnoreSyscall) { - return 0; - - } else { - HandleScope scope(ssl_env()->isolate()); - BUF_MEM* mem; - BIO *bio; - - CHECK(err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL); - - // XXX We need to drain the error queue for this thread or else OpenSSL - // has the possibility of blocking connections? This problem is not well - // understood. And we should be somehow propagating these errors up - // into JavaScript. There is no test which demonstrates this problem. - // https://github.com/joyent/node/issues/1719 - bio = BIO_new(BIO_s_mem()); - if (bio != nullptr) { - ERR_print_errors(bio); - BIO_get_mem_ptr(bio, &mem); - Local exception = Exception::Error( - OneByteString(ssl_env()->isolate(), - mem->data, - mem->length)); - object()->Set(ssl_env()->error_string(), exception); - BIO_free_all(bio); - } - - return rv; - } - - return 0; -} - - -void Connection::ClearError() { -#ifndef NDEBUG - HandleScope scope(ssl_env()->isolate()); - - // We should clear the error in JS-land - Local error_key = ssl_env()->error_string(); - Local error = object()->Get(error_key); - CHECK_EQ(error->BooleanValue(), false); -#endif // NDEBUG -} - - -void Connection::SetShutdownFlags() { - HandleScope scope(ssl_env()->isolate()); - - int flags = SSL_get_shutdown(ssl_); - - if (flags & SSL_SENT_SHUTDOWN) { - Local sent_shutdown_key = ssl_env()->sent_shutdown_string(); - object()->Set(sent_shutdown_key, True(ssl_env()->isolate())); - } - - if (flags & SSL_RECEIVED_SHUTDOWN) { - Local received_shutdown_key = ssl_env()->received_shutdown_string(); - object()->Set(received_shutdown_key, True(ssl_env()->isolate())); - } -} - - -void Connection::NewSessionDoneCb() { - HandleScope scope(env()->isolate()); - - MakeCallback(env()->onnewsessiondone_string(), 0, nullptr); -} - - -void Connection::Initialize(Environment* env, Local target) { - Local t = env->NewFunctionTemplate(Connection::New); - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Connection")); - - env->SetProtoMethod(t, "encIn", Connection::EncIn); - env->SetProtoMethod(t, "clearOut", Connection::ClearOut); - env->SetProtoMethod(t, "clearIn", Connection::ClearIn); - env->SetProtoMethod(t, "encOut", Connection::EncOut); - env->SetProtoMethod(t, "clearPending", Connection::ClearPending); - env->SetProtoMethod(t, "encPending", Connection::EncPending); - env->SetProtoMethod(t, "start", Connection::Start); - env->SetProtoMethod(t, "close", Connection::Close); - - SSLWrap::AddMethods(env, t); - - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - env->SetProtoMethod(t, "getServername", Connection::GetServername); - env->SetProtoMethod(t, "setSNICallback", Connection::SetSNICallback); -#endif - - target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Connection"), - t->GetFunction()); -} - - -inline int compar(const void* a, const void* b) { - return memcmp(a, b, CNNIC_WHITELIST_HASH_LEN); -} - - -inline int IsSelfSigned(X509* cert) { - return X509_NAME_cmp(X509_get_subject_name(cert), - X509_get_issuer_name(cert)) == 0; -} - - -inline X509* FindRoot(STACK_OF(X509)* sk) { - for (int i = 0; i < sk_X509_num(sk); i++) { - X509* cert = sk_X509_value(sk, i); - if (IsSelfSigned(cert)) - return cert; - } - return nullptr; -} - - -// Whitelist check for certs issued by CNNIC. See -// https://blog.mozilla.org/security/2015/04/02 -// /distrusting-new-cnnic-certificates/ -inline CheckResult CheckWhitelistedServerCert(X509_STORE_CTX* ctx) { - unsigned char hash[CNNIC_WHITELIST_HASH_LEN]; - unsigned int hashlen = CNNIC_WHITELIST_HASH_LEN; - - STACK_OF(X509)* chain = X509_STORE_CTX_get1_chain(ctx); - CHECK_NE(chain, nullptr); - CHECK_GT(sk_X509_num(chain), 0); - - // Take the last cert as root at the first time. - X509* root_cert = sk_X509_value(chain, sk_X509_num(chain)-1); - X509_NAME* root_name = X509_get_subject_name(root_cert); - - if (!IsSelfSigned(root_cert)) { - root_cert = FindRoot(chain); - CHECK_NE(root_cert, nullptr); - root_name = X509_get_subject_name(root_cert); - } - - // When the cert is issued from either CNNNIC ROOT CA or CNNNIC EV - // ROOT CA, check a hash of its leaf cert if it is in the whitelist. - if (X509_NAME_cmp(root_name, cnnic_name) == 0 || - X509_NAME_cmp(root_name, cnnic_ev_name) == 0) { - X509* leaf_cert = sk_X509_value(chain, 0); - int ret = X509_digest(leaf_cert, EVP_sha256(), hash, - &hashlen); - CHECK(ret); - - void* result = bsearch(hash, WhitelistedCNNICHashes, - ARRAY_SIZE(WhitelistedCNNICHashes), - CNNIC_WHITELIST_HASH_LEN, compar); - if (result == nullptr) { - sk_X509_pop_free(chain, X509_free); - return CHECK_CERT_REVOKED; - } - } - - sk_X509_pop_free(chain, X509_free); - return CHECK_OK; -} - - -inline int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) { - // Failure on verification of the cert is handled in - // Connection::VerifyError. - if (preverify_ok == 0 || X509_STORE_CTX_get_error(ctx) != X509_V_OK) - return 1; - - // Server does not need to check the whitelist. - SSL* ssl = static_cast( - X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); - - if (SSL_is_server(ssl)) - return 1; - - // Client needs to check if the server cert is listed in the - // whitelist when it is issued by the specific rootCAs. - CheckResult ret = CheckWhitelistedServerCert(ctx); - if (ret == CHECK_CERT_REVOKED) - X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED); - - return ret; -} - - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB -int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) { - Connection* conn = static_cast(SSL_get_app_data(s)); - Environment* env = conn->env(); - HandleScope scope(env->isolate()); - - const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); - - if (servername) { - conn->servername_.Reset(env->isolate(), - OneByteString(env->isolate(), servername)); - - // Call the SNI callback and use its return value as context - if (!conn->sniObject_.IsEmpty()) { - conn->sni_context_.Reset(); - - Local sni_obj = PersistentToLocal(env->isolate(), - conn->sniObject_); - - Local arg = PersistentToLocal(env->isolate(), conn->servername_); - Local ret = node::MakeCallback(env->isolate(), - sni_obj, - env->onselect_string(), - 1, - &arg); - - // If ret is SecureContext - Local secure_context_constructor_template = - env->secure_context_constructor_template(); - if (secure_context_constructor_template->HasInstance(ret)) { - conn->sni_context_.Reset(env->isolate(), ret); - SecureContext* sc = Unwrap(ret.As()); - conn->SetSNIContext(sc); - } else { - return SSL_TLSEXT_ERR_NOACK; - } - } - } - - return SSL_TLSEXT_ERR_OK; -} -#endif - -void Connection::New(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - if (args.Length() < 1 || !args[0]->IsObject()) { - env->ThrowError("First argument must be a tls module SecureContext"); - return; - } - - SecureContext* sc = Unwrap(args[0]->ToObject(env->isolate())); - - bool is_server = args[1]->BooleanValue(); - - SSLWrap::Kind kind = - is_server ? SSLWrap::kServer : SSLWrap::kClient; - Connection* conn = new Connection(env, args.This(), sc, kind); - conn->bio_read_ = NodeBIO::New(); - conn->bio_write_ = NodeBIO::New(); - - SSL_set_app_data(conn->ssl_, conn); - - if (is_server) - SSL_set_info_callback(conn->ssl_, SSLInfoCallback); - - InitNPN(sc); - - SSL_set_cert_cb(conn->ssl_, SSLWrap::SSLCertCallback, conn); - -#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 node::Utf8Value servername(env->isolate(), args[2]); - SSL_set_tlsext_host_name(conn->ssl_, *servername); - } -#endif - - SSL_set_bio(conn->ssl_, conn->bio_read_, conn->bio_write_); - -#ifdef SSL_MODE_RELEASE_BUFFERS - long mode = SSL_get_mode(conn->ssl_); - SSL_set_mode(conn->ssl_, mode | SSL_MODE_RELEASE_BUFFERS); -#endif - - - 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; - } - } 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(conn->ssl_, verify_mode, VerifyCallback); - - if (is_server) { - SSL_set_accept_state(conn->ssl_); - } else { - SSL_set_connect_state(conn->ssl_); - } -} - - -void Connection::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_); - Connection* conn = static_cast(SSL_get_app_data(ssl)); - Environment* env = conn->env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - if (where & SSL_CB_HANDSHAKE_START) { - conn->MakeCallback(env->onhandshakestart_string(), 0, nullptr); - } - - if (where & SSL_CB_HANDSHAKE_DONE) { - conn->MakeCallback(env->onhandshakedone_string(), 0, nullptr); - } -} - - -void Connection::EncIn(const FunctionCallbackInfo& args) { - Connection* conn = Unwrap(args.Holder()); - Environment* env = conn->env(); - - if (args.Length() < 3) { - return env->ThrowTypeError( - "Data, offset, and length arguments are mandatory"); - } - - THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); - - 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 (!Buffer::IsWithinBounds(off, len, buffer_length)) - return env->ThrowRangeError("offset + length > buffer.length"); - - int bytes_written; - char* data = buffer_data + off; - - 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(); - } - - args.GetReturnValue().Set(bytes_written); -} - - -void Connection::ClearOut(const FunctionCallbackInfo& args) { - Connection* conn = Unwrap(args.Holder()); - Environment* env = conn->env(); - - if (args.Length() < 3) { - return env->ThrowTypeError( - "Data, offset, and length arguments are mandatory"); - } - - THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); - - 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 (!Buffer::IsWithinBounds(off, len, buffer_length)) - return env->ThrowRangeError("offset + length > buffer.length"); - - if (!SSL_is_init_finished(conn->ssl_)) { - int rv; - - 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); - } - - if (rv < 0) { - return args.GetReturnValue().Set(rv); - } - } - - int bytes_read = SSL_read(conn->ssl_, buffer_data + off, len); - conn->HandleSSLError("SSL_read:ClearOut", - bytes_read, - kZeroIsNotAnError, - kSyscallError); - conn->SetShutdownFlags(); - - args.GetReturnValue().Set(bytes_read); -} - - -void Connection::ClearPending(const FunctionCallbackInfo& args) { - Connection* conn = Unwrap(args.Holder()); - int bytes_pending = BIO_pending(conn->bio_read_); - args.GetReturnValue().Set(bytes_pending); -} - - -void Connection::EncPending(const FunctionCallbackInfo& args) { - Connection* conn = Unwrap(args.Holder()); - int bytes_pending = BIO_pending(conn->bio_write_); - args.GetReturnValue().Set(bytes_pending); -} - - -void Connection::EncOut(const FunctionCallbackInfo& args) { - Connection* conn = Unwrap(args.Holder()); - Environment* env = conn->env(); - - if (args.Length() < 3) { - return env->ThrowTypeError( - "Data, offset, and length arguments are mandatory"); - } - - THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); - - 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 (!Buffer::IsWithinBounds(off, len, buffer_length)) - return env->ThrowRangeError("offset + length > buffer.length"); - - int bytes_read = BIO_read(conn->bio_write_, buffer_data + off, len); - - conn->HandleBIOError(conn->bio_write_, "BIO_read:EncOut", bytes_read); - conn->SetShutdownFlags(); - - args.GetReturnValue().Set(bytes_read); -} - - -void Connection::ClearIn(const FunctionCallbackInfo& args) { - Connection* conn = Unwrap(args.Holder()); - Environment* env = conn->env(); - - if (args.Length() < 3) { - return env->ThrowTypeError( - "Data, offset, and length arguments are mandatory"); - } - - THROW_AND_RETURN_IF_NOT_BUFFER(args[0], "Data"); - - 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 (!Buffer::IsWithinBounds(off, len, buffer_length)) - return env->ThrowRangeError("offset + length > buffer.length"); - - 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); - } - - 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(); - - args.GetReturnValue().Set(bytes_written); -} - - -void Connection::Start(const FunctionCallbackInfo& args) { - Connection* conn = Unwrap(args.Holder()); - - 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); -} - - -void Connection::Close(const FunctionCallbackInfo& args) { - Connection* conn = Unwrap(args.Holder()); - - if (conn->ssl_ != nullptr) { - SSL_free(conn->ssl_); - conn->ssl_ = nullptr; - } -} - - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB -void Connection::GetServername(const FunctionCallbackInfo& args) { - Connection* conn = Unwrap(args.Holder()); - - if (conn->is_server() && !conn->servername_.IsEmpty()) { - args.GetReturnValue().Set(conn->servername_); - } else { - args.GetReturnValue().Set(false); - } -} - - -void Connection::SetSNICallback(const FunctionCallbackInfo& args) { - Connection* conn = Unwrap(args.Holder()); - Environment* env = conn->env(); - - if (args.Length() < 1 || !args[0]->IsFunction()) { - return env->ThrowError("Must give a Function as first argument"); - } - - Local obj = Object::New(env->isolate()); - obj->Set(FIXED_ONE_BYTE_STRING(args.GetIsolate(), "onselect"), args[0]); - conn->sniObject_.Reset(args.GetIsolate(), obj); -} -#endif - - void CipherBase::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -5803,7 +5094,6 @@ void InitCrypto(Local target, Environment* env = Environment::GetCurrent(context); SecureContext::Initialize(env, target); - Connection::Initialize(env, target); CipherBase::Initialize(env, target); DiffieHellman::Initialize(env, target); ECDH::Initialize(env, target); diff --git a/src/node_crypto.h b/src/node_crypto.h index 66c50efa2c040d..88390e322fcbb1 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -54,18 +54,8 @@ struct MarkPopErrorOnReturn { ~MarkPopErrorOnReturn() { ERR_pop_to_mark(); } }; -enum CheckResult { - CHECK_CERT_REVOKED = 0, - CHECK_OK = 1 -}; - -extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx); - extern X509_STORE* root_cert_store; -// Forward declaration -class Connection; - class SecureContext : public BaseObject { public: ~SecureContext() override { @@ -319,99 +309,6 @@ class SSLWrap { friend class SecureContext; }; -// 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, public AsyncWrap { - public: - ~Connection() override { -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - sniObject_.Reset(); - servername_.Reset(); -#endif - } - - static void Initialize(Environment* env, v8::Local target); - void NewSessionDoneCb(); - -#ifdef OPENSSL_NPN_NEGOTIATED - v8::Persistent npnProtos_; - v8::Persistent selectedNPNProto_; -#endif - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - v8::Persistent sniObject_; - v8::Persistent servername_; -#endif - - size_t self_size() const override { return sizeof(*this); } - - protected: - static void New(const v8::FunctionCallbackInfo& args); - static void EncIn(const v8::FunctionCallbackInfo& args); - static void ClearOut(const v8::FunctionCallbackInfo& args); - static void ClearPending(const v8::FunctionCallbackInfo& args); - static void EncPending(const v8::FunctionCallbackInfo& args); - static void EncOut(const v8::FunctionCallbackInfo& args); - static void ClearIn(const v8::FunctionCallbackInfo& args); - static void Start(const v8::FunctionCallbackInfo& args); - static void Close(const v8::FunctionCallbackInfo& args); - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - // SNI - static void GetServername(const v8::FunctionCallbackInfo& args); - static void SetSNICallback(const v8::FunctionCallbackInfo& args); - static int SelectSNIContextCallback_(SSL* s, int* ad, void* arg); -#endif - - static void OnClientHelloParseEnd(void* arg); - - int HandleBIOError(BIO* bio, const char* func, int rv); - - enum ZeroStatus { - kZeroIsNotAnError, - kZeroIsAnError - }; - - enum SyscallStatus { - kIgnoreSyscall, - kSyscallError - }; - - int HandleSSLError(const char* func, int rv, ZeroStatus zs, SyscallStatus ss); - - void ClearError(); - void SetShutdownFlags(); - - Connection(Environment* env, - v8::Local wrap, - SecureContext* sc, - SSLWrap::Kind kind) - : SSLWrap(env, sc, kind), - AsyncWrap(env, wrap, AsyncWrap::PROVIDER_CRYPTO), - bio_read_(nullptr), - bio_write_(nullptr), - hello_offset_(0) { - MakeWeak(this); - hello_parser_.Start(SSLWrap::OnClientHello, - OnClientHelloParseEnd, - this); - enable_session_callbacks(); - } - - private: - static void SSLInfoCallback(const SSL *ssl, int where, int ret); - - BIO *bio_read_; - BIO *bio_write_; - - uint8_t hello_data_[18432]; - size_t hello_offset_; - - friend class ClientHelloParser; - friend class SecureContext; -}; - class CipherBase : public BaseObject { public: ~CipherBase() override { diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 2885ef900a533b..ab65877a63e338 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -12,6 +12,10 @@ #include "stream_base-inl.h" #include "util.h" #include "util-inl.h" +// CNNIC Hash WhiteList is taken from +// https://hg.mozilla.org/mozilla-central/raw-file/98820360ab66/security/ +// certverifier/CNNICHashWhitelist.inc +#include "CNNICHashWhitelist.inc" namespace node { @@ -31,6 +35,127 @@ using v8::Object; using v8::String; using v8::Value; + +enum CheckResult { + CHECK_CERT_REVOKED = 0, + CHECK_OK = 1 +}; + +// Subject DER of CNNIC ROOT CA and CNNIC EV ROOT CA are taken from +// https://hg.mozilla.org/mozilla-central/file/98820360ab66/security/ +// certverifier/NSSCertDBTrustDomain.cpp#l672 +// C = CN, O = CNNIC, CN = CNNIC ROOT +static const uint8_t CNNIC_ROOT_CA_SUBJECT_DATA[] = + "\x30\x32\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x0E\x30" + "\x0C\x06\x03\x55\x04\x0A\x13\x05\x43\x4E\x4E\x49\x43\x31\x13\x30\x11\x06" + "\x03\x55\x04\x03\x13\x0A\x43\x4E\x4E\x49\x43\x20\x52\x4F\x4F\x54"; +static const uint8_t* cnnic_p = CNNIC_ROOT_CA_SUBJECT_DATA; +static X509_NAME* cnnic_name = + d2i_X509_NAME(nullptr, &cnnic_p, sizeof(CNNIC_ROOT_CA_SUBJECT_DATA)-1); + +// C = CN, O = China Internet Network Information Center, CN = China +// Internet Network Information Center EV Certificates Root +static const uint8_t CNNIC_EV_ROOT_CA_SUBJECT_DATA[] = + "\x30\x81\x8A\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02\x43\x4E\x31\x32" + "\x30\x30\x06\x03\x55\x04\x0A\x0C\x29\x43\x68\x69\x6E\x61\x20\x49\x6E\x74" + "\x65\x72\x6E\x65\x74\x20\x4E\x65\x74\x77\x6F\x72\x6B\x20\x49\x6E\x66\x6F" + "\x72\x6D\x61\x74\x69\x6F\x6E\x20\x43\x65\x6E\x74\x65\x72\x31\x47\x30\x45" + "\x06\x03\x55\x04\x03\x0C\x3E\x43\x68\x69\x6E\x61\x20\x49\x6E\x74\x65\x72" + "\x6E\x65\x74\x20\x4E\x65\x74\x77\x6F\x72\x6B\x20\x49\x6E\x66\x6F\x72\x6D" + "\x61\x74\x69\x6F\x6E\x20\x43\x65\x6E\x74\x65\x72\x20\x45\x56\x20\x43\x65" + "\x72\x74\x69\x66\x69\x63\x61\x74\x65\x73\x20\x52\x6F\x6F\x74"; +static const uint8_t* cnnic_ev_p = CNNIC_EV_ROOT_CA_SUBJECT_DATA; +static X509_NAME *cnnic_ev_name = + d2i_X509_NAME(nullptr, &cnnic_ev_p, + sizeof(CNNIC_EV_ROOT_CA_SUBJECT_DATA)-1); + +inline int compar(const void* a, const void* b) { + return memcmp(a, b, CNNIC_WHITELIST_HASH_LEN); +} + + +inline int IsSelfSigned(X509* cert) { + return X509_NAME_cmp(X509_get_subject_name(cert), + X509_get_issuer_name(cert)) == 0; +} + + +inline X509* FindRoot(STACK_OF(X509)* sk) { + for (int i = 0; i < sk_X509_num(sk); i++) { + X509* cert = sk_X509_value(sk, i); + if (IsSelfSigned(cert)) + return cert; + } + return nullptr; +} + + +// Whitelist check for certs issued by CNNIC. See +// https://blog.mozilla.org/security/2015/04/02 +// /distrusting-new-cnnic-certificates/ +inline CheckResult CheckWhitelistedServerCert(X509_STORE_CTX* ctx) { + unsigned char hash[CNNIC_WHITELIST_HASH_LEN]; + unsigned int hashlen = CNNIC_WHITELIST_HASH_LEN; + + STACK_OF(X509)* chain = X509_STORE_CTX_get1_chain(ctx); + CHECK_NE(chain, nullptr); + CHECK_GT(sk_X509_num(chain), 0); + + // Take the last cert as root at the first time. + X509* root_cert = sk_X509_value(chain, sk_X509_num(chain)-1); + X509_NAME* root_name = X509_get_subject_name(root_cert); + + if (!IsSelfSigned(root_cert)) { + root_cert = FindRoot(chain); + CHECK_NE(root_cert, nullptr); + root_name = X509_get_subject_name(root_cert); + } + + // When the cert is issued from either CNNNIC ROOT CA or CNNNIC EV + // ROOT CA, check a hash of its leaf cert if it is in the whitelist. + if (X509_NAME_cmp(root_name, cnnic_name) == 0 || + X509_NAME_cmp(root_name, cnnic_ev_name) == 0) { + X509* leaf_cert = sk_X509_value(chain, 0); + int ret = X509_digest(leaf_cert, EVP_sha256(), hash, + &hashlen); + CHECK(ret); + + void* result = bsearch(hash, WhitelistedCNNICHashes, + ARRAY_SIZE(WhitelistedCNNICHashes), + CNNIC_WHITELIST_HASH_LEN, compar); + if (result == nullptr) { + sk_X509_pop_free(chain, X509_free); + return CHECK_CERT_REVOKED; + } + } + + sk_X509_pop_free(chain, X509_free); + return CHECK_OK; +} + + +inline int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) { + // Failure on verification of the cert is handled + if (preverify_ok == 0 || X509_STORE_CTX_get_error(ctx) != X509_V_OK) + return 1; + + // Server does not need to check the whitelist. + SSL* ssl = static_cast( + X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); + + if (SSL_is_server(ssl)) + return 1; + + // Client needs to check if the server cert is listed in the + // whitelist when it is issued by the specific rootCAs. + CheckResult ret = CheckWhitelistedServerCert(ctx); + if (ret == CHECK_CERT_REVOKED) + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED); + + return ret; +} + + TLSWrap::TLSWrap(Environment* env, Kind kind, StreamBase* stream, @@ -124,7 +249,7 @@ void TLSWrap::InitSSL() { SSL_set_bio(ssl_, enc_in_, enc_out_); // NOTE: This could be overriden in SetVerifyMode - SSL_set_verify(ssl_, SSL_VERIFY_NONE, crypto::VerifyCallback); + SSL_set_verify(ssl_, SSL_VERIFY_NONE, VerifyCallback); #ifdef SSL_MODE_RELEASE_BUFFERS long mode = SSL_get_mode(ssl_); @@ -759,7 +884,7 @@ void TLSWrap::SetVerifyMode(const FunctionCallbackInfo& args) { } // Always allow a connection. We'll reject in javascript. - SSL_set_verify(wrap->ssl_, verify_mode, crypto::VerifyCallback); + SSL_set_verify(wrap->ssl_, verify_mode, VerifyCallback); } diff --git a/test/parallel/test-tls-basic-validations.js b/test/parallel/test-tls-basic-validations.js index a741f3b49c47ac..a105e4f2750a75 100644 --- a/test/parallel/test-tls-basic-validations.js +++ b/test/parallel/test-tls-basic-validations.js @@ -30,6 +30,3 @@ assert.throws(() => tls.createServer({ticketKeys: 'abcd'}), assert.throws(() => tls.createServer({ticketKeys: new Buffer(0)}), /TypeError: Ticket keys length must be 48 bytes/); - -assert.throws(() => tls.createSecurePair({}), - /Error: First argument must be a tls module SecureContext/); diff --git a/test/parallel/test-tls-external-accessor.js b/test/parallel/test-tls-external-accessor.js index 919af0e8f33e4f..6f0954986b7700 100644 --- a/test/parallel/test-tls-external-accessor.js +++ b/test/parallel/test-tls-external-accessor.js @@ -16,9 +16,3 @@ const tls = require('tls'); assert.throws(() => cctx._external, /incompatible receiver/); pctx._external; } -{ - const pctx = tls.createSecurePair().credentials.context; - const cctx = Object.create(pctx); - assert.throws(() => cctx._external, /incompatible receiver/); - pctx._external; -} diff --git a/test/parallel/test-tls-handshake-nohang.js b/test/parallel/test-tls-handshake-nohang.js deleted file mode 100644 index 374ac02a2f45b5..00000000000000 --- a/test/parallel/test-tls-handshake-nohang.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; -var common = require('../common'); - -if (!common.hasCrypto) { - console.log('1..0 # Skipped: missing crypto'); - return; -} -var tls = require('tls'); - -// neither should hang -tls.createSecurePair(null, false, false, false); -tls.createSecurePair(null, true, false, false); diff --git a/test/parallel/test-tls-legacy-onselect.js b/test/parallel/test-tls-legacy-onselect.js deleted file mode 100644 index d3b20d5d8a4cef..00000000000000 --- a/test/parallel/test-tls-legacy-onselect.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; -var common = require('../common'); -var assert = require('assert'); - -if (!common.hasCrypto) { - console.log('1..0 # Skipped: missing crypto'); - return; -} -var tls = require('tls'); -var net = require('net'); - -var success = false; - -var server = net.Server(function(raw) { - var pair = tls.createSecurePair(null, true, false, false); - pair.on('error', function() {}); - pair.ssl.setSNICallback(function() { - raw.destroy(); - server.close(); - success = true; - }); - require('_tls_legacy').pipe(pair, raw); -}).listen(common.PORT, function() { - tls.connect({ - port: common.PORT, - rejectUnauthorized: false, - servername: 'server' - }, function() { - }).on('error', function() { - // Just ignore - }); -}); -process.on('exit', function() { - assert(success); -}); diff --git a/test/parallel/test-tls-securepair-fiftharg.js b/test/parallel/test-tls-securepair-fiftharg.js deleted file mode 100644 index b4610117889cc2..00000000000000 --- a/test/parallel/test-tls-securepair-fiftharg.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const common = require('../common'); -const assert = require('assert'); -const fs = require('fs'); -const tls = require('tls'); - -const sslcontext = tls.createSecureContext({ - cert: fs.readFileSync(common.fixturesDir + '/test_cert.pem'), - key: fs.readFileSync(common.fixturesDir + '/test_key.pem') -}); - -var catchedServername; -const pair = tls.createSecurePair(sslcontext, true, false, false, { - SNICallback: common.mustCall(function(servername, cb) { - catchedServername = servername; - }) -}); - -// captured traffic from browser's request to https://www.google.com -const sslHello = fs.readFileSync(common.fixturesDir + '/google_ssl_hello.bin'); - -pair.encrypted.write(sslHello); - -process.on('exit', function() { - assert.strictEqual('www.google.com', catchedServername); -}); diff --git a/test/parallel/test-tls-securepair-server.js b/test/parallel/test-tls-securepair-server.js deleted file mode 100644 index ef182f3b5ded19..00000000000000 --- a/test/parallel/test-tls-securepair-server.js +++ /dev/null @@ -1,138 +0,0 @@ -'use strict'; -var common = require('../common'); -var assert = require('assert'); - -if (!common.hasCrypto) { - console.log('1..0 # Skipped: missing crypto'); - return; -} -var tls = require('tls'); - -var join = require('path').join; -var net = require('net'); -var fs = require('fs'); -var spawn = require('child_process').spawn; - -var connections = 0; -var key = fs.readFileSync(join(common.fixturesDir, 'agent.key')).toString(); -var cert = fs.readFileSync(join(common.fixturesDir, 'agent.crt')).toString(); - -function log(a) { - console.error('***server*** ' + a); -} - -var server = net.createServer(function(socket) { - connections++; - log('connection fd=' + socket.fd); - var sslcontext = tls.createSecureContext({key: key, cert: cert}); - sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA'); - - var pair = tls.createSecurePair(sslcontext, true); - - assert.ok(pair.encrypted.writable); - assert.ok(pair.cleartext.writable); - - pair.encrypted.pipe(socket); - socket.pipe(pair.encrypted); - - log('i set it secure'); - - pair.on('secure', function() { - log('connected+secure!'); - pair.cleartext.write('hello\r\n'); - log(pair.cleartext.getPeerCertificate()); - log(pair.cleartext.getCipher()); - }); - - pair.cleartext.on('data', function(data) { - log('read bytes ' + data.length); - pair.cleartext.write(data); - }); - - socket.on('end', function() { - log('socket end'); - }); - - pair.cleartext.on('error', function(err) { - log('got error: '); - log(err); - log(err.stack); - socket.destroy(); - }); - - pair.encrypted.on('error', function(err) { - log('encrypted error: '); - log(err); - log(err.stack); - socket.destroy(); - }); - - socket.on('error', function(err) { - log('socket error: '); - log(err); - log(err.stack); - socket.destroy(); - }); - - socket.on('close', function(err) { - log('socket closed'); - }); - - pair.on('error', function(err) { - log('secure error: '); - log(err); - log(err.stack); - socket.destroy(); - }); -}); - -var gotHello = false; -var sentWorld = false; -var gotWorld = false; -var opensslExitCode = -1; - -server.listen(common.PORT, function() { - // To test use: openssl s_client -connect localhost:8000 - - var args = ['s_client', '-connect', '127.0.0.1:' + common.PORT]; - - // for the performance and stability issue in s_client on Windows - if (common.isWindows) - args.push('-no_rand_screen'); - - var client = spawn(common.opensslCli, args); - - - var out = ''; - - client.stdout.setEncoding('utf8'); - client.stdout.on('data', function(d) { - out += d; - - if (!gotHello && /hello/.test(out)) { - gotHello = true; - client.stdin.write('world\r\n'); - sentWorld = true; - } - - if (!gotWorld && /world/.test(out)) { - gotWorld = true; - client.stdin.end(); - } - }); - - client.stdout.pipe(process.stdout, { end: false }); - - client.on('exit', function(code) { - opensslExitCode = code; - server.close(); - }); -}); - -process.on('exit', function() { - assert.equal(1, connections); - assert.ok(gotHello); - assert.ok(sentWorld); - assert.ok(gotWorld); - assert.equal(0, opensslExitCode); -}); diff --git a/test/pummel/test-tls-securepair-client.js b/test/pummel/test-tls-securepair-client.js deleted file mode 100644 index 3d33ef8f21623f..00000000000000 --- a/test/pummel/test-tls-securepair-client.js +++ /dev/null @@ -1,171 +0,0 @@ -'use strict'; -// - -var common = require('../common'); - -if (!common.opensslCli) { - console.log('1..0 # Skipped: node compiled without OpenSSL CLI.'); - return; -} - -if (!common.hasCrypto) { - console.log('1..0 # Skipped: missing crypto'); - return; -} - -var join = require('path').join; -var net = require('net'); -var assert = require('assert'); -var fs = require('fs'); -var tls = require('tls'); -var spawn = require('child_process').spawn; - -test1(); - -// simple/test-tls-securepair-client -function test1() { - test('agent.key', 'agent.crt', null, test2); -} - -// simple/test-tls-ext-key-usage -function test2() { - function check(pair) { - // "TLS Web Client Authentication" - assert.equal(pair.cleartext.getPeerCertificate().ext_key_usage.length, 1); - assert.equal(pair.cleartext.getPeerCertificate().ext_key_usage[0], - '1.3.6.1.5.5.7.3.2'); - } - test('keys/agent4-key.pem', 'keys/agent4-cert.pem', check); -} - -function test(keyfn, certfn, check, next) { - // FIXME: Avoid the common PORT as this test currently hits a C-level - // assertion error with node_g. The program aborts without HUPing - // the openssl s_server thus causing many tests to fail with - // EADDRINUSE. - var PORT = common.PORT + 5; - - keyfn = join(common.fixturesDir, keyfn); - var key = fs.readFileSync(keyfn).toString(); - - certfn = join(common.fixturesDir, certfn); - var cert = fs.readFileSync(certfn).toString(); - - var server = spawn(common.opensslCli, ['s_server', - '-accept', PORT, - '-cert', certfn, - '-key', keyfn]); - server.stdout.pipe(process.stdout); - server.stderr.pipe(process.stdout); - - - var state = 'WAIT-ACCEPT'; - - var serverStdoutBuffer = ''; - server.stdout.setEncoding('utf8'); - server.stdout.on('data', function(s) { - serverStdoutBuffer += s; - console.error(state); - switch (state) { - case 'WAIT-ACCEPT': - if (/ACCEPT/g.test(serverStdoutBuffer)) { - // Give s_server half a second to start up. - setTimeout(startClient, 500); - state = 'WAIT-HELLO'; - } - break; - - case 'WAIT-HELLO': - if (/hello/g.test(serverStdoutBuffer)) { - - // End the current SSL connection and exit. - // See s_server(1ssl). - server.stdin.write('Q'); - - state = 'WAIT-SERVER-CLOSE'; - } - break; - - default: - break; - } - }); - - - var timeout = setTimeout(function() { - server.kill(); - process.exit(1); - }, 5000); - - var gotWriteCallback = false; - var serverExitCode = -1; - - server.on('exit', function(code) { - serverExitCode = code; - clearTimeout(timeout); - if (next) next(); - }); - - - function startClient() { - var s = new net.Stream(); - - var sslcontext = tls.createSecureContext({key: key, cert: cert}); - sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA'); - - var pair = tls.createSecurePair(sslcontext, false); - - assert.ok(pair.encrypted.writable); - assert.ok(pair.cleartext.writable); - - pair.encrypted.pipe(s); - s.pipe(pair.encrypted); - - s.connect(PORT); - - s.on('connect', function() { - console.log('client connected'); - }); - - pair.on('secure', function() { - console.log('client: connected+secure!'); - console.log('client pair.cleartext.getPeerCertificate(): %j', - pair.cleartext.getPeerCertificate()); - console.log('client pair.cleartext.getCipher(): %j', - pair.cleartext.getCipher()); - if (check) check(pair); - setTimeout(function() { - pair.cleartext.write('hello\r\n', function() { - gotWriteCallback = true; - }); - }, 500); - }); - - pair.cleartext.on('data', function(d) { - console.log('cleartext: %s', d.toString()); - }); - - s.on('close', function() { - console.log('client close'); - }); - - pair.encrypted.on('error', function(err) { - console.log('encrypted error: ' + err); - }); - - s.on('error', function(err) { - console.log('socket error: ' + err); - }); - - pair.on('error', function(err) { - console.log('secure error: ' + err); - }); - } - - - process.on('exit', function() { - assert.equal(0, serverExitCode); - assert.equal('WAIT-SERVER-CLOSE', state); - assert.ok(gotWriteCallback); - }); -}