Skip to content

Commit

Permalink
Add support for TLSv1.3
Browse files Browse the repository at this point in the history
Motivation:

TLSv1.3 support is included in java11 and is also supported by OpenSSL 1.1.1, so we should support when possible.

Modifications:
- Add support for TLSv1.3 using either the JDK implementation or the native implementation provided by netty-tcnative when compiled against openssl 1.1.1
- Adjust unit tests for semantics provided by TLSv1.3
- Correctly handle custom Provider implementations that not support TLSv1.3

Result:

Be able to use TLSv1.3 with netty.
  • Loading branch information
normanmaurer committed Oct 15, 2018
1 parent 4a2d035 commit fdacb32
Show file tree
Hide file tree
Showing 27 changed files with 862 additions and 327 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,33 +122,6 @@ static boolean isO2JCached(String key, String protocol, String value) {
}
}

/**
* Converts the specified Java cipher suites to the colon-separated OpenSSL cipher suite specification.
*/
static String toOpenSsl(Iterable<String> javaCipherSuites) {
final StringBuilder buf = new StringBuilder();
for (String c: javaCipherSuites) {
if (c == null) {
break;
}

String converted = toOpenSsl(c);
if (converted != null) {
c = converted;
}

buf.append(c);
buf.append(':');
}

if (buf.length() > 0) {
buf.setLength(buf.length() - 1);
return buf.toString();
} else {
return "";
}
}

/**
* Converts the specified Java cipher suite to its corresponding OpenSSL cipher suite name.
*
Expand Down Expand Up @@ -423,5 +396,47 @@ private static String toJavaHmacAlgo(String hmacAlgo) {
return hmacAlgo;
}

/**
* Convert the given ciphers if needed to OpenSSL format and append them to the correct {@link StringBuilder}
* depending on if its a TLSv1.3 cipher or not. If this methods returns without throwing an exception its
* guaranteed that at least one of the {@link StringBuilder}s contain some ciphers that can be used to configure
* OpenSSL.
*/
static void convertToCipherStrings(
Iterable<String> cipherSuites, StringBuilder cipherBuilder, StringBuilder cipherTLSv13Builder) {
for (String c: cipherSuites) {
if (c == null) {
break;
}

String converted = toOpenSsl(c);
if (converted == null) {
converted = c;
}

if (!OpenSsl.isCipherSuiteAvailable(converted)) {
throw new IllegalArgumentException("unsupported cipher suite: " + c + '(' + converted + ')');
}

if (SslUtils.isTLSv13Cipher(converted)) {
cipherTLSv13Builder.append(converted);
cipherTLSv13Builder.append(':');
} else {
cipherBuilder.append(converted);
cipherBuilder.append(':');
}
}

if (cipherBuilder.length() == 0 && cipherTLSv13Builder.length() == 0) {
throw new IllegalArgumentException("empty cipher suites");
}
if (cipherBuilder.length() > 0) {
cipherBuilder.setLength(cipherBuilder.length() - 1);
}
if (cipherTLSv13Builder.length() > 0) {
cipherTLSv13Builder.setLength(cipherTLSv13Builder.length() - 1);
}
}

private CipherSuiteConverter() { }
}
110 changes: 92 additions & 18 deletions handler/src/main/java/io/netty/handler/ssl/JdkSslContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.netty.handler.ssl;

import io.netty.buffer.ByteBufAllocator;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

Expand All @@ -26,6 +27,7 @@
import java.security.KeyException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
Expand All @@ -34,6 +36,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

