Skip to content

Commit

Permalink
Add CRL support
Browse files Browse the repository at this point in the history
The PKI has been revamped and the setup now has a Root CA,
a CA meant for a cluster and proper databases setup to
hold revocations. The generation is fully scripted.

The cryptographic objects are accessed in tests using the
SslResourceBuilder which now has added support for installing
revocations.

Each server has an associated CRL generated.
  • Loading branch information
martinfurmanski committed Jun 21, 2017
1 parent f1d114d commit ccc0e99
Show file tree
Hide file tree
Showing 107 changed files with 2,564 additions and 919 deletions.
Expand Up @@ -25,7 +25,6 @@
import org.neo4j.configuration.Description;
import org.neo4j.configuration.Internal;
import org.neo4j.graphdb.config.Setting;
import org.neo4j.graphdb.config.SettingGroup;
import org.neo4j.kernel.configuration.Group;
import org.neo4j.kernel.configuration.GroupSettingSupport;
import org.neo4j.ssl.ClientAuth;
Expand Down Expand Up @@ -72,8 +71,7 @@ public class SslPolicyConfig
@Description( "Path to directory of X.509 certificates in PEM format for trusted parties." )
public final Setting<File> trusted_dir;

@Internal // not yet implemented
@Description( "Path to directory of CRLs." )
@Description( "Path to directory of CRLs (Certificate Revocation Lists) in PEM format." )
public final Setting<File> revoked_dir;

@Description( "Client authentication stance." )
Expand Down
Expand Up @@ -28,14 +28,24 @@
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.CRLException;
import java.security.cert.CertStore;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CRL;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.TrustManagerFactory;

import org.neo4j.kernel.configuration.Config;
Expand All @@ -61,8 +71,6 @@
*
* @see SslPolicyConfig
*/
// TODO: Use FileSystemAbstraction
// TODO: Create SslPolicies class in SSL module (distinct from loader)
public class SslPolicyLoader
{
private final Map<String,SslPolicy> policies = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -113,7 +121,8 @@ else if ( policyName.equals( LEGACY_POLICY_NAME ) )

if ( sslPolicy == null )
{
throw new IllegalArgumentException( format( "Cannot find enabled SSL policy with name '%s' in the configuration", policyName ) );
throw new IllegalArgumentException(
format( "Cannot find enabled SSL policy with name '%s' in the configuration", policyName ) );
}
return sslPolicy;
}
Expand Down Expand Up @@ -150,12 +159,8 @@ private SslPolicy loadOrCreateLegacyPolicy()
PrivateKey privateKey = loadPrivateKey( privateKeyFile, null );
X509Certificate[] keyCertChain = loadCertificateChain( certficateFile );

return new SslPolicy( privateKey, keyCertChain, null, null, ClientAuth.OPTIONAL, trustAllFactory() );
}

private TrustManagerFactory trustAllFactory()
{
return InsecureTrustManagerFactory.INSTANCE;
return new SslPolicy( privateKey, keyCertChain, null, null,
ClientAuth.OPTIONAL, InsecureTrustManagerFactory.INSTANCE );
}

private void load( Config config, Log log )
Expand All @@ -178,7 +183,8 @@ private void load( Config config, Log log )

if ( !baseDirectory.exists() )
{
throw new IllegalArgumentException( format( "Base directory '%s' for SSL policy with name '%s' does not exist.", baseDirectory, policyName ) );
throw new IllegalArgumentException(
format( "Base directory '%s' for SSL policy with name '%s' does not exist.", baseDirectory, policyName ) );
}

boolean allowKeyGeneration = policyConfig.allow_key_generation.from( config );
Expand All @@ -200,8 +206,7 @@ private void load( Config config, Log log )
pkiUtils.createSelfSignedCertificate( keyCertChainFile, privateKeyFile, hostname );

trustedCertificatesDir.mkdir();
// TODO: Add back when supporting loading of certificate revocation lists (CRL).
//revokedCertificatesDir.mkdir();
revokedCertificatesDir.mkdir();
}
catch ( GeneralSecurityException | IOException | OperatorCreationException e )
{
Expand All @@ -216,34 +221,17 @@ private void load( Config config, Log log )
boolean trustAll = policyConfig.trust_all.from( config );
TrustManagerFactory trustManagerFactory;

if ( trustAll )
Collection<X509CRL> crls = getCRLs( revokedCertificatesDir );

try
{
trustManagerFactory = trustAllFactory();
trustManagerFactory = createTrustManagerFactory( trustAll, trustedCertificatesDir, crls, clientAuth );
}
else
catch ( Exception e )
{
try
{
trustManagerFactory = createTrustManagerFactory( trustedCertificatesDir, clientAuth );
}
catch ( Exception e )
{
throw new RuntimeException( "Failed to create trust manager based on: " + trustedCertificatesDir, e );
}
throw new RuntimeException( "Failed to create trust manager based on: " + trustedCertificatesDir, e );
}

// TODO: Add back when supporting loading of certificate revocation lists (CRL).
// File[] revokedCertificateFiles = revokedCertificatesDir.listFiles();
// if ( revokedCertificateFiles == null )
// {
// throw new RuntimeException( format( "Could not find or list files in revoked directory: %s", revokedCertificatesDir ) );
// }
//
// if ( revokedCertificateFiles.length != 0 )
// {
// throw new UnsupportedOperationException( "Loading of certificate revocation lists is not yet supported" );
// }

List<String> tlsVersions = policyConfig.tls_versions.from( config );
List<String> ciphers = policyConfig.ciphers.from( config );

Expand All @@ -253,6 +241,47 @@ private void load( Config config, Log log )
}
}

