You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by dc...@apache.org on 2020/12/03 00:45:04 UTC

[cassandra] branch trunk updated: Bring back the accepted encryption protocols list as configurable option

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

dcapwell pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 919a896  Bring back the accepted encryption protocols list as configurable option
919a896 is described below

commit 919a8964a83511d96766c3e53ba603e77bca626c
Author: Jon Meredith <jm...@apple.com>
AuthorDate: Wed Dec 2 15:33:36 2020 -0800

    Bring back the accepted encryption protocols list as configurable option
    
    patch by Jon Meredith; reviewed by Berenguer Blasi, David Capwell, Dinesh Joshi for CASSANDRA-13325
---
 CHANGES.txt                                        |   1 +
 .../cassandra/config/DatabaseDescriptor.java       |  10 +-
 .../apache/cassandra/config/EncryptionOptions.java | 176 ++++++++++++++++----
 .../apache/cassandra/db/virtual/SettingsTable.java |   5 +-
 .../org/apache/cassandra/security/SSLFactory.java  | 181 ++++++++++++++++-----
 .../org/apache/cassandra/tools/BulkLoader.java     |   2 +-
 .../test/AbstractEncryptionOptionsImpl.java        |  75 ++++++++-
 .../test/InternodeEncryptionOptionsTest.java       |  86 ++++++++++
 .../test/NativeTransportEncryptionOptionsTest.java |  93 ++++++++++-
 .../cassandra/config/EncryptionOptionsTest.java    |  12 +-
 .../cassandra/db/virtual/SettingsTableTest.java    |  17 +-
 .../apache/cassandra/security/SSLFactoryTest.java  |  21 +--
 .../cassandra/stress/util/JavaDriverClient.java    |   2 +-
 13 files changed, 557 insertions(+), 124 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index b539e2b..172ebcc 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -16,6 +16,7 @@
  * TLS connections to the storage port on a node without server encryption configured causes java.io.IOException accessing missing keystore (CASSANDRA-16144)
  * Internode messaging catches OOMs and does not rethrow (CASSANDRA-15214)
  * When a table attempts to clean up metrics, it was cleaning up all global table metrics (CASSANDRA-16095)
+ * Bring back the accepted encryption protocols list as configurable option (CASSANDRA-13325)
 Merged from 3.11:
  * SASI's `max_compaction_flush_memory_in_mb` settings over 100GB revert to default of 1GB (CASSANDRA-16071)
 Merged from 3.0:
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 9f659fd..daac364 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -360,7 +360,7 @@ public class DatabaseDescriptor
 
         applyEncryptionContext();
 
-        applySslContextHotReload();
+        applySslContext();
     }
 
     private static void applySimpleConfig()
@@ -988,15 +988,17 @@ public class DatabaseDescriptor
         encryptionContext = new EncryptionContext(conf.transparent_data_encryption_options);
     }
 
-    public static void applySslContextHotReload()
+    public static void applySslContext()
     {
         try
         {
+            SSLFactory.validateSslContext("Internode messaging", conf.server_encryption_options, true, true);
+            SSLFactory.validateSslContext("Native transport", conf.client_encryption_options, conf.client_encryption_options.require_client_auth, true);
             SSLFactory.initHotReloading(conf.server_encryption_options, conf.client_encryption_options, false);
         }
-        catch(IOException e)
+        catch (IOException e)
         {
-            throw new ConfigurationException("Failed to initialize SSL hot reloading", e);
+            throw new ConfigurationException("Failed to initialize SSL", e);
         }
     }
 
diff --git a/src/java/org/apache/cassandra/config/EncryptionOptions.java b/src/java/org/apache/cassandra/config/EncryptionOptions.java
index 26bf458..fad6190 100644
--- a/src/java/org/apache/cassandra/config/EncryptionOptions.java
+++ b/src/java/org/apache/cassandra/config/EncryptionOptions.java
@@ -21,6 +21,7 @@ import java.io.File;
 import java.util.List;
 import java.util.Objects;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 
 import org.slf4j.Logger;
@@ -55,7 +56,8 @@ public class EncryptionOptions
     public final String truststore;
     public final String truststore_password;
     public final List<String> cipher_suites;
-    public final String protocol;
+    protected String protocol;
+    protected List<String> accepted_protocols;
     public final String algorithm;
     public final String store_type;
     public final boolean require_client_auth;
@@ -79,8 +81,9 @@ public class EncryptionOptions
         keystore_password = "cassandra";
         truststore = "conf/.truststore";
         truststore_password = "cassandra";
-        cipher_suites = ImmutableList.of();
-        protocol = "TLS";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
         algorithm = null;
         store_type = "JKS";
         require_client_auth = false;
@@ -89,7 +92,7 @@ public class EncryptionOptions
         optional = null;
     }
 
-    public EncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List<String> cipher_suites, String protocol, String algorithm, String store_type, boolean require_client_auth, boolean require_endpoint_verification, Boolean enabled, Boolean optional)
+    public EncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List<String> cipher_suites, String protocol, List<String> accepted_protocols, String algorithm, String store_type, boolean require_client_auth, boolean require_endpoint_verification, Boolean enabled, Boolean optional)
     {
         this.keystore = keystore;
         this.keystore_password = keystore_password;
@@ -97,6 +100,7 @@ public class EncryptionOptions
         this.truststore_password = truststore_password;
         this.cipher_suites = cipher_suites;
         this.protocol = protocol;
+        this.accepted_protocols = accepted_protocols;
         this.algorithm = algorithm;
         this.store_type = store_type;
         this.require_client_auth = require_client_auth;
@@ -113,6 +117,7 @@ public class EncryptionOptions
         truststore_password = options.truststore_password;
         cipher_suites = options.cipher_suites;
         protocol = options.protocol;
+        accepted_protocols = options.accepted_protocols;
         algorithm = options.algorithm;
         store_type = options.store_type;
         require_client_auth = options.require_client_auth;
@@ -209,6 +214,83 @@ public class EncryptionOptions
         this.optional = optional;
     }
 
