diff --git a/package.json b/package.json index 818f2d3d4..e186130bf 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "big-number": "0.3.1", "bl": "^1.2.2", "depd": "^1.1.2", + "native-duplexpair": "^1.0.0", "iconv-lite": "^0.4.23", "punycode": "^2.1.0", "readable-stream": "^2.3.6", diff --git a/src/message-io.js b/src/message-io.js index 59b893c94..1f3739f96 100644 --- a/src/message-io.js +++ b/src/message-io.js @@ -1,5 +1,6 @@ const tls = require('tls'); const crypto = require('crypto'); +const DuplexPair = require('native-duplexpair'); const EventEmitter = require('events').EventEmitter; const Transform = require('readable-stream').Transform; @@ -58,6 +59,8 @@ module.exports = class MessageIO extends EventEmitter { this._packetSize = _packetSize; this.debug = debug; + this.tlsNegotiationComplete = false; + this.packetStream = new ReadablePacketStream(); this.packetStream.on('data', (packet) => { this.logPacket('Received', packet); @@ -83,47 +86,37 @@ module.exports = class MessageIO extends EventEmitter { startTls(credentialsDetails, hostname, trustServerCertificate) { const credentials = tls.createSecureContext ? tls.createSecureContext(credentialsDetails) : crypto.createCredentials(credentialsDetails); - this.securePair = tls.createSecurePair(credentials); - this.tlsNegotiationComplete = false; - - this.securePair.on('secure', () => { - const cipher = this.securePair.cleartext.getCipher(); + const duplexpair = new DuplexPair(); + const securePair = this.securePair = { + cleartext: tls.connect({ + socket: duplexpair.socket1, + servername: hostname, + secureContext: credentials, + rejectUnauthorized: !trustServerCertificate + }), + encrypted: duplexpair.socket2 + }; - if (!trustServerCertificate) { - let verifyError = this.securePair.ssl.verifyError(); - - // Verify that server's identity matches it's certificate's names - if (!verifyError) { - verifyError = tls.checkServerIdentity(hostname, this.securePair.cleartext.getPeerCertificate()); - } - - if (verifyError) { - this.securePair.destroy(); - this.socket.destroy(verifyError); - return; - } + // If an error happens in the TLS layer, there is nothing we can do about it. + // Forward the error to the socket so the connection gets properly cleaned up. + securePair.cleartext.on('error', (err) => { + // Streams in node.js versions before 8.0.0 don't support `.destroy` + if (typeof securePair.encrypted.destroy === 'function') { + securePair.encrypted.destroy(); } + this.socket.destroy(err); + }); + securePair.cleartext.on('secureConnect', () => { + const cipher = securePair.cleartext.getCipher(); this.debug.log('TLS negotiated (' + cipher.name + ', ' + cipher.version + ')'); - this.emit('secure', this.securePair.cleartext); + this.emit('secure', securePair.cleartext); this.encryptAllFutureTraffic(); }); - this.securePair.encrypted.on('data', (data) => { + securePair.encrypted.on('data', (data) => { this.sendMessage(TYPE.PRELOGIN, data); }); - - // If an error happens in the TLS layer, there is nothing we can do about it. - // Forward the error to the socket so the connection gets properly cleaned up. - this.securePair.cleartext.on('error', (err) => { - this.socket.destroy(err); - }); - - // On Node >= 0.12, the encrypted stream automatically starts spewing out - // data once we attach a `data` listener. But on Node <= 0.10.x, this is not - // the case. We need to kick the cleartext stream once to get the - // encrypted end of the secure pair to emit the TLS handshake data. - this.securePair.cleartext.write(''); } encryptAllFutureTraffic() { diff --git a/test/integration/connection-retry-test.js b/test/integration/connection-retry-test.js index 860ab95f2..2d91f8a84 100644 --- a/test/integration/connection-retry-test.js +++ b/test/integration/connection-retry-test.js @@ -74,7 +74,7 @@ exports['connection retry tests'] = { const config = getConfig(); config.options.connectTimeout = config.options.connectionRetryInterval / 2; - const clock = this.sinon.useFakeTimers(); + const clock = this.sinon.useFakeTimers('setTimeout'); test.expect(1);