Expand All @@ -58,43 +61,68 @@ public class JdkSslContext extends SslContext {
static final String PROTOCOL = "TLS";
private static final String[] DEFAULT_PROTOCOLS;
private static final List<String> DEFAULT_CIPHERS;
private static final List<String> DEFAULT_CIPHERS_NON_TLSV13;
private static final Set<String> SUPPORTED_CIPHERS;
private static final Set<String> SUPPORTED_CIPHERS_NON_TLSV13;
private static final Provider DEFAULT_PROVIDER;

static {
SSLContext context;
int i;
try {
context = SSLContext.getInstance(PROTOCOL);
context.init(null, null, null);
} catch (Exception e) {
throw new Error("failed to initialize the default SSL context", e);
}

DEFAULT_PROVIDER = context.getProvider();

SSLEngine engine = context.createSSLEngine();
DEFAULT_PROTOCOLS = defaultProtocols(engine);

SUPPORTED_CIPHERS = Collections.unmodifiableSet(supportedCiphers(engine));
DEFAULT_CIPHERS = Collections.unmodifiableList(defaultCiphers(engine, SUPPORTED_CIPHERS));

List<String> ciphersNonTLSv13 = new ArrayList<String>(DEFAULT_CIPHERS);
ciphersNonTLSv13.removeAll(Arrays.asList(SslUtils.DEFAULT_TLSV13_CIPHER_SUITES));
DEFAULT_CIPHERS_NON_TLSV13 = Collections.unmodifiableList(ciphersNonTLSv13);

Set<String> suppertedCiphersNonTLSv13 = new LinkedHashSet<String>(SUPPORTED_CIPHERS);
suppertedCiphersNonTLSv13.removeAll(Arrays.asList(SslUtils.DEFAULT_TLSV13_CIPHER_SUITES));
SUPPORTED_CIPHERS_NON_TLSV13 = Collections.unmodifiableSet(suppertedCiphersNonTLSv13);

if (logger.isDebugEnabled()) {
logger.debug("Default protocols (JDK): {} ", Arrays.asList(DEFAULT_PROTOCOLS));
logger.debug("Default cipher suites (JDK): {}", DEFAULT_CIPHERS);
}
}

private static String[] defaultProtocols(SSLEngine engine) {
// Choose the sensible default list of protocols.
final String[] supportedProtocols = engine.getSupportedProtocols();
Set<String> supportedProtocolsSet = new HashSet<String>(supportedProtocols.length);
for (i = 0; i < supportedProtocols.length; ++i) {
for (int i = 0; i < supportedProtocols.length; ++i) {
supportedProtocolsSet.add(supportedProtocols[i]);
}
List<String> protocols = new ArrayList<String>();
addIfSupported(
supportedProtocolsSet, protocols,
"TLSv1.2", "TLSv1.1", "TLSv1");
// Do not include TLSv1.3 for now by default.
SslUtils.PROTOCOL_TLS_V1_2, SslUtils.PROTOCOL_TLS_V1_1, SslUtils.PROTOCOL_TLS_V1);

if (!protocols.isEmpty()) {
DEFAULT_PROTOCOLS = protocols.toArray(new String[0]);
} else {
DEFAULT_PROTOCOLS = engine.getEnabledProtocols();
return protocols.toArray(new String[0]);
}
return engine.getEnabledProtocols();
}

private static Set<String> supportedCiphers(SSLEngine engine) {
// Choose the sensible default list of cipher suites.
final String[] supportedCiphers = engine.getSupportedCipherSuites();
SUPPORTED_CIPHERS = new HashSet<String>(supportedCiphers.length);
for (i = 0; i < supportedCiphers.length; ++i) {
Set<String> supportedCiphersSet = new LinkedHashSet<String>(supportedCiphers.length);
for (int i = 0; i < supportedCiphers.length; ++i) {
String supportedCipher = supportedCiphers[i];
SUPPORTED_CIPHERS.add(supportedCipher);
supportedCiphersSet.add(supportedCipher);
// IBM's J9 JVM utilizes a custom naming scheme for ciphers and only returns ciphers with the "SSL_"
// prefix instead of the "TLS_" prefix (as defined in the JSSE cipher suite names [1]). According to IBM's
// documentation [2] the "SSL_" prefix is "interchangeable" with the "TLS_" prefix.
Expand All @@ -108,21 +136,29 @@ public class JdkSslContext extends SslContext {
final String tlsPrefixedCipherName = "TLS_" + supportedCipher.substring("SSL_".length());
try {
engine.setEnabledCipherSuites(new String[]{tlsPrefixedCipherName});
SUPPORTED_CIPHERS.add(tlsPrefixedCipherName);
supportedCiphersSet.add(tlsPrefixedCipherName);
} catch (IllegalArgumentException ignored) {
// The cipher is not supported ... move on to the next cipher.
}
}
}
return supportedCiphersSet;
}

private static List<String> defaultCiphers(SSLEngine engine, Set<String> supportedCiphers) {
List<String> ciphers = new ArrayList<String>();
addIfSupported(SUPPORTED_CIPHERS, ciphers, DEFAULT_CIPHER_SUITES);
addIfSupported(supportedCiphers, ciphers, DEFAULT_CIPHER_SUITES);
useFallbackCiphersIfDefaultIsEmpty(ciphers, engine.getEnabledCipherSuites());
DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
return ciphers;
}

if (logger.isDebugEnabled()) {
logger.debug("Default protocols (JDK): {} ", Arrays.asList(DEFAULT_PROTOCOLS));
logger.debug("Default cipher suites (JDK): {}", DEFAULT_CIPHERS);
private static boolean isTlsV13Supported(String[] protocols) {
for (String protocol: protocols) {
if (SslUtils.PROTOCOL_TLS_V1_3.equals(protocol)) {
return true;
}
}
return false;
}

private final String[] protocols;
Expand Down Expand Up @@ -169,11 +205,49 @@ public JdkSslContext(SSLContext sslContext, boolean isClient, Iterable<String> c
super(startTls);
this.apn = checkNotNull(apn, "apn");
this.clientAuth = checkNotNull(clientAuth, "clientAuth");
this.sslContext = checkNotNull(sslContext, "sslContext");

final List<String> defaultCiphers;
final Set<String> supportedCiphers;
if (DEFAULT_PROVIDER.equals(sslContext.getProvider())) {
this.protocols = protocols == null? DEFAULT_PROTOCOLS : protocols;
if (isTlsV13Supported(this.protocols)) {
supportedCiphers = SUPPORTED_CIPHERS;
defaultCiphers = DEFAULT_CIPHERS;
} else {
// TLSv1.3 is not supported, ensure we do not include any TLSv1.3 ciphersuite.
supportedCiphers = SUPPORTED_CIPHERS_NON_TLSV13;
defaultCiphers = DEFAULT_CIPHERS_NON_TLSV13;
}
} else {
// This is a different Provider then the one used by the JDK by default so we can not just assume
// the same protocols and ciphers are supported. For example even if Java11+ is used Conscrypt will
// not support TLSv1.3 and the TLSv1.3 ciphersuites.
SSLEngine engine = sslContext.createSSLEngine();
try {
if (protocols == null) {
this.protocols = defaultProtocols(engine);
} else {
this.protocols = protocols;
}
supportedCiphers = supportedCiphers(engine);
defaultCiphers = defaultCiphers(engine, supportedCiphers);
if (!isTlsV13Supported(this.protocols)) {
// TLSv1.3 is not supported, ensure we do not include any TLSv1.3 ciphersuite.
for (String cipher: SslUtils.DEFAULT_TLSV13_CIPHER_SUITES) {
supportedCiphers.remove(cipher);
defaultCiphers.remove(cipher);
}
}
} finally {
ReferenceCountUtil.release(engine);
}
}

cipherSuites = checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites(
ciphers, DEFAULT_CIPHERS, SUPPORTED_CIPHERS);
this.protocols = protocols == null ? DEFAULT_PROTOCOLS : protocols;
ciphers, defaultCiphers, supportedCiphers);

unmodifiableCipherSuites = Collections.unmodifiableList(Arrays.asList(cipherSuites));
this.sslContext = checkNotNull(sslContext, "sslContext");
this.isClient = isClient;
}

Expand Down
51 changes: 47 additions & 4 deletions handler/src/main/java/io/netty/handler/ssl/OpenSsl.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import javax.net.ssl.SSLException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
Expand All @@ -48,6 +49,7 @@
import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1;
import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_1;
import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_2;
import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_3;

/**
* Tells if <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> and its OpenSSL support
Expand All @@ -66,6 +68,12 @@ public final class OpenSsl {
private static final boolean SUPPORTS_HOSTNAME_VALIDATION;
private static final boolean USE_KEYMANAGER_FACTORY;
private static final boolean SUPPORTS_OCSP;
private static final String TLSV13_CIPHERS = "TLS_AES_256_GCM_SHA384" + ':' +
"TLS_CHACHA20_POLY1305_SHA256" + ':' +
"TLS_AES_128_GCM_SHA256" + ':' +
"TLS_AES_128_CCM_8_SHA256" + ':' +
"TLS_AES_128_CCM_SHA256";
private static final boolean TLSV13_SUPPORTED;

static final Set<String> SUPPORTED_PROTOCOLS_SET;

Expand Down Expand Up @@ -139,17 +147,30 @@ public final class OpenSsl {
boolean supportsKeyManagerFactory = false;
boolean useKeyManagerFactory = false;
boolean supportsHostNameValidation = false;
boolean tlsv13Supported = false;

try {
final long sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
long certBio = 0;
SelfSignedCertificate cert = null;
try {
SSLContext.setCipherSuite(sslCtx, "ALL");
if (PlatformDependent.javaVersion() >= 11) {
try {
SSLContext.setCipherSuite(sslCtx, TLSV13_CIPHERS, true);
tlsv13Supported = true;
} catch (Exception ignore) {
tlsv13Supported = false;
}
}
SSLContext.setCipherSuite(sslCtx, "ALL", false);

final long ssl = SSL.newSSL(sslCtx, true);
try {
for (String c: SSL.getCiphers(ssl)) {
// Filter out bad input.
if (c == null || c.isEmpty() || availableOpenSslCipherSuites.contains(c)) {
if (c == null || c.isEmpty() || availableOpenSslCipherSuites.contains(c) ||
// Filter out TLSv1.3 ciphers if not supported.
!tlsv13Supported && SslUtils.isTLSv13Cipher(c)) {
continue;
}
availableOpenSslCipherSuites.add(c);
Expand Down Expand Up @@ -200,8 +221,13 @@ public Boolean run() {
AVAILABLE_OPENSSL_CIPHER_SUITES.size() * 2);
for (String cipher: AVAILABLE_OPENSSL_CIPHER_SUITES) {
// Included converted but also openssl cipher name
availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "TLS"));
availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "SSL"));
if (!SslUtils.isTLSv13Cipher(cipher)) {
availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "TLS"));
availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "SSL"));
} else {
// TLSv1.3 ciphers have the correct format.
availableJavaCipherSuites.add(cipher);
}
}

addIfSupported(availableJavaCipherSuites, defaultCiphers, DEFAULT_CIPHER_SUITES);
Expand Down Expand Up @@ -239,6 +265,18 @@ public Boolean run() {
protocols.add(PROTOCOL_TLS_V1_2);
}

// This is only supported by java11 and later.
if (tlsv13Supported && doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_3, SSL.SSL_OP_NO_TLSv1_3)
&& PlatformDependent.javaVersion() >= 11) {
// We can only support TLS1.3 when using Java 11 or higher as otherwise it will fail to create the
// internal instance of an sun.security.ssl.ProtocolVersion as can not parse the version string :/
// See http://mail.openjdk.java.net/pipermail/security-dev/2018-September/018242.html
protocols.add(PROTOCOL_TLS_V1_3);
TLSV13_SUPPORTED = true;
} else {
TLSV13_SUPPORTED = false;
}

SUPPORTED_PROTOCOLS_SET = Collections.unmodifiableSet(protocols);
SUPPORTS_OCSP = doesSupportOcsp();

Expand All @@ -256,6 +294,7 @@ public Boolean run() {
USE_KEYMANAGER_FACTORY = false;
SUPPORTED_PROTOCOLS_SET = Collections.emptySet();
SUPPORTS_OCSP = false;
TLSV13_SUPPORTED = false;
}
}

Expand Down Expand Up @@ -450,4 +489,8 @@ static void releaseIfNeeded(ReferenceCounted counted) {
ReferenceCountUtil.safeRelease(counted);
}
}

static boolean isTlsv13Supported() {
return TLSV13_SUPPORTED;
}
}

0 comments on commit fdacb32

Please sign in to comment.