+    /**
+     * Sets accepted TLS protocol for this channel. Note that this should only be called by
+     * the configuration parser or tests. It is public only for that purpose, mutating protocol state
+     * is probably a bad idea.
+     * @param protocol value to set
+     */
+    @VisibleForTesting
+    public void setProtocol(String protocol) {
+        this.protocol = protocol;
+    }
+
+    /**
+     * Sets accepted TLS protocols for this channel. Note that this should only be called by
+     * the configuration parser or tests. It is public only for that purpose, mutating protocol state
+     * is probably a bad idea. The function casing is required for snakeyaml to find this setter for the protected field.
+     * @param accepted_protocols value to set
+     */
+    public void setaccepted_protocols(List<String> accepted_protocols) {
+        this.accepted_protocols = accepted_protocols == null ? null : ImmutableList.copyOf(accepted_protocols);
+    }
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * it is not a 'default' list or 'support protocol versions' list.  It is just an attempt to preserve the
+     * original intent for the user configuration
+     */
+    private final List<String> TLS_PROTOCOL_SUBSTITUTION = ImmutableList.of("TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1");
+
+    /**
+     * Combine the pre-4.0 protocol field with the accepted_protocols list, substituting a list of
+     * explicit protocols for the previous catchall default of "TLS"
+     * @return array of protocol names suitable for passing to SslContextBuilder.protocols, or null if the default
+     */
+    public List<String> acceptedProtocols()
+    {
+        if (accepted_protocols == null)
+        {
+            if (protocol == null)
+            {
+                return null;
+            }
+            // TLS is accepted by SSLContext.getInstance as a shorthand for give me an engine that
+            // can speak some of the TLS protocols.  It is not supported by SSLEngine.setAcceptedProtocols
+            // so substitute if the user hasn't provided an accepted protocol configuration
+            else if (protocol.equalsIgnoreCase("TLS"))
+            {
+                return TLS_PROTOCOL_SUBSTITUTION;
+            }
+            else // the user was trying to limit to a single specific protocol, so try that
+            {
+                return ImmutableList.of(protocol);
+            }
+        }
+
+        if (protocol != null && !protocol.equalsIgnoreCase("TLS") &&
+            accepted_protocols.stream().noneMatch(ap -> ap.equalsIgnoreCase(protocol)))
+        {
+            // If the user provided a non-generic default protocol, append it to accepted_protocols - they wanted
+            // it after all.
+            return ImmutableList.<String>builder().addAll(accepted_protocols).add(protocol).build();
+        }
+        else
+        {
+            return accepted_protocols;
+        }
+    }
+
+    public String[] acceptedProtocolsArray()
+    {
+        List<String> ap = acceptedProtocols();
+        return ap == null ?  new String[0] : ap.toArray(new String[0]);
+    }
+
+    public String[] cipherSuitesArray()
+    {
+        return cipher_suites == null ? new String[0] : cipher_suites.toArray(new String[0]);
+    }
+
     public TlsEncryptionPolicy tlsEncryptionPolicy()
     {
         if (isOptional())
@@ -228,91 +310,100 @@ public class EncryptionOptions
     public EncryptionOptions withKeyStore(String keystore)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withKeyStorePassword(String keystore_password)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withTrustStore(String truststore)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withTrustStorePassword(String truststore_password)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withCipherSuites(List<String> cipher_suites)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withCipherSuites(String ... cipher_suites)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, ImmutableList.copyOf(cipher_suites),
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withProtocol(String protocol)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
+
+    public EncryptionOptions withAcceptedProtocols(List<String> accepted_protocols)
+    {
+        return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol,
+                                     accepted_protocols == null ? null : ImmutableList.copyOf(accepted_protocols),
+                                     algorithm, store_type, require_client_auth, require_endpoint_verification, enabled, optional).applyConfig();
+    }
+
+
     public EncryptionOptions withAlgorithm(String algorithm)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withStoreType(String store_type)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withRequireClientAuth(boolean require_client_auth)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withRequireEndpointVerification(boolean require_endpoint_verification)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withEnabled(boolean enabled)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
     public EncryptionOptions withOptional(Boolean optional)
     {
         return new EncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                           protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                           protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                            enabled, optional).applyConfig();
     }
 
@@ -338,6 +429,7 @@ public class EncryptionOptions
                Objects.equals(truststore, opt.truststore) &&
                Objects.equals(truststore_password, opt.truststore_password) &&
                Objects.equals(protocol, opt.protocol) &&
+               Objects.equals(accepted_protocols, opt.accepted_protocols) &&
                Objects.equals(algorithm, opt.algorithm) &&
                Objects.equals(store_type, opt.store_type) &&
                Objects.equals(cipher_suites, opt.cipher_suites);
@@ -356,6 +448,7 @@ public class EncryptionOptions
         result += 31 * (truststore == null ? 0 : truststore.hashCode());
         result += 31 * (truststore_password == null ? 0 : truststore_password.hashCode());
         result += 31 * (protocol == null ? 0 : protocol.hashCode());
+        result += 31 * (accepted_protocols == null ? 0 : accepted_protocols.hashCode());
         result += 31 * (algorithm == null ? 0 : algorithm.hashCode());
         result += 31 * (store_type == null ? 0 : store_type.hashCode());
         result += 31 * (enabled == null ? 0 : Boolean.hashCode(enabled));
@@ -382,9 +475,16 @@ public class EncryptionOptions
             this.enable_legacy_ssl_storage_port = false;
         }
 
-        public ServerEncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List<String> cipher_suites, String protocol, String algorithm, String store_type, boolean require_client_auth, boolean require_endpoint_verification, Boolean optional, InternodeEncryption internode_encryption, boolean enable_legacy_ssl_storage_port)
+        public ServerEncryptionOptions(String keystore, String keystore_password, String truststore,
+                                       String truststore_password, List<String> cipher_suites, String protocol,
+                                       List<String> accepted_protocols, String algorithm, String store_type,
+                                       boolean require_client_auth, boolean require_endpoint_verification,
+                                       Boolean optional, InternodeEncryption internode_encryption,
+                                       boolean enable_legacy_ssl_storage_port)
         {
-            super(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol, algorithm, store_type, require_client_auth, require_endpoint_verification, null, optional);
+            super(keystore, keystore_password, truststore, truststore_password, cipher_suites, protocol,
+                  accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                  null, optional);
             this.internode_encryption = internode_encryption;
             this.enable_legacy_ssl_storage_port = enable_legacy_ssl_storage_port;
         }
