tls: make deprecated tls.createSecurePair() use public API

Make the deprecated `tls.createSecurePair()` method use other public
APIs only (`TLSSocket` in particular).

Since `tls.createSecurePair()` has been runtime-deprecated only
since Node 8, it probably isn’t quite time to remove it yet,
but this patch removes almost all of the code complexity that
is retained by it.

The API, as it is documented, is retained. However, it is very likely
that some users have come to rely on parts of undocumented API
of the `SecurePair` class, especially since some of the existing
tests checked for those. Therefore, this should definitely be
considered a breaking change.

PR-URL: https://github.com/nodejs/node/pull/17882
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
This commit is contained in:
Anna Henningsen 2017-12-23 05:55:37 +01:00
parent 02fef8ad5a
commit 9301b8a9c6
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
15 changed files with 90 additions and 1871 deletions

View File

@ -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
};

View File

@ -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;

View File

@ -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');

View File

@ -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',

View File

@ -67,7 +67,6 @@ namespace node {
#if HAVE_OPENSSL
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
V(SSLCONNECTION) \
V(PBKDF2REQUEST) \
V(RANDOMBYTESREQUEST) \
V(TLSWRAP)

View File

@ -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") \

View File

@ -637,8 +637,6 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& 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<Connection>::GetSessionCallback);
SSL_CTX_sess_set_new_cb(sc->ctx_, SSLWrap<Connection>::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<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) {
env->SetProtoMethod(t, "getALPNNegotiatedProtocol", GetALPNNegotiatedProto);
env->SetProtoMethod(t, "setALPNProtocols", SetALPNProtocols);
Local<FunctionTemplate> 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<FunctionTemplate>(),
static_cast<PropertyAttribute>(ReadOnly | DontDelete));
}
@ -2808,16 +2794,6 @@ void SSLWrap<Base>::CertCbDone(const FunctionCallbackInfo<Value>& args) {
}
template <class Base>
void SSLWrap<Base>::SSLGetter(const FunctionCallbackInfo<Value>& info) {
Base* base;
ASSIGN_OR_RETURN_UNWRAP(&base, info.This());
SSL* ssl = base->ssl_;
Local<External> ext = External::New(info.GetIsolate(), ssl);
info.GetReturnValue().Set(ext);
}
template <class Base>
void SSLWrap<Base>::DestroySSL() {
if (ssl_ == nullptr)
@ -2853,205 +2829,6 @@ int SSLWrap<Base>::SetCACerts(SecureContext* sc) {
}
Connection::Connection(Environment* env,
v8::Local<v8::Object> wrap,
SecureContext* sc,
SSLWrap<Connection>::Kind kind)
: AsyncWrap(env, wrap, AsyncWrap::PROVIDER_SSLCONNECTION),
SSLWrap<Connection>(env, sc, kind),
bio_read_(nullptr),
bio_write_(nullptr),
hello_offset_(0) {
MakeWeak<Connection>(this);
Wrap(wrap, this);
hello_parser_.Start(SSLWrap<Connection>::OnClientHello,
OnClientHelloParseEnd,
this);
enable_session_callbacks();
}
void Connection::OnClientHelloParseEnd(void* arg) {
Connection* conn = static_cast<Connection*>(arg);
// Write all accumulated data
int r = BIO_write(conn->bio_read_,
reinterpret_cast<char*>(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<Value> 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<Value> 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<Value> 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<String> sent_shutdown_key = ssl_env()->sent_shutdown_string();
object()->Set(sent_shutdown_key, True(ssl_env()->isolate()));
}
if (flags & SSL_RECEIVED_SHUTDOWN) {
Local<String> 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<Object> target) {
Local<FunctionTemplate> 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<Connection>::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<Connection*>(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<Object> sni_obj = PersistentToLocal(env->isolate(),
conn->sniObject_);
Local<Value> arg = PersistentToLocal(env->isolate(), conn->servername_);
Local<Value> ret = node::MakeCallback(env->isolate(),
sni_obj,
env->onselect_string(),
1,
&arg);
// If ret is SecureContext
Local<FunctionTemplate> 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<Object>(), SSL_TLSEXT_ERR_NOACK);
conn->SetSNIContext(sc);
} else {
return SSL_TLSEXT_ERR_NOACK;
}
}
}
return SSL_TLSEXT_ERR_OK;
}
#endif
void Connection::New(const FunctionCallbackInfo<Value>& 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<Object>());
bool is_server = args[1]->BooleanValue();
SSLWrap<Connection>::Kind kind =
is_server ? SSLWrap<Connection>::kServer : SSLWrap<Connection>::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<Connection>::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*>(ssl_);
Connection* conn = static_cast<Connection*>(SSL_get_app_data(ssl));
Environment* env = conn->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
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<Value>& 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<Value>& 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<Value>& 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<Value>& 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<Value>& 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<Value>& 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<Value>& 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<Value>& args) {
Connection* conn;
ASSIGN_OR_RETURN_UNWRAP(&conn, args.Holder());
conn->DestroySSL();
}
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
void Connection::GetServername(const FunctionCallbackInfo<Value>& 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<Value>& 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<Object> 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<Object> target) {
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
@ -6227,7 +5598,6 @@ void InitCrypto(Local<Object> 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);

View File

@ -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<v8::Value>& 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<Connection> {
public:
~Connection() override {
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
sniObject_.Reset();
servername_.Reset();
#endif
}
static void Initialize(Environment* env, v8::Local<v8::Object> target);
void NewSessionDoneCb();
#ifndef OPENSSL_NO_NEXTPROTONEG
v8::Persistent<v8::Object> npnProtos_;
v8::Persistent<v8::Value> selectedNPNProto_;
#endif
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
v8::Persistent<v8::Object> sniObject_;
v8::Persistent<v8::String> servername_;
#endif
size_t self_size() const override { return sizeof(*this); }
protected:
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EncIn(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ClearOut(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ClearPending(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EncPending(const v8::FunctionCallbackInfo<v8::Value>& args);
static void EncOut(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ClearIn(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
// SNI
static void GetServername(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSNICallback(const v8::FunctionCallbackInfo<v8::Value>& 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<v8::Object> wrap,
SecureContext* sc,
SSLWrap<Connection>::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 {

View File

@ -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');
}

View File

@ -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' } ]
);
}

View File

@ -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'
);
}
}

View File

@ -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');

View File

@ -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
});
});

View File

@ -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);
});

View File

@ -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');