From 9677e2c54bbc66283f3f4341a4e1166006069fc3 Mon Sep 17 00:00:00 2001 From: Chris Hostetter Date: Tue, 3 May 2016 14:01:23 -0700 Subject: [PATCH] SOLR-5776: refactor SSLConfig so that SSLTestConfig can provide SSLContexts using a NullSecureRandom to prevent SSL tests from blocking on entropy starved machines --- .../solr/client/solrj/embedded/SSLConfig.java | 73 +++++++++----- .../org/apache/solr/util/SSLTestConfig.java | 99 ++++++++++++++++++- 2 files changed, 145 insertions(+), 27 deletions(-) diff --git a/solr/core/src/java/org/apache/solr/client/solrj/embedded/SSLConfig.java b/solr/core/src/java/org/apache/solr/client/solrj/embedded/SSLConfig.java index f7779510531..60f383ce6ee 100644 --- a/solr/core/src/java/org/apache/solr/client/solrj/embedded/SSLConfig.java +++ b/solr/core/src/java/org/apache/solr/client/solrj/embedded/SSLConfig.java @@ -77,38 +77,63 @@ public class SSLConfig { /** * Returns an SslContextFactory that should be used by a jetty server based on the specified - * configuration, or null if no SSL should be used. + * SSLConfig param which may be null. * - * The specified sslConfig will be completely ignored if the "tests.jettySsl" system property is - * true - in which case standard "javax.net.ssl.*" system properties will be used instead, along - * with "tests.jettySsl.clientAuth" + * if the SSLConfig param is non-null, then this method will return the results of + * {@link #createContextFactory()}. * - * @see #isSSLMode + * If the SSLConfig param is null, then this method will return null unless the + * tests.jettySsl system property is true, in which case standard "javax.net.ssl.*" + * system properties will be used instead, along with "tests.jettySsl.clientAuth". + * + * @see #createContextFactory() */ public static SslContextFactory createContextFactory(SSLConfig sslConfig) { - if (sslConfig == null) { - if (Boolean.getBoolean("tests.jettySsl")) { - return configureSslFromSysProps(); - } + if (sslConfig != null) { + return sslConfig.createContextFactory(); + } + // else... + if (Boolean.getBoolean("tests.jettySsl")) { + return configureSslFromSysProps(); + } + // else... + return null; + } + + /** + * Returns an SslContextFactory that should be used by a jetty server based on this SSLConfig instance, + * or null if SSL should not be used. + * + * The default implementation generates a simple factory according to the keystore, truststore, + * and clientAuth properties of this object. + * + * @see #getKeyStore + * @see #getKeyStorePassword + * @see #isClientAuthMode + * @see #getTrustStore + * @see #getTrustStorePassword + */ + public SslContextFactory createContextFactory() { + + if (! isSSLMode()) { return null; } - - if (!sslConfig.isSSLMode()) - return null; - - SslContextFactory factory = new SslContextFactory(false); - if (sslConfig.getKeyStore() != null) - factory.setKeyStorePath(sslConfig.getKeyStore()); - if (sslConfig.getKeyStorePassword() != null) - factory.setKeyStorePassword(sslConfig.getKeyStorePassword()); - factory.setNeedClientAuth(sslConfig.isClientAuthMode()); + // else... - if (sslConfig.isClientAuthMode()) { - if (sslConfig.getTrustStore() != null) - factory.setTrustStorePath(sslConfig.getTrustStore()); - if (sslConfig.getTrustStorePassword() != null) - factory.setTrustStorePassword(sslConfig.getTrustStorePassword()); + SslContextFactory factory = new SslContextFactory(false); + if (getKeyStore() != null) + factory.setKeyStorePath(getKeyStore()); + if (getKeyStorePassword() != null) + factory.setKeyStorePassword(getKeyStorePassword()); + + factory.setNeedClientAuth(isClientAuthMode()); + + if (isClientAuthMode()) { + if (getTrustStore() != null) + factory.setTrustStorePath(getTrustStore()); + if (getTrustStorePassword() != null) + factory.setTrustStorePassword(getTrustStorePassword()); } return factory; diff --git a/solr/test-framework/src/java/org/apache/solr/util/SSLTestConfig.java b/solr/test-framework/src/java/org/apache/solr/util/SSLTestConfig.java index 90496186574..9d65a88223c 100644 --- a/solr/test-framework/src/java/org/apache/solr/util/SSLTestConfig.java +++ b/solr/test-framework/src/java/org/apache/solr/util/SSLTestConfig.java @@ -21,6 +21,8 @@ import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.SecureRandomSpi; import java.security.UnrecoverableKeyException; import javax.net.ssl.SSLContext; @@ -40,8 +42,10 @@ import org.apache.solr.client.solrj.embedded.SSLConfig; import org.apache.solr.client.solrj.impl.HttpClientUtil; import org.apache.solr.client.solrj.impl.HttpClientUtil.SchemaRegistryProvider; import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder; + import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.security.CertificateUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; public class SSLTestConfig extends SSLConfig { public static File TEST_KEYSTORE = ExternalPaths.SERVER_HOME == null ? null @@ -82,7 +86,10 @@ public class SSLTestConfig extends SSLConfig { /** * Builds a new SSLContext for HTTP clients to use when communicating with servers which have - * been configured based on the settings of this object. Also explicitly allows the use of self-signed + * been configured based on the settings of this object. + * + * NOTE: Uses a completely insecure {@link SecureRandom} instance to prevent tests from blocking + * due to lack of entropy, also explicitly allows the use of self-signed * certificates (since that's what is almost always used during testing). */ public SSLContext buildClientSSLContext() throws KeyManagementException, @@ -91,7 +98,8 @@ public class SSLTestConfig extends SSLConfig { assert isSSLMode(); SSLContextBuilder builder = SSLContexts.custom(); - + builder.setSecureRandom(NullSecureRandom.INSTANCE); + // NOTE: KeyStore & TrustStore are swapped because they are from configured from server perspective... // we are a client - our keystore contains the keys the server trusts, and vice versa builder.loadTrustMaterial(buildKeyStore(getKeyStore(), getKeyStorePassword()), new TrustSelfSignedStrategy()).build(); @@ -104,6 +112,54 @@ public class SSLTestConfig extends SSLConfig { return builder.build(); } + /** + * Builds a new SSLContext for jetty servers which have been configured based on the settings of + * this object. + * + * NOTE: Uses a completely insecure {@link SecureRandom} instance to prevent tests from blocking + * due to lack of entropy, also explicitly allows the use of self-signed + * certificates (since that's what is almost always used during testing). + * almost always used during testing). + */ + public SSLContext buildServerSSLContext() throws KeyManagementException, + UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { + + assert isSSLMode(); + + SSLContextBuilder builder = SSLContexts.custom(); + builder.setSecureRandom(NullSecureRandom.INSTANCE); + + builder.loadKeyMaterial(buildKeyStore(getKeyStore(), getKeyStorePassword()), getKeyStorePassword().toCharArray()); + + if (isClientAuthMode()) { + builder.loadTrustMaterial(buildKeyStore(getTrustStore(), getTrustStorePassword()), new TrustSelfSignedStrategy()).build(); + + } + + return builder.build(); + } + + /** + * Returns an SslContextFactory using {@link buildServerSSLContext} if SSL should be used, else returns null. + */ + @Override + public SslContextFactory createContextFactory() { + if (!isSSLMode()) { + return null; + } + // else... + + + SslContextFactory factory = new SslContextFactory(false); + try { + factory.setSslContext(buildServerSSLContext()); + } catch (Exception e) { + throw new RuntimeException("ssl context init failure: " + e.getMessage(), e); + } + factory.setNeedClientAuth(isClientAuthMode()); + return factory; + } + /** * Constructs a KeyStore using the specified filename and password */ @@ -202,5 +258,42 @@ public class SSLTestConfig extends SSLConfig { System.clearProperty("javax.net.ssl.trustStore"); System.clearProperty("javax.net.ssl.trustStorePassword"); } - + + /** + * A mocked up instance of SecureRandom that always does the minimal amount of work to generate + * "random" numbers. This is to prevent blocking issues that arise in platform default + * SecureRandom instances due to too many instances / not enough random entropy. + * Tests do not need secure SSL. + */ + private static class NullSecureRandom extends SecureRandom { + public static final SecureRandom INSTANCE = new NullSecureRandom(); + + /** SPI Used to init all instances */ + private static final SecureRandomSpi NULL_SPI = new SecureRandomSpi() { + /** NOOP: returns new uninitialized byte[] */ + public byte[] engineGenerateSeed(int numBytes) { + return new byte[numBytes]; + } + /** NOOP */ + public void engineNextBytes(byte[] bytes) { /* NOOP */ } + /** NOOP */ + public void engineSetSeed(byte[] seed) { /* NOOP */ } + }; + + private NullSecureRandom() { + super(NULL_SPI, null) ; + } + + /** NOOP: returns new uninitialized byte[] */ + public byte[] generateSeed(int numBytes) { + return new byte[numBytes]; + } + /** NOOP */ + synchronized public void nextBytes(byte[] bytes) { /* NOOP */ } + /** NOOP */ + synchronized public void setSeed(byte[] seed) { /* NOOP */ } + /** NOOP */ + synchronized public void setSeed(long seed) { /* NOOP */ } + + } }