@@ -404,8 +504,6 @@ public class EncryptionOptions
 
         private ServerEncryptionOptions applyConfigInternal()
         {
-
-
             super.applyConfig();
 
             isEnabled = this.internode_encryption != InternodeEncryption.none;
@@ -449,98 +547,106 @@ public class EncryptionOptions
         public ServerEncryptionOptions withKeyStore(String keystore)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withKeyStorePassword(String keystore_password)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withTrustStore(String truststore)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withTrustStorePassword(String truststore_password)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withCipherSuites(List<String> cipher_suites)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withCipherSuites(String ... cipher_suites)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, ImmutableList.copyOf(cipher_suites),
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withProtocol(String protocol)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
+        }
+
+        public ServerEncryptionOptions withAcceptedProtocols(List<String> accepted_protocols)
+        {
+            return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
+                                               protocol, accepted_protocols == null ? null : ImmutableList.copyOf(accepted_protocols),
+                                               algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withAlgorithm(String algorithm)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withStoreType(String store_type)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withRequireClientAuth(boolean require_client_auth)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withRequireEndpointVerification(boolean require_endpoint_verification)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withOptional(boolean optional)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withInternodeEncryption(InternodeEncryption internode_encryption)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
         public ServerEncryptionOptions withLegacySslStoragePort(boolean enable_legacy_ssl_storage_port)
         {
             return new ServerEncryptionOptions(keystore, keystore_password, truststore, truststore_password, cipher_suites,
-                                               protocol, algorithm, store_type, require_client_auth, require_endpoint_verification,
+                                               protocol, accepted_protocols, algorithm, store_type, require_client_auth, require_endpoint_verification,
                                                optional, internode_encryption, enable_legacy_ssl_storage_port).applyConfigInternal();
         }
 
diff --git a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java
index e47ce8c..b0ae018 100644
--- a/src/java/org/apache/cassandra/db/virtual/SettingsTable.java
+++ b/src/java/org/apache/cassandra/db/virtual/SettingsTable.java
@@ -21,6 +21,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
@@ -162,8 +163,8 @@ final class SettingsTable extends AbstractVirtualTable
         EncryptionOptions value = (EncryptionOptions) getValue(f);
         result.row(f.getName() + "_enabled").column(VALUE, Boolean.toString(value.isEnabled()));
         result.row(f.getName() + "_algorithm").column(VALUE, value.algorithm);
-        result.row(f.getName() + "_protocol").column(VALUE, value.protocol);
-        result.row(f.getName() + "_cipher_suites").column(VALUE, value.cipher_suites.toString());
+        result.row(f.getName() + "_protocol").column(VALUE, Objects.toString(value.acceptedProtocols(), null));
+        result.row(f.getName() + "_cipher_suites").column(VALUE, Objects.toString(value.cipher_suites, null));
         result.row(f.getName() + "_client_auth").column(VALUE, Boolean.toString(value.require_client_auth));
         result.row(f.getName() + "_endpoint_verification").column(VALUE, Boolean.toString(value.require_endpoint_verification));
         result.row(f.getName() + "_optional").column(VALUE, Boolean.toString(value.isOptional()));
diff --git a/src/java/org/apache/cassandra/security/SSLFactory.java b/src/java/org/apache/cassandra/security/SSLFactory.java
index 94db564..7dc0256 100644
--- a/src/java/org/apache/cassandra/security/SSLFactory.java
+++ b/src/java/org/apache/cassandra/security/SSLFactory.java
@@ -31,29 +31,29 @@ import java.util.Date;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.handler.ssl.CipherSuiteFilter;
 import io.netty.handler.ssl.ClientAuth;
 import io.netty.handler.ssl.OpenSsl;
 import io.netty.handler.ssl.SslContext;
 import io.netty.handler.ssl.SslContextBuilder;
 import io.netty.handler.ssl.SslProvider;
-import io.netty.handler.ssl.SupportedCipherSuiteFilter;
 import io.netty.util.ReferenceCountUtil;
 import org.apache.cassandra.concurrent.ScheduledExecutors;
 import org.apache.cassandra.config.Config;
@@ -164,7 +164,6 @@ public final class SSLFactory
     /**
      * Create a JSSE {@link SSLContext}.
      */
-    @SuppressWarnings("resource")
     public static SSLContext createSSLContext(EncryptionOptions options, boolean buildTruststore) throws IOException
     {
         TrustManager[] trustManagers = null;
@@ -175,7 +174,8 @@ public final class SSLFactory
 
         try
         {
-            SSLContext ctx = SSLContext.getInstance(options.protocol);
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.getDefaultSSLParameters().setProtocols(options.acceptedProtocolsArray());
             ctx.init(kmf.getKeyManagers(), trustManagers, null);
             return ctx;
         }
@@ -233,21 +233,6 @@ public final class SSLFactory
         }
     }
 
-    public static String[] filterCipherSuites(String[] supported, String[] desired)
-    {
-        if (Arrays.equals(supported, desired))
-            return desired;
-        List<String> ldesired = Arrays.asList(desired);
-        ImmutableSet<String> ssupported = ImmutableSet.copyOf(supported);
-        String[] ret = Iterables.toArray(Iterables.filter(ldesired, Predicates.in(ssupported)), String.class);
-        if (desired.length > ret.length && logger.isWarnEnabled())
-        {
-            Iterable<String> missing = Iterables.filter(ldesired, Predicates.not(Predicates.in(Sets.newHashSet(ret))));
-            logger.warn("Filtering out {} as it isn't supported by the socket", Iterables.toString(missing));
-        }
-        return ret;
-    }
-
     /**
      * get a netty {@link SslContext} instance
      */
