Make the TLSv1.3 check more robust and not depend on the Java version… (#10409)

Motivation:

TLSv1.3 is not strictly limited to Java11+ anymore as different vendors backported TLSv1.3 to Java8 as well. We should ensure we make the detection of if TLSv1.3 is supported not depend on the Java version that is used.

Modifications:

- Add SslProvider.isTlsv13Supported(...) and use it in tests to detect if we should run tests against TLSv1.3 as well
- Adjust testcase to work on latest JDK 8 release as well

Result:

Correct detection of TLSv1.3 support even if Java version < 11.
This commit is contained in:
Norman Maurer 2020-07-17 07:12:11 +02:00
parent 8c0f1428af
commit 220995f155
11 changed files with 76 additions and 30 deletions

View File

@ -44,7 +44,7 @@ final class OpenSslTlsv13X509ExtendedTrustManager extends X509ExtendedTrustManag
}
static X509ExtendedTrustManager wrap(X509ExtendedTrustManager tm) {
if (PlatformDependent.javaVersion() < 11 && OpenSsl.isTlsv13Supported()) {
if (!SslProvider.isTlsv13Supported(SslProvider.JDK) && SslProvider.isTlsv13Supported(SslProvider.OPENSSL)) {
return new OpenSslTlsv13X509ExtendedTrustManager(tm);
}
return tm;

View File

@ -214,10 +214,12 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
// Create a new SSL_CTX and configure it.
boolean success = false;
try {
boolean tlsv13Supported = OpenSsl.isTlsv13Supported();
try {
int protocolOpts = SSL.SSL_PROTOCOL_SSLV3 | SSL.SSL_PROTOCOL_TLSV1 |
SSL.SSL_PROTOCOL_TLSV1_1 | SSL.SSL_PROTOCOL_TLSV1_2;
if (OpenSsl.isTlsv13Supported()) {
if (tlsv13Supported) {
protocolOpts |= SSL.SSL_PROTOCOL_TLSV1_3;
}
ctx = SSLContext.make(protocolOpts, mode);
@ -225,7 +227,6 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
throw new SSLException("failed to create an SSL_CTX", e);
}
boolean tlsv13Supported = OpenSsl.isTlsv13Supported();
StringBuilder cipherBuilder = new StringBuilder();
StringBuilder cipherTLSv13Builder = new StringBuilder();

View File

@ -52,4 +52,20 @@ public enum SslProvider {
throw new Error("Unknown SslProvider: " + provider);
}
}
/**
* Returns {@code true} if the specified {@link SslProvider} supports
* <a href="https://tools.ietf.org/html/rfc8446">TLS 1.3</a>, {@code false} otherwise.
*/
public static boolean isTlsv13Supported(final SslProvider provider) {
switch (provider) {
case JDK:
return SslUtils.isTLSv13SupportedByJDK();
case OPENSSL:
case OPENSSL_REFCNT:
return OpenSsl.isTlsv13Supported();
default:
throw new Error("Unknown SslProvider: " + provider);
}
}
}

View File

@ -23,7 +23,8 @@ import io.netty.handler.codec.base64.Base64;
import io.netty.handler.codec.base64.Base64Dialect;
import io.netty.util.NetUtil;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@ -33,7 +34,9 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import static java.util.Arrays.asList;
@ -41,6 +44,8 @@ import static java.util.Arrays.asList;
* Constants for SSL packets.
*/
final class SslUtils {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(SslUtils.class);
// See https://tools.ietf.org/html/rfc8446#appendix-B.4
static final Set<String> TLSV13_CIPHERS = Collections.unmodifiableSet(new LinkedHashSet<>(
asList("TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256",
@ -101,8 +106,31 @@ final class SslUtils {
static final String[] DEFAULT_TLSV13_CIPHER_SUITES;
static final String[] TLSV13_CIPHER_SUITES = { "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384" };
private static final boolean TLSV1_3_SUPPORTED;
static {
if (PlatformDependent.javaVersion() >= 11) {
boolean tlsv13Supported = false;
Throwable cause = null;
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[0], null);
for (String supported: context.getSupportedSSLParameters().getProtocols()) {
if (PROTOCOL_TLS_V1_3.equals(supported)) {
tlsv13Supported = true;
break;
}
}
} catch (Throwable error) {
cause = error;
}
if (cause == null) {
logger.debug("JDK SSLEngine supports TLSv1.3: {}", tlsv13Supported);
} else {
logger.debug("Unable to detect if JDK SSLEngine supports TLSv1.3, assuming no", cause);
}
TLSV1_3_SUPPORTED = tlsv13Supported;
if (TLSV1_3_SUPPORTED) {
DEFAULT_TLSV13_CIPHER_SUITES = TLSV13_CIPHER_SUITES;
} else {
DEFAULT_TLSV13_CIPHER_SUITES = EmptyArrays.EMPTY_STRINGS;
@ -128,6 +156,13 @@ final class SslUtils {
DEFAULT_CIPHER_SUITES = defaultCiphers.toArray(new String[0]);
}
/**
* Returns {@code true} if the JDK itself supports TLSv1.3, {@code false} otherwise.
*/
static boolean isTLSv13SupportedByJDK() {
return TLSV1_3_SUPPORTED;
}
/**
* Add elements from {@code names} into {@code enabled} if they are in {@code supported}.
*/

View File

@ -17,7 +17,6 @@ package io.netty.handler.ssl;
import com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider;
import com.amazon.corretto.crypto.provider.SelfTestStatus;
import io.netty.util.internal.PlatformDependent;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -44,7 +43,7 @@ public class AmazonCorrettoSslEngineTest extends SSLEngineTest {
params.add(new Object[] { type, ProtocolCipherCombo.tlsv12(), false });
params.add(new Object[] { type, ProtocolCipherCombo.tlsv12(), true });
if (PlatformDependent.javaVersion() >= 11) {
if (SslProvider.isTlsv13Supported(SslProvider.JDK)) {
params.add(new Object[] { type, ProtocolCipherCombo.tlsv13(), true });
params.add(new Object[] { type, ProtocolCipherCombo.tlsv13(), false });
}

View File

@ -15,7 +15,6 @@
*/
package io.netty.handler.ssl;
import io.netty.util.internal.PlatformDependent;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -43,7 +42,7 @@ public class JdkOpenSslEngineInteroptTest extends SSLEngineTest {
params.add(new Object[] { type, ProtocolCipherCombo.tlsv12(), true, false });
params.add(new Object[] { type, ProtocolCipherCombo.tlsv12(), true, true });
if (PlatformDependent.javaVersion() >= 11 && OpenSsl.isTlsv13Supported()) {
if (SslProvider.isTlsv13Supported(SslProvider.JDK) && SslProvider.isTlsv13Supported(SslProvider.OPENSSL)) {
params.add(new Object[] { type, ProtocolCipherCombo.tlsv13(), false, false });
params.add(new Object[] { type, ProtocolCipherCombo.tlsv13(), false, true });

View File

@ -26,7 +26,6 @@ import java.util.ArrayList;
import java.util.Collection;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.PlatformDependent;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -148,7 +147,7 @@ public class JdkSslEngineTest extends SSLEngineTest {
params.add(new Object[]{ providerType, bufferType, ProtocolCipherCombo.tlsv12(), true });
params.add(new Object[]{ providerType, bufferType, ProtocolCipherCombo.tlsv12(), false });
if (PlatformDependent.javaVersion() >= 11) {
if (SslProvider.isTlsv13Supported(SslProvider.JDK)) {
params.add(new Object[] { providerType, bufferType, ProtocolCipherCombo.tlsv13(), true });
params.add(new Object[] { providerType, bufferType, ProtocolCipherCombo.tlsv13(), false });
}

View File

@ -84,7 +84,7 @@ public class OpenSslEngineTest extends SSLEngineTest {
params.add(new Object[] { type, ProtocolCipherCombo.tlsv12(), true, false });
params.add(new Object[] { type, ProtocolCipherCombo.tlsv12(), true, true });
if (OpenSsl.isTlsv13Supported()) {
if (SslProvider.isTlsv13Supported(SslProvider.OPENSSL)) {
params.add(new Object[] { type, ProtocolCipherCombo.tlsv13(), false, false });
params.add(new Object[] { type, ProtocolCipherCombo.tlsv13(), false, true });

View File

@ -45,7 +45,7 @@ public class OpenSslJdkSslEngineInteroptTest extends SSLEngineTest {
params.add(new Object[] { type, ProtocolCipherCombo.tlsv12(), true, false});
params.add(new Object[] { type, ProtocolCipherCombo.tlsv12(), true, true });
if (PlatformDependent.javaVersion() >= 11 && OpenSsl.isTlsv13Supported()) {
if (SslProvider.isTlsv13Supported(SslProvider.JDK) && SslProvider.isTlsv13Supported(SslProvider.OPENSSL)) {
params.add(new Object[] { type, ProtocolCipherCombo.tlsv13(), false, false });
params.add(new Object[] { type, ProtocolCipherCombo.tlsv13(), false, true });

View File

@ -2345,15 +2345,13 @@ public abstract class SSLEngineTest {
encryptedClientToServer.flip();
assertEquals(SSLEngineResult.Status.CLOSED, result.getStatus());
SSLEngineResult.HandshakeStatus hs = result.getHandshakeStatus();
// Need an UNWRAP to read the response of the close_notify
if ((PlatformDependent.javaVersion() >= 12 && sslClientProvider() == SslProvider.JDK)
|| Conscrypt.isEngineSupported(client)) {
// This is a workaround for a possible JDK12+ bug.
//
// See http://mail.openjdk.java.net/pipermail/security-dev/2019-February/019406.html.
assertEquals(SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus());
if (sslClientProvider() == SslProvider.JDK || Conscrypt.isEngineSupported(client)) {
assertTrue(hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING
|| hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP);
} else {
assertEquals(SSLEngineResult.HandshakeStatus.NEED_UNWRAP, result.getHandshakeStatus());
assertEquals(SSLEngineResult.HandshakeStatus.NEED_UNWRAP, hs);
}
int produced = result.bytesProduced();

View File

@ -1090,38 +1090,37 @@ public class SslHandlerTest {
@Test(timeout = 5000L)
public void testSessionTicketsWithTLSv12() throws Throwable {
testSessionTickets(SslUtils.PROTOCOL_TLS_V1_2, true);
testSessionTickets(SslProvider.OPENSSL, SslUtils.PROTOCOL_TLS_V1_2, true);
}
@Test(timeout = 5000L)
public void testSessionTicketsWithTLSv13() throws Throwable {
assumeTrue(OpenSsl.isTlsv13Supported());
testSessionTickets(SslUtils.PROTOCOL_TLS_V1_3, true);
assumeTrue(SslProvider.isTlsv13Supported(SslProvider.OPENSSL));
testSessionTickets(SslProvider.OPENSSL, SslUtils.PROTOCOL_TLS_V1_3, true);
}
@Test(timeout = 5000L)
public void testSessionTicketsWithTLSv12AndNoKey() throws Throwable {
testSessionTickets(SslUtils.PROTOCOL_TLS_V1_2, false);
testSessionTickets(SslProvider.OPENSSL, SslUtils.PROTOCOL_TLS_V1_2, false);
}
@Test(timeout = 5000L)
public void testSessionTicketsWithTLSv13AndNoKey() throws Throwable {
assumeTrue(OpenSsl.isTlsv13Supported());
assumeTrue(OpenSsl.isBoringSSL());
testSessionTickets(SslUtils.PROTOCOL_TLS_V1_3, false);
assumeTrue(SslProvider.isTlsv13Supported(SslProvider.OPENSSL));
testSessionTickets(SslProvider.OPENSSL, SslUtils.PROTOCOL_TLS_V1_3, false);
}
private static void testSessionTickets(String protocol, boolean withKey) throws Throwable {
private static void testSessionTickets(SslProvider provider, String protocol, boolean withKey) throws Throwable {
assumeTrue(OpenSsl.isAvailable());
final SslContext sslClientCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(SslProvider.OPENSSL)
.sslProvider(provider)
.protocols(protocol)
.build();
final SelfSignedCertificate cert = new SelfSignedCertificate();
final SslContext sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert())
.sslProvider(SslProvider.OPENSSL)
.sslProvider(provider)
.protocols(protocol)
.build();