diff --git a/geode-core/src/distributedTest/java/org/apache/geode/cache/client/internal/CustomSSLProviderDistributedTest.java b/geode-core/src/distributedTest/java/org/apache/geode/cache/client/internal/CustomSSLProviderDistributedTest.java new file mode 100644 index 000000000000..b5b29cd2238a --- /dev/null +++ b/geode-core/src/distributedTest/java/org/apache/geode/cache/client/internal/CustomSSLProviderDistributedTest.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.geode.cache.client.internal; + +import static org.apache.geode.distributed.ConfigurationProperties.SSL_ENABLED_COMPONENTS; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_ENDPOINT_IDENTIFICATION_ENABLED; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_KEYSTORE; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_REQUIRE_AUTHENTICATION; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_USE_DEFAULT_CONTEXT; +import static org.apache.geode.security.SecurableCommunicationChannels.ALL; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import java.io.IOException; +import java.net.InetAddress; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.Properties; + +import javax.net.ssl.SSLContext; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionFactory; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.ClientCacheFactory; +import org.apache.geode.cache.client.ClientRegionFactory; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.cache.client.NoAvailableServersException; +import org.apache.geode.cache.client.internal.provider.CustomKeyManagerFactory; +import org.apache.geode.cache.client.internal.provider.CustomTrustManagerFactory; +import org.apache.geode.cache.ssl.CertStores; +import org.apache.geode.cache.ssl.TestSSLUtils.CertificateBuilder; +import org.apache.geode.distributed.internal.tcpserver.LocatorCancelException; +import org.apache.geode.internal.net.SocketCreatorFactory; +import org.apache.geode.test.dunit.IgnoredException; +import org.apache.geode.test.dunit.rules.ClusterStartupRule; +import org.apache.geode.test.dunit.rules.MemberVM; +import org.apache.geode.test.junit.categories.ClientServerTest; + +@Category({ClientServerTest.class}) +public class CustomSSLProviderDistributedTest { + private static MemberVM locator; + private static MemberVM server; + + @Rule + public ClusterStartupRule cluster = new ClusterStartupRule(); + + private CustomKeyManagerFactory.PKIXFactory keyManagerFactory; + private CustomTrustManagerFactory.PKIXFactory trustManagerFactory; + + private void setupCluster(Properties locatorSSLProps, Properties serverSSLProps) { + // create a cluster + locator = cluster.startLocatorVM(0, locatorSSLProps); + server = cluster.startServerVM(1, serverSSLProps, locator.getPort()); + + // create region + server.invoke(CustomSSLProviderDistributedTest::createServerRegion); + locator.waitUntilRegionIsReadyOnExactlyThisManyServers("/region", 1); + } + + private static void createServerRegion() { + RegionFactory factory = + ClusterStartupRule.getCache().createRegionFactory(RegionShortcut.REPLICATE); + Region r = factory.create("region"); + r.put("serverkey", "servervalue"); + } + + @Test + public void hostNameIsValidatedWhenUsingDefaultContext() throws Exception { + CertificateBuilder locatorCertificate = new CertificateBuilder() + .commonName("locator") + // ClusterStartupRule uses 'localhost' as locator host + .sanDnsName(InetAddress.getLoopbackAddress().getHostName()) + .sanDnsName(InetAddress.getLocalHost().getHostName()) + .sanIpAddress(InetAddress.getLocalHost()) + .sanIpAddress(InetAddress.getByName("0.0.0.0")); // to pass on windows + + CertificateBuilder serverCertificate = new CertificateBuilder() + .commonName("server") + .sanDnsName(InetAddress.getLocalHost().getHostName()) + .sanIpAddress(InetAddress.getLocalHost()); + + CertificateBuilder clientCertificate = new CertificateBuilder() + .commonName("client"); + + validateClientSSLConnection(locatorCertificate, serverCertificate, clientCertificate, true, + true, false, null); + } + + @Test + public void clientCanChooseNotToValidateHostName() throws Exception { + CertificateBuilder locatorCertificate = new CertificateBuilder() + .commonName("locator"); + + CertificateBuilder serverCertificate = new CertificateBuilder() + .commonName("server"); + + CertificateBuilder clientCertificate = new CertificateBuilder() + .commonName("client"); + + validateClientSSLConnection(locatorCertificate, serverCertificate, clientCertificate, false, + false, true, null); + } + + @Test + public void clientConnectionFailsIfNoHostNameInLocatorKey() throws Exception { + CertificateBuilder locatorCertificate = new CertificateBuilder() + .commonName("locator"); + + CertificateBuilder serverCertificate = new CertificateBuilder() + .commonName("server"); + + CertificateBuilder clientCertificate = new CertificateBuilder() + .commonName("client"); + + validateClientSSLConnection(locatorCertificate, serverCertificate, clientCertificate, false, + false, false, LocatorCancelException.class); + } + + @Test + public void clientConnectionFailsWhenWrongHostNameInLocatorKey() throws Exception { + CertificateBuilder locatorCertificate = new CertificateBuilder() + .commonName("locator") + .sanDnsName("example.com");; + + CertificateBuilder serverCertificate = new CertificateBuilder() + .commonName("server") + .sanDnsName("example.com");; + + CertificateBuilder clientCertificate = new CertificateBuilder() + .commonName("client"); + + validateClientSSLConnection(locatorCertificate, serverCertificate, clientCertificate, false, + false, + false, + LocatorCancelException.class); + } + + @Test + public void expectConnectionFailureWhenNoHostNameInServerKey() throws Exception { + CertificateBuilder locatorCertificateWithSan = new CertificateBuilder() + .commonName("locator") + .sanDnsName(InetAddress.getLoopbackAddress().getHostName()) + .sanDnsName(InetAddress.getLocalHost().getHostName()) + .sanIpAddress(InetAddress.getLocalHost()); + + CertificateBuilder serverCertificateWithNoSan = new CertificateBuilder() + .commonName("server"); + + CertificateBuilder clientCertificate = new CertificateBuilder() + .commonName("client"); + + validateClientSSLConnection(locatorCertificateWithSan, serverCertificateWithNoSan, + clientCertificate, false, false, false, + NoAvailableServersException.class); + } + + private void validateClientSSLConnection(CertificateBuilder locatorCertificate, + CertificateBuilder serverCertificate, CertificateBuilder clientCertificate, + boolean enableHostNameVerficationForLocator, boolean enableHostNameVerificationForServer, + boolean disableHostNameVerificationForClient, + Class expectedExceptionOnClient) + throws GeneralSecurityException, IOException { + + CertStores locatorStore = CertStores.locatorStore(); + locatorStore.withCertificate(locatorCertificate); + + CertStores serverStore = CertStores.serverStore(); + serverStore.withCertificate(serverCertificate); + + CertStores clientStore = CertStores.clientStore(); + clientStore.withCertificate(clientCertificate); + + Properties locatorSSLProps = locatorStore + .trustSelf() + .trust(serverStore.alias(), serverStore.certificate()) + .trust(clientStore.alias(), clientStore.certificate()) + .propertiesWith(ALL, false, enableHostNameVerficationForLocator); + + Properties serverSSLProps = serverStore + .trustSelf() + .trust(locatorStore.alias(), locatorStore.certificate()) + .trust(clientStore.alias(), clientStore.certificate()) + .propertiesWith(ALL, true, enableHostNameVerificationForServer); + + // this props is only to create temp keystore and truststore and get paths + Properties clientSSLProps = clientStore + .trust(locatorStore.alias(), locatorStore.certificate()) + .trust(serverStore.alias(), serverStore.certificate()) + .propertiesWith(ALL, true, true); + + setupCluster(locatorSSLProps, serverSSLProps); + + // setup client + keyManagerFactory = + new CustomKeyManagerFactory.PKIXFactory(clientSSLProps.getProperty(SSL_KEYSTORE)); + keyManagerFactory.engineInit(null, null); + + trustManagerFactory = + new CustomTrustManagerFactory.PKIXFactory(clientSSLProps.getProperty(SSL_TRUSTSTORE)); + trustManagerFactory.engineInit((KeyStore) null); + + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(keyManagerFactory.engineGetKeyManagers(), + trustManagerFactory.engineGetTrustManagers(), null); + // set default context + SSLContext.setDefault(sslContext); + + Properties clientSSLProperties = new Properties(); + clientSSLProperties.setProperty(SSL_ENABLED_COMPONENTS, ALL); + clientSSLProperties.setProperty(SSL_REQUIRE_AUTHENTICATION, String.valueOf("true")); + clientSSLProperties.setProperty(SSL_USE_DEFAULT_CONTEXT, String.valueOf("true")); + + if (disableHostNameVerificationForClient) { + // client chose to override default + clientSSLProperties.setProperty(SSL_ENDPOINT_IDENTIFICATION_ENABLED, String.valueOf("false")); + } + + ClientCacheFactory clientCacheFactory = new ClientCacheFactory(clientSSLProperties); + clientCacheFactory.addPoolLocator(locator.getVM().getHost().getHostName(), locator.getPort()); + ClientCache clientCache = clientCacheFactory.create(); + + ClientRegionFactory regionFactory = + clientCache.createClientRegionFactory(ClientRegionShortcut.PROXY); + + if (expectedExceptionOnClient != null) { + IgnoredException.addIgnoredException("javax.net.ssl.SSLHandshakeException"); + IgnoredException.addIgnoredException("java.net.SocketException"); + + Region clientRegion = regionFactory.create("region"); + assertThatExceptionOfType(expectedExceptionOnClient) + .isThrownBy(() -> clientRegion.put("clientkey", "clientvalue")); + } else { + // test client can read and write to server + Region clientRegion = regionFactory.create("region"); + assertThat("servervalue").isEqualTo(clientRegion.get("serverkey")); + clientRegion.put("clientkey", "clientvalue"); + + // test server can see data written by client + server.invoke(CustomSSLProviderDistributedTest::doServerRegionTest); + } + + SocketCreatorFactory.close(); + } + + private static void doServerRegionTest() { + Region region = ClusterStartupRule.getCache().getRegion("region"); + assertThat("servervalue").isEqualTo(region.get("serverkey")); + assertThat("clientvalue").isEqualTo(region.get("clientkey")); + } +} diff --git a/geode-core/src/distributedTest/java/org/apache/geode/cache/client/internal/provider/CustomKeyManagerFactory.java b/geode-core/src/distributedTest/java/org/apache/geode/cache/client/internal/provider/CustomKeyManagerFactory.java new file mode 100644 index 000000000000..75cf24aab6cb --- /dev/null +++ b/geode-core/src/distributedTest/java/org/apache/geode/cache/client/internal/provider/CustomKeyManagerFactory.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.geode.cache.client.internal.provider; + +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.UndeclaredThrowableException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.logging.Logger; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.KeyManagerFactorySpi; +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.X509ExtendedKeyManager; + + +public abstract class CustomKeyManagerFactory extends KeyManagerFactorySpi { + + private final Logger logger = Logger.getLogger(this.getClass().getName()); + + private final String algorithm; + private final String keyStorePath; + private KeyManagerFactory customKeyManagerFactory; + private X509ExtendedKeyManager customKeyManager; + + private CustomKeyManagerFactory(String algorithm, String keyStorePath) { + this.algorithm = algorithm; + this.keyStorePath = keyStorePath; + } + + @Override + public final KeyManager[] engineGetKeyManagers() { + X509ExtendedKeyManager systemKeyManager = getCustomKeyManager(); + return new KeyManager[] {systemKeyManager}; + } + + @Override + protected final void engineInit(ManagerFactoryParameters managerFactoryParameters) { + // not supported right now + throw new UnsupportedOperationException("use engineInit with keystore"); + } + + @Override + public final void engineInit(KeyStore keyStore, char[] chars) { + // ignore the passed in keystore as it will be null + init(); + } + + private void init() { + String SSL_KEYSTORE_TYPE = "JKS"; + String SSL_KEYSTORE_PASSWORD = "password"; + + try { + FileInputStream fileInputStream = new FileInputStream(keyStorePath); + KeyStore keyStore = KeyStore.getInstance(SSL_KEYSTORE_TYPE); + keyStore.load(fileInputStream, SSL_KEYSTORE_PASSWORD.toCharArray()); + this.customKeyManagerFactory = KeyManagerFactory.getInstance(this.algorithm, "SunJSSE"); + this.customKeyManagerFactory.init(keyStore, SSL_KEYSTORE_PASSWORD.toCharArray()); + } catch (NoSuchAlgorithmException | IOException | CertificateException + | UnrecoverableKeyException | KeyStoreException | NoSuchProviderException e) { + throw new UndeclaredThrowableException(e); + } + } + + private X509ExtendedKeyManager getCustomKeyManager() { + if (this.customKeyManager == null) { + for (KeyManager candidate : this.customKeyManagerFactory.getKeyManagers()) { + if (candidate instanceof X509ExtendedKeyManager) { + this.logger.info("Adding System Key Manager"); + this.customKeyManager = (X509ExtendedKeyManager) candidate; + break; + } + } + } + return this.customKeyManager; + } + + public static final class PKIXFactory extends CustomKeyManagerFactory { + public PKIXFactory(String keyStorePath) { + super("PKIX", keyStorePath); + } + } + + public static final class SimpleFactory extends CustomKeyManagerFactory { + public SimpleFactory(String keyStorePath) { + super("SunX509", keyStorePath); + } + } +} diff --git a/geode-core/src/distributedTest/java/org/apache/geode/cache/client/internal/provider/CustomTrustManagerFactory.java b/geode-core/src/distributedTest/java/org/apache/geode/cache/client/internal/provider/CustomTrustManagerFactory.java new file mode 100644 index 000000000000..6d1145551514 --- /dev/null +++ b/geode-core/src/distributedTest/java/org/apache/geode/cache/client/internal/provider/CustomTrustManagerFactory.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.geode.cache.client.internal.provider; + +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.UndeclaredThrowableException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.CertificateException; +import java.util.logging.Logger; + +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.TrustManagerFactorySpi; +import javax.net.ssl.X509ExtendedTrustManager; + + +public abstract class CustomTrustManagerFactory extends TrustManagerFactorySpi { + + private final Logger logger = Logger.getLogger(this.getClass().getName()); + + private final String algorithm; + private final String trustStorePath; + private TrustManagerFactory customTrustManagerFactory; + private X509ExtendedTrustManager customTrustManager; + + private CustomTrustManagerFactory(String algorithm, String trustStorePath) { + this.algorithm = algorithm; + this.trustStorePath = trustStorePath; + } + + @Override + public final TrustManager[] engineGetTrustManagers() { + X509ExtendedTrustManager systemTrustManager = getCustomTrustManager(); + return new TrustManager[] {systemTrustManager}; + } + + @Override + public final void engineInit(ManagerFactoryParameters managerFactoryParameters) { + // not supported right now + throw new UnsupportedOperationException("use engineInit with keystore"); + } + + @Override + public final void engineInit(KeyStore keyStore) { + // ignore the passed in keystore as it will be null + init(); + } + + private X509ExtendedTrustManager getCustomTrustManager() { + if (this.customTrustManager == null) { + for (TrustManager candidate : this.customTrustManagerFactory.getTrustManagers()) { + if (candidate instanceof X509ExtendedTrustManager) { + this.logger.info("Adding System Trust Manager"); + this.customTrustManager = (X509ExtendedTrustManager) candidate; + break; + } + } + } + return this.customTrustManager; + } + + private void init() { + String trustStoreType = "JKS"; + String trustStorePassword = "password"; + + try { + FileInputStream fileInputStream = new FileInputStream(trustStorePath); + KeyStore trustStore = KeyStore.getInstance(trustStoreType); + trustStore.load(fileInputStream, trustStorePassword.toCharArray()); + this.customTrustManagerFactory = TrustManagerFactory.getInstance(this.algorithm, "SunJSSE"); + this.customTrustManagerFactory.init(trustStore); + } catch (NoSuchAlgorithmException | IOException | CertificateException | KeyStoreException + | NoSuchProviderException e) { + throw new UndeclaredThrowableException(e); + } + } + + public static final class PKIXFactory extends CustomTrustManagerFactory { + public PKIXFactory(String trustStorePath) { + super("PKIX", trustStorePath); + } + } + + public static final class SimpleFactory extends CustomTrustManagerFactory { + public SimpleFactory(String trustStorePath) { + super("SunX509", trustStorePath); + } + } +} diff --git a/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPClientSSLIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPClientSSLIntegrationTest.java index 1613f75fdbf2..dc55a23f7bf1 100644 --- a/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPClientSSLIntegrationTest.java +++ b/geode-core/src/integrationTest/java/org/apache/geode/distributed/internal/tcpserver/TCPClientSSLIntegrationTest.java @@ -15,7 +15,10 @@ package org.apache.geode.distributed.internal.tcpserver; import static org.apache.geode.security.SecurableCommunicationChannels.LOCATOR; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; import java.io.IOException; import java.net.InetAddress; @@ -62,7 +65,7 @@ public void setup() { } private void startServerAndClient(CertificateBuilder serverCertificate, - CertificateBuilder clientCertificate) + CertificateBuilder clientCertificate, boolean enableHostNameValidation) throws GeneralSecurityException, IOException { CertStores serverStore = CertStores.locatorStore(); @@ -74,11 +77,11 @@ private void startServerAndClient(CertificateBuilder serverCertificate, Properties serverProperties = serverStore .trustSelf() .trust(clientStore.alias(), clientStore.certificate()) - .propertiesWith(LOCATOR, true, true); + .propertiesWith(LOCATOR, true, enableHostNameValidation); Properties clientProperties = clientStore .trust(serverStore.alias(), serverStore.certificate()) - .propertiesWith(LOCATOR, true, true); + .propertiesWith(LOCATOR, true, enableHostNameValidation); startTcpServer(serverProperties); @@ -94,8 +97,11 @@ private void startTcpServer(Properties sslProperties) throws IOException { localhost = InetAddress.getLocalHost(); port = AvailablePort.getRandomAvailablePort(AvailablePort.SOCKET); + TcpHandler tcpHandler = Mockito.mock(TcpHandler.class); + when(tcpHandler.processRequest(any())).thenReturn("Running!"); + server = new FakeTcpServer(port, localhost, sslProperties, null, - Mockito.mock(TcpHandler.class), Mockito.mock(PoolStatHelper.class), + tcpHandler, Mockito.mock(PoolStatHelper.class), Thread.currentThread().getThreadGroup(), "server thread"); server.start(); } @@ -109,7 +115,25 @@ public void clientConnectsIfServerCertificateHasHostname() throws Exception { CertificateBuilder clientCertificate = new CertificateBuilder() .commonName("tcp-client"); - startServerAndClient(serverCertificate, clientCertificate); + startServerAndClient(serverCertificate, clientCertificate, true); + String response = + (String) client.requestToServer(localhost, port, Boolean.valueOf(false), 5 * 1000); + assertThat(response).isEqualTo("Running!"); + } + + @Test + public void clientChooseToDisableHasHostnameValidation() throws Exception { + // no host name in server cert + CertificateBuilder serverCertificate = new CertificateBuilder() + .commonName("tcp-server"); + + CertificateBuilder clientCertificate = new CertificateBuilder() + .commonName("tcp-client"); + + startServerAndClient(serverCertificate, clientCertificate, false); + String response = + (String) client.requestToServer(localhost, port, Boolean.valueOf(false), 5 * 1000); + assertThat(response).isEqualTo("Running!"); } @Test @@ -120,7 +144,7 @@ public void clientFailsToConnectIfServerCertificateNoHostname() throws Exception CertificateBuilder clientCertificate = new CertificateBuilder() .commonName("tcp-client"); - startServerAndClient(serverCertificate, clientCertificate); + startServerAndClient(serverCertificate, clientCertificate, true); assertThatExceptionOfType(LocatorCancelException.class) .isThrownBy(() -> client.requestToServer(localhost, port, Boolean.valueOf(false), 5 * 1000)) @@ -138,7 +162,7 @@ public void clientFailsToConnectIfServerCertificateWrongHostname() throws Except CertificateBuilder clientCertificate = new CertificateBuilder() .commonName("tcp-client"); - startServerAndClient(serverCertificate, clientCertificate); + startServerAndClient(serverCertificate, clientCertificate, true); assertThatExceptionOfType(LocatorCancelException.class) .isThrownBy(() -> client.requestToServer(localhost, port, Boolean.valueOf(false), 5 * 1000)) @@ -147,7 +171,7 @@ public void clientFailsToConnectIfServerCertificateWrongHostname() throws Except + localhost.getHostName() + " found.")); } - private class FakeTcpServer extends TcpServer { + private static class FakeTcpServer extends TcpServer { private DistributionConfig distributionConfig; public FakeTcpServer(int port, InetAddress bind_address, Properties sslConfig, diff --git a/geode-core/src/main/java/org/apache/geode/distributed/ConfigurationProperties.java b/geode-core/src/main/java/org/apache/geode/distributed/ConfigurationProperties.java index 2dd36f35be2a..f4bdada08198 100644 --- a/geode-core/src/main/java/org/apache/geode/distributed/ConfigurationProperties.java +++ b/geode-core/src/main/java/org/apache/geode/distributed/ConfigurationProperties.java @@ -1929,15 +1929,36 @@ public interface ConfigurationProperties { * Geode 1.0 */ String DISTRIBUTED_TRANSACTIONS = "distributed-transactions"; + + /** + * The static String definition of the "ssl-use-default-context" property + *

+ * + * Description When true, either uses the default context as returned by + * SSLContext.getInstance('Default') or uses the context as set by using + * SSLContext.setDefault(). If false, then specify the keystore and the truststore by setting + * ssl-keystore-* and ssl-truststore-* properties. If true, then + * ssl-endpoint-identification-enabled + * is set to true. This property does not enable SSL. + *

+ * + * Default: "false" + *

+ * + * Since: Geode 1.7 + *

+ */ + String SSL_USE_DEFAULT_CONTEXT = "ssl-use-default-context"; /** * The static String definition of the "ssl-endpoint-identification-enabled" property *

* Description: If true, clients validate server hostname using server certificate during - * SSL handshake. + * SSL handshake. It defaults to true when ssl-use-default-context is true or else false. *

* Default: code>"false" - * Since: Geode 1.8 + *

+ * Since: Geode 1.7 */ String SSL_ENDPOINT_IDENTIFICATION_ENABLED = "ssl-endpoint-identification-enabled"; /** diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/AbstractDistributionConfig.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/AbstractDistributionConfig.java index 07f33a7c0d99..5cfc33bbfd74 100644 --- a/geode-core/src/main/java/org/apache/geode/distributed/internal/AbstractDistributionConfig.java +++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/AbstractDistributionConfig.java @@ -161,6 +161,7 @@ import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE; import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE_PASSWORD; import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE_TYPE; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_USE_DEFAULT_CONTEXT; import static org.apache.geode.distributed.ConfigurationProperties.SSL_WEB_ALIAS; import static org.apache.geode.distributed.ConfigurationProperties.SSL_WEB_SERVICE_REQUIRE_AUTHENTICATION; import static org.apache.geode.distributed.ConfigurationProperties.START_DEV_REST_API; @@ -1362,7 +1363,11 @@ public static Class _getAttributeType(String attName) { "User defined fully qualified class name implementing PostProcessor interface for integrated security. Defaults to \"{0}\". Legal values can be any \"class name\" implementing PostProcessor that is present in the classpath."); m.put(SSL_ENDPOINT_IDENTIFICATION_ENABLED, - "If true, clients validate server hostname using server certificate during SSL handshake."); + "If true, clients validate server hostname using server certificate during SSL handshake. It defaults to true when ssl-use-default-context is true or else false."); + + m.put(SSL_USE_DEFAULT_CONTEXT, + "When true, either uses the default context as returned by SSLContext.getInstance('Default') or uses the context as set by using SSLContext.setDefault(). " + + "If false, then specify the keystore and the truststore by setting ssl-keystore-* and ssl-truststore-* properties. If true, then ssl-endpoint-identification-enabled is set to true. This property does not enable SSL."); m.put(SSL_ENABLED_COMPONENTS, "A comma delimited list of components that require SSL communications"); diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java index d6ee27ced0fd..e4950f48995b 100644 --- a/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java +++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java @@ -162,6 +162,7 @@ import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE; import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE_PASSWORD; import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE_TYPE; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_USE_DEFAULT_CONTEXT; import static org.apache.geode.distributed.ConfigurationProperties.SSL_WEB_ALIAS; import static org.apache.geode.distributed.ConfigurationProperties.SSL_WEB_SERVICE_REQUIRE_AUTHENTICATION; import static org.apache.geode.distributed.ConfigurationProperties.START_DEV_REST_API; @@ -4879,26 +4880,19 @@ public interface DistributionConfig extends Config, LogConfig { @ConfigAttribute(type = String.class) String SERVER_SSL_ALIAS_NAME = SSL_SERVER_ALIAS; - /** - * The default {@link ConfigurationProperties#SSL_ENDPOINT_IDENTIFICATION_ENABLED} value. - *

- * Actual value of this constant is false. - */ - boolean DEFAULT_SSL_ENDPOINT_IDENTIFICATION_ENABLED = false; - /** * Returns the value of the {@link ConfigurationProperties#SSL_ENDPOINT_IDENTIFICATION_ENABLED} * property. */ @ConfigAttributeGetter(name = SSL_ENDPOINT_IDENTIFICATION_ENABLED) - boolean getSSLEndpointIdentificationEnabled(); + boolean getSSLEndPointIdentificationEnabled(); /** * Sets the value of the {@link ConfigurationProperties#SSL_ENDPOINT_IDENTIFICATION_ENABLED} * property. */ @ConfigAttributeSetter(name = SSL_ENDPOINT_IDENTIFICATION_ENABLED) - void setSSLEndpointIdentificationEnabled(boolean enabled); + void setSSLEndPointIdentificationEnabled(boolean enabled); /** * The name of the {@link ConfigurationProperties#SSL_ENDPOINT_IDENTIFICATION_ENABLED} property @@ -4938,6 +4932,23 @@ public interface DistributionConfig extends Config, LogConfig { SecurableCommunicationChannel[] DEFAULT_SSL_ENABLED_COMPONENTS = new SecurableCommunicationChannel[] {}; + boolean DEFAULT_SSL_USE_DEFAULT_CONTEXT = false; + + @ConfigAttribute(type = Boolean.class) + String SSL_USE_DEFAULT_CONTEXT_NAME = SSL_USE_DEFAULT_CONTEXT; + + /** + * Returns the value of the {@link ConfigurationProperties#SSL_USE_DEFAULT_CONTEXT} property. + */ + @ConfigAttributeGetter(name = SSL_USE_DEFAULT_CONTEXT) + boolean getSSLUseDefaultContext(); + + /** + * Sets the value of the {@link ConfigurationProperties#SSL_USE_DEFAULT_CONTEXT} property. + */ + @ConfigAttributeSetter(name = SSL_USE_DEFAULT_CONTEXT) + void setSSLUseDefaultContext(boolean defaultContext); + /** * Returns the value of the {@link ConfigurationProperties#SSL_PROTOCOLS} property. */ diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfigImpl.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfigImpl.java index 851698af5634..050573739665 100644 --- a/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfigImpl.java +++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfigImpl.java @@ -603,11 +603,12 @@ public class DistributionConfigImpl extends AbstractDistributionConfig implement private String httpServiceSSLAlias = DEFAULT_SSL_ALIAS; - private boolean sslEndpointIdentificationEnabled = DEFAULT_SSL_ENDPOINT_IDENTIFICATION_ENABLED; + private Boolean sslEndPointIdentificationEnabled = null; private SecurableCommunicationChannel[] securableCommunicationChannels = DEFAULT_SSL_ENABLED_COMPONENTS; + private boolean sslUseDefaultSSLContext = DEFAULT_SSL_USE_DEFAULT_CONTEXT; private String sslProtocols = DEFAULT_SSL_PROTOCOLS; private String sslCiphers = DEFAULT_SSL_CIPHERS; private boolean sslRequireAuthentication = DEFAULT_SSL_REQUIRE_AUTHENTICATION; @@ -849,10 +850,11 @@ public DistributionConfigImpl(DistributionConfig other) { this.serverSSLAlias = other.getServerSSLAlias(); this.locatorSSLAlias = other.getLocatorSSLAlias(); - this.sslEndpointIdentificationEnabled = other.getSSLEndpointIdentificationEnabled(); + this.sslEndPointIdentificationEnabled = other.getSSLEndPointIdentificationEnabled(); this.securableCommunicationChannels = ((DistributionConfigImpl) other).securableCommunicationChannels; + this.sslUseDefaultSSLContext = other.getSSLUseDefaultContext(); this.sslCiphers = other.getSSLCiphers(); this.sslProtocols = other.getSSLProtocols(); this.sslRequireAuthentication = other.getSSLRequireAuthentication(); @@ -2761,13 +2763,19 @@ public void setServerSSLAlias(final String alias) { } @Override - public boolean getSSLEndpointIdentificationEnabled() { - return sslEndpointIdentificationEnabled; + public boolean getSSLEndPointIdentificationEnabled() { + // sslEndPointIdentificationEnabled is a boxed boolean and no default value is set, so that + // we can differentiate between an assigned default vs user provided override. This is set + // to true when ssl-use-default-context is true or else its false. So return false if its null. + if (this.sslEndPointIdentificationEnabled == null) { + return false; + } + return sslEndPointIdentificationEnabled; } @Override - public void setSSLEndpointIdentificationEnabled(final boolean sslEnabledIdentification) { - this.sslEndpointIdentificationEnabled = sslEnabledIdentification; + public void setSSLEndPointIdentificationEnabled(final boolean sslEndPointIdentificationEnabled) { + this.sslEndPointIdentificationEnabled = sslEndPointIdentificationEnabled; } @Override @@ -2781,6 +2789,19 @@ public void setSecurableCommunicationChannels( this.securableCommunicationChannels = sslEnabledComponents; } + @Override + public boolean getSSLUseDefaultContext() { + return sslUseDefaultSSLContext; + } + + @Override + public void setSSLUseDefaultContext(final boolean sslUseDefaultSSLContext) { + if (this.sslEndPointIdentificationEnabled == null) { + this.sslEndPointIdentificationEnabled = Boolean.TRUE; + } + this.sslUseDefaultSSLContext = sslUseDefaultSSLContext; + } + @Override public String getSSLProtocols() { return sslProtocols; diff --git a/geode-core/src/main/java/org/apache/geode/internal/admin/SSLConfig.java b/geode-core/src/main/java/org/apache/geode/internal/admin/SSLConfig.java index 5344ca653365..eda1dde9ab45 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/admin/SSLConfig.java +++ b/geode-core/src/main/java/org/apache/geode/internal/admin/SSLConfig.java @@ -32,8 +32,8 @@ */ public class SSLConfig { - private boolean endpointIdentification = - DistributionConfig.DEFAULT_SSL_ENDPOINT_IDENTIFICATION_ENABLED; + private boolean endpointIdentification; + private boolean useDefaultSSLContext = DistributionConfig.DEFAULT_SSL_USE_DEFAULT_CONTEXT; private boolean enabled = DistributionConfig.DEFAULT_SSL_ENABLED; private String protocols = DistributionConfig.DEFAULT_SSL_PROTOCOLS; private String ciphers = DistributionConfig.DEFAULT_SSL_CIPHERS; @@ -119,6 +119,14 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } + public boolean useDefaultSSLContext() { + return this.useDefaultSSLContext; + } + + public void setUseDefaultSSLContext(boolean useDefaultSSLContext) { + this.useDefaultSSLContext = useDefaultSSLContext; + } + public String getProtocols() { return this.protocols; } diff --git a/geode-core/src/main/java/org/apache/geode/internal/net/SSLConfigurationFactory.java b/geode-core/src/main/java/org/apache/geode/internal/net/SSLConfigurationFactory.java index 23c0c5c48f87..3e9c65d186cd 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/net/SSLConfigurationFactory.java +++ b/geode-core/src/main/java/org/apache/geode/internal/net/SSLConfigurationFactory.java @@ -168,7 +168,7 @@ private SSLConfig createSSLConfig(final DistributionConfig distributionConfig, SSLConfig sslConfig = new SSLConfig(); sslConfig.setCiphers(distributionConfig.getSSLCiphers()); sslConfig - .setEndpointIdentificationEnabled(distributionConfig.getSSLEndpointIdentificationEnabled()); + .setEndpointIdentificationEnabled(distributionConfig.getSSLEndPointIdentificationEnabled()); sslConfig .setEnabled(determineIfSSLEnabledForSSLComponent(distributionConfig, sslEnabledComponent)); sslConfig.setKeystore(distributionConfig.getSSLKeyStore()); @@ -180,6 +180,7 @@ private SSLConfig createSSLConfig(final DistributionConfig distributionConfig, sslConfig.setProtocols(distributionConfig.getSSLProtocols()); sslConfig.setRequireAuth(distributionConfig.getSSLRequireAuthentication()); sslConfig.setAlias(distributionConfig.getSSLDefaultAlias()); + sslConfig.setUseDefaultSSLContext(distributionConfig.getSSLUseDefaultContext()); return sslConfig; } diff --git a/geode-core/src/main/java/org/apache/geode/internal/net/SocketCreator.java b/geode-core/src/main/java/org/apache/geode/internal/net/SocketCreator.java index 9d7501313866..c93e3c41b5ff 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/net/SocketCreator.java +++ b/geode-core/src/main/java/org/apache/geode/internal/net/SocketCreator.java @@ -14,6 +14,7 @@ */ package org.apache.geode.internal.net; + import java.io.FileInputStream; import java.io.IOException; import java.net.BindException; @@ -390,6 +391,10 @@ private void initialize() { */ private SSLContext createAndConfigureSSLContext() throws GeneralSecurityException, IOException { + if (sslConfig.useDefaultSSLContext()) { + return SSLContext.getDefault(); + } + SSLContext newSSLContext = getSSLContextInstance(); KeyManager[] keyManagers = getKeyManagers(); TrustManager[] trustManagers = getTrustManagers(); @@ -1036,6 +1041,9 @@ private void configureClientSSLSocket(Socket socket, int timeout) throws IOExcep SSLParameters sslParameters = sslSocket.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); sslSocket.setSSLParameters(sslParameters); + } else { + logger.warn("Your SSL configuration disables hostname validation. " + + "Future releases will mandate hostname validation."); } String[] protocols = this.sslConfig.getProtocolsAsStringArray(); diff --git a/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java b/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java index 80154a210f1a..68d3ef24441e 100644 --- a/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java +++ b/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java @@ -32,6 +32,7 @@ import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_POST_PROCESSOR; import static org.apache.geode.distributed.ConfigurationProperties.SSL_ENABLED_COMPONENTS; import static org.apache.geode.distributed.ConfigurationProperties.SSL_ENDPOINT_IDENTIFICATION_ENABLED; +import static org.apache.geode.distributed.ConfigurationProperties.SSL_USE_DEFAULT_CONTEXT; import static org.apache.geode.distributed.ConfigurationProperties.START_LOCATOR; import static org.apache.geode.distributed.ConfigurationProperties.STATISTIC_ARCHIVE_FILE; import static org.apache.geode.distributed.ConfigurationProperties.STATISTIC_SAMPLE_RATE; @@ -100,7 +101,7 @@ public void before() { @Test public void testGetAttributeNames() { String[] attNames = AbstractDistributionConfig._getAttNames(); - assertEquals(attNames.length, 163); + assertThat(attNames.length).isEqualTo(164); List boolList = new ArrayList(); List intList = new ArrayList(); @@ -134,7 +135,7 @@ public void testGetAttributeNames() { // TODO - This makes no sense. One has no idea what the correct expected number of attributes // are. - assertEquals(32, boolList.size()); + assertEquals(33, boolList.size()); assertEquals(35, intList.size()); assertEquals(87, stringList.size()); assertEquals(5, fileList.size()); @@ -423,11 +424,22 @@ public void testSSLEnabledComponentsLegacyPass() { } @Test - public void testSSLEnabledEndpointValidationIsSetDefaultToFalse() { + public void testSSLEnabledEndpointValidationIsSetDefaultToTrueWhenSetUseDefaultContextIsUsed() { Properties props = new Properties(); + props.put(SSL_ENABLED_COMPONENTS, "all"); + props.put(SSL_USE_DEFAULT_CONTEXT, "true"); + + DistributionConfig config = new DistributionConfigImpl(props); + assertThat(config.getSSLEndPointIdentificationEnabled()).isEqualTo(true); + } + + @Test + public void testSSLEnabledEndpointValidationIsSetDefaultToFalseWhenDefaultContextNotUsed() { + Properties props = new Properties(); + props.put(SSL_ENABLED_COMPONENTS, "all"); DistributionConfig config = new DistributionConfigImpl(props); - assertThat(config.getSSLEndpointIdentificationEnabled()).isEqualTo(false); + assertThat(config.getSSLEndPointIdentificationEnabled()).isEqualTo(false); } @Test @@ -436,6 +448,6 @@ public void testSSLUseEndpointValidationIsSet() { props.put(SSL_ENDPOINT_IDENTIFICATION_ENABLED, "true"); DistributionConfig config = new DistributionConfigImpl(props); - assertThat(config.getSSLEndpointIdentificationEnabled()).isEqualTo(true); + assertThat(config.getSSLEndPointIdentificationEnabled()).isEqualTo(true); } } diff --git a/geode-protobuf/src/integrationTest/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheConnectionJUnitTest.java b/geode-protobuf/src/integrationTest/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheConnectionJUnitTest.java index 12d9130bf892..1f135fb1f1ab 100644 --- a/geode-protobuf/src/integrationTest/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheConnectionJUnitTest.java +++ b/geode-protobuf/src/integrationTest/java/org/apache/geode/internal/protocol/protobuf/v1/acceptance/CacheConnectionJUnitTest.java @@ -252,6 +252,7 @@ private Socket getSSLSocket() throws IOException { sslConfig.setKeystorePassword("password"); sslConfig.setTruststore(trustStorePath); sslConfig.setKeystorePassword("password"); + sslConfig.setEndpointIdentificationEnabled(false); SocketCreator socketCreator = new SocketCreator(sslConfig); return socketCreator.connectForClient("localhost", cacheServerPort, 5000);