@@ -289,6 +274,16 @@ public final class SSLFactory
     static SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore,
                                             SocketType socketType, boolean useOpenSsl) throws IOException
     {
+        return createNettySslContext(options, buildTruststore, socketType, useOpenSsl,
+                                     LoggingCipherSuiteFilter.QUIET_FILTER);
+    }
+
+    /**
+     * Create a Netty {@link SslContext} with a supplied cipherFilter
+     */
+    static SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore,
+                                            SocketType socketType, boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws IOException
+    {
         /*
             There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
             I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
@@ -311,10 +306,12 @@ public final class SSLFactory
 
         builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
 
+        builder.protocols(options.acceptedProtocols());
+
         // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
         // for each ssl implemention (jdk or openssl)
         if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
-            builder.ciphers(options.cipher_suites, SupportedCipherSuiteFilter.INSTANCE);
+            builder.ciphers(options.cipher_suites, cipherFilter);
 
         if (buildTruststore)
             builder.trustManager(buildTrustManagerFactory(options));
@@ -366,8 +363,6 @@ public final class SSLFactory
 
         logger.debug("Initializing hot reloading SSLContext");
 
-        validateSslCerts(serverOpts, clientOpts);
-
         List<HotReloadableFile> fileList = new ArrayList<>();
 
         if (serverOpts != null && serverOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
@@ -397,41 +392,137 @@ public final class SSLFactory
         isHotReloadingInitialized = true;
     }
 
-
-    /**
-     * Sanity checks all certificates to ensure we can actually load them
+    // Non-logging
+    /*
+     * This class will filter all requested ciphers out that are not supported by the current {@link SSLEngine},
+     * logging messages for all dropped ciphers, and throws an exception if no ciphers are supported
      */
-    public static void validateSslCerts(EncryptionOptions.ServerEncryptionOptions serverOpts, EncryptionOptions clientOpts) throws IOException
+    public static final class LoggingCipherSuiteFilter implements CipherSuiteFilter
     {
-        try
+        // Version without logging the ciphers, make sure same filtering logic is used
+        // all the time, regardless of user output.
+        public static final CipherSuiteFilter QUIET_FILTER = new LoggingCipherSuiteFilter();
+        final String settingDescription;
+
+        private LoggingCipherSuiteFilter()
         {
-            // Ensure we're able to create both server & client SslContexts if they might ever be needed
-            if (serverOpts != null && serverOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
-            {
-                createNettySslContext(serverOpts, true, SocketType.SERVER, openSslIsAvailable());
-                createNettySslContext(serverOpts, true, SocketType.CLIENT, openSslIsAvailable());
-            }
+            this.settingDescription = null;
         }
-        catch (Exception e)
+
+        public LoggingCipherSuiteFilter(String settingDescription)
         {
-            throw new IOException("Failed to create SSL context using server_encryption_options!", e);
+            this.settingDescription = settingDescription;
         }
 
-        try
+
+        @Override
+        public String[] filterCipherSuites(Iterable<String> ciphers, List<String> defaultCiphers,
+                                           Set<String> supportedCiphers)
         {
-            // Ensure we're able to create both server & client SslContexts if they might ever be needed
-            if (clientOpts != null && clientOpts.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
+            Objects.requireNonNull(defaultCiphers, "defaultCiphers");
+            Objects.requireNonNull(supportedCiphers, "supportedCiphers");
+
+            final List<String> newCiphers;
+            if (ciphers == null)
+            {
+                newCiphers = new ArrayList<>(defaultCiphers.size());
+                ciphers = defaultCiphers;
+            }
+            else
             {
-                createNettySslContext(clientOpts, clientOpts.require_client_auth, SocketType.SERVER, openSslIsAvailable());
-                createNettySslContext(clientOpts, clientOpts.require_client_auth, SocketType.CLIENT, openSslIsAvailable());
+                newCiphers = new ArrayList<>(supportedCiphers.size());
             }
+            for (String c : ciphers)
+            {
+                if (c == null)
+                {
+                    break;
+                }
+                if (supportedCiphers.contains(c))
+                {
+                    newCiphers.add(c);
+                }
+                else
+                {
+                    if (settingDescription != null)
+                    {
+                        logger.warn("Dropping unsupported cipher_suite {} from {} configuration",
+                                    c, settingDescription.toLowerCase());
+                    }
+                }
+            }
+            if (newCiphers.isEmpty())
+            {
+                throw new IllegalStateException("No ciphers left after filtering supported cipher suite");
+            }
+
+            return newCiphers.toArray(new String[0]);
         }
-        catch (Exception e)
+    }
+
+    public static void validateSslContext(String contextDescription, EncryptionOptions options, boolean buildTrustStore, boolean logProtocolAndCiphers) throws IOException
+    {
+        if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
         {
-            throw new IOException("Failed to create SSL context using client_encryption_options!", e);
+            try
+            {
+                CipherSuiteFilter loggingCipherSuiteFilter = logProtocolAndCiphers ? new LoggingCipherSuiteFilter(contextDescription)
+                                                                                   : LoggingCipherSuiteFilter.QUIET_FILTER;
+                SslContext serverSslContext = createNettySslContext(options, buildTrustStore, SocketType.SERVER, openSslIsAvailable(), loggingCipherSuiteFilter);
+                try
+                {
+                    SSLEngine engine = serverSslContext.newEngine(ByteBufAllocator.DEFAULT);
+                    try
+                    {
+                        if (logProtocolAndCiphers)
+                        {
+                            String[] supportedProtocols = engine.getSupportedProtocols();
+                            String[] supportedCiphers = engine.getSupportedCipherSuites();
+                            String[] enabledProtocols = engine.getEnabledProtocols();
+                            String[] enabledCiphers = engine.getEnabledCipherSuites();
+
+                            logger.debug("{} supported TLS protocols: {}", contextDescription,
+                                         supportedProtocols == null ? "system default" : String.join(", ", supportedProtocols));
+                            logger.info("{} enabled TLS protocols: {}", contextDescription,
+                                        enabledProtocols == null ? "system default" : String.join(", ", enabledProtocols));
+                            logger.debug("{} supported cipher suites: {}", contextDescription,
+                                         supportedCiphers == null ? "system default" : String.join(", ", supportedCiphers));
+                            logger.info("{} enabled cipher suites: {}", contextDescription,
+                                        enabledCiphers == null ? "system default" : String.join(", ", enabledCiphers));
+                        }
+                    }
+                    finally
+                    {
+                        engine.closeInbound();
+                        engine.closeOutbound();
+                        ReferenceCountUtil.release(engine);
+                    }
+                }
+                finally
+                {
+                    ReferenceCountUtil.release(serverSslContext);
+                }
+
+                // Make sure it is possible to build the client context too
+                SslContext clientSslContext = createNettySslContext(options, buildTrustStore, SocketType.CLIENT, openSslIsAvailable());
+                ReferenceCountUtil.release(clientSslContext);
+            }
+            catch (Exception e)
+            {
+                throw new IOException("Failed to create SSL context using " + contextDescription, e);
+            }
         }
     }
 
+    /**
+     * Sanity checks all certificates to ensure we can actually load them
+     */
+    public static void validateSslCerts(EncryptionOptions.ServerEncryptionOptions serverOpts, EncryptionOptions clientOpts) throws IOException
+    {
+        validateSslContext("server_encryption_options", serverOpts, true, false);
+        validateSslContext("client_encryption_options", clientOpts, clientOpts.require_client_auth, false);
+    }
+
     static class CacheKey
     {
         private final EncryptionOptions encryptionOptions;
diff --git a/src/java/org/apache/cassandra/tools/BulkLoader.java b/src/java/org/apache/cassandra/tools/BulkLoader.java
index c08881b..5fd3885 100644
--- a/src/java/org/apache/cassandra/tools/BulkLoader.java
+++ b/src/java/org/apache/cassandra/tools/BulkLoader.java
@@ -263,7 +263,7 @@ public class BulkLoader
 
         return JdkSSLOptions.builder()
                             .withSSLContext(sslContext)
-                            .withCipherSuites(clientEncryptionOptions.cipher_suites.toArray(new String[0]))
+                            .withCipherSuites(clientEncryptionOptions.cipherSuitesArray())
                             .build();
     }
 
diff --git a/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java b/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java
index 6c48299..3ca69c8 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/AbstractEncryptionOptionsImpl.java
@@ -18,12 +18,16 @@
 
 package org.apache.cassandra.distributed.test;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSession;
+
 import com.google.common.collect.ImmutableMap;
 import org.junit.Assert;
 import org.slf4j.Logger;
@@ -51,15 +55,28 @@ import org.apache.cassandra.utils.concurrent.SimpleCondition;
 
 public class AbstractEncryptionOptionsImpl extends TestBaseImpl
 {
-    Logger logger = LoggerFactory.getLogger(EncryptionOptions.class);
+    final Logger logger = LoggerFactory.getLogger(EncryptionOptions.class);
     final static String validKeyStorePath = "test/conf/cassandra_ssl_test.keystore";
     final static String validKeyStorePassword = "cassandra";
     final static String validTrustStorePath = "test/conf/cassandra_ssl_test.truststore";
     final static String validTrustStorePassword = "cassandra";
+
+    // Base configuration map for a valid keystore that can be opened
     final static Map<String,Object> validKeystore = ImmutableMap.of("keystore", validKeyStorePath,
-                                                                   "keystore_password", validKeyStorePassword,
-                                                                   "truststore", validTrustStorePath,
-                                                                   "truststore_password", validTrustStorePassword);
+                                                                    "keystore_password", validKeyStorePassword,
+                                                                    "truststore", validTrustStorePath,
+                                                                    "truststore_password", validTrustStorePassword);
+
+    // Configuration with a valid keystore, but an unknown protocol
+    final static Map<String,Object> nonExistantProtocol = ImmutableMap.<String,Object>builder()
+                                                                           .putAll(validKeystore)
+                                                                           .put("accepted_protocols", Collections.singletonList("NoProtocolIKnow"))
+                                                                           .build();
+    // Configuration with a valid keystore, but an unknown cipher suite
+    final static Map<String,Object> nonExistantCipher = ImmutableMap.<String,Object>builder()
+                                                                           .putAll(validKeystore)
+                                                                           .put("cipher_suites", Collections.singletonList("NoCipherIKnow"))
+                                                                           .build();
 
     // Result of a TlsConnection.connect call.  The result is updated as the TLS connection
     // sequence takes place.  The nextOnFailure/nextOnSuccess allows the discard handler
@@ -91,16 +108,32 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
     {
         final String host;
         final int port;
+        final List<String> acceptedProtocols;
+        final List<String> cipherSuites;
         final EncryptionOptions encryptionOptions = new EncryptionOptions()
                                                     .withEnabled(true)
                                                     .withKeyStore(validKeyStorePath).withKeyStorePassword(validKeyStorePassword)
                                                     .withTrustStore(validTrustStorePath).withTrustStorePassword(validTrustStorePassword);
         private Throwable lastThrowable;
+        private String lastProtocol;
+        private String lastCipher;
 
         public TlsConnection(String host, int port)
         {
+            this(host, port, null, null);
+        }
+
+        public TlsConnection(String host, int port, List<String> acceptedProtocols)
+        {
+            this(host, port, acceptedProtocols, null);
+        }
+
+        public TlsConnection(String host, int port, List<String> acceptedProtocols, List<String> cipherSuites)
+        {
             this.host = host;
             this.port = port;
+            this.acceptedProtocols = acceptedProtocols;
+            this.cipherSuites = cipherSuites;
         }
 
         public synchronized Throwable lastThrowable()
@@ -112,6 +145,20 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
             lastThrowable = cause;
         }
 
+        public synchronized String lastProtocol()
+        {
+            return lastProtocol;
+        }
+        public synchronized String lastCipher()
+        {
+            return lastCipher;
+        }
+        private synchronized void setProtocolAndCipher(String protocol, String cipher)
+        {
+            lastProtocol = protocol;
+            lastCipher = cipher;
+        }
+
         final AtomicReference<ConnectResult> result = new AtomicReference<>(ConnectResult.UNINITIALIZED);
 
         void setResult(String why, ConnectResult expected, ConnectResult newResult)
@@ -144,9 +191,11 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
             AtomicInteger connectAttempts = new AtomicInteger(0);
             result.set(ConnectResult.UNINITIALIZED);
             setLastThrowable(null);
+            setProtocolAndCipher(null, null);
 
-            SslContext sslContext = SSLFactory.getOrCreateSslContext(encryptionOptions, true,
-                                                                     SSLFactory.SocketType.CLIENT);
+            SslContext sslContext = SSLFactory.getOrCreateSslContext(
+                encryptionOptions.withAcceptedProtocols(acceptedProtocols).withCipherSuites(cipherSuites),
+                true, SSLFactory.SocketType.CLIENT);
 
             EventLoopGroup workerGroup = new NioEventLoopGroup();
             Bootstrap b = new Bootstrap();
@@ -160,7 +209,11 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
                 try
                 {
                     logger.debug("handshakeFuture() listener called");
-                    channelFuture.get();
+                    Channel channel = channelFuture.get();
+                    SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
+                    SSLSession session = sslHandler.engine().getSession();
+                    setProtocolAndCipher(session.getProtocol(), session.getCipherSuite());
+
                     successProgress();
                 }
                 catch (Throwable cause)
@@ -268,6 +321,14 @@ public class AbstractEncryptionOptionsImpl extends TestBaseImpl
                 // verify it was not possible to connect before starting the server
             }
         }
+
+        void assertReceivedHandshakeException()
+        {
+            Assert.assertTrue("Expected a J8 handshake_failure or J11 protocol_version exception: " + lastThrowable.getMessage(),
+                              lastThrowable().getMessage().contains("Received fatal alert: handshake_failure") ||
+                              lastThrowable().getMessage().contains("Received fatal alert: protocol_version") ||
+                              lastThrowable.getCause() instanceof  SSLHandshakeException);
+        }
     }
 
     /* Provde the cluster cannot start with the configured options */
