Skip to content

Commit 45ae671

Browse files
author
Zhen Li
authored
Merge pull request #103 from nigelsmall/1.1-encrypted-non-local
ENCRYPTED_NON_LOCAL
2 parents 3f3f0ab + a1e7979 commit 45ae671

File tree

10 files changed

+125
-29
lines changed

10 files changed

+125
-29
lines changed

src/v1/driver.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,11 @@ let USER_AGENT = "neo4j-javascript/" + VERSION;
174174
* options are as follows:
175175
*
176176
* {
177-
* // Enable TLS encryption. This is on by default in modern NodeJS installs,
177+
* // Encryption level: one of ENCRYPTION_ON, ENCRYPTION_OFF or ENCRYPTION_NON_LOCAL.
178+
* // ENCRYPTION_NON_LOCAL is on by default in modern NodeJS installs,
178179
* // but off by default in the Web Bundle and old (<=1.0.0) NodeJS installs
179180
* // due to technical limitations on those platforms.
180-
* encrypted: true|false,
181+
* encrypted: ENCRYPTION_ON|ENCRYPTION_OFF|ENCRYPTION_NON_LOCAL
181182
*
182183
* // Trust strategy to use if encryption is enabled. There is no mode to disable
183184
* // trust other than disabling encryption altogether. The reason for

src/v1/internal/ch-dummy.js

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class DummyChannel {
2929
constructor(opts) {
3030
this.written = [];
3131
}
32+
isEncrypted() {
33+
return false;
34+
}
3235
write( buf ) {
3336
this.written.push(buf);
3437
observer.updateInstance(this);

src/v1/internal/ch-node.js

+15-6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import fs from 'fs';
2323
import path from 'path';
2424
import {EOL} from 'os';
2525
import {NodeBuffer} from './buf';
26+
import {isLocalHost, ENCRYPTION_NON_LOCAL, ENCRYPTION_OFF} from './util';
2627
import {newError} from './../error';
2728

2829
let _CONNECTION_IDGEN = 0;
@@ -71,7 +72,7 @@ const TrustStrategy = {
7172
"to verify trust for encrypted connections, but have not configured any " +
7273
"trustedCertificates. You must specify the path to at least one trusted " +
7374
"X.509 certificate for this to work. Two other alternatives is to use " +
74-
"TRUST_ON_FIRST_USE or to disable encryption by setting encrypted=false " +
75+
"TRUST_ON_FIRST_USE or to disable encryption by setting encrypted=\"" + ENCRYPTION_OFF + "\"" +
7576
"in your driver configuration."));
7677
return;
7778
}
@@ -89,7 +90,8 @@ const TrustStrategy = {
8990
" the signing certificate, or the server certificate, to the list of certificates trusted by this driver" +
9091
" using `neo4j.v1.driver(.., { trustedCertificates:['path/to/certificate.crt']}). This " +
9192
" is a security measure to protect against man-in-the-middle attacks. If you are just trying " +
92-
" Neo4j out and are not concerned about encryption, simply disable it using `encrypted=false` in the driver" +
93+
" Neo4j out and are not concerned about encryption, simply disable it using `encrypted=\"" + ENCRYPTION_OFF +
94+
"\"` in the driver" +
9395
" options."));
9496
} else {
9597
onSuccess();
@@ -115,7 +117,7 @@ const TrustStrategy = {
115117
onFailure(newError("You are using a version of NodeJS that does not " +
116118
"support trust-on-first use encryption. You can either upgrade NodeJS to " +
117119
"a newer version, use `trust:TRUST_SIGNED_CERTIFICATES` in your driver " +
118-
"config instead, or disable encryption using `encrypted:false`."));
120+
"config instead, or disable encryption using `encrypted:\"" + ENCRYPTION_OFF+ "\"`."));
119121
return;
120122
}
121123

