Skip to content

Remove ENCRYPTION_NON_LOCAL, deprecate TOFU, add TRUST_ALL_CERTIFICATES #176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/v1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,18 @@ let USER_AGENT = "neo4j-javascript/" + VERSION;
* options are as follows:
*
* {
* // Encryption level: one of ENCRYPTION_ON, ENCRYPTION_OFF or ENCRYPTION_NON_LOCAL.
* // ENCRYPTION_NON_LOCAL is on by default in modern NodeJS installs,
* // but off by default in the Web Bundle and old (<=1.0.0) NodeJS installs
* // due to technical limitations on those platforms.
* encrypted: ENCRYPTION_ON|ENCRYPTION_OFF|ENCRYPTION_NON_LOCAL
* // Encryption level: ENCRYPTION_ON or ENCRYPTION_OFF.
* encrypted: ENCRYPTION_ON|ENCRYPTION_OFF
*
* // Trust strategy to use if encryption is enabled. There is no mode to disable
* // trust other than disabling encryption altogether. The reason for
* // this is that if you don't know who you are talking to, it is easy for an
* // attacker to hijack your encrypted connection, rendering encryption pointless.
* //
* // TRUST_ON_FIRST_USE is the default for modern NodeJS deployments, and works
* // TRUST_ALL_CERTIFICATES is the default choice for NodeJS deployments. It only requires
* // new host to provide a certificate and does no verification of the provided certificate.
* //
* // TRUST_ON_FIRST_USE is available for modern NodeJS deployments, and works
* // similarly to how `ssl` works - the first time we connect to a new host,
* // we remember the certificate they use. If the certificate ever changes, we
* // assume it is an attempt to hijack the connection and require manual intervention.
Expand All @@ -84,8 +84,8 @@ let USER_AGENT = "neo4j-javascript/" + VERSION;
* //
* // TRUST_SYSTEM_CA_SIGNED_CERTIFICATES meand that you trust whatever certificates
* // are in the default certificate chain of th
* trust: "TRUST_ON_FIRST_USE" | "TRUST_SIGNED_CERTIFICATES" | TRUST_CUSTOM_CA_SIGNED_CERTIFICATES |
* TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
* trust: "TRUST_ALL_CERTIFICATES" | "TRUST_ON_FIRST_USE" | "TRUST_SIGNED_CERTIFICATES" |
* "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES" | "TRUST_SYSTEM_CA_SIGNED_CERTIFICATES",
*
* // List of one or more paths to trusted encryption certificates. This only
* // works in the NodeJS bundle, and only matters if you use "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES".
Expand Down
52 changes: 38 additions & 14 deletions src/v1/internal/ch-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import net from 'net';
import tls from 'tls';
import fs from 'fs';
import path from 'path';
import {EOL} from 'os';
import {NodeBuffer} from './buf';
import {isLocalHost, ENCRYPTION_NON_LOCAL, ENCRYPTION_OFF} from './util';
import {newError, SESSION_EXPIRED} from './../error';
import net from "net";
import tls from "tls";
import fs from "fs";
import path from "path";
import {EOL} from "os";
import {NodeBuffer} from "./buf";
import {ENCRYPTION_OFF, isEmptyObjectOrNull} from "./util";
import {newError, SESSION_EXPIRED} from "./../error";

let _CONNECTION_IDGEN = 0;

Expand Down Expand Up @@ -106,7 +105,7 @@ function storeFingerprint( serverId, knownHostsPath, fingerprint, cb ) {

const TrustStrategy = {
/**
* @deprecated Since version 1.0. Will be deleted in a future version. TRUST_CUSTOM_CA_SIGNED_CERTIFICATES.
* @deprecated Since version 1.0. Will be deleted in a future version. {@link #TRUST_CUSTOM_CA_SIGNED_CERTIFICATES}.
*/
TRUST_SIGNED_CERTIFICATES: function( opts, onSuccess, onFailure ) {
console.log("`TRUST_SIGNED_CERTIFICATES` has been deprecated as option and will be removed in a future version of " +
Expand All @@ -119,7 +118,7 @@ const TrustStrategy = {
"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_ON_FIRST_USE or to disable encryption by setting encrypted=\"" + ENCRYPTION_OFF + "\"" +
"TRUST_ALL_CERTIFICATES or to disable encryption by setting encrypted=\"" + ENCRYPTION_OFF + "\"" +
"in your driver configuration."));
return;
}
Expand Down Expand Up @@ -169,7 +168,13 @@ const TrustStrategy = {
socket.on('error', onFailure);
return socket;
},
/**
* @deprecated in 1.1 in favour of {@link #TRUST_ALL_CERTIFICATES}. Will be deleted in a future version.
*/
TRUST_ON_FIRST_USE : function( opts, onSuccess, onFailure ) {
console.log("`TRUST_ON_FIRST_USE` has been deprecated as option and will be removed in a future version of " +
"the driver. Please use `TRUST_ALL_CERTIFICATES` instead.");

let tlsOpts = {
// Because we manually verify the certificate against known_hosts
rejectUnauthorized: false
Expand Down Expand Up @@ -221,21 +226,40 @@ const TrustStrategy = {
});
socket.on('error', onFailure);
return socket;
},

TRUST_ALL_CERTIFICATES: function (opts, onSuccess, onFailure) {
const tlsOpts = {
rejectUnauthorized: false
};
const socket = tls.connect(opts.port, opts.host, tlsOpts, function () {
const certificate = socket.getPeerCertificate();
if (isEmptyObjectOrNull(certificate)) {
onFailure(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 socket;
}
};

function connect( opts, onSuccess, onFailure=(()=>null) ) {
//still allow boolean for backwards compatibility
if (opts.encrypted === false || opts.encrypted === ENCRYPTION_OFF ||
(opts.encrypted === ENCRYPTION_NON_LOCAL && isLocalHost(opts.host))) {
if (opts.encrypted === false || opts.encrypted === ENCRYPTION_OFF) {
var conn = net.connect(opts.port, opts.host, onSuccess);
conn.on('error', onFailure);
return conn;
} else if( TrustStrategy[opts.trust]) {
return TrustStrategy[opts.trust](opts, onSuccess, onFailure);
} else {
onFailure(newError("Unknown trust strategy: " + opts.trust + ". Please use either " +
"trust:'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES' or trust:'TRUST_ON_FIRST_USE' in your driver " +
"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 " +
Expand Down
8 changes: 3 additions & 5 deletions src/v1/internal/ch-websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@
* limitations under the License.
*/

import debug from "./log";
import {HeapBuffer} from "./buf";
import {HeapBuffer} from './buf';
import {newError} from './../error';
import {isLocalHost, ENCRYPTION_NON_LOCAL, ENCRYPTION_ON, ENCRYPTION_OFF} from './util';
import {ENCRYPTION_ON, ENCRYPTION_OFF} from './util';

/**
* Create a new WebSocketChannel to be used in web browsers.
Expand All @@ -45,8 +44,7 @@ class WebSocketChannel {

let scheme = "ws";
//Allow boolean for backwards compatibility
if( opts.encrypted === true || opts.encrypted === ENCRYPTION_ON ||
(opts.encrypted === ENCRYPTION_NON_LOCAL && !isLocalHost(opts.host)) ) {
if( opts.encrypted === true || opts.encrypted === ENCRYPTION_ON) {
if((!opts.trust) || opts.trust === "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES" ) {
scheme = "wss";
} else {
Expand Down
23 changes: 10 additions & 13 deletions src/v1/internal/connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import WebSocketChannel from "./ch-websocket";
import NodeChannel from "./ch-node";
import WebSocketChannel from './ch-websocket';
import NodeChannel from './ch-node';
import {Dechunker, Chunker} from "./chunking";
import hasFeature from "./features";
import {Packer,Unpacker} from "./packstream";
import {alloc, CombinedBuffer} from "./buf";
import {Node, Relationship, UnboundRelationship, Path, PathSegment} from '../graph-types';
import {int, isInt} from '../integer';
import hasFeature from './features';
import {Packer, Unpacker} from './packstream';
import {alloc} from './buf';
import {Node, Relationship, UnboundRelationship, Path, PathSegment} from '../graph-types'
import {newError} from './../error';
import {ENCRYPTION_NON_LOCAL, ENCRYPTION_OFF, shouldEncrypt} from './util';

let Channel;
if( WebSocketChannel.available ) {
Expand Down Expand Up @@ -470,10 +467,10 @@ function connect( url, config = {}) {
return new Connection( new Ch({
host: parseHost(url),
port: parsePort(url) || 7687,
// Default to using ENCRYPTION_NON_LOCAL if trust-on-first-use is available
encrypted : shouldEncrypt(config.encrypted, (hasFeature("trust_on_first_use") ? ENCRYPTION_NON_LOCAL : ENCRYPTION_OFF), parseHost(url)),
// Default to using TRUST_ON_FIRST_USE if it is available
trust : config.trust || (hasFeature("trust_on_first_use") ? "TRUST_ON_FIRST_USE" : "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES"),
// Default to using encryption if trust-on-first-use is available
encrypted : (config.encrypted == null) ? hasFeature("trust_all_certificates") : config.encrypted,
// Default to using TRUST_ALL_CERTIFICATES if it is available
trust : config.trust || (hasFeature("trust_all_certificates") ? "TRUST_ALL_CERTIFICATES" : "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES"),
trustedCertificates : config.trustedCertificates || [],
knownHosts : config.knownHosts
}), url);
Expand Down
13 changes: 11 additions & 2 deletions src/v1/internal/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,20 @@ const FEATURES = {
// This is insane. We are verifying that we have a version of getPeerCertificate
// that supports reading the whole certificate, eg this commit:
// https://github.com/nodejs/node/commit/345c40b6
let desc = require('tls').TLSSocket.prototype.getPeerCertificate;
return desc.length >= 1;
const getPeerCertificateFunction = require('tls').TLSSocket.prototype.getPeerCertificate;
const numberOfParameters = getPeerCertificateFunction.length;
return numberOfParameters >= 1;
} catch( e ) {
return false;
}
},
trust_all_certificates: () => {
try {
const getPeerCertificateFunction = require('tls').TLSSocket.prototype.getPeerCertificate;
return true;
} catch (e) {
return false;
}
}
};

Expand Down
40 changes: 15 additions & 25 deletions src/v1/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,25 @@
* limitations under the License.
*/

let LOCALHOST_MATCHER = /^(localhost|127(\.\d+){3})$/i;
let ENCRYPTION_ON = "ENCRYPTION_ON";
let ENCRYPTION_OFF = "ENCRYPTION_OFF";
let ENCRYPTION_NON_LOCAL = "ENCRYPTION_NON_LOCAL";
const ENCRYPTION_ON = "ENCRYPTION_ON";
const ENCRYPTION_OFF = "ENCRYPTION_OFF";

function isLocalHost(host) {
return LOCALHOST_MATCHER.test(host);
}
function isEmptyObjectOrNull(object) {
if (!object) {
return true;
}

/* Coerce an encryption setting to a definitive boolean value,
* given a valid default and a target host. If encryption is
* explicitly set on or off, then the mapping is a simple
* conversion to true or false respectively. If set to
* ENCRYPTION_NON_LOCAL then respond according to whether or
* not the host is localhost/127.x.x.x. In all other cases
* (including undefined) then fall back to the default and
* re-evaluate.
*/
function shouldEncrypt(encryption, encryptionDefault, host) {
if (encryption === ENCRYPTION_ON || encryption === true) return true;
if (encryption === ENCRYPTION_OFF || encryption === false) return false;
if (encryption === ENCRYPTION_NON_LOCAL) return !isLocalHost(host);
return shouldEncrypt(encryptionDefault, ENCRYPTION_OFF, host);
for (let prop in object) {
if (object.hasOwnProperty(prop)) {
return false;
}
}

return true;
}

export {
isLocalHost,
shouldEncrypt,
isEmptyObjectOrNull,
ENCRYPTION_ON,
ENCRYPTION_OFF,
ENCRYPTION_NON_LOCAL
ENCRYPTION_OFF
}
9 changes: 8 additions & 1 deletion src/v1/routing-driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import Integer from './integer'
class RoutingDriver extends Driver {

constructor(url, userAgent, token = {}, config = {}) {
super(url, userAgent, token, config);
super(url, userAgent, token, RoutingDriver._validateConfig(config));
this._clusterView = new ClusterView(new RoundRobinArray([url]));
}

Expand Down Expand Up @@ -148,6 +148,13 @@ class RoutingDriver extends Driver {
this._pool.purge(url);
this._clusterView.remove(url);
}

static _validateConfig(config) {
if(config.trust === 'TRUST_ON_FIRST_USE') {
throw newError('The chosen trust mode is not compatible with a routing driver');
}
return config;
}
}

class ClusterView {
Expand Down
Loading