diff --git a/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java b/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java
index 4b05ade..1462b03 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/InternodeEncryptionOptionsTest.java
@@ -19,6 +19,7 @@
 package org.apache.cassandra.distributed.test;
 
 import java.net.InetAddress;
+import java.util.Collections;
 
 import com.google.common.collect.ImmutableMap;
 import org.junit.Assert;
@@ -215,4 +216,89 @@ public class InternodeEncryptionOptionsTest extends AbstractEncryptionOptionsImp
             }
         }
     }
+
+    @Test
+    public void negotiatedProtocolMustBeAcceptedProtocolTest() throws Throwable
+    {
+        try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+            c.with(Feature.NETWORK);
+            c.set("server_encryption_options",
+                  ImmutableMap.builder().putAll(validKeystore)
+                              .put("internode_encryption", "all")
+                              .put("accepted_protocols", Collections.singletonList("TLSv1.1"))
+                              .build());
+        }).start())
+        {
+            InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
+            int port = cluster.get(1).config().broadcastAddress().getPort();
+
+            TlsConnection tls10Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1"));
+            Assert.assertEquals("Should not be possible to establish a TLSv1 connection",
+                                ConnectResult.FAILED_TO_NEGOTIATE, tls10Connection.connect());
+            tls10Connection.assertReceivedHandshakeException();
+
+            TlsConnection tls11Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1.1"));
+            Assert.assertEquals("Should be possible to establish a TLSv1.1 connection",
+                                ConnectResult.NEGOTIATED, tls11Connection.connect());
+            Assert.assertEquals("TLSv1.1", tls11Connection.lastProtocol());
+
+            TlsConnection tls12Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1.2"));
+            Assert.assertEquals("Should not be possible to establish a TLSv1.2 connection",
+                                ConnectResult.FAILED_TO_NEGOTIATE, tls12Connection.connect());
+            tls12Connection.assertReceivedHandshakeException();
+        }
+    }
+
+    @Test
+    public void connectionCannotAgreeOnClientAndServer() throws Throwable
+    {
+        try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+            c.with(Feature.NETWORK);
+            c.set("server_encryption_options",
+                  ImmutableMap.builder().putAll(validKeystore)
+                              .put("internode_encryption", "all")
+                              .put("accepted_protocols", Collections.singletonList("TLSv1.2"))
+                              .put("cipher_suites", Collections.singletonList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"))
+                              .build());
+        }).start())
+        {
+            InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
+            int port = cluster.get(1).config().broadcastAddress().getPort();
+
+            TlsConnection connection = new TlsConnection(address.getHostAddress(), port,
+                                                         Collections.singletonList("TLSv1.2"),
+                                                         Collections.singletonList("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"));
+            Assert.assertEquals("Should not be possible to establish a TLSv1.2 connection with different ciphers",
+                                ConnectResult.FAILED_TO_NEGOTIATE, connection.connect());
+            connection.assertReceivedHandshakeException();
+        }
+    }
+
+    @Test
+    public void nodeMustNotStartWithNonExistantProtocol() throws Throwable
+    {
+        try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+            c.with(Feature.NETWORK);
+            c.set("server_encryption_options",
+                  ImmutableMap.<String,Object>builder().putAll(nonExistantProtocol)
+                                                       .put("internode_encryption", "all").build());
+        }).createWithoutStarting())
+        {
+            assertCannotStartDueToConfigurationException(cluster);
+        }
+    }
+
+    @Test
+    public void nodeMustNotStartWithNonExistantCipher() throws Throwable
+    {
+        try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+            c.with(Feature.NETWORK);
+            c.set("server_encryption_options",
+                  ImmutableMap.<String,Object>builder().putAll(nonExistantCipher)
+                                                       .put("internode_encryption", "all").build());
+        }).createWithoutStarting())
+        {
+            assertCannotStartDueToConfigurationException(cluster);
+        }
+    }
 }