@@ -140,7 +142,7 @@ const TrustStrategy = {
140142
"update the file with the new certificate. You can configure which file the driver " +
141143
"should use to store this information by setting `knownHosts` to another path in " +
142144
"your driver configuration - and you can disable encryption there as well using " +
143-
"`encrypted:false`."))
145+
"`encrypted:\"" + ENCRYPTION_OFF + "\"`."))
144146
}
145147
});
146148
});
@@ -150,7 +152,9 @@ const TrustStrategy = {
150152
};
151153

152154
function connect( opts, onSuccess, onFailure=(()=>null) ) {
153-
if( opts.encrypted === false ) {
155+
//still allow boolean for backwards compatibility
156+
if (opts.encrypted === false || opts.encrypted === ENCRYPTION_OFF ||
157+
(opts.encrypted === ENCRYPTION_NON_LOCAL && isLocalHost(opts.host))) {
154158
var conn = net.connect(opts.port, opts.host, onSuccess);
155159
conn.on('error', onFailure);
156160
return conn;
@@ -160,7 +164,7 @@ function connect( opts, onSuccess, onFailure=(()=>null) ) {
160164
onFailure(newError("Unknown trust strategy: " + opts.trust + ". Please use either " +
161165
"trust:'TRUST_SIGNED_CERTIFICATES' or trust:'TRUST_ON_FIRST_USE' in your driver " +
162166
"configuration. Alternatively, you can disable encryption by setting " +
163-
"`encrypted:false`. There is no mechanism to use encryption without trust verification, " +
167+
"`encrypted:\"" + ENCRYPTION_OFF + "\"`. There is no mechanism to use encryption without trust verification, " +
164168
"because this incurs the overhead of encryption without improving security. If " +
165169
"the driver does not verify that the peer it is connected to is really Neo4j, it " +
166170
"is very easy for an attacker to bypass the encryption by pretending to be Neo4j."));
@@ -190,6 +194,7 @@ class NodeChannel {
190194
this._error = null;
191195
this._handleConnectionError = this._handleConnectionError.bind(this);
192196

197+
this._encrypted = opts.encrypted;
193198
this._conn = connect(opts, () => {
194199
if(!self._open) {
195200
return;
@@ -219,6 +224,10 @@ class NodeChannel {
219224
}
220225
}
221226

227+
isEncrypted() {
228+
return this._encrypted;
229+
}
230+
222231
/**
223232
* Write the passed in buffer to connection
224233
* @param {NodeBuffer} buffer - Buffer to write

src/v1/internal/ch-websocket.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import {debug} from "./log";
2121
import {HeapBuffer} from "./buf";
2222
import {newError} from './../error';
23+
import {isLocalHost, ENCRYPTION_NON_LOCAL, ENCRYPTION_ON, ENCRYPTION_OFF} from './util';
2324

2425
/**
2526
* Create a new WebSocketChannel to be used in web browsers.
@@ -40,15 +41,19 @@ class WebSocketChannel {
4041
this._error = null;
4142
this._handleConnectionError = this._handleConnectionError.bind(this);
4243

44+
this._encrypted = opts.encrypted;
45+
4346
let scheme = "ws";
44-
if( opts.encrypted ) {
47+
//Allow boolean for backwards compatibility
48+
if( opts.encrypted === true || opts.encrypted === ENCRYPTION_ON ||
49+
(opts.encrypted === ENCRYPTION_NON_LOCAL && !isLocalHost(opts.host))) {
4550
if( (!opts.trust) || opts.trust === "TRUST_SIGNED_CERTIFICATES" ) {
4651
scheme = "wss";
4752
} else {
4853
this._error = newError("The browser version of this driver only supports one trust " +
4954
"strategy, 'TRUST_SIGNED_CERTIFICATES'. "+opts.trust+" is not supported. Please " +
5055
"either use TRUST_SIGNED_CERTIFICATES or disable encryption by setting " +
51-
"`encrypted:false` in the driver configuration.");
56+
"`encrypted:\"" + ENCRYPTION_OFF + "\"` in the driver configuration.");
5257
return;
5358
}
5459
}
@@ -98,6 +103,10 @@ class WebSocketChannel {
98103
}
99104
}
100105
}
106+
107+
isEncrypted() {
108+
return this._encrypted;
109+
}
101110

102111
/**
103112
* Write the passed in buffer to connection

src/v1/internal/connector.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {alloc, CombinedBuffer} from "./buf";
2626
import GraphType from '../graph-types';
2727
import {int, isInt} from '../integer';
2828
import {newError} from './../error';
29+
import {ENCRYPTION_NON_LOCAL, ENCRYPTION_OFF, shouldEncrypt} from './util';
2930

3031
let Channel;
3132
if( WebSocketChannel.available ) {
@@ -404,6 +405,10 @@ class Connection {
404405
return !this._isBroken && this._ch._open;
405406
}
406407

408+
isEncrypted() {
409+
return this._ch.isEncrypted();
410+
}
411+
407412
/**
408413
* Call close on the channel.
409414
* @param {function} cb - Function to call on close.
@@ -429,9 +434,9 @@ function connect( url, config = {}) {
429434
return new Connection( new Ch({
430435
host: host(url),
431436
port: port(url) || 7687,
432-
// Default to using encryption if trust-on-first-use is available
433-
encrypted : (config.encrypted == null) ? hasFeature("trust_on_first_use") : config.encrypted,
434-
// Default to using trust-on-first-use if it is available
437+
// Default to using ENCRYPTION_NON_LOCAL if trust-on-first-use is available
438+
encrypted : shouldEncrypt(config.encrypted, (hasFeature("trust_on_first_use") ? ENCRYPTION_NON_LOCAL : ENCRYPTION_OFF), host(url)),
439+
// Default to using TRUST_ON_FIRST_USE if it is available
435440
trust : config.trust || (hasFeature("trust_on_first_use") ? "TRUST_ON_FIRST_USE" : "TRUST_SIGNED_CERTIFICATES"),
436441
trustedCertificates : config.trustedCertificates || [],
437442
knownHosts : config.knownHosts

src/v1/internal/util.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Copyright (c) 2002-2016 "Neo Technology,"
3+
* Network Engine for Objects in Lund AB [http://neotechnology.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
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";
24+
25+
function isLocalHost(host) {
26+
return LOCALHOST_MATCHER.test(host);
27+
}
28+
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);
43+
}
44+
45+
export {
46+
isLocalHost,
47+
shouldEncrypt,
48+
ENCRYPTION_ON,
49+
ENCRYPTION_OFF,
50+
ENCRYPTION_NON_LOCAL
51+
}

src/v1/session.js

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class Session {
4040
this._hasTx = false;
4141
}
4242

43+
isEncrypted() {
44+
return this._conn.isEncrypted();
45+
}
46+
4347
/**
4448
* Run Cypher statement
4549
* Could be called with a statement object i.e.: {statement: "MATCH ...", parameters: {param: 1}}

test/internal/tls.test.js

+20-6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var NodeChannel = require('../../lib/v1/internal/ch-node.js');
2020
var neo4j = require("../../lib/v1");
2121
var fs = require("fs");
2222
var hasFeature = require("../../lib/v1/internal/features");
23+
var isLocalHost = require("../../lib/v1/internal/util").isLocalHost;
2324

2425
describe('trust-signed-certificates', function() {
2526

@@ -34,15 +35,15 @@ describe('trust-signed-certificates', function() {
3435

3536
// Given
3637
driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {
37-
encrypted: true,
38+
encrypted: "ENCRYPTION_ON",
3839
trust: "TRUST_SIGNED_CERTIFICATES",
3940
trustedCertificates: ["test/resources/random.certificate"]
4041
});
4142

4243
// When
4344
driver.session().run( "RETURN 1").catch( function(err) {
44-
expect( err.message ).toContain( "Server certificate is not trusted" );
45-
done();
45+
expect( err.message ).toContain( "Server certificate is not trusted" );
46+
done();
4647
});
4748
});
4849

@@ -55,7 +56,7 @@ describe('trust-signed-certificates', function() {
5556

5657
// Given
5758
driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {
58-
encrypted: true,
59+
encrypted: "ENCRYPTION_ON",
5960
trust: "TRUST_SIGNED_CERTIFICATES",
6061
trustedCertificates: ["build/neo4j/certificates/neo4j.cert"]
6162
});
@@ -90,7 +91,7 @@ describe('trust-on-first-use', function() {
9091
}
9192

9293
driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {
93-
encrypted: true,
94+
encrypted: "ENCRYPTION_ON",
9495
trust: "TRUST_ON_FIRST_USE",
9596
knownHosts: knownHostsPath
9697
});
@@ -115,7 +116,7 @@ describe('trust-on-first-use', function() {
115116
var knownHostsPath = "test/resources/random_known_hosts";
116117

117118
driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {
118-
encrypted: true,
119+
encrypted: "ENCRYPTION_ON",
119120
trust: "TRUST_ON_FIRST_USE",
120121
knownHosts: knownHostsPath
121122
});
@@ -128,6 +129,19 @@ describe('trust-on-first-use', function() {
128129
});
129130
});
130131

132+
it('should detect localhost', function() {
133+
expect(isLocalHost('localhost')).toBe(true);
134+
expect(isLocalHost('LOCALHOST')).toBe(true);
135+
expect(isLocalHost('localHost')).toBe(true);
136+
expect(isLocalHost('127.0.0.1')).toBe(true);
137+
expect(isLocalHost('127.0.0.11')).toBe(true);
138+
expect(isLocalHost('127.1.0.0')).toBe(true);
139+
140+
expect(isLocalHost('172.1.0.0')).toBe(false);
141+
expect(isLocalHost('127.0.0.0.0')).toBe(false);
142+
expect(isLocalHost("google.com")).toBe(false);
143+
});
144+
131145
afterEach(function(){
132146
if( driver ) {
133147
driver.close();

test/v1/examples.test.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,8 @@ describe('examples', function() {
344344
var neo4j = neo4jv1;
345345
// tag::tls-require-encryption[]
346346
var driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {
347-
// In NodeJS, encryption is on by default. In the web bundle, it is off.
348-
encrypted:true
347+
//In NodeJS, encryption is ENCRYPTION_NON_LOCAL on by default. In the web bundle, it is ENCRYPTION_OFF.
348+
encrypted:"ENCRYPTION_ON"
349349
});
350350
// end::tls-require-encryption[]
351351
driver.close();
@@ -359,7 +359,7 @@ describe('examples', function() {
359359
// in NodeJS, trust-on-first-use is the default trust mode. In the browser
360360
// it is TRUST_SIGNED_CERTIFICATES.
361361
trust: "TRUST_ON_FIRST_USE",
362-
encrypted:true
362+
encrypted:"ENCRYPTION_NON_LOCAL"
363363
});
364364
// end::tls-trust-on-first-use[]
365365
driver.close();
@@ -374,7 +374,7 @@ describe('examples', function() {
374374
// in NodeJS. In the browser bundle the browsers list of trusted
375375
// certificates is used, due to technical limitations in some browsers.
376376
trustedCertificates : ["path/to/ca.crt"],
377-
encrypted:true
377+
encrypted:"ENCRYPTION_NON_LOCAL"
378378
});
379379
// end::tls-signed[]
380380
driver.close();
@@ -385,7 +385,7 @@ describe('examples', function() {
385385
// tag::connect-with-auth-disabled[]
386386
var driver = neo4j.driver("bolt://localhost", {
387387
// In NodeJS, encryption is on by default. In the web bundle, it is off.
388-
encrypted:true
388+
encrypted:"ENCRYPTION_NON_LOCAL"
389389
});
390390
// end::connect-with-auth-disabled[]
391391
driver.close();

0 commit comments

Comments
 (0)