Skip to content

Commit

Permalink
Tests for Akka SSL engine provider.
Browse files Browse the repository at this point in the history
New package for integration tests in private/
  • Loading branch information
andrewkerr9000 committed Sep 5, 2018
1 parent d80e41c commit 1c5cb7b
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 72 deletions.
8 changes: 4 additions & 4 deletions community/ssl/src/test/java/org/neo4j/ssl/SslResource.java
Expand Up @@ -36,22 +36,22 @@ public class SslResource
this.revokedDirectory = revokedDirectory; this.revokedDirectory = revokedDirectory;
} }


File privateKey() public File privateKey()
{ {
return privateKey; return privateKey;
} }


File publicCertificate() public File publicCertificate()
{ {
return publicCertificate; return publicCertificate;
} }


File trustedDirectory() public File trustedDirectory()
{ {
return trustedDirectory; return trustedDirectory;
} }


File revokedDirectory() public File revokedDirectory()
{ {
return revokedDirectory; return revokedDirectory;
} }
Expand Down
Expand Up @@ -110,7 +110,7 @@ public SslResourceBuilder trustSignedByCA()
return this; return this;
} }


SslResourceBuilder revoke( int keyId ) public SslResourceBuilder revoke( int keyId )
{ {
revoked.add( keyId ); revoked.add( keyId );
return this; return this;
Expand Down
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2002-2018 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j Enterprise Edition. The included source
* code can be redistributed and/or modified under the terms of the
* GNU AFFERO GENERAL PUBLIC LICENSE Version 3
* (http://www.fsf.org/licensing/licenses/agpl-3.0.html) with the
* Commons Clause, as found in the associated LICENSE.txt file.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* Neo4j object code can be licensed independently from the source
* under separate terms from the AGPL. Inquiries can be directed to:
* licensing@neo4j.com
*
* More information is also available at:
* https://neo4j.com/licensing/
*/
package org.neo4j.ssl;

import org.bouncycastle.operator.OperatorCreationException;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.util.UUID;

import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.configuration.ssl.SslPolicyConfig;
import org.neo4j.test.rule.TestDirectory;

public class HostnameVerificationHelper
{
public static final String POLICY_NAME = "fakePolicy";
private static final PkiUtils PKI_UTILS = new PkiUtils();
private static final SslPolicyConfig SSL_POLICY_CONFIG = new SslPolicyConfig( POLICY_NAME );

public static Config aConfig( String hostname, TestDirectory testDirectory ) throws GeneralSecurityException, IOException, OperatorCreationException
{
String random = UUID.randomUUID().toString();
File baseDirectory = testDirectory.directory( "base_directory_" + random );
File validCertificatePath = new File( baseDirectory, "certificate.crt" );
File validPrivateKeyPath = new File( baseDirectory, "private.pem" );
File revoked = new File( baseDirectory, "revoked" );
File trusted = new File( baseDirectory, "trusted" );
trusted.mkdirs();
revoked.mkdirs();
PKI_UTILS.createSelfSignedCertificate( validCertificatePath, validPrivateKeyPath, hostname ); // Sets Subject Alternative Name(s) to hostname
return Config.builder()
.withSetting( SSL_POLICY_CONFIG.base_directory, baseDirectory.toString() )
.withSetting( SSL_POLICY_CONFIG.trusted_dir, trusted.toString() )
.withSetting( SSL_POLICY_CONFIG.revoked_dir, revoked.toString() )
.withSetting( SSL_POLICY_CONFIG.private_key, validPrivateKeyPath.toString() )
.withSetting( SSL_POLICY_CONFIG.public_certificate, validCertificatePath.toString() )

.withSetting( SSL_POLICY_CONFIG.tls_versions, "TLSv1.2" )
.withSetting( SSL_POLICY_CONFIG.ciphers, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" )

.withSetting( SSL_POLICY_CONFIG.client_auth, "none" )
.withSetting( SSL_POLICY_CONFIG.allow_key_generation, "false" )

// Even if we trust all, certs should be rejected if don't match Common Name (CA) or Subject Alternative Name
.withSetting( SSL_POLICY_CONFIG.trust_all, "false" )
.withSetting( SSL_POLICY_CONFIG.verify_hostname, "true" )
.build();
}

public static void trust( Config target, Config subject ) throws IOException
{
SslPolicyConfig sslPolicyConfig = new SslPolicyConfig( POLICY_NAME );
File trustedDirectory = target.get( sslPolicyConfig.trusted_dir );
File certificate = subject.get( sslPolicyConfig.public_certificate );
Path trustedCertFilePath = trustedDirectory.toPath().resolve( certificate.getName() );
Files.copy( certificate.toPath(), trustedCertFilePath );
}
}
Expand Up @@ -25,8 +25,6 @@
import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SslProvider;


import java.io.File; import java.io.File;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;


Expand Down Expand Up @@ -81,26 +79,25 @@ public String toString()
} }
} }