diff --git a/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java b/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java
index d64b038..c5a810c 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/NativeTransportEncryptionOptionsTest.java
@@ -19,6 +19,7 @@
 package org.apache.cassandra.distributed.test;
 
 import java.net.InetAddress;
+import java.util.Collections;
 
 import com.google.common.collect.ImmutableMap;
 import org.junit.Assert;
@@ -35,10 +36,9 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
         try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
             c.with(Feature.NATIVE_PROTOCOL);
             c.set("client_encryption_options",
-                  ImmutableMap.of("enabled", true,
-                                   "optional", true,
-                                   "keystore", "/path/to/bad/keystore/that/should/not/exist",
-                                   "truststore", "/path/to/bad/truststore/that/should/not/exist"));
+                  ImmutableMap.of("optional", true,
+                                  "keystore", "/path/to/bad/keystore/that/should/not/exist",
+                                  "truststore", "/path/to/bad/truststore/that/should/not/exist"));
         }).createWithoutStarting())
         {
             assertCannotStartDueToConfigurationException(cluster);
@@ -134,4 +134,89 @@ public class NativeTransportEncryptionOptionsTest extends AbstractEncryptionOpti
             assertCannotStartDueToConfigurationException(cluster);
         }
     }
+
+
+    @Test
+    public void negotiatedProtocolMustBeAcceptedProtocolTest() throws Throwable
+    {
+        try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+            c.with(Feature.NATIVE_PROTOCOL);
+            c.set("client_encryption_options",
+                  ImmutableMap.builder().putAll(validKeystore)
+                              .put("enabled", true)
+                              .put("accepted_protocols", Collections.singletonList("TLSv1.1"))
+                              .build());
+        }).start())
+        {
+            InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
+            int port = (int) cluster.get(1).config().get("native_transport_port");
+
+            TlsConnection tls10Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1"));
+            Assert.assertEquals("Should not be possible to establish a TLSv1 connection",
+                                ConnectResult.FAILED_TO_NEGOTIATE, tls10Connection.connect());
+            tls10Connection.assertReceivedHandshakeException();
+
+            TlsConnection tls11Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1.1"));
+            Assert.assertEquals("Should be possible to establish a TLSv1.1 connection",
+                                ConnectResult.NEGOTIATED, tls11Connection.connect());
+            Assert.assertEquals("TLSv1.1", tls11Connection.lastProtocol());
+
+            TlsConnection tls12Connection = new TlsConnection(address.getHostAddress(), port, Collections.singletonList("TLSv1.2"));
+            Assert.assertEquals("Should be possible to establish a TLSv1.2 connection",
+                                ConnectResult.FAILED_TO_NEGOTIATE, tls12Connection.connect());
+            tls12Connection.assertReceivedHandshakeException();
+        }
+    }
+
+    @Test
+    public void connectionCannotAgreeOnClientAndServerTest() throws Throwable
+    {
+        try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+            c.with(Feature.NATIVE_PROTOCOL);
+            c.set("client_encryption_options",
+                  ImmutableMap.builder().putAll(validKeystore)
+                              .put("enabled", true)
+                              .put("accepted_protocols", Collections.singletonList("TLSv1.2"))
+                              .put("cipher_suites", Collections.singletonList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"))
+                              .build());
+        }).start())
+        {
+            InetAddress address = cluster.get(1).config().broadcastAddress().getAddress();
+            int port = (int) cluster.get(1).config().get("native_transport_port");
+
+            TlsConnection connection = new TlsConnection(address.getHostAddress(), port,
+                                                         Collections.singletonList("TLSv1.2"),
+                                                         Collections.singletonList("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"));
+            Assert.assertEquals("Should not be possible to establish a TLSv1.2 connection with different ciphers",
+                                ConnectResult.FAILED_TO_NEGOTIATE, connection.connect());
+            connection.assertReceivedHandshakeException();
+        }
+    }
+
+    @Test
+    public void nodeMustNotStartWithNonExistantProtocolTest() throws Throwable
+    {
+        try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+            c.with(Feature.NATIVE_PROTOCOL);
+            c.set("client_encryption_options",
+                  ImmutableMap.<String,Object>builder().putAll(nonExistantProtocol).put("enabled", true).build());
+        }).createWithoutStarting())
+        {
+            assertCannotStartDueToConfigurationException(cluster);
+        }
+    }
+
+    @Test
+    public void nodeMustNotStartWithNonExistantCiphersTest() throws Throwable
+    {
+        try (Cluster cluster = builder().withNodes(1).withConfig(c -> {
+            c.with(Feature.NATIVE_PROTOCOL);
+            c.set("client_encryption_options",
+                  ImmutableMap.<String,Object>builder().putAll(nonExistantCipher).put("enabled", true).build());
+        }).createWithoutStarting())
+        {
+            // Should also log "Dropping unsupported cipher_suite NoCipherIKnow from from native transport configuration"
+            assertCannotStartDueToConfigurationException(cluster);
+        }
+    }
 }
