Correctly include TLS1.3 ciphers in the enabled ciphersuites when using BoringSSL (#10388)

Motivation:

BoringSSL behaves differently then OpenSSL and not include any TLS1.3 ciphers in the returned array when calling SSL_get_ciphers(...). This is due the fact that it also not allow to explicit configure which are supported and which not for TLS1.3. To mimic the behaviour expected by the SSLEngine API we should workaround this.

Modifications:

- Add a unit test that verifies enabled protocols / ciphers
- Add special handling for BoringSSL and tls1.3

Result:

Make behaviour consistent
This commit is contained in:
Norman Maurer 2020-07-02 21:34:37 +02:00 committed by GitHub
parent 523dc5c269
commit cbe238a95b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 12 deletions

View File

@ -26,6 +26,7 @@ import io.netty.internal.tcnative.SSLContext;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.NativeLibraryLoader;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SystemPropertyUtil;
@ -62,6 +63,7 @@ public final class OpenSsl {
private static final boolean TLSV13_SUPPORTED;
private static final boolean IS_BORINGSSL;
static final Set<String> SUPPORTED_PROTOCOLS_SET;
static final String[] EXTRA_SUPPORTED_TLS_1_3_CIPHERS;
// self-signed certificate for netty.io and the matching private-key
private static final String CERT = "-----BEGIN CERTIFICATE-----\n" +
@ -177,6 +179,13 @@ public final class OpenSsl {
boolean tlsv13Supported = false;
IS_BORINGSSL = "BoringSSL".equals(versionString());
if (IS_BORINGSSL) {
EXTRA_SUPPORTED_TLS_1_3_CIPHERS = new String [] { "TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384" ,
"TLS_CHACHA20_POLY1305_SHA256" };
} else {
EXTRA_SUPPORTED_TLS_1_3_CIPHERS = EmptyArrays.EMPTY_STRINGS;
}
try {
final long sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
@ -222,10 +231,8 @@ public final class OpenSsl {
if (IS_BORINGSSL) {
// Currently BoringSSL does not include these when calling SSL.getCiphers() even when these
// are supported.
Collections.addAll(availableOpenSslCipherSuites, EXTRA_SUPPORTED_TLS_1_3_CIPHERS);
Collections.addAll(availableOpenSslCipherSuites,
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384" ,
"TLS_CHACHA20_POLY1305_SHA256",
"AEAD-AES128-GCM-SHA256",
"AEAD-AES256-GCM-SHA384",
"AEAD-CHACHA20-POLY1305-SHA256");
@ -372,6 +379,7 @@ public final class OpenSsl {
SUPPORTS_OCSP = false;
TLSV13_SUPPORTED = false;
IS_BORINGSSL = false;
EXTRA_SUPPORTED_TLS_1_3_CIPHERS = EmptyArrays.EMPTY_STRINGS;
}
}

View File

@ -72,8 +72,6 @@ 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;
import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH;
import static io.netty.util.internal.EmptyArrays.EMPTY_CERTIFICATES;
import static io.netty.util.internal.EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static java.lang.Integer.MAX_VALUE;
import static java.lang.Math.min;
@ -1490,10 +1488,17 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
@Override
public final String[] getEnabledCipherSuites() {
final String[] extraCiphers;
final String[] enabled;
synchronized (this) {
if (!isDestroyed()) {
enabled = SSL.getCiphers(ssl);
int opts = SSL.getOptions(ssl);
if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_3, PROTOCOL_TLS_V1_3)) {
extraCiphers = OpenSsl.EXTRA_SUPPORTED_TLS_1_3_CIPHERS;
} else {
extraCiphers = EmptyArrays.EMPTY_STRINGS;
}
} else {
return EmptyArrays.EMPTY_STRINGS;
}
@ -1501,7 +1506,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
if (enabled == null) {
return EmptyArrays.EMPTY_STRINGS;
} else {
List<String> enabledList = new ArrayList<String>();
Set<String> enabledSet = new LinkedHashSet<String>(enabled.length + extraCiphers.length);
synchronized (this) {
for (int i = 0; i < enabled.length; i++) {
String mapped = toJavaCipherSuite(enabled[i]);
@ -1509,10 +1514,11 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
if (!OpenSsl.isTlsv13Supported() && SslUtils.isTLSv13Cipher(cipher)) {
continue;
}
enabledList.add(cipher);
enabledSet.add(cipher);
}
Collections.addAll(enabledSet, extraCiphers);
}
return enabledList.toArray(new String[0]);
return enabledSet.toArray(new String[0]);
}
}
@ -2254,8 +2260,8 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
byte[][] chain = SSL.getPeerCertChain(ssl);
if (clientMode) {
if (isEmpty(chain)) {
peerCerts = EMPTY_CERTIFICATES;
x509PeerCerts = EMPTY_JAVAX_X509_CERTIFICATES;
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
} else {
peerCerts = new Certificate[chain.length];
x509PeerCerts = new X509Certificate[chain.length];
@ -2269,8 +2275,8 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
// See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html
byte[] clientCert = SSL.getPeerCertificate(ssl);
if (isEmpty(clientCert)) {
peerCerts = EMPTY_CERTIFICATES;
x509PeerCerts = EMPTY_JAVAX_X509_CERTIFICATES;
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
} else {
if (isEmpty(chain)) {
peerCerts = new Certificate[] {new OpenSslX509Certificate(clientCert)};

View File

@ -3427,6 +3427,54 @@ public abstract class SSLEngineTest {
}
}
@Test
public void testEnabledProtocolsAndCiphers() throws Exception {
clientSslCtx = wrapContext(SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(sslClientProvider())
.sslContextProvider(clientSslContextProvider())
.protocols(protocols())
.ciphers(ciphers())
.build());
SelfSignedCertificate ssc = new SelfSignedCertificate();
serverSslCtx = wrapContext(SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(sslServerProvider())
.sslContextProvider(serverSslContextProvider())
.protocols(protocols())
.ciphers(ciphers())
.build());
SSLEngine clientEngine = null;
SSLEngine serverEngine = null;
try {
clientEngine = wrapEngine(clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT));
serverEngine = wrapEngine(serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT));
handshake(clientEngine, serverEngine);
assertEnabledProtocolsAndCipherSuites(clientEngine);
assertEnabledProtocolsAndCipherSuites(serverEngine);
} finally {
cleanupClientSslEngine(clientEngine);
cleanupServerSslEngine(serverEngine);
ssc.delete();
}
}
private static void assertEnabledProtocolsAndCipherSuites(SSLEngine engine) {
String protocol = engine.getSession().getProtocol();
String cipherSuite = engine.getSession().getCipherSuite();
assertArrayContains(protocol, engine.getEnabledProtocols());
assertArrayContains(cipherSuite, engine.getEnabledCipherSuites());
}
private static void assertArrayContains(String expected, String[] array) {
for (String value: array) {
if (expected.equals(value)) {
return;
}
}
fail("Array did not contain '" + expected + "':" + Arrays.toString(array));
}
@Test
public void testMasterKeyLogging() throws Exception {