public static SslPolicy makeSslPolicy( SslResource sslResource, SslParameters params ) throws CertificateException, IOException public static SslPolicy makeSslPolicy( SslResource sslResource, SslParameters params )
{ {
return makeSslPolicy( sslResource, SslProvider.JDK.name(), params.protocols, params.ciphers ); return makeSslPolicy( sslResource, SslProvider.JDK, params.protocols, params.ciphers );
} }


public static SslPolicy makeSslPolicy( SslResource sslResource, String sslProvider ) throws CertificateException, IOException public static SslPolicy makeSslPolicy( SslResource sslResource, SslProvider sslProvider )
{ {
return makeSslPolicy( sslResource, sslProvider, null, null ); return makeSslPolicy( sslResource, sslProvider, null, null );
} }


public static SslPolicy makeSslPolicy( SslResource sslResource ) throws CertificateException, IOException public static SslPolicy makeSslPolicy( SslResource sslResource )
{ {
return makeSslPolicy( sslResource, SslProvider.JDK.name(), null, null ); return makeSslPolicy( sslResource, SslProvider.JDK, null, null );
} }


public static SslPolicy makeSslPolicy( SslResource sslResource, String sslProvider, String protocols, String ciphers ) public static SslPolicy makeSslPolicy( SslResource sslResource, SslProvider sslProvider, String protocols, String ciphers )
throws CertificateException, IOException
{ {
Map<String,String> config = new HashMap<>(); Map<String,String> config = new HashMap<>();
config.put( SslSystemSettings.netty_ssl_provider.name(), sslProvider ); config.put( SslSystemSettings.netty_ssl_provider.name(), sslProvider.name() );


SslPolicyConfig policyConfig = new SslPolicyConfig( "default" ); SslPolicyConfig policyConfig = new SslPolicyConfig( "default" );
File baseDirectory = sslResource.privateKey().getParentFile(); File baseDirectory = sslResource.privateKey().getParentFile();
Expand All @@ -127,7 +124,6 @@ public static SslPolicy makeSslPolicy( SslResource sslResource, String sslProvid
SslPolicyLoader sslPolicyFactory = SslPolicyLoader sslPolicyFactory =
SslPolicyLoader.create( Config.fromSettings( config ).build(), NullLogProvider.getInstance() ); SslPolicyLoader.create( Config.fromSettings( config ).build(), NullLogProvider.getInstance() );


SslPolicy sslPolicy = sslPolicyFactory.getPolicy( "default" ); return sslPolicyFactory.getPolicy( "default" );
return sslPolicy;
} }
} }
Expand Up @@ -66,10 +66,10 @@ public void shouldSupportOpenSSLOnSupportedPlatforms() throws Exception
SslResource sslServerResource = selfSignedKeyId( 0 ).trustKeyId( 1 ).install( testDir.directory( "server" ) ); SslResource sslServerResource = selfSignedKeyId( 0 ).trustKeyId( 1 ).install( testDir.directory( "server" ) );
SslResource sslClientResource = selfSignedKeyId( 1 ).trustKeyId( 0 ).install( testDir.directory( "client" ) ); SslResource sslClientResource = selfSignedKeyId( 1 ).trustKeyId( 0 ).install( testDir.directory( "client" ) );


