Skip to content

Commit 16bdde3

Browse files
authored
OCSP Stapling Checks (#756)
Adds the ability for TrustStategy to configure whether or not certificate revocation checking is to be carried out. By default revocation checking is disabled. If enabled the driver will check the validity of stapled OCSP (Online Certificate Status Protocol) response(s). These responses are returned during the TLS handshake by the server and if not present, the driver will fail to accept the certificate. See: https://tools.ietf.org/html/rfc6961
1 parent 26b2d2d commit 16bdde3

File tree

12 files changed

+296
-57
lines changed

12 files changed

+296
-57
lines changed

driver/src/main/java/org/neo4j/driver/Config.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import org.neo4j.driver.exceptions.ServiceUnavailableException;
3131
import org.neo4j.driver.exceptions.SessionExpiredException;
3232
import org.neo4j.driver.exceptions.TransientException;
33-
import org.neo4j.driver.internal.ConnectionSettings;
33+
import org.neo4j.driver.internal.RevocationStrategy;
3434
import org.neo4j.driver.internal.SecuritySettings;
3535
import org.neo4j.driver.internal.async.pool.PoolSettings;
3636
import org.neo4j.driver.internal.cluster.RoutingSettings;
@@ -802,6 +802,7 @@ public enum Strategy
802802
private final Strategy strategy;
803803
private final File certFile;
804804
private boolean hostnameVerificationEnabled = true;
805+
private RevocationStrategy revocationStrategy = RevocationStrategy.NO_CHECKS;
805806

806807
private TrustStrategy( Strategy strategy )
807808
{
@@ -901,5 +902,53 @@ public static TrustStrategy trustAllCertificates()
901902
{
902903
return new TrustStrategy( Strategy.TRUST_ALL_CERTIFICATES );
903904
}
905+
906+
/**
907+
* The revocation strategy used for verifying certificates.
908+
* @return this {@link TrustStrategy}'s revocation strategy
909+
*/
910+
public RevocationStrategy revocationStrategy()
911+
{
912+
return revocationStrategy;
913+
}
914+
915+
/**
916+
* Configures the {@link TrustStrategy} to not carry out OCSP revocation checks on certificates. This is the
917+
* option that is configured by default.
918+
* @return the current trust strategy
919+
*/
920+
public TrustStrategy withoutCertificateRevocationChecks()
921+
{
922+
this.revocationStrategy = RevocationStrategy.NO_CHECKS;
923+
return this;
924+
}
925+
926+
/**
927+
* Configures the {@link TrustStrategy} to carry out OCSP revocation checks when the revocation status is
928+
* stapled to the certificate. If no stapled response is found, then certificate verification continues
929+
* (and does not fail verification). This setting also requires the server to be configured to enable
930+
* OCSP stapling.
931+
* @return the current trust strategy
932+
*/
933+
public TrustStrategy withVerifyIfPresentRevocationChecks()
934+
{
935+
this.revocationStrategy = RevocationStrategy.VERIFY_IF_PRESENT;
936+
return this;
937+
}
938+
939+
/**
940+
* Configures the {@link TrustStrategy} to carry out strict OCSP revocation checks for revocation status that
941+
* are stapled to the certificate. If no stapled response is found, then the driver will fail certificate verification
942+
* and not connect to the server. This setting also requires the server to be configured to enable OCSP stapling.
943+
*
944+
* Note: enabling this setting will prevent the driver connecting to the server when the server is unable to reach
945+
* the certificate's configured OCSP responder URL.
946+
* @return the current trust strategy
947+
*/
948+
public TrustStrategy withStrictRevocationChecks()
949+
{
950+
this.revocationStrategy = RevocationStrategy.STRICT;
951+
return this;
952+
}
904953
}
905954
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) 2002-2020 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.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+
package org.neo4j.driver.internal;
21+
22+
public enum RevocationStrategy
23+
{
24+
/** Don't do any OCSP revocation checks, regardless whether there are stapled revocation statuses or not. */
25+
NO_CHECKS,
26+
/** Verify OCSP revocation checks when the revocation status is stapled to the certificate, continue if not. */
27+
VERIFY_IF_PRESENT,
28+
/** Require stapled revocation status and verify OCSP revocation checks, fail if no revocation status is stapled to the certificate. */
29+
STRICT;
30+
31+
public static boolean requiresRevocationChecking( RevocationStrategy revocationStrategy )
32+
{
33+
return revocationStrategy.equals( STRICT ) || revocationStrategy.equals( VERIFY_IF_PRESENT );
34+
}
35+
}