diff --git a/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java b/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java
index 59638ad..23a6c00 100644
--- a/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java
+++ b/test/unit/org/apache/cassandra/config/EncryptionOptionsTest.java
@@ -56,16 +56,16 @@ public class EncryptionOptionsTest
         public static EncryptionOptionsTestCase of(Boolean optional, String keystorePath, Boolean enabled, EncryptionOptions.TlsEncryptionPolicy expected)
         {
             return new EncryptionOptionsTestCase(new EncryptionOptions(keystorePath, "dummypass", "dummytruststore", "dummypass",
-                                                                       Collections.emptyList(), "TLS", null, "JKS", false, false, enabled, optional)
+                                                                       Collections.emptyList(), null, null, null, "JKS", false, false, enabled, optional)
                                                  .applyConfig(),
                                                  expected,
                                                  String.format("optional=%s keystore=%s enabled=%s", optional, keystorePath, enabled));
         }
     }
 
-    static String absentKeystore = "test/conf/missing-keystore-is-not-here";
-    static String presentKeystore = "test/conf/keystore.jks";
-    EncryptionOptionsTestCase[] encryptionOptionTestCases = {
+    static final String absentKeystore = "test/conf/missing-keystore-is-not-here";
+    static final String presentKeystore = "test/conf/keystore.jks";
+    final EncryptionOptionsTestCase[] encryptionOptionTestCases = {
         //                         Optional    Keystore     Enabled  Expected
         EncryptionOptionsTestCase.of(null, absentKeystore,  false, UNENCRYPTED),
         EncryptionOptionsTestCase.of(null, absentKeystore,  true,  ENCRYPTED),
@@ -106,7 +106,7 @@ public class EncryptionOptionsTest
                                                          EncryptionOptions.TlsEncryptionPolicy expected)
         {
             return new ServerEncryptionOptionsTestCase(new EncryptionOptions.ServerEncryptionOptions(keystorePath, "dummypass", "dummytruststore", "dummypass",
-                                                                                               Collections.emptyList(), "TLS", null, "JKS", false, false, optional, internodeEncryption, false)
+                                                                                               Collections.emptyList(), null, null, null, "JKS", false, false, optional, internodeEncryption, false)
                                                        .applyConfig(),
                                                  expected,
                                                  String.format("optional=%s keystore=%s internode=%s", optional, keystorePath, internodeEncryption));
@@ -141,7 +141,7 @@ public class EncryptionOptionsTest
                   .hasMessage("Invalid yaml. Please remove properties [isOptional] from your cassandra.yaml");
     }
 
