OpenSslEngine protocol selection must be contiguous
Motivation: TLS doesn't support a way to advertise non-contiguous versions from the client's perspective, and the client just advertises the max supported version. The TLS protocol also doesn't support all different combinations of discrete protocols, and instead assumes contiguous ranges. OpenSSL has some unexpected behavior (e.g. handshake failures) if non-contiguous protocols are used even where there is a compatible set of protocols and ciphers. For these reasons this method will determine the minimum protocol and the maximum protocol and enabled a contiguous range from [min protocol, max protocol] in OpenSSL. Modifications: - ReferenceCountedOpenSslEngine#setEnabledProtocols should determine the min/max protocol versions and enable a contiguous range Result: OpenSslEngine is more consistent with the JDK's SslEngineImpl and no more unexpected handshake failures due to protocol selection quirks.
This commit is contained in:
parent
43ae9748d0
commit
6152990073
@ -93,6 +93,18 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
|||||||
new SSLException("renegotiation unsupported"), ReferenceCountedOpenSslEngine.class, "beginHandshake()");
|
new SSLException("renegotiation unsupported"), ReferenceCountedOpenSslEngine.class, "beginHandshake()");
|
||||||
private static final ResourceLeakDetector<ReferenceCountedOpenSslEngine> leakDetector =
|
private static final ResourceLeakDetector<ReferenceCountedOpenSslEngine> leakDetector =
|
||||||
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslEngine.class);
|
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslEngine.class);
|
||||||
|
private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2 = 0;
|
||||||
|
private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3 = 1;
|
||||||
|
private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1 = 2;
|
||||||
|
private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1 = 3;
|
||||||
|
private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2 = 4;
|
||||||
|
private static final int[] OPENSSL_OP_NO_PROTOCOLS = new int[] {
|
||||||
|
SSL.SSL_OP_NO_SSLv2,
|
||||||
|
SSL.SSL_OP_NO_SSLv3,
|
||||||
|
SSL.SSL_OP_NO_TLSv1,
|
||||||
|
SSL.SSL_OP_NO_TLSv1_1,
|
||||||
|
SSL.SSL_OP_NO_TLSv1_2
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* <a href="https://www.openssl.org/docs/man1.0.2/crypto/X509_check_host.html">The flags argument is usually 0</a>.
|
* <a href="https://www.openssl.org/docs/man1.0.2/crypto/X509_check_host.html">The flags argument is usually 0</a>.
|
||||||
*/
|
*/
|
||||||
@ -1363,54 +1375,77 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
|||||||
return (opts & disableMask) == 0 && OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(protocolString);
|
return (opts & disableMask) == 0 && OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(protocolString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* TLS doesn't support a way to advertise non-contiguous versions from the client's perspective, and the client
|
||||||
|
* just advertises the max supported version. The TLS protocol also doesn't support all different combinations of
|
||||||
|
* discrete protocols, and instead assumes contiguous ranges. OpenSSL has some unexpected behavior
|
||||||
|
* (e.g. handshake failures) if non-contiguous protocols are used even where there is a compatible set of protocols
|
||||||
|
* and ciphers. For these reasons this method will determine the minimum protocol and the maximum protocol and
|
||||||
|
* enabled a contiguous range from [min protocol, max protocol] in OpenSSL.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final void setEnabledProtocols(String[] protocols) {
|
public final void setEnabledProtocols(String[] protocols) {
|
||||||
if (protocols == null) {
|
if (protocols == null) {
|
||||||
// This is correct from the API docs
|
// This is correct from the API docs
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
boolean sslv2 = false;
|
int minProtocolIndex = OPENSSL_OP_NO_PROTOCOLS.length;
|
||||||
boolean sslv3 = false;
|
int maxProtocolIndex = 0;
|
||||||
boolean tlsv1 = false;
|
|
||||||
boolean tlsv1_1 = false;
|
|
||||||
boolean tlsv1_2 = false;
|
|
||||||
for (String p: protocols) {
|
for (String p: protocols) {
|
||||||
if (!OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(p)) {
|
if (!OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(p)) {
|
||||||
throw new IllegalArgumentException("Protocol " + p + " is not supported.");
|
throw new IllegalArgumentException("Protocol " + p + " is not supported.");
|
||||||
}
|
}
|
||||||
if (p.equals(OpenSsl.PROTOCOL_SSL_V2)) {
|
if (p.equals(OpenSsl.PROTOCOL_SSL_V2)) {
|
||||||
sslv2 = true;
|
if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2) {
|
||||||
|
minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2;
|
||||||
|
}
|
||||||
|
if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2) {
|
||||||
|
maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2;
|
||||||
|
}
|
||||||
} else if (p.equals(OpenSsl.PROTOCOL_SSL_V3)) {
|
} else if (p.equals(OpenSsl.PROTOCOL_SSL_V3)) {
|
||||||
sslv3 = true;
|
if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3) {
|
||||||
|
minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3;
|
||||||
|
}
|
||||||
|
if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3) {
|
||||||
|
maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3;
|
||||||
|
}
|
||||||
} else if (p.equals(OpenSsl.PROTOCOL_TLS_V1)) {
|
} else if (p.equals(OpenSsl.PROTOCOL_TLS_V1)) {
|
||||||
tlsv1 = true;
|
if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1) {
|
||||||
|
minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1;
|
||||||
|
}
|
||||||
|
if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1) {
|
||||||
|
maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1;
|
||||||
|
}
|
||||||
} else if (p.equals(OpenSsl.PROTOCOL_TLS_V1_1)) {
|
} else if (p.equals(OpenSsl.PROTOCOL_TLS_V1_1)) {
|
||||||
tlsv1_1 = true;
|
if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1) {
|
||||||
|
minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1;
|
||||||
|
}
|
||||||
|
if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1) {
|
||||||
|
maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1;
|
||||||
|
}
|
||||||
} else if (p.equals(OpenSsl.PROTOCOL_TLS_V1_2)) {
|
} else if (p.equals(OpenSsl.PROTOCOL_TLS_V1_2)) {
|
||||||
tlsv1_2 = true;
|
if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2) {
|
||||||
|
minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2;
|
||||||
|
}
|
||||||
|
if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2) {
|
||||||
|
maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (!isDestroyed()) {
|
if (!isDestroyed()) {
|
||||||
// Clear out options which disable protocols
|
// Clear out options which disable protocols
|
||||||
SSL.clearOptions(ssl, SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 |
|
SSL.clearOptions(ssl, SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 |
|
||||||
SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2);
|
SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2);
|
||||||
|
|
||||||
int opts = 0;
|
int opts = 0;
|
||||||
if (!sslv2) {
|
for (int i = 0; i < minProtocolIndex; ++i) {
|
||||||
opts |= SSL.SSL_OP_NO_SSLv2;
|
opts |= OPENSSL_OP_NO_PROTOCOLS[i];
|
||||||
}
|
}
|
||||||
if (!sslv3) {
|
assert maxProtocolIndex != MAX_VALUE;
|
||||||
opts |= SSL.SSL_OP_NO_SSLv3;
|
for (int i = maxProtocolIndex + 1; i < OPENSSL_OP_NO_PROTOCOLS.length; ++i) {
|
||||||
}
|
opts |= OPENSSL_OP_NO_PROTOCOLS[i];
|
||||||
if (!tlsv1) {
|
|
||||||
opts |= SSL.SSL_OP_NO_TLSv1;
|
|
||||||
}
|
|
||||||
if (!tlsv1_1) {
|
|
||||||
opts |= SSL.SSL_OP_NO_TLSv1_1;
|
|
||||||
}
|
|
||||||
if (!tlsv1_2) {
|
|
||||||
opts |= SSL.SSL_OP_NO_TLSv1_2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable protocols we do not want
|
// Disable protocols we do not want
|
||||||
|
@ -57,13 +57,15 @@ import java.net.InetSocketAddress;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ClosedChannelException;
|
import java.nio.channels.ClosedChannelException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
import java.security.Provider;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.Provider;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLEngineResult;
|
import javax.net.ssl.SSLEngineResult;
|
||||||
@ -1680,6 +1682,36 @@ public abstract class SSLEngineTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHandshakeCompletesWithNonContiguousProtocolsTLSv1_2CipherOnly() throws Exception {
|
||||||
|
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||||
|
// Select a mandatory cipher from the TLSv1.2 RFC https://www.ietf.org/rfc/rfc5246.txt so handshakes won't fail
|
||||||
|
// due to no shared/supported cipher.
|
||||||
|
final String sharedCipher = "TLS_RSA_WITH_AES_128_CBC_SHA";
|
||||||
|
clientSslCtx = SslContextBuilder.forClient()
|
||||||
|
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||||
|
.ciphers(Arrays.asList(sharedCipher))
|
||||||
|
.protocols(OpenSsl.PROTOCOL_TLS_V1_2, OpenSsl.PROTOCOL_TLS_V1)
|
||||||
|
.sslProvider(sslClientProvider())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||||
|
.ciphers(Arrays.asList(sharedCipher))
|
||||||
|
.protocols(OpenSsl.PROTOCOL_TLS_V1_2, OpenSsl.PROTOCOL_TLS_V1)
|
||||||
|
.sslProvider(sslServerProvider())
|
||||||
|
.build();
|
||||||
|
SSLEngine clientEngine = null;
|
||||||
|
SSLEngine serverEngine = null;
|
||||||
|
try {
|
||||||
|
clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
|
||||||
|
serverEngine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
|
||||||
|
handshake(clientEngine, serverEngine);
|
||||||
|
} finally {
|
||||||
|
cleanupClientSslEngine(clientEngine);
|
||||||
|
cleanupServerSslEngine(serverEngine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPacketBufferSizeLimit() throws Exception {
|
public void testPacketBufferSizeLimit() throws Exception {
|
||||||
SelfSignedCertificate cert = new SelfSignedCertificate();
|
SelfSignedCertificate cert = new SelfSignedCertificate();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user