private Collection<X509CRL> getCRLs( File revokedCertificatesDir )
{
Collection<X509CRL> crls = new ArrayList<>();
File[] revocationFiles = revokedCertificatesDir.listFiles();

if ( revocationFiles == null )
{
throw new RuntimeException( format( "Could not find or list files in revoked directory: %s", revokedCertificatesDir ) );
}

if ( revocationFiles.length == 0 )
{
return crls;
}

CertificateFactory certificateFactory;

try
{
certificateFactory = CertificateFactory.getInstance( "X.509" );
}
catch ( CertificateException e )
{
throw new RuntimeException( "Could not generated certificate factory", e );
}

for ( File crl : revocationFiles )
{
try ( FileInputStream input = new FileInputStream( crl ) )
{
crls.addAll( (Collection<X509CRL>) certificateFactory.generateCRLs( input ) );
}
catch ( IOException | CRLException e )
{
throw new RuntimeException( format( "Could not load CRL: %s", crl ), e );
}
}

return crls;
}

private X509Certificate[] loadCertificateChain( File keyCertChainFile )
{
try
Expand All @@ -279,14 +308,21 @@ private PrivateKey loadPrivateKey( File privateKeyFile, String privateKeyPasswor
}
catch ( Exception e )
{
throw new RuntimeException( "Failed to load private key: " + privateKeyFile + (privateKeyPassword == null ? "" : " (using configured password)"), e );
throw new RuntimeException( "Failed to load private key: " + privateKeyFile +
(privateKeyPassword == null ? "" : " (using configured password)"), e );
}
}

private TrustManagerFactory createTrustManagerFactory( File trustedCertificatesDir, ClientAuth clientAuth ) throws Exception
private TrustManagerFactory createTrustManagerFactory( boolean trustAll, File trustedCertificatesDir,
Collection<X509CRL> crls, ClientAuth clientAuth ) throws Exception
{
KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
keyStore.load( null, null );
if ( trustAll )
{
return InsecureTrustManagerFactory.INSTANCE;
}

KeyStore trustStore = KeyStore.getInstance( KeyStore.getDefaultType() );
trustStore.load( null, null );

File[] trustedCertFiles = trustedCertificatesDir.listFiles();

Expand All @@ -308,13 +344,27 @@ else if ( clientAuth == ClientAuth.REQUIRE && trustedCertFiles.length == 0 )
while ( input.available() > 0 )
{
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate( input );
keyStore.setCertificateEntry( Integer.toString( i++ ), cert );
trustStore.setCertificateEntry( Integer.toString( i++ ), cert );
}
}
}

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
trustManagerFactory.init( keyStore );

if ( !crls.isEmpty() )
{
PKIXBuilderParameters pkixParamsBuilder = new PKIXBuilderParameters( trustStore, new X509CertSelector() );
pkixParamsBuilder.setRevocationEnabled( true );

pkixParamsBuilder.addCertStore( CertStore.getInstance( "Collection",
new CollectionCertStoreParameters( crls ) ) );

trustManagerFactory.init( new CertPathTrustManagerParameters( pkixParamsBuilder ) );
}
else
{
trustManagerFactory.init( trustStore );
}

return trustManagerFactory;
}
Expand Down
9 changes: 8 additions & 1 deletion community/ssl/src/test/java/org/neo4j/ssl/SslResource.java
Expand Up @@ -26,12 +26,14 @@ public class SslResource
private final File privateKey;
private final File publicCertificate;
private final File trustedDirectory;
private final File revokedDirectory;

SslResource( File privateKey, File publicCertificate, File trustedDirectory )
SslResource( File privateKey, File publicCertificate, File trustedDirectory, File revokedDirectory )
{
this.privateKey = privateKey;
this.publicCertificate = publicCertificate;
this.trustedDirectory = trustedDirectory;
this.revokedDirectory = revokedDirectory;
}

public File privateKey()
Expand All @@ -48,4 +50,9 @@ public File trustedDirectory()
{
return trustedDirectory;
}

public File revokedDirectory()
{
return revokedDirectory;
}
}

0 comments on commit ccc0e99

Please sign in to comment.