Skip to content

Commit 34d8935

Browse files
author
Zhen Li
authored
Merge pull request #176 from lutovich/1.1-trust-all-certificates
Remove ENCRYPTION_NON_LOCAL, deprecate TOFU, add TRUST_ALL_CERTIFICATES
2 parents 7cffacc + 32b2775 commit 34d8935

12 files changed

+190
-112
lines changed

src/v1/index.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,18 @@ let USER_AGENT = "neo4j-javascript/" + VERSION;
5858
* options are as follows:
5959
*
6060
* {
61-
* // Encryption level: one of ENCRYPTION_ON, ENCRYPTION_OFF or ENCRYPTION_NON_LOCAL.
62-
* // ENCRYPTION_NON_LOCAL is on by default in modern NodeJS installs,
63-
* // but off by default in the Web Bundle and old (<=1.0.0) NodeJS installs
64-
* // due to technical limitations on those platforms.
65-
* encrypted: ENCRYPTION_ON|ENCRYPTION_OFF|ENCRYPTION_NON_LOCAL
61+
* // Encryption level: ENCRYPTION_ON or ENCRYPTION_OFF.
62+
* encrypted: ENCRYPTION_ON|ENCRYPTION_OFF
6663
*
6764
* // Trust strategy to use if encryption is enabled. There is no mode to disable
6865
* // trust other than disabling encryption altogether. The reason for
6966
* // this is that if you don't know who you are talking to, it is easy for an
7067
* // attacker to hijack your encrypted connection, rendering encryption pointless.
7168
* //
72-
* // TRUST_ON_FIRST_USE is the default for modern NodeJS deployments, and works
69+
* // TRUST_ALL_CERTIFICATES is the default choice for NodeJS deployments. It only requires
70+
* // new host to provide a certificate and does no verification of the provided certificate.
71+
* //
72+
* // TRUST_ON_FIRST_USE is available for modern NodeJS deployments, and works
7373
* // similarly to how `ssl` works - the first time we connect to a new host,
7474
* // we remember the certificate they use. If the certificate ever changes, we
7575
* // assume it is an attempt to hijack the connection and require manual intervention.
@@ -84,8 +84,8 @@ let USER_AGENT = "neo4j-javascript/" + VERSION;
8484
* //
8585
* // TRUST_SYSTEM_CA_SIGNED_CERTIFICATES meand that you trust whatever certificates
8686
* // are in the default certificate chain of th
87-
* trust: "TRUST_ON_FIRST_USE" | "TRUST_SIGNED_CERTIFICATES" | TRUST_CUSTOM_CA_SIGNED_CERTIFICATES |
88-
* TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
87+
* trust: "TRUST_ALL_CERTIFICATES" | "TRUST_ON_FIRST_USE" | "TRUST_SIGNED_CERTIFICATES" |
88+
* "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES" | "TRUST_SYSTEM_CA_SIGNED_CERTIFICATES",
8989
*
9090
* // List of one or more paths to trusted encryption certificates. This only
9191
* // works in the NodeJS bundle, and only matters if you use "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES".

src/v1/internal/ch-node.js

+38-14
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@
1616
* See the License for the specific language governing permissions and
1717
* limitations under the License.
1818
*/
19-
20-
import net from 'net';
21-
import tls from 'tls';
22-
import fs from 'fs';
23-
import path from 'path';
24-
import {EOL} from 'os';
25-
import {NodeBuffer} from './buf';
26-
import {isLocalHost, ENCRYPTION_NON_LOCAL, ENCRYPTION_OFF} from './util';
27-
import {newError, SESSION_EXPIRED} from './../error';
19+
import net from "net";
20+
import tls from "tls";
21+
import fs from "fs";
22+
import path from "path";
23+
import {EOL} from "os";
24+
import {NodeBuffer} from "./buf";
25+
import {ENCRYPTION_OFF, isEmptyObjectOrNull} from "./util";
26+
import {newError, SESSION_EXPIRED} from "./../error";
2827

2928
let _CONNECTION_IDGEN = 0;
3029

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

107106
const TrustStrategy = {
108107
/**
109-
* @deprecated Since version 1.0. Will be deleted in a future version. TRUST_CUSTOM_CA_SIGNED_CERTIFICATES.
108+
* @deprecated Since version 1.0. Will be deleted in a future version. {@link #TRUST_CUSTOM_CA_SIGNED_CERTIFICATES}.
110109
*/
111110
TRUST_SIGNED_CERTIFICATES: function( opts, onSuccess, onFailure ) {
112111
console.log("`TRUST_SIGNED_CERTIFICATES` has been deprecated as option and will be removed in a future version of " +
@@ -119,7 +118,7 @@ const TrustStrategy = {
119118
"to verify trust for encrypted connections, but have not configured any " +
120119
"trustedCertificates. You must specify the path to at least one trusted " +
121120
"X.509 certificate for this to work. Two other alternatives is to use " +
122-
"TRUST_ON_FIRST_USE or to disable encryption by setting encrypted=\"" + ENCRYPTION_OFF + "\"" +
121+
"TRUST_ALL_CERTIFICATES or to disable encryption by setting encrypted=\"" + ENCRYPTION_OFF + "\"" +
123122
"in your driver configuration."));
124123
return;
125124
}
@@ -169,7 +168,13 @@ const TrustStrategy = {
169168
socket.on('error', onFailure);
170169
return socket;
171170
},
171+
/**
172+
* @deprecated in 1.1 in favour of {@link #TRUST_ALL_CERTIFICATES}. Will be deleted in a future version.
173+
*/
172174
TRUST_ON_FIRST_USE : function( opts, onSuccess, onFailure ) {
175+
console.log("`TRUST_ON_FIRST_USE` has been deprecated as option and will be removed in a future version of " +
176+
"the driver. Please use `TRUST_ALL_CERTIFICATES` instead.");
177+
173178
let tlsOpts = {
174179
// Because we manually verify the certificate against known_hosts
175180
rejectUnauthorized: false
@@ -221,21 +226,40 @@ const TrustStrategy = {
221226
});
222227
socket.on('error', onFailure);
223228
return socket;
229+
},
230+
231+
TRUST_ALL_CERTIFICATES: function (opts, onSuccess, onFailure) {
232+
const tlsOpts = {
233+
rejectUnauthorized: false
234+
};
235+
const socket = tls.connect(opts.port, opts.host, tlsOpts, function () {
236+
const certificate = socket.getPeerCertificate();
237+
if (isEmptyObjectOrNull(certificate)) {
238+
onFailure(newError("Secure connection was successful but server did not return any valid " +
239+
"certificates. Such connection can not be trusted. If you are just trying " +
240+
" Neo4j out and are not concerned about encryption, simply disable it using " +
241+
"`encrypted=\"" + ENCRYPTION_OFF + "\"` in the driver options. " +
242+
"Socket responded with: " + socket.authorizationError));
243+
} else {
244+
onSuccess();
245+
}
246+
});
247+
socket.on('error', onFailure);
248+
return socket;
224249
}
225250
};
226251

227252
function connect( opts, onSuccess, onFailure=(()=>null) ) {
228253
//still allow boolean for backwards compatibility
229-
if (opts.encrypted === false || opts.encrypted === ENCRYPTION_OFF ||
230-
(opts.encrypted === ENCRYPTION_NON_LOCAL && isLocalHost(opts.host))) {
254+
if (opts.encrypted === false || opts.encrypted === ENCRYPTION_OFF) {
231255
var conn = net.connect(opts.port, opts.host, onSuccess);
232256
conn.on('error', onFailure);
233257
return conn;
234258
} else if( TrustStrategy[opts.trust]) {
235259
return TrustStrategy[opts.trust](opts, onSuccess, onFailure);
236260
} else {
237261
onFailure(newError("Unknown trust strategy: " + opts.trust + ". Please use either " +
238-
"trust:'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES' or trust:'TRUST_ON_FIRST_USE' in your driver " +
262+
"trust:'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES' or trust:'TRUST_ALL_CERTIFICATES' in your driver " +
239263
"configuration. Alternatively, you can disable encryption by setting " +
240264
"`encrypted:\"" + ENCRYPTION_OFF + "\"`. There is no mechanism to use encryption without trust verification, " +
241265
"because this incurs the overhead of encryption without improving security. If " +

src/v1/internal/ch-websocket.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@
1717
* limitations under the License.
1818
*/
1919

20-
import debug from "./log";
21-
import {HeapBuffer} from "./buf";
20+
import {HeapBuffer} from './buf';
2221
import {newError} from './../error';
23-
import {isLocalHost, ENCRYPTION_NON_LOCAL, ENCRYPTION_ON, ENCRYPTION_OFF} from './util';
22+
import {ENCRYPTION_ON, ENCRYPTION_OFF} from './util';
2423

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

4645
let scheme = "ws";
4746
//Allow boolean for backwards compatibility
48-
if( opts.encrypted === true || opts.encrypted === ENCRYPTION_ON ||
49-
(opts.encrypted === ENCRYPTION_NON_LOCAL && !isLocalHost(opts.host)) ) {
47+
if( opts.encrypted === true || opts.encrypted === ENCRYPTION_ON) {
5048
if((!opts.trust) || opts.trust === "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES" ) {
5149
scheme = "wss";
5250
} else {

src/v1/internal/connector.js

+10-13
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,14 @@
1616
* See the License for the specific language governing permissions and
1717
* limitations under the License.
1818
*/
19-
20-
import WebSocketChannel from "./ch-websocket";
21-
import NodeChannel from "./ch-node";
19+
import WebSocketChannel from './ch-websocket';
20+
import NodeChannel from './ch-node';
2221
import {Dechunker, Chunker} from "./chunking";
23-
import hasFeature from "./features";
24-
import {Packer,Unpacker} from "./packstream";
25-
import {alloc, CombinedBuffer} from "./buf";
26-
import {Node, Relationship, UnboundRelationship, Path, PathSegment} from '../graph-types';
27-
import {int, isInt} from '../integer';
22+
import hasFeature from './features';
23+
import {Packer, Unpacker} from './packstream';
24+
import {alloc} from './buf';
25+
import {Node, Relationship, UnboundRelationship, Path, PathSegment} from '../graph-types'
2826
import {newError} from './../error';
29-
import {ENCRYPTION_NON_LOCAL, ENCRYPTION_OFF, shouldEncrypt} from './util';
3027

3128
let Channel;
3229
if( WebSocketChannel.available ) {
@@ -470,10 +467,10 @@ function connect( url, config = {}) {
470467
return new Connection( new Ch({
471468
host: parseHost(url),
472469
port: parsePort(url) || 7687,
473-
// Default to using ENCRYPTION_NON_LOCAL if trust-on-first-use is available
474-
encrypted : shouldEncrypt(config.encrypted, (hasFeature("trust_on_first_use") ? ENCRYPTION_NON_LOCAL : ENCRYPTION_OFF), parseHost(url)),
475-
// Default to using TRUST_ON_FIRST_USE if it is available
476-
trust : config.trust || (hasFeature("trust_on_first_use") ? "TRUST_ON_FIRST_USE" : "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES"),
470+
// Default to using encryption if trust-on-first-use is available
471+
encrypted : (config.encrypted == null) ? hasFeature("trust_all_certificates") : config.encrypted,
472+
// Default to using TRUST_ALL_CERTIFICATES if it is available
473+
trust : config.trust || (hasFeature("trust_all_certificates") ? "TRUST_ALL_CERTIFICATES" : "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES"),
477474
trustedCertificates : config.trustedCertificates || [],
478475
knownHosts : config.knownHosts
479476
}), url);

src/v1/internal/features.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,20 @@ const FEATURES = {
2626
// This is insane. We are verifying that we have a version of getPeerCertificate
2727
// that supports reading the whole certificate, eg this commit:
2828
// https://github.com/nodejs/node/commit/345c40b6
29-
let desc = require('tls').TLSSocket.prototype.getPeerCertificate;
30-
return desc.length >= 1;
29+
const getPeerCertificateFunction = require('tls').TLSSocket.prototype.getPeerCertificate;
30+
const numberOfParameters = getPeerCertificateFunction.length;
31+
return numberOfParameters >= 1;
3132
} catch( e ) {
3233
return false;
3334
}
35+
},
36+
trust_all_certificates: () => {
37+
try {
38+
const getPeerCertificateFunction = require('tls').TLSSocket.prototype.getPeerCertificate;
39+
return true;
40+
} catch (e) {
41+
return false;
42+
}
3443
}
3544
};
3645

src/v1/internal/util.js

+15-25
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,25 @@
1717
* limitations under the License.
1818
*/
1919

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

25-
function isLocalHost(host) {
26-
return LOCALHOST_MATCHER.test(host);
27-
}
23+
function isEmptyObjectOrNull(object) {
24+
if (!object) {
25+
return true;
26+
}
2827

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

4537
export {
46-
isLocalHost,
47-
shouldEncrypt,
38+
isEmptyObjectOrNull,
4839
ENCRYPTION_ON,
49-
ENCRYPTION_OFF,
50-
ENCRYPTION_NON_LOCAL
40+
ENCRYPTION_OFF
5141
}

src/v1/routing-driver.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import Integer from './integer'
3030
class RoutingDriver extends Driver {
3131

3232
constructor(url, userAgent, token = {}, config = {}) {
33-
super(url, userAgent, token, config);
33+
super(url, userAgent, token, RoutingDriver._validateConfig(config));
3434
this._clusterView = new ClusterView(new RoundRobinArray([url]));
3535
}
3636

@@ -148,6 +148,13 @@ class RoutingDriver extends Driver {
148148
this._pool.purge(url);
149149
this._clusterView.remove(url);
150150
}
151+
152+
static _validateConfig(config) {
153+
if(config.trust === 'TRUST_ON_FIRST_USE') {
154+
throw newError('The chosen trust mode is not compatible with a routing driver');
155+
}
156+
return config;
157+
}
151158
}
152159

153160
class ClusterView {

0 commit comments

Comments
 (0)