server = new SecureServer( SslContextFactory.makeSslPolicy( sslServerResource, SslProvider.OPENSSL.name() ) ); server = new SecureServer( SslContextFactory.makeSslPolicy( sslServerResource, SslProvider.OPENSSL ) );


server.start(); server.start();
client = new SecureClient( SslContextFactory.makeSslPolicy( sslClientResource, SslProvider.OPENSSL.name() ) ); client = new SecureClient( SslContextFactory.makeSslPolicy( sslClientResource, SslProvider.OPENSSL ) );
client.connect( server.port() ); client.connect( server.port() );


// when // when
Expand Down
Expand Up @@ -29,20 +29,15 @@
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;


import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;


import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.configuration.ssl.SslPolicyConfig;
import org.neo4j.kernel.configuration.ssl.SslPolicyLoader; import org.neo4j.kernel.configuration.ssl.SslPolicyLoader;
import org.neo4j.logging.FormattedLogProvider; import org.neo4j.logging.FormattedLogProvider;
import org.neo4j.logging.Level; import org.neo4j.logging.Level;
Expand All @@ -52,25 +47,25 @@
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.neo4j.ssl.HostnameVerificationHelper.POLICY_NAME;
import static org.neo4j.ssl.HostnameVerificationHelper.aConfig;
import static org.neo4j.ssl.HostnameVerificationHelper.trust;


