Skip to content

Commit

Permalink
Update ciphers for TLSv1.3 and JDK11 if available (#42082)
Browse files Browse the repository at this point in the history
This commit updates the default ciphers and TLS protocols that are used
when the runtime JDK supports them. New cipher support has been
introduced in JDK 11 and 12 along with performance fixes for AES GCM.
The ciphers are ordered with PFS ciphers being most preferred, then
AEAD ciphers, and finally those with mainstream hardware support. When
available stronger encryption is preferred for a given cipher.

This is a backport of #41385 and #41808. There are known JDK bugs with
TLSv1.3 that have been fixed in various versions. These are:

1. The JDK's bundled HttpsServer will endless loop under JDK11 and JDK
12.0 (Fixed in 12.0.1) based on the way the Apache HttpClient performs
a close (half close).
2. In all versions of JDK 11 and 12, the HttpsServer will endless loop
when certificates are not trusted or another handshake error occurs. An
email has been sent to the openjdk security-dev list and #38646 is open
to track this.
3. In JDK 11.0.2 and prior there is a race condition with session
resumption that leads to handshake errors when multiple concurrent
handshakes are going on between the same client and server. This bug
does not appear when client authentication is in use. This is
JDK-8213202, which was fixed in 11.0.3 and 12.0.
4. In JDK 11.0.2 and prior there is a bug where resumed TLS sessions do
not retain peer certificate information. This is JDK-8212885.

The way these issues are addressed is that the current java version is
checked and used to determine the supported protocols for tests that
provoke these issues.
  • Loading branch information
jaymode authored May 20, 2019
1 parent fd2d4d7 commit dbbdcea
Show file tree
Hide file tree
Showing 27 changed files with 553 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.AccessController;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivilegedAction;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.PKCS8EncodedKeySpec;
Expand Down Expand Up @@ -106,7 +108,7 @@ private RestClient buildRestClient() {
}

private static SSLContext getSslContext() throws Exception {
SSLContext sslContext = SSLContext.getInstance("TLS");
SSLContext sslContext = SSLContext.getInstance(getProtocol());
try (InputStream certFile = RestClientBuilderIntegTests.class.getResourceAsStream("/test.crt")) {
// Build a keystore of default type programmatically since we can't use JKS keystores to
// init a KeyManagerFactory in FIPS 140 JVMs.
Expand All @@ -126,4 +128,37 @@ private static SSLContext getSslContext() throws Exception {
}
return sslContext;
}

/**
* The {@link HttpsServer} in the JDK has issues with TLSv1.3 when running in a JDK that supports TLSv1.3 prior to
* 12.0.1 so we pin to TLSv1.2 when running on an earlier JDK.
*/
private static String getProtocol() {
String version = AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty("java.version"));
String[] components = version.split("\\.");
if (components.length > 0) {
final int major = Integer.valueOf(components[0]);
if (major < 11) {
return "TLS";
} if (major > 12) {
return "TLS";
} else if (major == 12 && components.length > 2) {
final int minor = Integer.valueOf(components[1]);
if (minor > 0) {
return "TLS";
} else {
String patch = components[2];
final int index = patch.indexOf("_");
if (index > -1) {
patch = patch.substring(0, index);
}

if (Integer.valueOf(patch) >= 1) {
return "TLS";
}
}
}
}
return "TLSv1.2";
}
}
31 changes: 24 additions & 7 deletions docs/reference/settings/security-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -1526,13 +1526,30 @@ Controls the verification of certificates. Valid values are:
The default value is `full`.

`*.ssl.cipher_suites`::
Supported cipher suites can be found in Oracle's http://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html[
Java Cryptography Architecture documentation]. Defaults to `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`,
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`, `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`, `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`,
`TLS_RSA_WITH_AES_128_CBC_SHA256`, `TLS_RSA_WITH_AES_128_CBC_SHA`. If the _Java Cryptography Extension (JCE) Unlimited Strength
Jurisdiction Policy Files_ has been installed, the default value also includes `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384`,
`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384`, `TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`, `TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`,
`TLS_RSA_WITH_AES_256_CBC_SHA256`, `TLS_RSA_WITH_AES_256_CBC_SHA`.
Supported cipher suites can be found in Oracle's
https://docs.oracle.com/en/java/javase/11/security/oracle-providers.html#GUID-7093246A-31A3-4304-AC5F-5FB6400405E2[Java
Cryptography Architecture documentation].
Defaults to `TLS_AES_256_GCM_SHA384`, `TLS_AES_128_GCM_SHA256`,
`TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`, `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`,
`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`, `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`,
`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384`, `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`,
`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384`, `TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`,
`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`, `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`,
`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`, `TLS_ECDHE_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`.
+
--
NOTE: The default cipher suites list above includes TLSv1.3 ciphers and ciphers
that require the _Java Cryptography Extension (JCE) Unlimited Strength
Jurisdiction Policy Files_ for 256-bit AES encryption. If TLSv1.3 is not
available, the TLSv1.3 ciphers TLS_AES_256_GCM_SHA384`, `TLS_AES_128_GCM_SHA256`
will not be included in the default list. If 256-bit AES is unavailable, ciphers
with `AES_256` in their names wil not be included in the default list. Finally,
AES GCM has known performance issues in Java versions prior to 11 and will only
be included in the default list when using Java 11 or above.
--