-    ServerEncryptionOptionsTestCase[] serverEncryptionOptionTestCases = {
+    final ServerEncryptionOptionsTestCase[] serverEncryptionOptionTestCases = {
 
         //                               Optional    Keystore    Internode  Expected
         ServerEncryptionOptionsTestCase.of(null, absentKeystore, none, UNENCRYPTED),
diff --git a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
index 65297c7..c81da51 100644
--- a/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
+++ b/test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java
@@ -145,13 +145,24 @@ public class SettingsTableTest extends CQLTester
         config.server_encryption_options = config.server_encryption_options.withAlgorithm("SUPERSSL");
         check(pre + "algorithm", "SUPERSSL");
 
-        check(pre + "cipher_suites", "[]");
+        check(pre + "cipher_suites", null);
         config.server_encryption_options = config.server_encryption_options.withCipherSuites("c1", "c2");
         check(pre + "cipher_suites", "[c1, c2]");
 
-        check(pre + "protocol", config.server_encryption_options.protocol);
+        check(pre + "protocol", null);
         config.server_encryption_options = config.server_encryption_options.withProtocol("TLSv5");
-        check(pre + "protocol", "TLSv5");
+        check(pre + "protocol", "[TLSv5]");
+
+        config.server_encryption_options = config.server_encryption_options.withProtocol("TLS");
+        check(pre + "protocol", "[TLSv1.3, TLSv1.2, TLSv1.1, TLSv1]");
+
+        config.server_encryption_options = config.server_encryption_options.withProtocol("TLS");
+        config.server_encryption_options = config.server_encryption_options.withAcceptedProtocols(ImmutableList.of("TLSv1.2","TLSv1.1"));
+        check(pre + "protocol", "[TLSv1.2, TLSv1.1]");
+
+        config.server_encryption_options = config.server_encryption_options.withProtocol("TLSv2");
+        config.server_encryption_options = config.server_encryption_options.withAcceptedProtocols(ImmutableList.of("TLSv1.2","TLSv1.1"));
+        check(pre + "protocol", "[TLSv1.2, TLSv1.1, TLSv2]"); // protocol goes after the explicit accept list if non-TLS
 
         check(pre + "optional", "false");
         config.server_encryption_options = config.server_encryption_options.withOptional(true);
diff --git a/test/unit/org/apache/cassandra/security/SSLFactoryTest.java b/test/unit/org/apache/cassandra/security/SSLFactoryTest.java
index 307a276..4cbc095 100644
--- a/test/unit/org/apache/cassandra/security/SSLFactoryTest.java
+++ b/test/unit/org/apache/cassandra/security/SSLFactoryTest.java
@@ -55,7 +55,7 @@ public class SSLFactoryTest
         }
         catch (CertificateException e)
         {
-            throw new RuntimeException("fialed to create test certs");
+            throw new RuntimeException("failed to create test certs");
         }
     }
 
@@ -74,17 +74,6 @@ public class SSLFactoryTest
     }
 
     @Test
-    public void testFilterCipherSuites()
-    {
-        String[] supported = new String[] {"x", "b", "c", "f"};
-        String[] desired = new String[] { "k", "a", "b", "c" };
-        assertArrayEquals(new String[] { "b", "c" }, SSLFactory.filterCipherSuites(supported, desired));
-
-        desired = new String[] { "c", "b", "x" };
-        assertArrayEquals(desired, SSLFactory.filterCipherSuites(supported, desired));
-    }
-
-    @Test
     public void getSslContext_OpenSSL() throws IOException
     {
         // only try this test if OpenSsl is available
@@ -174,11 +163,11 @@ public class SSLFactoryTest
                                                                                                            .isAvailable());
             File keystoreFile = new File(options.keystore);
 
-            SSLFactory.checkCertFilesForHotReloading((ServerEncryptionOptions) options, options);
+            SSLFactory.checkCertFilesForHotReloading(options, options);
 
             keystoreFile.setLastModified(System.currentTimeMillis() + 15000);
 
-            SSLFactory.checkCertFilesForHotReloading((ServerEncryptionOptions) options, options);;
+            SSLFactory.checkCertFilesForHotReloading(options, options);
             SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, SSLFactory.SocketType.CLIENT, OpenSsl
                                                                                                           .isAvailable());
 
@@ -201,7 +190,7 @@ public class SSLFactoryTest
                                     .withKeyStorePassword("bad password")
                                     .withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all);
 
-        SSLFactory.initHotReloading(options, options, true);
+        SSLFactory.validateSslContext("testSslFactorySslInit_BadPassword_ThrowsException", options, false, true);
     }
 
     @Test
@@ -253,7 +242,7 @@ public class SSLFactoryTest
             testKeystoreFile.setLastModified(System.currentTimeMillis() + 15000);
             FileUtils.forceDelete(testKeystoreFile);
 
-            SSLFactory.checkCertFilesForHotReloading(options, options);;
+            SSLFactory.checkCertFilesForHotReloading(options, options);
             SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, SSLFactory.SocketType.CLIENT, OpenSsl
                                                                                                           .isAvailable());
 
diff --git a/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java b/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java
index b054a6e..b934769 100644
--- a/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java
+++ b/tools/stress/src/org/apache/cassandra/stress/util/JavaDriverClient.java
@@ -144,7 +144,7 @@ public class JavaDriverClient
             sslContext = SSLFactory.createSSLContext(encryptionOptions, true);
             SSLOptions sslOptions = JdkSSLOptions.builder()
                                                  .withSSLContext(sslContext)
-                                                 .withCipherSuites(encryptionOptions.cipher_suites.toArray(new String[0])).build();
+                                                 .withCipherSuites(encryptionOptions.cipherSuitesArray()).build();
             clusterBuilder.withSSL(sslOptions);
         }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org