Skip to content

Commit a381db9

Browse files
committed
Update config API for TLS to match DIP.
This modifes the Config API to talk about encryption rather than TLS, as well as renaming the on/off and the tls auth config to EncryptionLevel and TrustStrategy, respectively.
1 parent c6d01dd commit a381db9

24 files changed

+470
-292
lines changed

driver/src/main/java/org/neo4j/driver/internal/connector/socket/SSLContextFactory.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,34 @@
2727
import javax.net.ssl.TrustManagerFactory;
2828

2929
import org.neo4j.driver.v1.Config;
30+
import org.neo4j.driver.v1.exceptions.ClientException;
31+
import org.neo4j.driver.internal.spi.Logger;
3032

3133
import static org.neo4j.driver.internal.util.CertificateTool.loadX509Cert;
3234

3335
class SSLContextFactory
3436
{
35-
3637
private final String host;
3738
private final int port;
38-
private final Config.TlsAuthenticationConfig authConfig;
39+
private final Config.TrustStrategy authConfig;
40+
private final Logger logger;
3941

40-
SSLContextFactory( String host, int port, Config.TlsAuthenticationConfig authConfig )
42+
SSLContextFactory( String host, int port, Config.TrustStrategy authConfig, Logger logger )
4143
{
4244
this.host = host;
4345
this.port = port;
4446
this.authConfig = authConfig;
47+
this.logger = logger;
4548
}
4649

4750
public SSLContext create()
4851
throws GeneralSecurityException, IOException
4952
{
5053
SSLContext sslContext = SSLContext.getInstance( "TLS" );
54+
TrustManager[] trustManagers;
5155

52-
// TODO Do we also want the server to verify the client's cert, a.k.a mutual authentication?
53-
// Ref: http://logicoy.com/blogs/ssl-keystore-truststore-and-mutual-authentication/
54-
KeyManager[] keyManagers = new KeyManager[0];
55-
TrustManager[] trustManagers = null;
56-
57-
if ( authConfig.isFullAuthEnabled() )
58-
{
56+
switch ( authConfig.strategy() ) {
57+
case TRUST_SIGNED_CERTIFICATES:
5958
// A certificate file is specified so we will load the certificates in the file
6059
// Init a in memory TrustedKeyStore
6160
KeyStore trustedKeyStore = KeyStore.getInstance( "JKS" );
@@ -68,13 +67,15 @@ public SSLContext create()
6867
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( "SunX509" );
6968
trustManagerFactory.init( trustedKeyStore );
7069
trustManagers = trustManagerFactory.getTrustManagers();
71-
}
72-
else
73-
{
74-
trustManagers = new TrustManager[]{new TrustOnFirstUseTrustManager( host, port, authConfig.certFile() )};
70+
break;
71+
case TRUST_ON_FIRST_USE:
72+
trustManagers = new TrustManager[]{new TrustOnFirstUseTrustManager( host, port, authConfig.certFile(), logger )};
73+
break;
74+
default:
75+
throw new ClientException( "Unknown TLS authentication strategy: " + authConfig.strategy().name() );
7576
}
7677

77-
sslContext.init( keyManagers, trustManagers, null );
78+
sslContext.init( new KeyManager[0], trustManagers, null );
7879
return sslContext;
7980
}
8081
}

driver/src/main/java/org/neo4j/driver/internal/connector/socket/SocketClient.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,22 @@ public static ByteChannel create( String host, int port, Config config, Logger l
179179
soChannel.setOption( StandardSocketOptions.SO_KEEPALIVE, true );
180180
soChannel.connect( new InetSocketAddress( host, port ) );
181181

182-
ByteChannel channel = null;
182+
ByteChannel channel;
183183

184-
if( config.isTlsEnabled() )
184+
switch ( config.encryptionLevel() )
185185
{
186-
channel = new SSLSocketChannel( host, port, soChannel, logger, config.tlsAuthConfig() );
186+
case REQUIRED:
187+
{
188+
channel = new TLSSocketChannel( host, port, soChannel, logger, config.trustStrategy() );
189+
break;
187190
}
188-
else
191+
case REJECTED:
189192
{
190193
channel = new AllOrNothingChannel( soChannel );
194+
break;
195+
}
196+
default:
197+
throw new ClientException( "Unknown TLS Level: " + config.encryptionLevel() );
191198
}
192199

193200
if( logger.isTraceEnabled() )

driver/src/main/java/org/neo4j/driver/internal/connector/socket/SocketConnector.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,19 @@
3030

3131
public class SocketConnector implements Connector
3232
{
33+
public static final String SCHEME = "bolt";
34+
public static final int DEFAULT_PORT = 7687;
35+
3336
@Override
3437
public boolean supports( String scheme )
3538
{
36-
return scheme.equals( Config.SCHEME );
39+
return scheme.equals( SCHEME );
3740
}
3841

3942
@Override
4043
public Connection connect( URI sessionURI, Config config ) throws ClientException
4144
{
42-
int port = sessionURI.getPort() == -1 ? Config.DEFAULT_PORT : sessionURI.getPort();
45+
int port = sessionURI.getPort() == -1 ? DEFAULT_PORT : sessionURI.getPort();
4346
SocketConnection conn = new SocketConnection( sessionURI.getHost(), port, config );
4447
conn.init( "bolt-java-driver/" + Version.driverVersion() );
4548
return conn;
@@ -48,6 +51,6 @@ public Connection connect( URI sessionURI, Config config ) throws ClientExceptio
4851
@Override
4952
public Collection<String> supportedSchemes()
5053
{
51-
return Collections.singletonList( Config.SCHEME );
54+
return Collections.singletonList( SCHEME );
5255
}
5356
}

driver/src/main/java/org/neo4j/driver/internal/connector/socket/SSLSocketChannel.java renamed to driver/src/main/java/org/neo4j/driver/internal/connector/socket/TLSSocketChannel.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@
3232

3333
import org.neo4j.driver.internal.spi.Logger;
3434
import org.neo4j.driver.internal.util.BytePrinter;
35-
import org.neo4j.driver.v1.Config.TlsAuthenticationConfig;
35+
import org.neo4j.driver.v1.Config.TrustStrategy;
3636
import org.neo4j.driver.v1.exceptions.ClientException;
3737

3838
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED;
3939
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
4040

4141
/**
42-
* A blocking SSL socket channel.
42+
* A blocking TLS socket channel.
4343
*
4444
* When debugging, we could enable JSSE system debugging by setting system property:
4545
* {@code -Djavax.net.debug=all} to value more information about handshake messages and other operations underway.
@@ -49,7 +49,7 @@
4949
* http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#SSLENG
5050
* http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html
5151
*/
52-
public class SSLSocketChannel implements ByteChannel
52+
public class TLSSocketChannel implements ByteChannel
5353
{
5454
private final SocketChannel channel; // The real channel the data is sent to and read from
5555
private final Logger logger;
@@ -64,25 +64,25 @@ public class SSLSocketChannel implements ByteChannel
6464
private ByteBuffer plainIn;
6565
private ByteBuffer plainOut;
6666

67-
public SSLSocketChannel( String host, int port, SocketChannel channel, Logger logger,
68-
TlsAuthenticationConfig authConfig )
67+
public TLSSocketChannel( String host, int port, SocketChannel channel, Logger logger,
68+
TrustStrategy trustStrategy )
6969
throws GeneralSecurityException, IOException
7070
{
7171
logger.debug( "TLS connection enabled" );
7272
this.logger = logger;
7373
this.channel = channel;
7474
this.channel.configureBlocking( true );
7575

76-
sslContext = new SSLContextFactory( host, port, authConfig ).create();
76+
sslContext = new SSLContextFactory( host, port, trustStrategy, logger ).create();
7777
createSSLEngine( host, port );
7878
createBuffers();
79-
runSSLHandShake();
79+
runHandshake();
8080
logger.debug( "TLS connection established" );
8181
}
8282

8383
/** Used in internal tests only */
84-
SSLSocketChannel( SocketChannel channel, Logger logger, SSLEngine sslEngine,
85-
ByteBuffer plainIn, ByteBuffer cipherIn, ByteBuffer plainOut, ByteBuffer cipherOut )
84+
TLSSocketChannel( SocketChannel channel, Logger logger, SSLEngine sslEngine,
85+
ByteBuffer plainIn, ByteBuffer cipherIn, ByteBuffer plainOut, ByteBuffer cipherOut )
8686
throws GeneralSecurityException, IOException
8787
{
8888
logger.debug( "Testing TLS buffers" );
@@ -109,7 +109,7 @@ public SSLSocketChannel( String host, int port, SocketChannel channel, Logger lo
109109
*
110110
* @throws IOException
111111
*/
112-
private void runSSLHandShake() throws IOException
112+
private void runHandshake() throws IOException
113113
{
114114
sslEngine.beginHandshake();
115115
HandshakeStatus handshakeStatus = sslEngine.getHandshakeStatus();

driver/src/main/java/org/neo4j/driver/internal/connector/socket/TrustOnFirstUseTrustManager.java

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,26 @@
2424
import java.io.FileReader;
2525
import java.io.FileWriter;
2626
import java.io.IOException;
27-
import java.net.InetAddress;
27+
import java.security.MessageDigest;
28+
import java.security.NoSuchAlgorithmException;
2829
import java.security.cert.CertificateException;
2930
import java.security.cert.X509Certificate;
3031
import javax.net.ssl.X509TrustManager;
31-
import javax.xml.bind.DatatypeConverter;
32+
33+
import org.neo4j.driver.internal.spi.Logger;
34+
import org.neo4j.driver.internal.util.BytePrinter;
3235

3336
import static org.neo4j.driver.internal.util.CertificateTool.X509CertToString;
3437

3538
/**
3639
* References:
3740
* http://stackoverflow.com/questions/6802421/how-to-compare-distinct-implementations-of-java-security-cert-x509certificate?answertab=votes#tab-top
3841
*/
39-
4042
class TrustOnFirstUseTrustManager implements X509TrustManager
4143
{
4244
/**
43-
* A list of pairs (known_server, certificate) are stored in this file.
44-
* When establishing a SSL connection to a new server, we will save the server's ip:port and its certificate in this
45+
* A list of pairs (known_server certificate) are stored in this file.
46+
* When establishing a SSL connection to a new server, we will save the server's host:port and its certificate in this
4547
* file.
4648
* Then when we try to connect to a known server again, we will authenticate the server by checking if it provides
4749
* the same certificate as the one saved in this file.
@@ -50,15 +52,15 @@ class TrustOnFirstUseTrustManager implements X509TrustManager
5052

5153
/** The server ip:port (in digits) of the server that we are currently connected to */
5254
private final String serverId;
55+
private final Logger logger;
5356

5457
/** The known certificate we've registered for this server */
55-
private String cert;
58+
private String fingerprint;
5659

57-
TrustOnFirstUseTrustManager( String host, int port, File knownCerts ) throws IOException
60+
TrustOnFirstUseTrustManager( String host, int port, File knownCerts, Logger logger ) throws IOException
5861
{
59-
String ip = InetAddress.getByName( host ).getHostAddress(); // localhost -> 127.0.0.1
60-
this.serverId = ip + ":" + port;
61-
62+
this.logger = logger;
63+
this.serverId = host + ":" + port;
6264
this.knownCerts = knownCerts;
6365
load();
6466
}
@@ -76,16 +78,16 @@ private void load() throws IOException
7678
}
7779

7880
BufferedReader reader = new BufferedReader( new FileReader( knownCerts ) );
79-
String line = null;
81+
String line;
8082
while ( (line = reader.readLine()) != null )
8183
{
82-
if ( (!line.trim().startsWith( "#" )) && line.contains( "," ) )
84+
if ( (!line.trim().startsWith( "#" )) )
8385
{
84-
String[] strings = line.split( "," );
86+
String[] strings = line.split( " " );
8587
if ( strings[0].trim().equals( serverId ) )
8688
{
8789
// load the certificate
88-
cert = strings[1].trim();
90+
fingerprint = strings[1].trim();
8991
return;
9092
}
9193
}
@@ -96,16 +98,17 @@ private void load() throws IOException
9698
/**
9799
* Save a new (server_ip, cert) pair into knownCerts file
98100
*
99-
* @param cert
101+
* @param fingerprint
100102
*/
101-
private void save( String cert ) throws IOException
103+
private void saveTrustedHost( String fingerprint ) throws IOException
102104
{
103-
this.cert = cert;
105+
this.fingerprint = fingerprint;
104106

107+
logger.warn( "Adding %s as known and trusted certificate for %s.", fingerprint, serverId );
105108
createKnownCertFileIfNotExists();
106109

107110
BufferedWriter writer = new BufferedWriter( new FileWriter( knownCerts, true ) );
108-
writer.write( serverId + "," + this.cert );
111+
writer.write( serverId + " " + this.fingerprint );
109112
writer.newLine();
110113
writer.close();
111114
}
@@ -126,15 +129,14 @@ public void checkServerTrusted( X509Certificate[] chain, String authType )
126129
throws CertificateException
127130
{
128131
X509Certificate certificate = chain[0];
129-
byte[] encoded = certificate.getEncoded();
130132

131-
String cert = DatatypeConverter.printBase64Binary( encoded );
133+
String cert = fingerprint( certificate );
132134

133-
if ( this.cert == null )
135+
if ( this.fingerprint == null )
134136
{
135137
try
136138
{
137-
save( cert );
139+
saveTrustedHost( cert );
138140
}
139141
catch ( IOException e )
140142
{
@@ -146,7 +148,7 @@ public void checkServerTrusted( X509Certificate[] chain, String authType )
146148
}
147149
else
148150
{
149-
if ( !this.cert.equals( cert ) )
151+
if ( !this.fingerprint.equals( cert ) )
150152
{
151153
throw new CertificateException( String.format(
152154
"Unable to connect to neo4j at `%s`, because the certificate the server uses has changed. " +
@@ -156,21 +158,47 @@ public void checkServerTrusted( X509Certificate[] chain, String authType )
156158
"in the file `%s`.\n" +
157159
"The old certificate saved in file is:\n%sThe New certificate received is:\n%s",
158160
serverId, serverId, knownCerts.getAbsolutePath(),
159-
X509CertToString( this.cert ), X509CertToString( cert ) ) );
161+
X509CertToString( this.fingerprint ), X509CertToString( cert ) ) );
160162
}
161163
}
162164
}
163165

166+
/**
167+
* Calculate the certificate fingerprint - simply the SHA-1 hash of the DER-encoded certificate.
168+
*/
169+
public static String fingerprint( X509Certificate cert ) throws CertificateException
170+
{
171+
try
172+
{
173+
MessageDigest md = MessageDigest.getInstance( "SHA-1" );
174+
md.update( cert.getEncoded() );
175+
return BytePrinter.compactHex( md.digest() );
176+
}
177+
catch( NoSuchAlgorithmException e )
178+
{
179+
// SHA-1 not available
180+
throw new CertificateException( "Cannot use TLS on this platform, because SHA-1 message digest algorithm is not available: " + e.getMessage(), e );
181+
}
182+
}
183+
164184
private File createKnownCertFileIfNotExists() throws IOException
165185
{
166186
if ( !knownCerts.exists() )
167187
{
168188
File parentDir = knownCerts.getParentFile();
169189
if( parentDir != null && !parentDir.exists() )
170190
{
171-
parentDir.mkdirs();
191+
if(!parentDir.mkdirs()) {
192+
throw new IOException( "Failed to create directories for the known hosts file in " + knownCerts.getAbsolutePath() + ". This is usually " +
193+
"because you do not have write permissions to the directory. Try configuring the Neo4j driver to use a file " +
194+
"system location you do have write permissions to." );
195+
}
196+
}
197+
if(!knownCerts.createNewFile()) {
198+
throw new IOException( "Failed to create a known hosts file at " + knownCerts.getAbsolutePath() + ". This is usually " +
199+
"because you do not have write permissions to the directory. Try configuring the Neo4j driver to use a file " +
200+
"system location you do have write permissions to." );
172201
}
173-
knownCerts.createNewFile();
174202
BufferedWriter writer = new BufferedWriter( new FileWriter( knownCerts ) );
175203
writer.write( "# This file contains trusted certificates for Neo4j servers, it's created by Neo4j drivers." );
176204
writer.newLine();

driver/src/main/java/org/neo4j/driver/internal/logging/DevNullLogger.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ public void info( String message, Object... params )
3535

3636
}
3737

38+
@Override
39+
public void warn( String message, Object... params )
40+
{
41+
42+
}
43+
3844
@Override
3945
public void debug( String message, Object... params )
4046
{

driver/src/main/java/org/neo4j/driver/internal/logging/JULogger.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ public void info( String format, Object... params )
4848
delegate.log( Level.INFO, String.format( format, params ) );
4949
}
5050

51+
@Override
52+
public void warn( String format, Object... params )
53+
{
54+
delegate.log( Level.WARNING, String.format( format, params ) );
55+
}
56+
5157
@Override
5258
public void debug( String format, Object... params )
5359
{

driver/src/main/java/org/neo4j/driver/internal/spi/Logger.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public interface Logger
2424

2525
void info( String message, Object... params );
2626

27+
void warn( String message, Object... params );
28+
2729
void debug( String message, Object... params );
2830

2931
void trace( String message, Object... params );

0 commit comments

Comments
 (0)