1818 */
1919
2020import net from 'net' ;
21+ import tls from 'tls' ;
22+ import fs from 'fs' ;
23+ import path from 'path' ;
24+ import { EOL } from 'os' ;
2125import { NodeBuffer } from './buf' ;
26+ import { newError } from './error' ;
2227
2328let _CONNECTION_IDGEN = 0 ;
2429
25- /**
26- * In a Node.js environment the 'net' module is used
27- * as transport.
28- * @access private
29- */
30+ function userHome ( ) {
31+ // For some reason, Browserify chokes on shimming `process`. This code
32+ // will never get executed on the browser anyway, to just hack around it
33+ let getOutOfHereBrowserifyYoureDrunk = require ;
34+ let process = getOutOfHereBrowserifyYoureDrunk ( 'process' ) ;
35+
36+ return process . env [ ( process . platform == 'win32' ) ? 'USERPROFILE' : 'HOME' ] ;
37+ }
38+
39+ function loadFingerprint ( serverId , knownHostsPath , cb ) {
40+ if ( ! fs . existsSync ( knownHostsPath ) ) {
41+ cb ( null ) ;
42+ return ;
43+ }
44+ let found = false ;
45+ require ( 'readline' ) . createInterface ( {
46+ input : fs . createReadStream ( knownHostsPath )
47+ } ) . on ( 'line' , ( line ) => {
48+ if ( line . startsWith ( serverId ) ) {
49+ found = true ;
50+ cb ( line . split ( " " ) [ 1 ] ) ;
51+ }
52+ } ) . on ( 'close' , ( ) => {
53+ if ( ! found ) {
54+ cb ( null ) ;
55+ }
56+ } ) ;
57+ }
58+
59+ function storeFingerprint ( serverId , knownHostsPath , fingerprint ) {
60+ fs . appendFile ( knownHostsPath , serverId + " " + fingerprint + EOL , "utf8" ) ;
61+ }
62+
63+ const TrustStrategy = {
64+ TRUST_SIGNED_CERTIFICATES : function ( opts , onSuccess , onFailure ) {
65+ if ( ! opts . trustedCertificates || opts . trustedCertificates . length == 0 ) {
66+ onFailure ( newError ( "You are using TRUST_SIGNED_CERTIFICATES as the method " +
67+ "to verify trust for encrypted connections, but have not configured any " +
68+ "trustedCertificates. You must specify the path to at least one trusted " +
69+ "X.509 certificate for this to work. Two other alternatives is to use " +
70+ "TRUST_ON_FIRST_USE or to disable encryption by setting encrypted=false " +
71+ "in your driver configuration." ) ) ;
72+ return ;
73+ }
74+
75+ let tlsOpts = {
76+ ca : opts . trustedCertificates . map ( fs . readFileSync ) ,
77+ // Because we manually check for this in the connect callback, to give
78+ // a more helpful error to the user
79+ rejectUnauthorized : false
80+ } ;
81+
82+ let socket = tls . connect ( opts . port , opts . host , tlsOpts , function ( ) {
83+ if ( ! socket . authorized ) {
84+ onFailure ( newError ( "Server certificate is not trusted. If you trust the database you are connecting to, add" +
85+ " the signing certificate, or the server certificate, to the list of certificates trusted by this driver" +
86+ " using `neo4j.v1.driver(.., { trustedCertificates:['path/to/certificate.crt']}). This " +
87+ " is a security measure to protect against man-in-the-middle attacks. If you are just trying " +
88+ " Neo4j out and are not concerned about encryption, simply disable it using `encrypted=false` in the driver" +
89+ " options." ) ) ;
90+ } else {
91+ onSuccess ( ) ;
92+ }
93+ } ) ;
94+ return socket ;
95+ } ,
96+ TRUST_ON_FIRST_USE : function ( opts , onSuccess , onFailure ) {
97+ let tlsOpts = {
98+ // Because we manually verify the certificate against known_hosts
99+ rejectUnauthorized : false
100+ } ;
101+
102+ let socket = tls . connect ( opts . port , opts . host , tlsOpts , function ( ) {
103+ var serverCert = socket . getPeerCertificate ( true ) ;
104+
105+ if ( ! serverCert . raw ) {
106+ // If `raw` is not available, we're on an old version of NodeJS, and
107+ // the raw cert cannot be accessed (or, at least I couldn't find a way to)
108+ // therefore, we can't generate a SHA512 fingerprint, meaning we can't
109+ // do TOFU, and the safe approach is to fail.
110+ onFailure ( newError ( "You are using a version of NodeJS that does not " +
111+ "support trust-on-first use encryption. You can either upgrade NodeJS to " +
112+ "a newer version, use `trust:TRUST_SIGNED_CERTIFICATES` in your driver " +
113+ "config instead, or disable encryption using `encrypted:false`." ) ) ;
114+ return ;
115+ }
116+
117+ var serverFingerprint = require ( 'crypto' ) . createHash ( 'sha512' ) . update ( serverCert . raw ) . digest ( "hex" ) ;
118+ let knownHostsPath = opts . knownHosts || path . join ( userHome ( ) , ".neo4j" , "known_hosts" ) ;
119+ let serverId = opts . host + ":" + opts . port ;
120+
121+ loadFingerprint ( serverId , knownHostsPath , ( knownFingerprint ) => {
122+ if ( knownFingerprint === serverFingerprint ) {
123+ onSuccess ( ) ;
124+ } else if ( knownFingerprint == null ) {
125+ storeFingerprint ( serverId , knownHostsPath , serverFingerprint ) ;
126+ onSuccess ( ) ;
127+ } else {
128+ onFailure ( newError ( "Database encryption certificate has changed, and no longer " +
129+ "matches the certificate stored for " + serverId + " in `" + knownHostsPath +
130+ "`. As a security precaution, this driver will not automatically trust the new " +
131+ "certificate, because doing so would allow an attacker to pretend to be the Neo4j " +
132+ "instance we want to connect to. The certificate provided by the server looks like: " +
133+ serverCert + ". If you trust that this certificate is valid, simply remove the line " +
134+ "starting with " + serverId + " in `" + knownHostsPath + "`, and the driver will " +
135+ "update the file with the new certificate. You can configure which file the driver " +
136+ "should use to store this information by setting `knownHosts` to another path in " +
137+ "your driver configuration - and you can disable encryption there as well using " +
138+ "`encrypted:false`." ) )
139+ }
140+ } ) ;
141+ } ) ;
142+ return socket ;
143+ }
144+ } ;
145+
146+ function connect ( opts , onSuccess , onFailure = ( ( ) => null ) ) {
147+ if ( opts . encrypted === false ) {
148+ return net . connect ( opts . port , opts . host , onSuccess ) ;
149+ } else if ( TrustStrategy [ opts . trust ] ) {
150+ return TrustStrategy [ opts . trust ] ( opts , onSuccess , onFailure ) ;
151+ } else {
152+ onFailure ( newError ( "Unknown trust strategy: " + opts . trust + ". Please use either " +
153+ "trust:'TRUST_SIGNED_CERTIFICATES' or trust:'TRUST_ON_FIRST_USE' in your driver " +
154+ "configuration. Alternatively, you can disable encryption by setting " +
155+ "`encrypted:false`. There is no mechanism to use encryption without trust verification, " +
156+ "because this incurs the overhead of encryption without improving security. If " +
157+ "the driver does not verify that the peer it is connected to is really Neo4j, it " +
158+ "is very easy for an attacker to bypass the encryption by pretending to be Neo4j." ) ) ;
159+ }
160+ }
30161
162+ /**
163+ * In a Node.js environment the 'net' module is used
164+ * as transport.
165+ * @access private
166+ */
31167class NodeChannel {
32168
33169 /**
@@ -37,35 +173,42 @@ class NodeChannel {
37173 * @param {Integer } opts.port - The port to use.
38174 */
39175 constructor ( opts ) {
40- let _self = this ;
176+ let self = this ;
41177
42178 this . id = _CONNECTION_IDGEN ++ ;
43179 this . available = true ;
44180 this . _pending = [ ] ;
45181 this . _open = true ;
46- this . _conn = net . connect ( ( opts . port || 7687 ) , opts . host , ( ) => {
47- if ( ! _self . _open ) {
182+ this . _error = null ;
183+ this . _handleConnectionError = this . _handleConnectionError . bind ( this ) ;
184+
185+ this . _conn = connect ( opts , ( ) => {
186+ if ( ! self . _open ) {
48187 return ;
49188 }
189+
190+ self . _conn . on ( 'data' , ( buffer ) => {
191+ if ( self . onmessage ) {
192+ self . onmessage ( new NodeBuffer ( buffer ) ) ;
193+ }
194+ } ) ;
195+
196+ self . _conn . on ( 'error' , self . _handleConnectionError ) ;
197+
50198 // Drain all pending messages
51- let pending = _self . _pending ;
52- _self . _pending = null ;
199+ let pending = self . _pending ;
200+ self . _pending = null ;
53201 for ( let i = 0 ; i < pending . length ; i ++ ) {
54- _self . write ( pending [ i ] ) ;
202+ self . write ( pending [ i ] ) ;
55203 }
56- } ) ;
57-
58- this . _conn . on ( 'data' , ( buffer ) => {
59- if ( _self . onmessage ) {
60- _self . onmessage ( new NodeBuffer ( buffer ) ) ;
61- }
62- } ) ;
204+ } , this . _handleConnectionError ) ;
205+ }
63206
64- this . _conn . on ( 'error' , function ( err ) {
65- if ( _self . onerror ) {
66- _self . onerror ( err ) ;
67- }
68- } ) ;
207+ _handleConnectionError ( err ) {
208+ this . _error = err ;
209+ if ( this . onerror ) {
210+ this . onerror ( err ) ;
211+ }
69212 }
70213
71214 /**
@@ -89,12 +232,14 @@ class NodeChannel {
89232 * Close the connection
90233 * @param {function } cb - Function to call on close.
91234 */
92- close ( cb ) {
93- if ( cb ) {
235+ close ( cb = ( ( ) => null ) ) {
236+ this . _open = false ;
237+ if ( this . _conn ) {
238+ this . _conn . end ( ) ;
94239 this . _conn . on ( 'end' , cb ) ;
240+ } else {
241+ cb ( ) ;
95242 }
96- this . _open = false ;
97- this . _conn . end ( ) ;
98243 }
99244}
100245let _nodeChannelModule = { channel : NodeChannel , available : true } ;
0 commit comments