[float]
[[tls-ssl-key-settings]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

package org.elasticsearch.common.ssl;

import org.elasticsearch.bootstrap.JavaVersion;

import javax.crypto.Cipher;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
Expand Down Expand Up @@ -338,30 +340,53 @@ private <V> List<V> resolveListSetting(String key, Function<String, V> parser, L
}

private static List<String> loadDefaultCiphers() {
final List<String> ciphers128 = Arrays.asList(
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA"
);
final List<String> ciphers256 = Arrays.asList(
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA"
);
if (has256BitAES()) {
List<String> ciphers = new ArrayList<>(ciphers256.size() + ciphers128.size());
ciphers.addAll(ciphers256);
ciphers.addAll(ciphers128);
return ciphers;
final boolean has256BitAES = has256BitAES();
final boolean useGCM = JavaVersion.current().compareTo(JavaVersion.parse("11")) >= 0;
final boolean tlsV13Supported = DEFAULT_PROTOCOLS.contains("TLSv1.3");
List<String> ciphers = new ArrayList<>();
if (tlsV13Supported) { // TLSv1.3 cipher has PFS, AEAD, hardware support
if (has256BitAES) {
ciphers.add("TLS_AES_256_GCM_SHA384");
}
ciphers.add("TLS_AES_128_GCM_SHA256");
}
if (useGCM) { // PFS, AEAD, hardware support
if (has256BitAES) {
ciphers.addAll(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"));
} else {
ciphers.addAll(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"));
}
}

// PFS, hardware support
if (has256BitAES) {
ciphers.addAll(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"));
} else {
ciphers.addAll(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"));
}

// AEAD, hardware support
if (useGCM) {
if (has256BitAES) {
ciphers.addAll(Arrays.asList("TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_RSA_WITH_AES_128_GCM_SHA256"));
} else {
ciphers.add("TLS_RSA_WITH_AES_128_GCM_SHA256");
}
}

// hardware support
if (has256BitAES) {
ciphers.addAll(Arrays.asList("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"));
} else {
return ciphers128;
ciphers.addAll(Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA"));
}
return ciphers;
}

private static boolean has256BitAES() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ public void testClientFailsWithUntrustedCertificate() throws IOException {
final List<Thread> threads = new ArrayList<>();
final Settings settings = Settings.builder()
.put("path.home", createTempDir())
.put("reindex.ssl.supported_protocols", "TLSv1.2")
.build();
final Environment environment = TestEnvironment.newEnvironment(settings);
final ReindexSslConfig ssl = new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class));
Expand All @@ -134,6 +135,7 @@ public void testClientSucceedsWithCertificateAuthorities() throws IOException {
final Settings settings = Settings.builder()
.put("path.home", createTempDir())
.putList("reindex.ssl.certificate_authorities", ca.toString())
.put("reindex.ssl.supported_protocols", "TLSv1.2")
.build();
final Environment environment = TestEnvironment.newEnvironment(settings);
final ReindexSslConfig ssl = new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class));
Expand All @@ -149,6 +151,7 @@ public void testClientSucceedsWithVerificationDisabled() throws IOException {
final Settings settings = Settings.builder()
.put("path.home", createTempDir())
.put("reindex.ssl.verification_mode", "NONE")
.put("reindex.ssl.supported_protocols", "TLSv1.2")
.build();
final Environment environment = TestEnvironment.newEnvironment(settings);
final ReindexSslConfig ssl = new ReindexSslConfig(settings, environment, mock(ResourceWatcherService.class));
Expand All @@ -169,6 +172,7 @@ public void testClientPassesClientCertificate() throws IOException {
.put("reindex.ssl.certificate", cert)
.put("reindex.ssl.key", key)
.put("reindex.ssl.key_passphrase", "client-password")
.put("reindex.ssl.supported_protocols", "TLSv1.2")
.build();
AtomicReference<Certificate[]> clientCertificates = new AtomicReference<>();
handler = https -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;
import org.apache.logging.log4j.LogManager;
import org.elasticsearch.bootstrap.JavaVersion;
import org.elasticsearch.cloud.azure.classic.management.AzureComputeService;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.io.FileSystemUtils;
Expand Down Expand Up @@ -59,7 +60,9 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.KeyStore;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -262,11 +265,30 @@ private static SSLContext getSSLContext() throws Exception {
kmf.init(ks, passphrase);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
SSLContext ssl = SSLContext.getInstance("TLS");
SSLContext ssl = SSLContext.getInstance(getProtocol());
ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return ssl;
}

/**
* The {@link HttpsServer} in the JDK has issues with TLSv1.3 when running in a JDK prior to
* 12.0.1 so we pin to TLSv1.2 when running on an earlier JDK
*/
private static String getProtocol() {
if (JavaVersion.current().compareTo(JavaVersion.parse("11")) < 0) {
return "TLS";
} else if (JavaVersion.current().compareTo(JavaVersion.parse("12")) < 0) {
return "TLSv1.2";
} else {
JavaVersion full =
AccessController.doPrivileged((PrivilegedAction<JavaVersion>) () -> JavaVersion.parse(System.getProperty("java.version")));
if (full.compareTo(JavaVersion.parse("12.0.1")) < 0) {
return "TLSv1.2";
}
}
return "TLS";
}

@AfterClass
public static void stopHttpd() throws IOException {
for (int i = 0; i < internalCluster().size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package org.elasticsearch.xpack.core;

import org.apache.logging.log4j.LogManager;
import org.elasticsearch.bootstrap.JavaVersion;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.xpack.core.security.SecurityField;
Expand Down Expand Up @@ -118,31 +119,79 @@ private XPackSettings() {
/** Setting for enabling or disabling sql. Defaults to true. */
public static final Setting<Boolean> SQL_ENABLED = Setting.boolSetting("xpack.sql.enabled", true, Setting.Property.NodeScope);

public static final List<String> DEFAULT_SUPPORTED_PROTOCOLS;

static {
boolean supportsTLSv13 = false;
try {
SSLContext.getInstance("TLSv1.3");
supportsTLSv13 = true;
} catch (NoSuchAlgorithmException e) {
LogManager.getLogger(XPackSettings.class).debug("TLSv1.3 is not supported", e);
}
DEFAULT_SUPPORTED_PROTOCOLS = supportsTLSv13 ?
Arrays.asList("TLSv1.3", "TLSv1.2", "TLSv1.1") : Arrays.asList("TLSv1.2", "TLSv1.1");
}

/*
* SSL settings. These are the settings that are specifically registered for SSL. Many are private as we do not explicitly use them
* but instead parse based on a prefix (eg *.ssl.*)
*/
public static final List<String> DEFAULT_CIPHERS;

static {
List<String> ciphers = Arrays.asList("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA");
List<String> ciphers = new ArrayList<>();
final boolean useGCM = JavaVersion.current().compareTo(JavaVersion.parse("11")) >= 0;
final boolean tlsV13Supported = DEFAULT_SUPPORTED_PROTOCOLS.contains("TLSv1.3");
try {
final boolean use256Bit = Cipher.getMaxAllowedKeyLength("AES") > 128;
if (tlsV13Supported) { // TLSv1.3 cipher has PFS, AEAD, hardware support
if (use256Bit) {
ciphers.add("TLS_AES_256_GCM_SHA384");
}
ciphers.add("TLS_AES_128_GCM_SHA256");
}
if (useGCM) { // PFS, AEAD, hardware support
if (use256Bit) {
ciphers.addAll(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"));
} else {
ciphers.addAll(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"));
}
}

// PFS, hardware support
if (use256Bit) {
ciphers.addAll(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"));
} else {
ciphers.addAll(Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"));
}

// AEAD, hardware support
if (useGCM) {
if (use256Bit) {
ciphers.addAll(Arrays.asList("TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_RSA_WITH_AES_128_GCM_SHA256"));
} else {
ciphers.add("TLS_RSA_WITH_AES_128_GCM_SHA256");
}
}

// hardware support
if (use256Bit) {
List<String> strongerCiphers = new ArrayList<>(ciphers.size() * 2);
strongerCiphers.addAll(Arrays.asList("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA"));
strongerCiphers.addAll(ciphers);
ciphers = strongerCiphers;
ciphers.addAll(Arrays.asList("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"));
} else {
ciphers.addAll(Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA"));
}
} catch (NoSuchAlgorithmException e) {
// ignore it here - there will be issues elsewhere and its not nice to throw in a static initializer
}

DEFAULT_CIPHERS = ciphers;
DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
}

/*
Expand All @@ -164,20 +213,6 @@ private XPackSettings() {
}
}, Setting.Property.NodeScope);

public static final List<String> DEFAULT_SUPPORTED_PROTOCOLS;

static {
boolean supportsTLSv13 = false;
try {
SSLContext.getInstance("TLSv1.3");
supportsTLSv13 = true;
} catch (NoSuchAlgorithmException e) {
LogManager.getLogger(XPackSettings.class).debug("TLSv1.3 is not supported", e);
}
DEFAULT_SUPPORTED_PROTOCOLS = supportsTLSv13 ?
Arrays.asList("TLSv1.3", "TLSv1.2", "TLSv1.1") : Arrays.asList("TLSv1.2", "TLSv1.1");
}

public static final SSLClientAuth CLIENT_AUTH_DEFAULT = SSLClientAuth.REQUIRED;
public static final SSLClientAuth HTTP_CLIENT_AUTH_DEFAULT = SSLClientAuth.NONE;
public static final VerificationMode VERIFICATION_MODE_DEFAULT = VerificationMode.FULL;
Expand Down
Binary file not shown.
Loading

0 comments on commit dbbdcea

Please sign in to comment.