driver/src/main/java/org/neo4j/driver/internal/SecuritySettings.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,11 @@ private SecurityPlan createSecurityPlanFromScheme( String scheme ) throws Genera
9292
{
9393
if ( isHighTrustScheme(scheme) )
9494
{
95-
return SecurityPlanImpl.forSystemCASignedCertificates( true );
95+
return SecurityPlanImpl.forSystemCASignedCertificates( true, RevocationStrategy.NO_CHECKS );
9696
}
9797
else
9898
{
99-
return SecurityPlanImpl.forAllCertificates( false );
99+
return SecurityPlanImpl.forAllCertificates( false, RevocationStrategy.NO_CHECKS );
100100
}
101101
}
102102

@@ -110,14 +110,15 @@ private static SecurityPlan createSecurityPlanImpl( boolean encrypted, Config.Tr
110110
if ( encrypted )
111111
{
112112
boolean hostnameVerificationEnabled = trustStrategy.isHostnameVerificationEnabled();
113+
RevocationStrategy revocationStrategy = trustStrategy.revocationStrategy();
113114
switch ( trustStrategy.strategy() )
114115
{
115116
case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES:
116-
return SecurityPlanImpl.forCustomCASignedCertificates( trustStrategy.certFile(), hostnameVerificationEnabled );
117+
return SecurityPlanImpl.forCustomCASignedCertificates( trustStrategy.certFile(), hostnameVerificationEnabled, revocationStrategy );
117118
case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES:
118-
return SecurityPlanImpl.forSystemCASignedCertificates( hostnameVerificationEnabled );
119+
return SecurityPlanImpl.forSystemCASignedCertificates( hostnameVerificationEnabled, revocationStrategy );
119120
case TRUST_ALL_CERTIFICATES:
120-
return SecurityPlanImpl.forAllCertificates( hostnameVerificationEnabled );
121+
return SecurityPlanImpl.forAllCertificates( hostnameVerificationEnabled, revocationStrategy );
121122
default:
122123
throw new ClientException(
123124
"Unknown TLS authentication strategy: " + trustStrategy.strategy().name() );

driver/src/main/java/org/neo4j/driver/internal/security/SecurityPlan.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import javax.net.ssl.SSLContext;
2222

23+
import org.neo4j.driver.internal.RevocationStrategy;
24+
2325
/**
2426
* A SecurityPlan consists of encryption and trust details.
2527
*/
@@ -30,4 +32,6 @@ public interface SecurityPlan
3032
SSLContext sslContext();
3133

3234
boolean requiresHostnameVerification();
35+
36+
RevocationStrategy revocationStrategy();
3337
}

driver/src/main/java/org/neo4j/driver/internal/security/SecurityPlanImpl.java

Lines changed: 93 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,70 +22,140 @@
2222
import java.io.IOException;
2323
import java.security.GeneralSecurityException;
2424
import java.security.KeyStore;
25-
import java.security.NoSuchAlgorithmException;
25+
import java.security.Security;
2626
import java.security.cert.CertificateException;
27+
import java.security.cert.PKIXBuilderParameters;
28+
import java.security.cert.X509CertSelector;
2729
import java.security.cert.X509Certificate;
30+
import javax.net.ssl.CertPathTrustManagerParameters;
2831
import javax.net.ssl.KeyManager;
2932
import javax.net.ssl.SSLContext;
3033
import javax.net.ssl.TrustManager;
3134
import javax.net.ssl.TrustManagerFactory;
3235
import javax.net.ssl.X509TrustManager;
3336

37+
import org.neo4j.driver.internal.RevocationStrategy;
38+
39+
import static org.neo4j.driver.internal.RevocationStrategy.VERIFY_IF_PRESENT;
40+
import static org.neo4j.driver.internal.RevocationStrategy.requiresRevocationChecking;
3441
import static org.neo4j.driver.internal.util.CertificateTool.loadX509Cert;
3542

3643
/**
3744
* A SecurityPlan consists of encryption and trust details.
3845
*/
3946
public class SecurityPlanImpl implements SecurityPlan
4047
{
41-
public static SecurityPlan forAllCertificates( boolean requiresHostnameVerification ) throws GeneralSecurityException
48+
public static SecurityPlan forAllCertificates( boolean requiresHostnameVerification, RevocationStrategy revocationStrategy ) throws GeneralSecurityException
4249
{
4350
SSLContext sslContext = SSLContext.getInstance( "TLS" );
4451
sslContext.init( new KeyManager[0], new TrustManager[]{new TrustAllTrustManager()}, null );
4552

46-
return new SecurityPlanImpl( true, sslContext, requiresHostnameVerification );
53+
return new SecurityPlanImpl( true, sslContext, requiresHostnameVerification, revocationStrategy );
54+
}
55+
56+
public static SecurityPlan forCustomCASignedCertificates( File certFile, boolean requiresHostnameVerification,
57+
RevocationStrategy revocationStrategy )
58+
throws GeneralSecurityException, IOException
59+
{
60+
SSLContext sslContext = configureSSLContext( certFile, revocationStrategy );
61+
return new SecurityPlanImpl( true, sslContext, requiresHostnameVerification, revocationStrategy );
62+
}
63+
64+
public static SecurityPlan forSystemCASignedCertificates( boolean requiresHostnameVerification, RevocationStrategy revocationStrategy )
65+
throws GeneralSecurityException, IOException
66+
{
67+
SSLContext sslContext = configureSSLContext( null, revocationStrategy );
68+
return new SecurityPlanImpl( true, sslContext, requiresHostnameVerification, revocationStrategy );
4769
}
4870

49-
public static SecurityPlan forCustomCASignedCertificates( File certFile, boolean requiresHostnameVerification )
71+
private static SSLContext configureSSLContext( File customCertFile, RevocationStrategy revocationStrategy )
5072
throws GeneralSecurityException, IOException
5173
{
52-
// A certificate file is specified so we will load the certificates in the file
53-
// Init a in memory TrustedKeyStore
54-
KeyStore trustedKeyStore = KeyStore.getInstance( "JKS" );
74+
KeyStore trustedKeyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
5575
trustedKeyStore.load( null, null );
5676

57-
// Load the certs from the file
58-
loadX509Cert( certFile, trustedKeyStore );
77+
if ( customCertFile != null )
78+
{
79+
// A certificate file is specified so we will load the certificates in the file
80+
loadX509Cert( customCertFile, trustedKeyStore );
81+
}
82+
else
83+
{
84+
loadSystemCertificates( trustedKeyStore );
85+
}
86+
87+
// Configure certificate revocation checking (X509CertSelector() selects all certificates)
88+
PKIXBuilderParameters pkixBuilderParameters = new PKIXBuilderParameters( trustedKeyStore, new X509CertSelector() );
5989

60-
// Create TrustManager from TrustedKeyStore
61-
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( "SunX509" );
62-
trustManagerFactory.init( trustedKeyStore );
90+
// sets checking of stapled ocsp response
91+
pkixBuilderParameters.setRevocationEnabled( requiresRevocationChecking( revocationStrategy ) );
92+
93+
if ( requiresRevocationChecking( revocationStrategy ) )
94+
{
95+
// enables status_request extension in client hello
96+
System.setProperty( "jdk.tls.client.enableStatusRequestExtension", "true" );
97+
98+
if ( revocationStrategy.equals( VERIFY_IF_PRESENT ) )
99+
{
100+
// enables soft-fail behaviour if no stapled response found.
101+
Security.setProperty( "ocsp.enable", "true" );
102+
}
103+
}
63104

64105
SSLContext sslContext = SSLContext.getInstance( "TLS" );
106+
107+
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
108+
trustManagerFactory.init( new CertPathTrustManagerParameters( pkixBuilderParameters ) );
65109
sslContext.init( new KeyManager[0], trustManagerFactory.getTrustManagers(), null );
66110

67-
return new SecurityPlanImpl( true, sslContext, requiresHostnameVerification );
111+
return sslContext;
68112
}
69113

70-
public static SecurityPlan forSystemCASignedCertificates( boolean requiresHostnameVerification ) throws NoSuchAlgorithmException
114+
private static void loadSystemCertificates( KeyStore trustedKeyStore ) throws GeneralSecurityException, IOException
71115
{
72-
return new SecurityPlanImpl( true, SSLContext.getDefault(), requiresHostnameVerification );
116+
// To customize the PKIXParameters we need to get hold of the default KeyStore, no other elegant way available
117+
TrustManagerFactory tempFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
118+
tempFactory.init( (KeyStore) null );
119+
120+
// Get hold of the default trust manager
121+
X509TrustManager x509TrustManager = null;
122+
for ( TrustManager trustManager : tempFactory.getTrustManagers() )
123+
{
124+
if ( trustManager instanceof X509TrustManager )
125+
{
126+
x509TrustManager = (X509TrustManager) trustManager;
127+
break;
128+
}
129+
}
130+
131+
if ( x509TrustManager == null )
132+
{
133+
throw new CertificateException( "No system certificates found" );
134+
}
135+
else
136+
{
137+
// load system default certificates into KeyStore
138+
loadX509Cert( x509TrustManager.getAcceptedIssuers(), trustedKeyStore );
139+
}
73140
}
74141

75142
public static SecurityPlan insecure()
76143
{
77-
return new SecurityPlanImpl( false, null, false );
144+
return new SecurityPlanImpl( false, null, false,
145+
RevocationStrategy.NO_CHECKS );
78146
}
79147

80148
private final boolean requiresEncryption;
81149
private final SSLContext sslContext;
82150
private final boolean requiresHostnameVerification;
151+
private final RevocationStrategy revocationStrategy;
83152

84-
private SecurityPlanImpl( boolean requiresEncryption, SSLContext sslContext, boolean requiresHostnameVerification )
153+
private SecurityPlanImpl( boolean requiresEncryption, SSLContext sslContext, boolean requiresHostnameVerification, RevocationStrategy revocationStrategy )
85154
{
86155
this.requiresEncryption = requiresEncryption;
87156
this.sslContext = sslContext;
88157
this.requiresHostnameVerification = requiresHostnameVerification;
158+
this.revocationStrategy = revocationStrategy;
89159
}
90160

91161
@Override
@@ -106,6 +176,12 @@ public boolean requiresHostnameVerification()
106176
return requiresHostnameVerification;
107177
}
108178

179+
@Override
180+
public RevocationStrategy revocationStrategy()
181+
{
182+
return revocationStrategy;
183+
}
184+
109185
private static class TrustAllTrustManager implements X509TrustManager
110186
{
111187
public void checkClientTrusted( X509Certificate[] chain, String authType ) throws CertificateException

driver/src/main/java/org/neo4j/driver/internal/util/CertificateTool.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@
3030
import java.security.cert.Certificate;
3131
import java.security.cert.CertificateException;
3232
import java.security.cert.CertificateFactory;
33+
import java.security.cert.X509Certificate;
3334
import java.util.Base64;
3435

3536
/**
3637
* A tool used to save, load certs, etc.
3738
*/
38-
public class CertificateTool
39+
public final class CertificateTool
3940
{
4041
private static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
4142
private static final String END_CERT = "-----END CERTIFICATE-----";
@@ -139,6 +140,14 @@ public static void loadX509Cert( File certFile, KeyStore keyStore ) throws Gener
139140
}
140141
}
141142

143+
public static void loadX509Cert( X509Certificate[] certificates, KeyStore keyStore ) throws GeneralSecurityException, IOException
144+
{
145+
for ( int i = 0; i < certificates.length; i++ )
146+
{
147+
loadX509Cert( certificates[i], "neo4j.javadriver.trustedcert." + i, keyStore );
148+
}
149+
}
150+
142151
/**
143152
* Load a certificate to a key store with a name
144153
*
@@ -161,6 +170,10 @@ public static String X509CertToString( String cert )
161170
String cert64CharPerLine = cert.replaceAll( "(.{64})", "$1\n" );
162171
return BEGIN_CERT + "\n" + cert64CharPerLine + "\n"+ END_CERT + "\n";
163172
}
173+
174+
private CertificateTool()
175+
{
176+
}
164177
}
165178

166179

0 commit comments

Comments
 (0)