UNPKG

neo4j-driver-bolt-connection

Version:

Implements the connection with the Neo4j Database using the Bolt Protocol

359 lines (358 loc) 16.4 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /** * Copyright (c) "Neo4j" * Neo4j Sweden AB [https://neo4j.com] * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var net_1 = __importDefault(require("net")); var tls_1 = __importDefault(require("tls")); var fs_1 = __importDefault(require("fs")); var channel_buf_1 = __importDefault(require("../channel-buf")); var neo4j_driver_core_1 = require("neo4j-driver-core"); var _a = neo4j_driver_core_1.internal.util, ENCRYPTION_OFF = _a.ENCRYPTION_OFF, ENCRYPTION_ON = _a.ENCRYPTION_ON, isEmptyObjectOrNull = _a.isEmptyObjectOrNull; var _CONNECTION_IDGEN = 0; var TrustStrategy = { TRUST_CUSTOM_CA_SIGNED_CERTIFICATES: function (config, onSuccess, onFailure) { if (!config.trustedCertificates || config.trustedCertificates.length === 0) { onFailure((0, neo4j_driver_core_1.newError)('You are using TRUST_CUSTOM_CA_SIGNED_CERTIFICATES as the method ' + 'to verify trust for encrypted connections, but have not configured any ' + 'trustedCertificates. You must specify the path to at least one trusted ' + 'X.509 certificate for this to work. Two other alternatives is to use ' + 'TRUST_ALL_CERTIFICATES or to disable encryption by setting encrypted="' + ENCRYPTION_OFF + '"' + 'in your driver configuration.')); return; } var tlsOpts = newTlsOptions(config.address.host(), config.trustedCertificates.map(function (f) { return fs_1.default.readFileSync(f); }), config.clientCertificate); var socket = tls_1.default.connect(config.address.port(), config.address.resolvedHost(), tlsOpts, function () { if (!socket.authorized) { onFailure((0, neo4j_driver_core_1.newError)('Server certificate is not trusted. If you trust the database you are connecting to, add' + ' the signing certificate, or the server certificate, to the list of certificates trusted by this driver' + " using `neo4j.driver(.., { trustedCertificates:['path/to/certificate.crt']}). This " + ' is a security measure to protect against man-in-the-middle attacks. If you are just trying ' + ' Neo4j out and are not concerned about encryption, simply disable it using `encrypted="' + ENCRYPTION_OFF + '"`' + ' in the driver options. Socket responded with: ' + socket.authorizationError)); } else { onSuccess(); } }); socket.on('error', onFailure); return configureSocket(socket); }, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES: function (config, onSuccess, onFailure) { var tlsOpts = newTlsOptions(config.address.host(), undefined, config.clientCertificate); var socket = tls_1.default.connect(config.address.port(), config.address.resolvedHost(), tlsOpts, function () { if (!socket.authorized) { onFailure((0, neo4j_driver_core_1.newError)('Server certificate is not trusted. If you trust the database you are connecting to, use ' + 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES and add' + ' the signing certificate, or the server certificate, to the list of certificates trusted by this driver' + " using `neo4j.driver(.., { trustedCertificates:['path/to/certificate.crt']}). This " + ' is a security measure to protect against man-in-the-middle attacks. If you are just trying ' + ' Neo4j out and are not concerned about encryption, simply disable it using `encrypted="' + ENCRYPTION_OFF + '"`' + ' in the driver options. Socket responded with: ' + socket.authorizationError)); } else { onSuccess(); } }); socket.on('error', onFailure); return configureSocket(socket); }, TRUST_ALL_CERTIFICATES: function (config, onSuccess, onFailure) { var tlsOpts = newTlsOptions(config.address.host(), undefined, config.clientCertificate); var socket = tls_1.default.connect(config.address.port(), config.address.resolvedHost(), tlsOpts, function () { var certificate = socket.getPeerCertificate(); if (isEmptyObjectOrNull(certificate)) { onFailure((0, neo4j_driver_core_1.newError)('Secure connection was successful but server did not return any valid ' + 'certificates. Such connection can not be trusted. If you are just trying ' + ' Neo4j out and are not concerned about encryption, simply disable it using ' + '`encrypted="' + ENCRYPTION_OFF + '"` in the driver options. ' + 'Socket responded with: ' + socket.authorizationError)); } else { onSuccess(); } }); socket.on('error', onFailure); return configureSocket(socket); } }; /** * Connect using node socket. * @param {ChannelConfig} config - configuration of this channel. * @param {function} onSuccess - callback to execute on connection success. * @param {function} onFailure - callback to execute on connection failure. * @return {*} socket connection. */ function _connect(config, onSuccess, onFailure) { if (onFailure === void 0) { onFailure = function () { return null; }; } var trustStrategy = trustStrategyName(config); if (!isEncrypted(config)) { var socket = net_1.default.connect(config.address.port(), config.address.resolvedHost(), onSuccess); socket.on('error', onFailure); return configureSocket(socket); } else if (TrustStrategy[trustStrategy]) { return TrustStrategy[trustStrategy](config, onSuccess, onFailure); } else { onFailure((0, neo4j_driver_core_1.newError)('Unknown trust strategy: ' + config.trust + '. Please use either ' + "trust:'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES' or trust:'TRUST_ALL_CERTIFICATES' in your driver " + 'configuration. Alternatively, you can disable encryption by setting ' + '`encrypted:"' + ENCRYPTION_OFF + '"`. There is no mechanism to use encryption without trust verification, ' + 'because this incurs the overhead of encryption without improving security. If ' + 'the driver does not verify that the peer it is connected to is really Neo4j, it ' + 'is very easy for an attacker to bypass the encryption by pretending to be Neo4j.')); } } function isEncrypted(config) { var encryptionNotConfigured = config.encrypted == null || config.encrypted === undefined; if (encryptionNotConfigured) { // default to using encryption if trust-all-certificates is available return false; } return config.encrypted === true || config.encrypted === ENCRYPTION_ON; } function trustStrategyName(config) { if (config.trust) { return config.trust; } return 'TRUST_SYSTEM_CA_SIGNED_CERTIFICATES'; } /** * Create a new configuration options object for the {@code tls.connect()} call. * @param {string} hostname the target hostname. * @param {string|undefined} ca an optional CA. * @param {string|undefined} cert an optional client cert. * @param {string|undefined} key an optional client cert key. * @param {string|undefined} passphrase an optional client cert passphrase * @return {Object} a new options object. */ function newTlsOptions(hostname, ca, clientCertificate) { if (ca === void 0) { ca = undefined; } if (clientCertificate === void 0) { clientCertificate = undefined; } return __assign({ rejectUnauthorized: false, servername: (net_1.default.isIPv4(hostname) || net_1.default.isIPv6(hostname)) ? undefined : hostname, // server name for the SNI (Server Name Indication) TLS extension ca: ca }, clientCertificate); } /** * Update socket options for the newly created socket. Accepts either `net.Socket` or its subclass `tls.TLSSocket`. * @param {net.Socket} socket the socket to configure. * @return {net.Socket} the given socket. */ function configureSocket(socket) { socket.setKeepAlive(true); return socket; } /** * In a Node.js environment the 'net' module is used * as transport. * @access private */ var NodeChannel = /** @class */ (function () { /** * Create new instance * @param {ChannelConfig} config - configuration for this channel. */ function NodeChannel(config, connect) { if (connect === void 0) { connect = _connect; } var self = this; this.id = _CONNECTION_IDGEN++; this._pending = []; this._open = true; this._error = null; this._handleConnectionError = this._handleConnectionError.bind(this); this._handleConnectionTerminated = this._handleConnectionTerminated.bind(this); this._connectionErrorCode = config.connectionErrorCode; this._receiveTimeout = null; this._receiveTimeoutStarted = false; this._conn = connect(config, function () { if (!self._open) { return; } self._conn.on('data', function (buffer) { if (self.onmessage) { self.onmessage(new channel_buf_1.default(buffer)); } }); self._conn.on('end', self._handleConnectionTerminated); // Drain all pending messages var pending = self._pending; self._pending = null; for (var i = 0; i < pending.length; i++) { self.write(pending[i]); } }, this._handleConnectionError); this._setupConnectionTimeout(config, this._conn); } NodeChannel.prototype._handleConnectionError = function (err) { var msg = 'Failed to connect to server. ' + 'Please ensure that your database is listening on the correct host and port ' + 'and that you have compatible encryption settings both on Neo4j server and driver. ' + 'Note that the default encryption setting has changed in Neo4j 4.0.'; if (err.message) msg += ' Caused by: ' + err.message; if (this._conn.destroyed) { this._open = false; } this._error = (0, neo4j_driver_core_1.newError)(msg, this._connectionErrorCode); if (this.onerror) { this.onerror(this._error); } }; NodeChannel.prototype._handleConnectionTerminated = function () { this._open = false; this._error = (0, neo4j_driver_core_1.newError)('Connection was closed by server', this._connectionErrorCode); if (this.onerror) { this.onerror(this._error); } }; /** * Setup connection timeout on the socket, if configured. * @param {ChannelConfig} config - configuration of this channel. * @param {Object} socket - `net.Socket` or `tls.TLSSocket` object. * @private */ NodeChannel.prototype._setupConnectionTimeout = function (config, socket) { var _this = this; var timeout = config.connectionTimeout; if (timeout) { var connectListener_1 = function () { // connected - clear connection timeout socket.setTimeout(0); }; var timeoutListener_1 = function () { // timeout fired - not connected within configured time. cancel timeout and destroy socket socket.setTimeout(0); socket.destroy((0, neo4j_driver_core_1.newError)("Failed to establish connection in ".concat(timeout, "ms"), config.connectionErrorCode)); }; socket.on('connect', connectListener_1); socket.on('timeout', timeoutListener_1); this._removeConnectionTimeoutListeners = function () { _this._conn.off('connect', connectListener_1); _this._conn.off('timeout', timeoutListener_1); }; socket.setTimeout(timeout); } }; /** * Setup the receive timeout for the channel. * * @param {number} receiveTimeout How long the channel will wait for receiving data before timing out (ms) * @returns {void} */ NodeChannel.prototype.setupReceiveTimeout = function (receiveTimeout) { var _this = this; if (this._removeConnectionTimeoutListeners) { this._removeConnectionTimeoutListeners(); } this._conn.on('timeout', function () { _this._conn.destroy((0, neo4j_driver_core_1.newError)("Connection lost. Server didn't respond in ".concat(receiveTimeout, "ms"), _this._connectionErrorCode)); }); this._receiveTimeout = receiveTimeout; }; /** * Stops the receive timeout for the channel. */ NodeChannel.prototype.stopReceiveTimeout = function () { if (this._receiveTimeout !== null && this._receiveTimeoutStarted) { this._receiveTimeoutStarted = false; this._conn.setTimeout(0); } }; /** * Start the receive timeout for the channel. */ NodeChannel.prototype.startReceiveTimeout = function () { if (this._receiveTimeout !== null && !this._receiveTimeoutStarted) { this._receiveTimeoutStarted = true; this._conn.setTimeout(this._receiveTimeout); } }; /** * Write the passed in buffer to connection * @param {ChannelBuffer} buffer - Buffer to write */ NodeChannel.prototype.write = function (buffer) { // If there is a pending queue, push this on that queue. This means // we are not yet connected, so we queue things locally. if (this._pending !== null) { this._pending.push(buffer); } else if (buffer instanceof channel_buf_1.default) { this._conn.write(buffer._buffer); } else { throw (0, neo4j_driver_core_1.newError)("Don't know how to write: " + buffer); } }; /** * Close the connection * @returns {Promise} A promise that will be resolved after channel is closed */ NodeChannel.prototype.close = function () { var _this = this; return new Promise(function (resolve, reject) { var cleanup = function () { if (!_this._conn.destroyed) { _this._conn.destroy(); } resolve(); }; if (_this._open) { _this._open = false; _this._conn.removeListener('end', _this._handleConnectionTerminated); _this._conn.on('end', function () { return cleanup(); }); _this._conn.on('close', function () { return cleanup(); }); _this._conn.end(); _this._conn.destroy(); } else { cleanup(); } }); }; return NodeChannel; }()); exports.default = NodeChannel;