public class SslPolicyLoaderIT public class SslPolicyLoaderIT
{ {
@Rule @Rule
public TestDirectory testDirectory = TestDirectory.testDirectory(); public TestDirectory testDirectory = TestDirectory.testDirectory();


private static final PkiUtils PKI_UTILS = new PkiUtils();
private static final LogProvider LOG_PROVIDER = FormattedLogProvider.withDefaultLogLevel( Level.DEBUG ).toOutputStream( System.out ); private static final LogProvider LOG_PROVIDER = FormattedLogProvider.withDefaultLogLevel( Level.DEBUG ).toOutputStream( System.out );
private static final String POLICY_NAME = "fakePolicy";
private static final SslPolicyConfig SSL_POLICY_CONFIG = new SslPolicyConfig( POLICY_NAME );


@Test @Test
public void certificatesWithInvalidCommonNameAreRejected() throws GeneralSecurityException, IOException, OperatorCreationException, InterruptedException public void certificatesWithInvalidCommonNameAreRejected() throws GeneralSecurityException, IOException, OperatorCreationException, InterruptedException
{ {
// given server has a certificate that matches an invalid hostname // given server has a certificate that matches an invalid hostname
Config serverConfig = aConfig( "invalid-not-localhost" ); Config serverConfig = aConfig( "invalid-not-localhost", testDirectory );


// and client has any certificate (valid), since hostname validation is done from the client side // and client has any certificate (valid), since hostname validation is done from the client side
Config clientConfig = aConfig( "localhost" ); Config clientConfig = aConfig( "localhost", testDirectory );


trust( serverConfig, clientConfig ); trust( serverConfig, clientConfig );
trust( clientConfig, serverConfig ); trust( clientConfig, serverConfig );
Expand Down Expand Up @@ -112,10 +107,10 @@ public void normalBehaviourIfServerCertificateMatchesClientExpectation()
throws GeneralSecurityException, IOException, OperatorCreationException, InterruptedException, TimeoutException, ExecutionException throws GeneralSecurityException, IOException, OperatorCreationException, InterruptedException, TimeoutException, ExecutionException
{ {
// given server has valid hostname // given server has valid hostname
Config serverConfig = aConfig( "localhost" ); Config serverConfig = aConfig( "localhost", testDirectory );


// and client has invalid hostname (which is irrelevant for hostname verification) // and client has invalid hostname (which is irrelevant for hostname verification)
Config clientConfig = aConfig( "invalid-localhost" ); Config clientConfig = aConfig( "invalid-localhost", testDirectory );


trust( serverConfig, clientConfig ); trust( serverConfig, clientConfig );
trust( clientConfig, serverConfig ); trust( clientConfig, serverConfig );
Expand All @@ -136,10 +131,10 @@ public void legacyPolicyDoesNotHaveHostnameVerification()
throws GeneralSecurityException, IOException, OperatorCreationException, InterruptedException, TimeoutException, ExecutionException throws GeneralSecurityException, IOException, OperatorCreationException, InterruptedException, TimeoutException, ExecutionException
{ {
// given server has an invalid hostname // given server has an invalid hostname
Config serverConfig = aConfig( "invalid-localhost" ); Config serverConfig = aConfig( "invalid-localhost", testDirectory );


// and client has invalid hostname (which is irrelevant for hostname verification) // and client has invalid hostname (which is irrelevant for hostname verification)
Config clientConfig = aConfig( "invalid-localhost" ); Config clientConfig = aConfig( "invalid-localhost", testDirectory );


trust( serverConfig, clientConfig ); trust( serverConfig, clientConfig );
trust( clientConfig, serverConfig ); trust( clientConfig, serverConfig );
Expand Down Expand Up @@ -175,45 +170,6 @@ private void clientCanCommunicateWithServer( SecureClient secureClient, SecureSe
} }
} }


private Config aConfig( String hostname ) throws GeneralSecurityException, IOException, OperatorCreationException
{
String random = UUID.randomUUID().toString();
File baseDirectory = testDirectory.directory( "base_directory_" + random );
File validCertificatePath = new File( baseDirectory, "certificate.crt" );
File validPrivateKeyPath = new File( baseDirectory, "private.pem" );
File revoked = new File( baseDirectory, "revoked" );
File trusted = new File( baseDirectory, "trusted" );
trusted.mkdirs();
revoked.mkdirs();
PKI_UTILS.createSelfSignedCertificate( validCertificatePath, validPrivateKeyPath, hostname ); // Sets Subject Alternative Name(s) to hostname
return Config.builder()
.withSetting( SSL_POLICY_CONFIG.base_directory, baseDirectory.toString() )
.withSetting( SSL_POLICY_CONFIG.trusted_dir, trusted.toString() )
.withSetting( SSL_POLICY_CONFIG.revoked_dir, revoked.toString() )
.withSetting( SSL_POLICY_CONFIG.private_key, validPrivateKeyPath.toString() )
.withSetting( SSL_POLICY_CONFIG.public_certificate, validCertificatePath.toString() )

.withSetting( SSL_POLICY_CONFIG.tls_versions, "TLSv1.2" )
.withSetting( SSL_POLICY_CONFIG.ciphers, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" )

.withSetting( SSL_POLICY_CONFIG.client_auth, "none" )
.withSetting( SSL_POLICY_CONFIG.allow_key_generation, "false" )

// Even if we trust all, certs should be rejected if don't match Common Name (CA) or Subject Alternative Name
.withSetting( SSL_POLICY_CONFIG.trust_all, "false" )
.withSetting( SSL_POLICY_CONFIG.verify_hostname, "true" )
.build();
}

private void trust( Config target, Config subject ) throws IOException
{
SslPolicyConfig sslPolicyConfig = new SslPolicyConfig( POLICY_NAME );
File trustedDirectory = target.get( sslPolicyConfig.trusted_dir );
File certificate = subject.get( sslPolicyConfig.public_certificate );
Path trustedCertFilePath = trustedDirectory.toPath().resolve( certificate.getName() );
Files.copy( certificate.toPath(), trustedCertFilePath );
}

private Stream<Throwable> causes( Throwable throwable ) private Stream<Throwable> causes( Throwable throwable )
{ {
Stream<Throwable> thisStream = Stream.of( throwable ).filter( Objects::nonNull ); Stream<Throwable> thisStream = Stream.of( throwable ).filter( Objects::nonNull );
Expand Down

0 comments on commit 1c5cb7b

Please sign in to comment.