This is an automated email from the ASF dual-hosted git repository.

donalevans pushed a commit to branch support/1.15
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/support/1.15 by this push:
     new b85f2616ed GEODE-10297: Create SSLContext using default protocols 
(#7680)
b85f2616ed is described below

commit b85f2616ed848e7f8bc7d5f3577570d869cccc39
Author: Donal Evans <[email protected]>
AuthorDate: Mon May 23 08:37:58 2022 -0700

    GEODE-10297: Create SSLContext using default protocols (#7680)
    
     - Modify SSLUtil to attempt to create SSLContext using the supported SSL
       contexts
     - Add WANSSLDistributedTest to test protocol/cipher combinations in WAN
       context
     - Do not hide exception when generating key pair in CertificateBuilder
     - Fix javadoc error in SocketCreator
    
    Authored-by: Donal Evans <[email protected]>
    (cherry picked from commit 1e873a670e0606f934666596ff70f346a3957d75)
---
 .../org/apache/geode/internal/net/SSLUtil.java     |  48 +-
 .../apache/geode/internal/net/SocketCreator.java   |   6 +-
 .../org/apache/geode/internal/net/SSLUtilTest.java |  98 +---
 .../cache/wan/misc/WANSSLDistributedTest.java      | 633 +++++++++++++++++++++
 4 files changed, 649 insertions(+), 136 deletions(-)

diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/net/SSLUtil.java 
b/geode-core/src/main/java/org/apache/geode/internal/net/SSLUtil.java
index a6712b0e08..e12c1772b4 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/net/SSLUtil.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/net/SSLUtil.java
@@ -20,7 +20,6 @@ import static 
org.apache.geode.internal.net.filewatch.FileWatchingX509ExtendedTr
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.security.cert.X509Certificate;
-import java.util.stream.Stream;
 
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.KeyManagerFactory;
@@ -36,48 +35,15 @@ import org.jetbrains.annotations.Nullable;
 
 public class SSLUtil {
   /**
-   * This is a list of the algorithms that are tried, in order, when "any" is 
specified. Update
-   * this list as new algorithms become available and are supported by Geode. 
Remove old,
-   * no-longer trusted algorithms.
+   * This is a list of the protocols that are tried, in order, to create an 
SSLContext. Update
+   * this list as new protocols become available and are supported by Geode. 
Remove old,
+   * no-longer trusted protocols.
    */
-  static final String[] DEFAULT_ALGORITHMS = {"TLSv1.3", "TLSv1.2"};
+  static final String[] SUPPORTED_CONTEXTS = {"TLSv1.3", "TLSv1.2"};
 
-  public static @NotNull SSLContext getSSLContextInstance(final @NotNull 
SSLConfig sslConfig)
+  public static @NotNull SSLContext getSSLContextInstance()
       throws NoSuchAlgorithmException {
-    final String[] protocols = combineProtocols(sslConfig);
-    return findSSLContextForProtocols(protocols, DEFAULT_ALGORITHMS);
-  }
-
-  static @NotNull String[] combineProtocols(final @NotNull SSLConfig 
sslConfig) {
-    return Stream.concat(
-        Stream.of(sslConfig.getServerProtocolsAsStringArray()),
-        Stream.of(sslConfig.getClientProtocolsAsStringArray()))
-        .distinct()
-        .toArray(String[]::new);
-  }
-
-  /**
-   * Search for a context supporting one of the given prioritized list of
-   * protocols. The second argument is a list of protocols to try if the
-   * first list contains "any". The second argument should also be in 
prioritized
-   * order. If there are no matches for any of the protocols in the second
-   * argument we will continue in the first argument list.
-   * with a first argument of A, B, any, C
-   * and a second argument of D, E
-   * the search order would be A, B, D, E, C
-   */
-  static @NotNull SSLContext findSSLContextForProtocols(final @NotNull 
String[] protocols,
-      final @NotNull String[] protocolsForAny)
-      throws NoSuchAlgorithmException {
-    for (String protocol : protocols) {
-      if (protocol.equalsIgnoreCase("any")) {
-        try {
-          return findSSLContextForProtocols(protocolsForAny, new String[0]);
-        } catch (NoSuchAlgorithmException e) {
-          // none of the default algorithms is available - continue to see if 
there
-          // are any others in the requested list
-        }
-      }
+    for (String protocol : SUPPORTED_CONTEXTS) {
       try {
         return SSLContext.getInstance(protocol);
       } catch (NoSuchAlgorithmException e) {
@@ -103,7 +69,7 @@ public class SSLUtil {
         return SSLContext.getDefault();
       }
 
-      final SSLContext ssl = getSSLContextInstance(sslConfig);
+      final SSLContext ssl = getSSLContextInstance();
 
       final KeyManager[] keyManagers;
       if (sslConfig.getKeystore() != null) {
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 89795b5377..caeda96322 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
@@ -247,7 +247,7 @@ public class SocketCreator extends TcpSocketCreatorImpl {
       return SSLContext.getDefault();
     }
 
-    SSLContext newSSLContext = SSLUtil.getSSLContextInstance(sslConfig);
+    SSLContext newSSLContext = SSLUtil.getSSLContextInstance();
 
     KeyManager[] keyManagers = null;
     if (sslConfig.getKeystore() != null) {
@@ -371,8 +371,8 @@ public class SocketCreator extends TcpSocketCreatorImpl {
   }
 
   /**
-   * @see <a
-   *      
href=https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLENG";>JSSE
+   * @see <a href=
+   *      
"https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLENG";>JSSE
    *      Reference Guide</a>
    *
    * @param socketChannel the socket's NIO channel
diff --git 
a/geode-core/src/test/java/org/apache/geode/internal/net/SSLUtilTest.java 
b/geode-core/src/test/java/org/apache/geode/internal/net/SSLUtilTest.java
index 61489dffc8..de67fcb2b0 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/net/SSLUtilTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/net/SSLUtilTest.java
@@ -15,17 +15,14 @@
 
 package org.apache.geode.internal.net;
 
-import static org.apache.geode.internal.net.SSLUtil.combineProtocols;
+import static org.apache.geode.internal.net.SSLUtil.SUPPORTED_CONTEXTS;
 import static org.apache.geode.internal.net.SSLUtil.split;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
 
 import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManagerFactory;
 
 import org.junit.jupiter.api.Test;
@@ -34,66 +31,10 @@ import org.junit.jupiter.api.Test;
 public class SSLUtilTest {
 
   @Test
-  public void failWhenNothingIsRequested() {
-    SSLConfig sslConfig = mock(SSLConfig.class);
-    when(sslConfig.getClientProtocolsAsStringArray())
-        .thenReturn(new String[0]);
-    when(sslConfig.getServerProtocolsAsStringArray())
-        .thenReturn(new String[0]);
-
-    assertThatThrownBy(() -> SSLUtil.getSSLContextInstance(sslConfig))
-        .isInstanceOf(NoSuchAlgorithmException.class);
-  }
-
-  @Test
-  public void failWithAnUnknownProtocol() throws Exception {
-    SSLConfig sslConfig = mock(SSLConfig.class);
-    when(sslConfig.getClientProtocolsAsStringArray())
-        .thenReturn(new String[] {"boulevard of broken dreams"});
-    when(sslConfig.getServerProtocolsAsStringArray())
-        .thenReturn(new String[] {"boulevard of broken dreams"});
-    assertThatThrownBy(() -> SSLUtil.getSSLContextInstance(sslConfig))
-        .isInstanceOf(NoSuchAlgorithmException.class);
-  }
-
-  @Test
-  public void getASpecificProtocol() throws Exception {
-    SSLConfig sslConfig = mock(SSLConfig.class);
-    when(sslConfig.getClientProtocolsAsStringArray()).thenReturn(new String[] 
{"TLSv1.2"});
-    when(sslConfig.getServerProtocolsAsStringArray()).thenReturn(new String[] 
{"TLSv1.2"});
-    final SSLContext sslContextInstance = 
SSLUtil.getSSLContextInstance(sslConfig);
-    
assertThat(sslContextInstance.getProtocol().equalsIgnoreCase("TLSv1.2")).isTrue();
-  }
-
-  @Test
-  public void getAnyProtocolWithAnUnknownInTheList() throws Exception {
-    SSLConfig sslConfig = mock(SSLConfig.class);
-    when(sslConfig.getClientProtocolsAsStringArray())
-        .thenReturn(new String[] {"the dream of the blue turtles", "any", 
"SSL"});
-    when(sslConfig.getServerProtocolsAsStringArray())
-        .thenReturn(new String[] {"the dream of the blue turtles", "any", 
"SSL"});
-    final SSLContext sslContextInstance = 
SSLUtil.getSSLContextInstance(sslConfig);
-    // make sure that we don't continue past "any" and use the following 
protocol (SSL)
-    
assertThat(sslContextInstance.getProtocol().equalsIgnoreCase("SSL")).isFalse();
-    String selectedProtocol = sslContextInstance.getProtocol();
-    String matchedProtocol = null;
-    for (String algorithm : SSLUtil.DEFAULT_ALGORITHMS) {
-      if (algorithm.equalsIgnoreCase(selectedProtocol)) {
-        matchedProtocol = algorithm;
-      }
-    }
-    assertThat(matchedProtocol).withFailMessage(
-        "selected protocol (%s) is not in the list of default algorithms, 
indicating that the \"any\" setting did not work correctly",
-        selectedProtocol).isNotNull();
-  }
-
-  @Test
-  public void getARealProtocolAfterProcessingAny() throws Exception {
-    final String[] algorithms = {"dream weaver", "any", "TLSv1.2"};
-    final String[] algorithmsForAny = new String[] {"sweet dreams (are made of 
this)"};
-    final SSLContext sslContextInstance = 
SSLUtil.findSSLContextForProtocols(algorithms,
-        algorithmsForAny);
-    
assertThat(sslContextInstance.getProtocol().equalsIgnoreCase("TLSv1.2")).isTrue();
+  public void getSSLContextInstanceUsesFirstSupportedContext()
+      throws NoSuchAlgorithmException {
+    assertThat(SSLUtil.getSSLContextInstance().getProtocol())
+        .isIn(Arrays.asList(SUPPORTED_CONTEXTS));
   }
 
   @Test
@@ -111,33 +52,6 @@ public class SSLUtilTest {
         .isEqualTo(TrustManagerFactory.getDefaultAlgorithm());
   }
 
-  @Test
-  void combineProtocolsReturnsEmptyWhenBothProtocolListsAreEmpty() {
-    final SSLConfig config = mock(SSLConfig.class);
-    when(config.getClientProtocolsAsStringArray()).thenReturn(new String[0]);
-    when(config.getServerProtocolsAsStringArray()).thenReturn(new String[0]);
-
-    assertThat(combineProtocols(config)).isEmpty();
-  }
-
-  @Test
-  void combineProtocolsReturnsUniqueValues() {
-    final SSLConfig config = mock(SSLConfig.class);
-    when(config.getClientProtocolsAsStringArray()).thenReturn(new String[] 
{"a"});
-    when(config.getServerProtocolsAsStringArray()).thenReturn(new String[] 
{"a", "b"});
-
-    assertThat(combineProtocols(config)).isEqualTo(new String[] {"a", "b"});
-  }
-
-  @Test
-  void combineProtocolsReturnPrefersSSLServerProtocols() {
-    final SSLConfig config = mock(SSLConfig.class);
-    when(config.getClientProtocolsAsStringArray()).thenReturn(new String[] 
{"a", "c"});
-    when(config.getServerProtocolsAsStringArray()).thenReturn(new String[] 
{"a", "b"});
-
-    assertThat(combineProtocols(config)).isEqualTo(new String[] {"a", "b", 
"c"});
-  }
-
   @Test
   void splitReturnsEmptyWhenNull() {
     assertThat(split(null)).isEmpty();
diff --git 
a/geode-wan/src/distributedTest/java/org/apache/geode/internal/cache/wan/misc/WANSSLDistributedTest.java
 
b/geode-wan/src/distributedTest/java/org/apache/geode/internal/cache/wan/misc/WANSSLDistributedTest.java
new file mode 100644
index 0000000000..6bc5ed3ee6
--- /dev/null
+++ 
b/geode-wan/src/distributedTest/java/org/apache/geode/internal/cache/wan/misc/WANSSLDistributedTest.java
@@ -0,0 +1,633 @@
+/*
+ * 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.internal.cache.wan.misc;
+
+import static java.lang.String.join;
+import static java.util.Arrays.copyOfRange;
+import static java.util.Arrays.stream;
+import static 
org.apache.geode.distributed.ConfigurationProperties.BIND_ADDRESS;
+import static 
org.apache.geode.distributed.ConfigurationProperties.DISTRIBUTED_SYSTEM_ID;
+import static 
org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS;
+import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
+import static 
org.apache.geode.distributed.ConfigurationProperties.REMOTE_LOCATORS;
+import static org.apache.geode.distributed.ConfigurationProperties.SSL_CIPHERS;
+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_KEYSTORE_PASSWORD;
+import static 
org.apache.geode.distributed.ConfigurationProperties.SSL_KEYSTORE_TYPE;
+import static 
org.apache.geode.distributed.ConfigurationProperties.SSL_PROTOCOLS;
+import static 
org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE;
+import static 
org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE_PASSWORD;
+import static 
org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPorts;
+import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
+import static org.apache.geode.test.dunit.IgnoredException.addIgnoredException;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+import junitparams.Parameters;
+import junitparams.naming.TestCaseName;
+import org.jetbrains.annotations.NotNull;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import util.TestException;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.client.NoAvailableServersException;
+import org.apache.geode.cache.client.internal.Connection;
+import org.apache.geode.cache.client.internal.PoolImpl;
+import org.apache.geode.cache.ssl.CertStores;
+import org.apache.geode.cache.ssl.CertificateBuilder;
+import org.apache.geode.cache.ssl.CertificateMaterial;
+import org.apache.geode.cache.wan.GatewayReceiver;
+import org.apache.geode.distributed.LocatorLauncher;
+import org.apache.geode.internal.cache.wan.AbstractGatewaySender;
+import org.apache.geode.test.dunit.IgnoredException;
+import org.apache.geode.test.dunit.VM;
+import org.apache.geode.test.dunit.rules.CacheRule;
+import org.apache.geode.test.dunit.rules.DistributedRule;
+import 
org.apache.geode.test.junit.rules.serializable.SerializableTemporaryFolder;
+import org.apache.geode.test.junit.runners.GeodeParamsRunner;
+
+@RunWith(GeodeParamsRunner.class)
+public class WANSSLDistributedTest implements Serializable {
+  private static final long serialVersionUID = -4552428155768617989L;
+
+  @Rule
+  public DistributedRule distributedRule = new DistributedRule();
+
+  @Rule
+  public CacheRule cacheRule = new CacheRule();
+
+  @Rule
+  public SerializableTemporaryFolder tempFolder = new 
SerializableTemporaryFolder();
+
+  // Cluster
+  private VM locatorSite1;
+  private VM locatorSite2;
+  private VM server1;
+  private VM server2;
+  private static final String LOCATOR_1_NAME = "locator1";
+  private static final String LOCATOR_2_NAME = "locator2";
+  private int[] ports;
+  private String hostName;
+
+  // Region
+  private static final String REGION_NAME = "regionName";
+  private static final String KEY_PREFIX = "key";
+  private static final String VALUE_PREFIX = "value";
+  private static final int ENTRIES_PER_SERVER = 100;
+
+  // WAN
+  private static final int SITE_1_DSID = 1;
+  private static final int SITE_2_DSID = 2;
+  private static final String SITE_1_SENDER_NAME = "site1Sender";
+  private static final String SITE_2_SENDER_NAME = "site2Sender";
+
+  // SSL
+  private static final String TLS13 = "TLSv1.3";
+  private static final String TLS12 = "TLSv1.2";
+  private File keyStoreFile;
+  private File trustStoreFile;
+  private static final String STORE_PASSWORD = "password";
+  private static final String SIGNING_ALGORITHM = "SHA256withRSA";
+  private static final String[] TLS12_CIPHERS = getAllowedCiphersTLS12();
+  private static final String[] TLS13_CIPHERS = getAllowedCiphersTLS13();
+
+  @Before
+  public void setUp() throws Exception {
+    // Ignore this exception so that if tests fail, they fail with assertion 
errors rather than
+    // suspect strings
+    addIgnoredException(SSLHandshakeException.class);
+
+    ports = getRandomAvailableTCPPorts(2);
+    hostName = InetAddress.getLocalHost().getCanonicalHostName();
+
+    keyStoreFile = tempFolder.newFile();
+    trustStoreFile = tempFolder.newFile();
+
+    locatorSite1 = VM.getVM(0);
+    locatorSite2 = VM.getVM(1);
+    server1 = VM.getVM(2);
+    server2 = VM.getVM(3);
+  }
+
+  @After
+  public void tearDown() {
+    IgnoredException.removeAllExpectedExceptions();
+  }
+
+  /*
+   * Sets up bidirectional WAN with a replicated region, serial gateway sender 
and gateway receiver
+   * on each server using TLSv1.2 and various ciphers for gateway 
communications and confirms
+   * that data is able to flow from each site to the other and that the 
expected protocol and
+   * ciphers are being used
+   */
+  @Test
+  @Parameters(method = "getAllowedCiphersTLS12")
+  @TestCaseName("{method} cipher:{0}")
+  public void 
biDirectionalWANSSLWithValidTLS12Ciphers_usesExpectedCipher(final String cipher)
+      throws GeneralSecurityException, IOException {
+    runValidProtocolsAndCiphersTest(TLS12, cipher, TLS12, cipher, TLS12, 
cipher);
+  }
+
+  /*
+   * Sets up bidirectional WAN with a replicated region, serial gateway sender 
and gateway receiver
+   * on each server using TLSv1.3 and various ciphers for gateway 
communications and confirms
+   * that data is able to flow from each site to the other and that the 
expected protocol and
+   * ciphers are being used
+   */
+  @Test
+  @Parameters(method = "getAllowedCiphersTLS13")
+  @TestCaseName("{method} cipher:{0}")
+  public void 
biDirectionalWANSSLWithValidTLS13Ciphers_usesExpectedCipher(final String cipher)
+      throws GeneralSecurityException, IOException {
+    runValidProtocolsAndCiphersTest(TLS13, cipher, TLS13, cipher, TLS13, 
cipher);
+  }
+
+  /*
+   * Sets up bidirectional WAN with a replicated region, serial gateway sender 
and gateway receiver
+   * on each server using various SSL protocols and ciphers for gateway 
communications and confirms
+   * that data is able to flow from each site to the other and that the 
expected protocol and
+   * ciphers are being used
+   */
+  @Test
+  @Parameters(method = "getOtherValidParams")
+  @TestCaseName("{method} server1 protocols:{0}, server1 ciphers:{1}, server2 
protocols:{2}, server2 ciphers:{3}")
+  public void 
biDirectionalWANSSLWithValidProtocolsAndCiphers_usesExpectedProtocolAndCipher(
+      final String server1Protocols, final String server1Ciphers, final String 
server2Protocols,
+      final String server2Ciphers, final String expectedNegotiatedProtocol,
+      final String expectedNegotiatedCipher) throws GeneralSecurityException, 
IOException {
+    runValidProtocolsAndCiphersTest(server1Protocols, server1Ciphers, 
server2Protocols,
+        server2Ciphers, expectedNegotiatedProtocol, expectedNegotiatedCipher);
+  }
+
+  private void runValidProtocolsAndCiphersTest(String server1Protocols, String 
server1Ciphers,
+      String server2Protocols,
+      String server2Ciphers, String expectedNegotiatedProtocol,
+      String expectedNegotiatedCipher)
+      throws IOException, GeneralSecurityException {
+    generateKeyAndTrustStore(hostName, keyStoreFile, trustStoreFile);
+
+    startLocatorsAndServers(server1Protocols, server1Ciphers, 
server2Protocols, server2Ciphers);
+
+    setUpWAN(server1, SITE_1_SENDER_NAME, SITE_2_DSID);
+
+    setUpWAN(server2, SITE_2_SENDER_NAME, SITE_1_DSID);
+
+    server1.invoke("check SSL parameters for gateway sender socket",
+        () -> checkProtocolAndCipher(SITE_1_SENDER_NAME, 
expectedNegotiatedProtocol,
+            expectedNegotiatedCipher));
+
+    server2.invoke("check SSL parameters for gateway sender socket",
+        () -> checkProtocolAndCipher(SITE_2_SENDER_NAME, 
expectedNegotiatedProtocol,
+            expectedNegotiatedCipher));
+
+    server1.invoke("do puts in server1", () -> createData(0));
+    server2.invoke("do puts in server2", () -> createData(ENTRIES_PER_SERVER));
+
+    server1.invoke("validate data in server1", this::validateData);
+    server2.invoke("validate data in server2", this::validateData);
+  }
+
+  /*
+   * Sets up bidirectional WAN with a replicated region, serial gateway sender 
and gateway receiver
+   * on each server using TLSv1.2 with unsupported cipher suites and confirms 
that servers cannot
+   * communicate via WAN
+   */
+  @Test
+  @Parameters(method = "getUnsupportedCiphersTLS12")
+  @TestCaseName("{method} cipher:{0}")
+  public void 
biDirectionalWANSSLWithInvalidTLS12Ciphers_failsToMakeConnection(final String 
cipher)
+      throws GeneralSecurityException, IOException {
+    runInvalidProtocolsAndCipherCombinationsTest(TLS12, cipher, TLS12, cipher);
+  }
+
+  /*
+   * Sets up bidirectional WAN with a replicated region, serial gateway sender 
and gateway receiver
+   * on each server using TLSv1.3 with unsupported cipher suites and confirms 
that servers cannot
+   * communicate via WAN
+   */
+  @Test
+  @Parameters(method = "getUnsupportedCiphersTLS13")
+  @TestCaseName("{method} cipher:{0}")
+  public void 
biDirectionalWANSSLWithInvalidTLS13Ciphers_failsToMakeConnection(final String 
cipher)
+      throws GeneralSecurityException, IOException {
+    runInvalidProtocolsAndCipherCombinationsTest(TLS13, cipher, TLS13, cipher);
+  }
+
+  /*
+   * Sets up bidirectional WAN with a replicated region, serial gateway sender 
and gateway receiver
+   * on each server using SSL protocol and cipher combinations such that 
servers cannot communicate
+   */
+  @Test
+  @Parameters(method = "getOtherInvalidProtocolAndCipherCombinationParams")
+  public void 
biDirectionalWANSSLWithInvalidProtocolsAndCiphers_failsToMakeConnection(
+      final String server1Protocols, final String server1Ciphers, final String 
server2Protocols,
+      final String server2Ciphers) throws GeneralSecurityException, 
IOException {
+    runInvalidProtocolsAndCipherCombinationsTest(server1Protocols, 
server1Ciphers, server2Protocols,
+        server2Ciphers);
+  }
+
+  private void runInvalidProtocolsAndCipherCombinationsTest(String 
server1Protocols,
+      String server1Ciphers,
+      String server2Protocols, String server2Ciphers) throws IOException, 
GeneralSecurityException {
+    generateKeyAndTrustStore(hostName, keyStoreFile, trustStoreFile);
+
+    startLocatorsAndServers(server1Protocols, server1Ciphers, 
server2Protocols, server2Ciphers);
+
+    setUpWAN(server1, SITE_1_SENDER_NAME, SITE_2_DSID);
+
+    setUpWAN(server2, SITE_2_SENDER_NAME, SITE_1_DSID);
+
+    server1.invoke("confirm sender cannot create connection",
+        () -> verifyConnectionCannotBeMade(SITE_1_SENDER_NAME));
+    server2.invoke("confirm sender cannot create connection",
+        () -> verifyConnectionCannotBeMade(SITE_2_SENDER_NAME));
+  }
+
+  /*
+   * Attempts to set up bidirectional WAN with a replicated region, serial 
gateway sender and
+   * gateway receiver on each server using nonexistent SSL protocol or cipher 
name and confirms that
+   */
+  @Test
+  @Parameters(method = "getNonexistentProtocolAndCipherParams")
+  public void 
biDirectionalWANSSLWithNonexistentProtocolsAndCiphers_failsToCreateReceiverAndSender(
+      final String server1Protocols, final String server1Ciphers, final String 
server2Protocols,
+      final String server2Ciphers) throws GeneralSecurityException, 
IOException {
+    generateKeyAndTrustStore(hostName, keyStoreFile, trustStoreFile);
+
+    startLocatorsAndServers(server1Protocols, server1Ciphers, 
server2Protocols, server2Ciphers);
+
+    server1.invoke("create replicated region", () -> 
createReplicatedRegionWithSender(
+        SITE_1_SENDER_NAME));
+    server1.invoke("verify gateway receiver cannot start", 
this::verifyReceiverCannotBeStarted);
+    server1.invoke("verify gateway sender cannot create connection",
+        () -> verifySenderCannotCreateConnection(SITE_1_SENDER_NAME, 
SITE_2_DSID));
+
+    server2.invoke("create replicated region", () -> 
createReplicatedRegionWithSender(
+        SITE_2_SENDER_NAME));
+    server2.invoke("create gateway receiver", 
this::verifyReceiverCannotBeStarted);
+    server2.invoke("verify gateway sender cannot create connection",
+        () -> verifySenderCannotCreateConnection(SITE_2_SENDER_NAME, 
SITE_1_DSID));
+  }
+
+  private void startLocatorsAndServers(final String server1Protocols, final 
String server1Ciphers,
+      final String server2Protocols,
+      final String server2Ciphers) {
+    int locator1Port = ports[0];
+    locatorSite1.invoke("start locator1 in site 1",
+        () -> startLocator(LOCATOR_1_NAME, locator1Port, SITE_1_DSID, -1));
+
+    int locator2Port = ports[1];
+    locatorSite2.invoke("start locator2 in site 2",
+        () -> startLocator(LOCATOR_2_NAME, locator2Port, SITE_2_DSID, 
locator1Port));
+
+    server1.invoke("start server1 in site 1",
+        () -> startServer(locator1Port, server1Protocols, server1Ciphers));
+
+    server2.invoke("start server2 in site 2",
+        () -> startServer(locator2Port, server2Protocols, server2Ciphers));
+  }
+
+  private void startLocator(final String locatorName, int locatorPort, int 
dsID,
+      int remoteLocatorPort) throws IOException {
+    LocatorLauncher.Builder builder = new LocatorLauncher.Builder()
+        .setMemberName(locatorName)
+        .setPort(locatorPort)
+        .set(BIND_ADDRESS, hostName)
+        
.setWorkingDirectory(tempFolder.newFolder(locatorName).getAbsolutePath())
+        .set(DISTRIBUTED_SYSTEM_ID, String.valueOf(dsID))
+        .set(MCAST_PORT, "0")
+        .set(HTTP_SERVICE_PORT, "0");
+    if (remoteLocatorPort != -1) {
+      builder.set(REMOTE_LOCATORS, hostName + "[" + remoteLocatorPort + "]");
+    }
+    LocatorLauncher locatorLauncher = builder.build();
+    locatorLauncher.start();
+  }
+
+  private void startServer(int locatorPort, final String protocols, final 
String ciphers) {
+    Properties serverProperties = new Properties();
+    serverProperties.setProperty(HTTP_SERVICE_PORT, "0");
+    serverProperties.setProperty(LOCATORS, hostName + "[" + locatorPort + "]");
+    serverProperties.setProperty(SSL_ENABLED_COMPONENTS, "gateway");
+    serverProperties.setProperty(SSL_ENDPOINT_IDENTIFICATION_ENABLED, "true");
+    serverProperties.setProperty(SSL_KEYSTORE_TYPE, "jks");
+    serverProperties.setProperty(SSL_KEYSTORE, keyStoreFile.getAbsolutePath());
+    serverProperties.setProperty(SSL_KEYSTORE_PASSWORD, STORE_PASSWORD);
+    serverProperties.setProperty(SSL_TRUSTSTORE, 
trustStoreFile.getAbsolutePath());
+    serverProperties.setProperty(SSL_TRUSTSTORE_PASSWORD, STORE_PASSWORD);
+    serverProperties.setProperty(SSL_PROTOCOLS, protocols);
+    serverProperties.setProperty(SSL_CIPHERS, ciphers);
+    cacheRule.createCache(serverProperties);
+  }
+
+  private void setUpWAN(VM serverVM, final String senderName, int remoteDSID) {
+    serverVM.invoke("create replicated region", () -> 
createReplicatedRegionWithSender(senderName));
+    serverVM.invoke("create gateway receiver", this::createReceiver);
+    serverVM.invoke("create gateway sender", () -> 
createSerialSender(senderName, remoteDSID));
+  }
+
+  private void createReplicatedRegionWithSender(final String senderName) {
+    Cache cache = cacheRule.getCache();
+    cache.<String, String>createRegionFactory(RegionShortcut.REPLICATE)
+        .addGatewaySenderId(senderName)
+        .create(REGION_NAME);
+  }
+
+  private void createReceiver() {
+    GatewayReceiver receiver = cacheRule.getCache()
+        .createGatewayReceiverFactory()
+        .create();
+    await().untilAsserted(() -> assertThat(receiver.isRunning()).isTrue());
+  }
+
+  private void createSerialSender(final String senderName, int remoteDSID) {
+    cacheRule.getCache().createGatewaySenderFactory()
+        .setParallel(false)
+        .setDispatcherThreads(1)
+        .create(senderName, remoteDSID);
+  }
+
+  private void checkProtocolAndCipher(final String senderName, final String 
expectedProtocol,
+      final String expectedCipher) {
+    PoolImpl pool =
+        ((AbstractGatewaySender) 
cacheRule.getCache().getGatewaySender(senderName)).getProxy();
+
+    Connection connection = pool.acquireConnection();
+    SSLSession session = ((SSLSocket) connection.getSocket()).getSession();
+
+    assertThat(session.getProtocol()).isEqualTo(expectedProtocol);
+    assertThat(session.getCipherSuite()).isEqualTo(expectedCipher);
+
+    pool.returnConnection(connection);
+  }
+
+  private void createData(int startingIndex) {
+    Region<String, String> region = 
cacheRule.getCache().getRegion(REGION_NAME);
+    for (int i = startingIndex; i < ENTRIES_PER_SERVER + startingIndex; ++i) {
+      region.put(KEY_PREFIX + i, VALUE_PREFIX + i);
+    }
+  }
+
+  private void validateData() {
+    Region<String, String> region = 
cacheRule.getCache().getRegion(REGION_NAME);
+    int totalExpectedEntries = ENTRIES_PER_SERVER * 2;
+    await().atMost(30, TimeUnit.SECONDS)
+        .untilAsserted(() -> 
assertThat(region.size()).isEqualTo(totalExpectedEntries));
+    for (int i = 0; i < ENTRIES_PER_SERVER * 2; ++i) {
+      String key = KEY_PREFIX + i;
+      String value = VALUE_PREFIX + i;
+      assertThat(region.get(key)).isEqualTo(value);
+    }
+  }
+
+  private void verifyConnectionCannotBeMade(final String senderName) {
+    PoolImpl pool =
+        ((AbstractGatewaySender) 
cacheRule.getCache().getGatewaySender(senderName)).getProxy();
+
+    
assertThatThrownBy(pool::acquireConnection).isInstanceOf(NoAvailableServersException.class);
+  }
+
+  private void verifyReceiverCannotBeStarted() {
+    
assertThatThrownBy(this::createReceiver).isInstanceOf(IllegalArgumentException.class);
+  }
+
+  private void verifySenderCannotCreateConnection(final String senderName, int 
remoteDSID) {
+    createSerialSender(senderName, remoteDSID);
+    verifyConnectionCannotBeMade(senderName);
+  }
+
+  private static void generateKeyAndTrustStore(final String hostName, final 
File keyStoreFile,
+      final File trustStoreFile)
+      throws IOException, GeneralSecurityException {
+    final CertificateMaterial ca = new CertificateBuilder(1,
+        WANSSLDistributedTest.SIGNING_ALGORITHM)
+            .commonName("Test CA")
+            .isCA()
+            .generate();
+
+    final CertificateMaterial certificate = new CertificateBuilder(1,
+        WANSSLDistributedTest.SIGNING_ALGORITHM)
+            .commonName(hostName)
+            .issuedBy(ca)
+            .sanDnsName(hostName)
+            .generate();
+
+    final CertStores store = new CertStores(hostName);
+    store.withCertificate("geode", certificate);
+    store.trust("ca", ca);
+
+    store.createKeyStore(keyStoreFile.getAbsolutePath(), STORE_PASSWORD);
+    store.createTrustStore(trustStoreFile.getPath(), STORE_PASSWORD);
+  }
+
+  // These allowed ciphers are well-defined and known for the TLSv1.2 protocol 
and should be updated
+  // if the protocol is changed to add or remove support
+  private static String[] getAllowedCiphersTLS12() {
+    String[] ciphers = {
+        "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
+        "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
+        "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+        "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
+        "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
+        "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
+        "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
+        "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
+        "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
+        "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
+        "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
+        "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
+        "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
+        "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
+        "TLS_RSA_WITH_AES_256_GCM_SHA384",
+        "TLS_RSA_WITH_AES_128_GCM_SHA256",
+        "TLS_RSA_WITH_AES_256_CBC_SHA256",
+        "TLS_RSA_WITH_AES_128_CBC_SHA256",
+        "TLS_RSA_WITH_AES_256_CBC_SHA",
+        "TLS_RSA_WITH_AES_128_CBC_SHA"
+    };
+    ciphers = removeUnsupportedCiphers(ciphers, TLS12);
+    return ciphers;
+  }
+
+  // These allowed ciphers are well-defined and known for the TLSv1.3 protocol 
and should be updated
+  // if the protocol is changed to add or remove support
+  private static String[] getAllowedCiphersTLS13() {
+    String[] ciphers = {
+        "TLS_AES_256_GCM_SHA384",
+        "TLS_AES_128_GCM_SHA256",
+        "TLS_CHACHA20_POLY1305_SHA256",
+        "TLS_AES_128_CCM_8_SHA256",
+        "TLS_AES_128_CCM_SHA256"
+    };
+    ciphers = removeUnsupportedCiphers(ciphers, TLS13);
+    return ciphers;
+  }
+
+  private static String[] removeUnsupportedCiphers(final String[] ciphers, 
final String protocol) {
+    Set<String> supportedCiphers = getSupportedCiphersForProtocol(protocol);
+    return stream(ciphers)
+        .filter(supportedCiphers::contains)
+        .toArray(String[]::new);
+  }
+
+  private static Set<String> getSupportedCiphersForProtocol(final String 
protocol) {
+    try {
+      SSLContext ssl = SSLContext.getInstance(protocol);
+      ssl.init(null, null, new java.security.SecureRandom());
+      return new 
HashSet<>(Arrays.asList(ssl.getSocketFactory().getSupportedCipherSuites()));
+    } catch (NoSuchAlgorithmException | KeyManagementException e) {
+      throw new TestException("Could not initialize supported ciphers", e);
+    }
+  }
+
+  // Parameters are: server1 protocols, server1 ciphers,
+  // server2 protocols, server2 ciphers,
+  // expected negotiated protocol, expected negotiated cipher suite
+  @NotNull
+  @SuppressWarnings("unused")
+  private String[][] getOtherValidParams() {
+    return new String[][] {
+        // Basic scenarios
+        new String[] {"any", "any", "any", "any", TLS13, TLS13_CIPHERS[0]},
+        new String[] {TLS12, "any", TLS12, "any", TLS12, TLS12_CIPHERS[0]},
+        new String[] {TLS13, "any", TLS13, "any", TLS13, TLS13_CIPHERS[0]},
+
+        // Protocol specified on one server only
+        new String[] {TLS12, "any", "any", "any", TLS12, TLS12_CIPHERS[0]},
+        new String[] {TLS13, "any", "any", "any", TLS13, TLS13_CIPHERS[0]},
+
+        // Cipher specified on one server only
+        new String[] {TLS12, TLS12_CIPHERS[1], TLS12, "any", TLS12, 
TLS12_CIPHERS[1]},
+        new String[] {TLS13, TLS13_CIPHERS[1], TLS13, "any", TLS13, 
TLS13_CIPHERS[1]},
+
+        // Cipher specified on one server only with "any" protocol
+        new String[] {"any", TLS12_CIPHERS[1], "any", "any", TLS12, 
TLS12_CIPHERS[1]},
+        new String[] {"any", TLS13_CIPHERS[1], "any", "any", TLS13, 
TLS13_CIPHERS[1]},
+
+        // Multiple protocols specified
+        new String[] {TLS13, "any", TLS13 + "," + TLS12, "any", TLS13, 
TLS13_CIPHERS[0]},
+        new String[] {TLS13, "any", TLS12 + "," + TLS13, "any", TLS13, 
TLS13_CIPHERS[0]},
+        new String[] {TLS12, "any", TLS13 + "," + TLS12, "any", TLS12, 
TLS12_CIPHERS[0]},
+        new String[] {TLS12, "any", TLS12 + "," + TLS13, "any", TLS12, 
TLS12_CIPHERS[0]},
+        new String[] {TLS13 + "," + TLS12, "any", TLS12 + "," + TLS13, "any",
+            TLS13, TLS13_CIPHERS[0]},
+        new String[] {TLS12 + "," + TLS13, "any", TLS13 + "," + TLS12, "any",
+            TLS13, TLS13_CIPHERS[0]},
+
+        // Multiple ciphers specified
+        new String[] {TLS13, join(",", TLS13_CIPHERS), TLS13, join(",", 
TLS13_CIPHERS),
+            TLS13, TLS13_CIPHERS[0]},
+        new String[] {TLS12, join(",", TLS12_CIPHERS), TLS12, join(",", 
TLS12_CIPHERS),
+            TLS12, TLS12_CIPHERS[0]},
+        new String[] {TLS13, join(",", TLS13_CIPHERS), TLS13, "any",
+            TLS13, TLS13_CIPHERS[0]},
+        new String[] {TLS12, join(",", TLS12_CIPHERS), TLS12, "any",
+            TLS12, TLS12_CIPHERS[0]},
+
+        // Overlapping ciphers requiring negotiation to the one they have in 
common
+        new String[] {TLS12, join(",", copyOfRange(TLS12_CIPHERS, 1, 3)),
+            TLS12, join(",", copyOfRange(TLS12_CIPHERS, 2, 4)),
+            TLS12, TLS12_CIPHERS[2]},
+
+        // Multiple protocols with only a TLSv1.3 cipher in common, but 
TLSv1.2 specified as first
+        // protocol
+        new String[] {TLS12 + "," + TLS13,
+            join(",", new String[] {TLS12_CIPHERS[0], TLS13_CIPHERS[1]}),
+            TLS12 + "," + TLS13,
+            join(",", new String[] {TLS12_CIPHERS[1], TLS13_CIPHERS[1]}),
+            TLS13, TLS13_CIPHERS[1]},
+    };
+  }
+
+  // Each array of parameters returned from this method uses the same protocol 
and single invalid
+  // cipher on both of the servers
+  @SuppressWarnings("unused")
+  private String[] getUnsupportedCiphersTLS12() {
+    Set<String> invalidCiphers = getSupportedCiphersForProtocol(TLS12);
+    Arrays.asList(TLS12_CIPHERS).forEach(invalidCiphers::remove);
+    return invalidCiphers.toArray(new String[] {});
+  }
+
+  // Each array of parameters returned from this method uses the same protocol 
and single invalid
+  // cipher on both of the servers
+  @SuppressWarnings("unused")
+  private String[] getUnsupportedCiphersTLS13() {
+    Set<String> invalidCiphers = getSupportedCiphersForProtocol(TLS13);
+    Arrays.asList(TLS13_CIPHERS).forEach(invalidCiphers::remove);
+    return invalidCiphers.toArray(new String[] {});
+  }
+
+  @NotNull
+  @SuppressWarnings("unused")
+  private String[][] getOtherInvalidProtocolAndCipherCombinationParams() {
+    return new String[][] {
+        // Protocols do not match
+        new String[] {TLS12, "any", TLS13, "any"},
+        new String[] {TLS13, "any", TLS12, "any"},
+
+        // Ciphers do not match
+        new String[] {"any", TLS12_CIPHERS[0], "any", TLS12_CIPHERS[1]},
+        new String[] {TLS12, TLS12_CIPHERS[0], TLS12, TLS12_CIPHERS[1]},
+        new String[] {"any", TLS13_CIPHERS[0], "any", TLS13_CIPHERS[1]},
+        new String[] {TLS13, TLS13_CIPHERS[0], TLS13, TLS13_CIPHERS[1]},
+
+        // Multiple protocols with only a TLSv1.2 cipher in common, but 
TLSv1.3 specified as first
+        // protocol
+        new String[] {TLS13 + "," + TLS12,
+            join(",", new String[] {TLS13_CIPHERS[0], TLS12_CIPHERS[2]}),
+            TLS13 + "," + TLS12,
+            join(",", new String[] {TLS13_CIPHERS[1], TLS12_CIPHERS[2]})},
+    };
+  }
+
+  @NotNull
+  @SuppressWarnings("unused")
+  private String[][] getNonexistentProtocolAndCipherParams() {
+    return new String[][] {
+        // Nonexistent protocol
+        new String[] {"myReallyCoolSSLProtocol", "any", 
"myReallyCoolSSLProtocol", "any"},
+
+        // Nonexistent cipher
+        new String[] {"any", "myReallyCoolSSLCipher", "any", 
"myReallyCoolSSLCipher"},
+    };
+  }
+}

Reply via email to