diff --git a/lib/_tls_legacy.js b/lib/_tls_legacy.js deleted file mode 100644 index 7beec4805c1b8b..00000000000000 --- a/lib/_tls_legacy.js +++ /dev/null @@ -1,956 +0,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. - -'use strict'; - -const internalUtil = require('internal/util'); -internalUtil.assertCrypto(); - -const assert = require('assert'); -const { Buffer } = require('buffer'); -const common = require('_tls_common'); -const { Connection } = process.binding('crypto'); -const EventEmitter = require('events'); -const stream = require('stream'); -const { Timer } = process.binding('timer_wrap'); -const tls = require('tls'); -const util = require('util'); - -const debug = util.debuglog('tls-legacy'); - -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); -}; - -Object.defineProperty(CryptoStream.prototype, 'bytesWritten', { - configurable: true, - enumerable: true, - get: 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; - 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(); - - 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. -// Deprecation Code: DEP0004 -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.readableLength > 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(); -}; - -Object.defineProperty(CleartextStream.prototype, 'remoteAddress', { - configurable: true, - enumerable: true, - get: function() { - return this.socket && this.socket.remoteAddress; - } -}); - -Object.defineProperty(CleartextStream.prototype, 'remoteFamily', { - configurable: true, - enumerable: true, - get: function() { - return this.socket && this.socket.remoteFamily; - } -}); - -Object.defineProperty(CleartextStream.prototype, 'remotePort', { - configurable: true, - enumerable: true, - get: function() { - return this.socket && this.socket.remotePort; - } -}); - -Object.defineProperty(CleartextStream.prototype, 'localAddress', { - configurable: true, - enumerable: true, - get: function() { - return this.socket && this.socket.localAddress; - } -}); - -Object.defineProperty(CleartextStream.prototype, 'localPort', { - configurable: true, - enumerable: true, - get: 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() { - // Old-style error is not being migrated to the newer style - // internal/errors.js because _tls_legacy.js has been deprecated. - 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(); - } -} - - -function createSecurePair(context, isServer, requestCert, - rejectUnauthorized, options) { - return new SecurePair(context, isServer, requestCert, - rejectUnauthorized, options); -} - - -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; -}; - - -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(); -} - -module.exports = { - createSecurePair: - internalUtil.deprecate(createSecurePair, - 'tls.createSecurePair() is deprecated. Please use ' + - 'tls.TLSSocket instead.', 'DEP0064'), - pipe -}; diff --git a/lib/internal/streams/duplexpair.js b/lib/internal/streams/duplexpair.js new file mode 100644 index 00000000000000..beaf9e9eb3d03d --- /dev/null +++ b/lib/internal/streams/duplexpair.js @@ -0,0 +1,42 @@ +'use strict'; +const { Duplex } = require('stream'); + +const kCallback = Symbol('Callback'); +const kOtherSide = Symbol('Other'); + +class DuplexSocket extends Duplex { + constructor() { + super(); + this[kCallback] = null; + this[kOtherSide] = null; + } + + _read() { + const callback = this[kCallback]; + if (callback) { + this[kCallback] = null; + callback(); + } + } + + _write(chunk, encoding, callback) { + this[kOtherSide][kCallback] = callback; + this[kOtherSide].push(chunk); + } + + _final(callback) { + this[kOtherSide].on('end', callback); + this[kOtherSide].push(null); + } +} + +class DuplexPair { + constructor() { + this.socket1 = new DuplexSocket(); + this.socket2 = new DuplexSocket(); + this.socket1[kOtherSide] = this.socket2; + this.socket2[kOtherSide] = this.socket1; + } +} + +module.exports = DuplexPair; diff --git a/lib/tls.js b/lib/tls.js index 554ddb77b816f9..96b6ec8d340e2f 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -31,6 +31,8 @@ const net = require('net'); const url = require('url'); const binding = process.binding('crypto'); const Buffer = require('buffer').Buffer; +const EventEmitter = require('events'); +const DuplexPair = require('internal/streams/duplexpair'); const canonicalizeIP = process.binding('cares_wrap').canonicalizeIP; // Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations @@ -230,6 +232,33 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) { } }; + +class SecurePair extends EventEmitter { + constructor(secureContext = exports.createSecureContext(), + isServer = false, + requestCert = !isServer, + rejectUnauthorized = false, + options = {}) { + super(); + const { socket1, socket2 } = new DuplexPair(); + + this.server = options.server; + this.credentials = secureContext; + + this.encrypted = socket1; + this.cleartext = new exports.TLSSocket(socket2, Object.assign({ + secureContext, isServer, requestCert, rejectUnauthorized + }, options)); + this.cleartext.once('secure', () => this.emit('secure')); + } + + destroy() { + this.cleartext.destroy(); + this.encrypted.destroy(); + } +} + + exports.parseCertString = internalUtil.deprecate( internalTLS.parseCertString, 'tls.parseCertString() is deprecated. ' + @@ -243,5 +272,9 @@ exports.Server = require('_tls_wrap').Server; exports.createServer = require('_tls_wrap').createServer; exports.connect = require('_tls_wrap').connect; -// Deprecated: DEP0064 -exports.createSecurePair = require('_tls_legacy').createSecurePair; +exports.createSecurePair = internalUtil.deprecate( + function createSecurePair(...args) { + return new SecurePair(...args); + }, + 'tls.createSecurePair() is deprecated. Please use ' + + 'tls.TLSSocket instead.', 'DEP0064'); diff --git a/node.gyp b/node.gyp index 736dc975a354ae..f4e4edd8682800 100644 --- a/node.gyp +++ b/node.gyp @@ -69,7 +69,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', @@ -140,6 +139,7 @@ 'lib/internal/streams/lazy_transform.js', 'lib/internal/streams/async_iterator.js', 'lib/internal/streams/BufferList.js', + 'lib/internal/streams/duplexpair.js', 'lib/internal/streams/legacy.js', 'lib/internal/streams/destroy.js', 'lib/internal/wrap_js_stream.js', diff --git a/src/async_wrap.h b/src/async_wrap.h index 9b5632598bcc0b..091224f5708df5 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -67,7 +67,6 @@ namespace node { #if HAVE_OPENSSL #define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \ - V(SSLCONNECTION) \ V(PBKDF2REQUEST) \ V(RANDOMBYTESREQUEST) \ V(TLSWRAP) diff --git a/src/env.h b/src/env.h index 94114446161cd2..e7ec682c8ce2e5 100644 --- a/src/env.h +++ b/src/env.h @@ -193,14 +193,12 @@ class ModuleWrap; V(onheaders_string, "onheaders") \ V(onmessage_string, "onmessage") \ V(onnewsession_string, "onnewsession") \ - V(onnewsessiondone_string, "onnewsessiondone") \ V(onocspresponse_string, "onocspresponse") \ V(ongoawaydata_string, "ongoawaydata") \ V(onpriority_string, "onpriority") \ V(onread_string, "onread") \ V(onreadstart_string, "onreadstart") \ V(onreadstop_string, "onreadstop") \ - V(onselect_string, "onselect") \ V(onsettings_string, "onsettings") \ V(onshutdown_string, "onshutdown") \ V(onsignal_string, "onsignal") \ @@ -224,7 +222,6 @@ class ModuleWrap; V(raw_string, "raw") \ V(read_host_object_string, "_readHostObject") \ V(readable_string, "readable") \ - V(received_shutdown_string, "receivedShutdown") \ V(refresh_string, "refresh") \ V(regexp_string, "regexp") \ V(rename_string, "rename") \ @@ -232,7 +229,6 @@ class ModuleWrap; V(retry_string, "retry") \ V(serial_string, "serial") \ V(scopeid_string, "scopeid") \ - V(sent_shutdown_string, "sentShutdown") \ V(serial_number_string, "serialNumber") \ V(service_string, "service") \ V(servername_string, "servername") \ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 127715e6c1ea65..d512266bffdb37 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -637,8 +637,6 @@ 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); #if OPENSSL_VERSION_NUMBER >= 0x10100000L // OpenSSL 1.1.0 changed the ticket key size, but the OpenSSL 1.0.x size was @@ -1637,18 +1635,6 @@ void SSLWrap::AddMethods(Environment* env, Local t) { env->SetProtoMethod(t, "getALPNNegotiatedProtocol", GetALPNNegotiatedProto); env->SetProtoMethod(t, "setALPNProtocols", SetALPNProtocols); - - Local ssl_getter_templ = - FunctionTemplate::New(env->isolate(), - SSLGetter, - env->as_external(), - Signature::New(env->isolate(), t)); - - t->PrototypeTemplate()->SetAccessorProperty( - FIXED_ONE_BYTE_STRING(env->isolate(), "_external"), - ssl_getter_templ, - Local(), - static_cast(ReadOnly | DontDelete)); } @@ -2808,16 +2794,6 @@ void SSLWrap::CertCbDone(const FunctionCallbackInfo& args) { } -template -void SSLWrap::SSLGetter(const FunctionCallbackInfo& info) { - Base* base; - ASSIGN_OR_RETURN_UNWRAP(&base, info.This()); - SSL* ssl = base->ssl_; - Local ext = External::New(info.GetIsolate(), ssl); - info.GetReturnValue().Set(ext); -} - - template void SSLWrap::DestroySSL() { if (ssl_ == nullptr) @@ -2853,205 +2829,6 @@ int SSLWrap::SetCACerts(SecureContext* sc) { } -Connection::Connection(Environment* env, - v8::Local wrap, - SecureContext* sc, - SSLWrap::Kind kind) - : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_SSLCONNECTION), - SSLWrap(env, sc, kind), - bio_read_(nullptr), - bio_write_(nullptr), - hello_offset_(0) { - MakeWeak(this); - Wrap(wrap, this); - hello_parser_.Start(SSLWrap::OnClientHello, - OnClientHelloParseEnd, - this); - enable_session_callbacks(); -} - - -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); - USE(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; - - 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::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")); - - AsyncWrap::AddWrapMethods(env, t); - env->SetProtoMethod(t, "encIn", EncIn); - env->SetProtoMethod(t, "clearOut", ClearOut); - env->SetProtoMethod(t, "clearIn", ClearIn); - env->SetProtoMethod(t, "encOut", EncOut); - env->SetProtoMethod(t, "clearPending", ClearPending); - env->SetProtoMethod(t, "encPending", EncPending); - env->SetProtoMethod(t, "start", Start); - env->SetProtoMethod(t, "close", Close); - - SSLWrap::AddMethods(env, t); - - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - env->SetProtoMethod(t, "getServername", GetServername); - env->SetProtoMethod(t, "setSNICallback", 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); } @@ -3103,7 +2880,6 @@ inline bool CheckStartComOrWoSign(X509_NAME* root_name, X509* cert) { return false; } - // Whitelist check for certs issued by CNNIC, StartCom and WoSign. See // https://blog.mozilla.org/security/2015/04/02 // /distrusting-new-cnnic-certificates/ and @@ -3154,8 +2930,7 @@ inline CheckResult CheckWhitelistedServerCert(X509_STORE_CTX* ctx) { return CHECK_OK; } - -inline int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) { +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) @@ -3177,410 +2952,6 @@ inline int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) { 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; - ASSIGN_OR_RETURN_UNWRAP(&sc, ret.As(), SSL_TLSEXT_ERR_NOACK); - 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; - ASSIGN_OR_RETURN_UNWRAP(&sc, args[0].As()); - - 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_); // NOLINT(runtime/int) - 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; - ASSIGN_OR_RETURN_UNWRAP(&conn, 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; - ASSIGN_OR_RETURN_UNWRAP(&conn, 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; - ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); - int bytes_pending = BIO_pending(conn->bio_read_); - args.GetReturnValue().Set(bytes_pending); -} - - -void Connection::EncPending(const FunctionCallbackInfo& args) { - Connection* conn; - ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); - int bytes_pending = BIO_pending(conn->bio_write_); - args.GetReturnValue().Set(bytes_pending); -} - - -void Connection::EncOut(const FunctionCallbackInfo& args) { - Connection* conn; - ASSIGN_OR_RETURN_UNWRAP(&conn, 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; - ASSIGN_OR_RETURN_UNWRAP(&conn, 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; - ASSIGN_OR_RETURN_UNWRAP(&conn, 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; - ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder()); - conn->DestroySSL(); -} - - -#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB -void Connection::GetServername(const FunctionCallbackInfo& args) { - Connection* conn; - ASSIGN_OR_RETURN_UNWRAP(&conn, 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; - ASSIGN_OR_RETURN_UNWRAP(&conn, 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(env->onselect_string(), args[0]); - conn->sniObject_.Reset(args.GetIsolate(), obj); -} -#endif - - void CipherBase::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); @@ -6227,7 +5598,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 b866117f844358..7ce40697d402cd 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -328,7 +328,6 @@ class SSLWrap { void* arg); static int TLSExtStatusCallback(SSL* s, void* arg); static int SSLCertCallback(SSL* s, void* arg); - static void SSLGetter(const v8::FunctionCallbackInfo& info); void DestroySSL(); void WaitForCertCb(CertCb cb, void* arg); @@ -364,87 +363,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 AsyncWrap, public SSLWrap { - 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(); - -#ifndef OPENSSL_NO_NEXTPROTONEG - 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 SetShutdownFlags(); - - Connection(Environment* env, - v8::Local wrap, - SecureContext* sc, - SSLWrap::Kind kind); - - 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/test/async-hooks/test-connection.ssl.js b/test/async-hooks/test-connection.ssl.js deleted file mode 100644 index faee0fdf080e42..00000000000000 --- a/test/async-hooks/test-connection.ssl.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); - -const initHooks = require('./init-hooks'); -const tick = require('./tick'); -const assert = require('assert'); -const { checkInvocations } = require('./hook-checks'); - -const tls = require('tls'); -const Connection = process.binding('crypto').Connection; -const hooks = initHooks(); -hooks.enable(); - -function createServerConnection( - onhandshakestart, - certificate = null, - isServer = true, - servername = 'some server', - rejectUnauthorized -) { - if (certificate == null) certificate = tls.createSecureContext(); - const ssl = new Connection( - certificate.context, isServer, servername, rejectUnauthorized - ); - if (isServer) { - ssl.onhandshakestart = onhandshakestart; - ssl.lastHandshakeTime = 0; - } - return ssl; -} - -// creating first server connection -const sc1 = createServerConnection(common.mustCall(onfirstHandShake)); - -let as = hooks.activitiesOfTypes('SSLCONNECTION'); -assert.strictEqual(as.length, 1); -const f1 = as[0]; -assert.strictEqual(f1.type, 'SSLCONNECTION'); -assert.strictEqual(typeof f1.uid, 'number'); -assert.strictEqual(typeof f1.triggerAsyncId, 'number'); -checkInvocations(f1, { init: 1 }, 'first connection, when first created'); - -// creating second server connection -const sc2 = createServerConnection(common.mustCall(onsecondHandShake)); - -as = hooks.activitiesOfTypes('SSLCONNECTION'); -assert.strictEqual(as.length, 2); -const f2 = as[1]; -assert.strictEqual(f2.type, 'SSLCONNECTION'); -assert.strictEqual(typeof f2.uid, 'number'); -assert.strictEqual(typeof f2.triggerAsyncId, 'number'); -checkInvocations(f1, { init: 1 }, 'first connection, when second created'); -checkInvocations(f2, { init: 1 }, 'second connection, when second created'); - -// starting the connections which results in handshake starts -sc1.start(); -sc2.start(); - -function onfirstHandShake() { - checkInvocations(f1, { init: 1, before: 1 }, - 'first connection, when first handshake'); - checkInvocations(f2, { init: 1 }, 'second connection, when first handshake'); -} - -function onsecondHandShake() { - checkInvocations(f1, { init: 1, before: 1, after: 1 }, - 'first connection, when second handshake'); - checkInvocations(f2, { init: 1, before: 1 }, - 'second connection, when second handshake'); - tick(1E4); -} - -process.on('exit', onexit); - -function onexit() { - hooks.disable(); - hooks.sanityCheck('SSLCONNECTION'); - - checkInvocations(f1, { init: 1, before: 1, after: 1 }, - 'first connection, when process exits'); - checkInvocations(f2, { init: 1, before: 1, after: 1 }, - 'second connection, when process exits'); -} diff --git a/test/async-hooks/test-graph.connection.js b/test/async-hooks/test-graph.connection.js deleted file mode 100644 index fcc764b5ccf218..00000000000000 --- a/test/async-hooks/test-graph.connection.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); - -const initHooks = require('./init-hooks'); -const verifyGraph = require('./verify-graph'); - -const tls = require('tls'); -const Connection = process.binding('crypto').Connection; -const hooks = initHooks(); -hooks.enable(); - -function createServerConnection( - onhandshakestart, - certificate = null, - isServer = true, - servername = 'some server', - rejectUnauthorized -) { - if (certificate == null) certificate = tls.createSecureContext(); - const ssl = new Connection( - certificate.context, isServer, servername, rejectUnauthorized - ); - if (isServer) { - ssl.onhandshakestart = onhandshakestart; - ssl.lastHandshakeTime = 0; - } - return ssl; -} - -// creating first server connection and start it -const sc1 = createServerConnection(common.mustCall(onfirstHandShake)); -sc1.start(); - -function onfirstHandShake() { - // Create second connection inside handshake of first to show - // that the triggerAsyncId of the second will be set to id of the first - const sc2 = createServerConnection(common.mustCall(onsecondHandShake)); - sc2.start(); -} -function onsecondHandShake() { } - -process.on('exit', onexit); - -function onexit() { - hooks.disable(); - verifyGraph( - hooks, - [ { type: 'CONNECTION', id: 'connection:1', triggerAsyncId: null }, - { type: 'CONNECTION', id: 'connection:2', - triggerAsyncId: 'connection:1' } ] - ); -} diff --git a/test/parallel/test-accessor-properties.js b/test/parallel/test-accessor-properties.js index 13535ceda9667f..b4ebf30f9b0440 100644 --- a/test/parallel/test-accessor-properties.js +++ b/test/parallel/test-accessor-properties.js @@ -59,20 +59,10 @@ const UDP = process.binding('udp_wrap').UDP; crypto.SecureContext.prototype._external; }, TypeError); - assert.throws(() => { - crypto.Connection.prototype._external; - }, TypeError); - assert.strictEqual( typeof Object.getOwnPropertyDescriptor( crypto.SecureContext.prototype, '_external'), 'object' ); - - assert.strictEqual( - typeof Object.getOwnPropertyDescriptor( - crypto.Connection.prototype, '_external'), - 'object' - ); } } diff --git a/test/parallel/test-tls-basic-validations.js b/test/parallel/test-tls-basic-validations.js index e747f5a5162db5..fc2743ce042064 100644 --- a/test/parallel/test-tls-basic-validations.js +++ b/test/parallel/test-tls-basic-validations.js @@ -40,7 +40,7 @@ assert.throws(() => tls.createServer({ ticketKeys: Buffer.alloc(0) }), /TypeError: Ticket keys length must be 48 bytes/); assert.throws(() => tls.createSecurePair({}), - /Error: First argument must be a tls module SecureContext/); + /TypeError: Second argument should be a SecureContext instance/); { const buffer = Buffer.from('abcd'); diff --git a/test/parallel/test-tls-legacy-onselect.js b/test/parallel/test-tls-legacy-onselect.js deleted file mode 100644 index efcc5c2c92b889..00000000000000 --- a/test/parallel/test-tls-legacy-onselect.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; -const common = require('../common'); - -if (!common.hasCrypto) - common.skip('missing crypto'); - -const tls = require('tls'); -const net = require('net'); - -const server = net.Server(common.mustCall(function(raw) { - const pair = tls.createSecurePair(null, true, false, false); - pair.on('error', function() {}); - pair.ssl.setSNICallback(common.mustCall(function() { - raw.destroy(); - server.close(); - })); - require('_tls_legacy').pipe(pair, raw); -})).listen(0, function() { - tls.connect({ - port: this.address().port, - rejectUnauthorized: false, - servername: 'server' - }, function() { - }).on('error', function() { - // Just ignore - }); -}); diff --git a/test/parallel/test-tls-securepair-leak.js b/test/parallel/test-tls-securepair-leak.js index cbc7c7daddd74b..4cd927d64ac088 100644 --- a/test/parallel/test-tls-securepair-leak.js +++ b/test/parallel/test-tls-securepair-leak.js @@ -7,7 +7,7 @@ if (!common.hasCrypto) const assert = require('assert'); const { createSecureContext } = require('tls'); -const { createSecurePair } = require('_tls_legacy'); +const { createSecurePair } = require('tls'); const before = process.memoryUsage().external; { @@ -16,11 +16,13 @@ const before = process.memoryUsage().external; for (let i = 0; i < 1e4; i += 1) createSecurePair(context, false, false, false, options).destroy(); } -global.gc(); -const after = process.memoryUsage().external; +setImmediate(() => { + global.gc(); + const after = process.memoryUsage().external; -// It's not an exact science but a SecurePair grows .external by about 45 kB. -// Unless AdjustAmountOfExternalAllocatedMemory() is called on destruction, -// 10,000 instances make it grow by well over 400 MB. Allow for some slop -// because objects like buffers also affect the external limit. -assert(after - before < 25 << 20); + // It's not an exact science but a SecurePair grows .external by about 45 kB. + // Unless AdjustAmountOfExternalAllocatedMemory() is called on destruction, + // 10,000 instances make it grow by well over 400 MB. Allow for some slop + // because objects like buffers also affect the external limit. + assert(after - before < 25 << 20); +}); diff --git a/test/sequential/test-async-wrap-getasyncid.js b/test/sequential/test-async-wrap-getasyncid.js index a96c5032ad6008..58d6b7746985c5 100644 --- a/test/sequential/test-async-wrap-getasyncid.js +++ b/test/sequential/test-async-wrap-getasyncid.js @@ -87,13 +87,6 @@ function testInitialized(req, ctor_name) { } -if (common.hasCrypto) { // eslint-disable-line crypto-check - const tls = require('tls'); - // SecurePair - testInitialized(tls.createSecurePair().ssl, 'Connection'); -} - - if (common.hasCrypto) { // eslint-disable-line crypto-check const crypto = require('crypto');