HTTPCLIENT-1969: Filter out weak cipher suites

This commit is contained in:
Artem Smotrakov 2019-03-01 17:49:17 +01:00
parent acd8fcd95f
commit 9049eb0145
2 changed files with 107 additions and 0 deletions

View File

@ -36,7 +36,9 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
@ -163,6 +165,15 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor
public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
= StrictHostnameVerifier.INSTANCE;
private static final String WEAK_KEY_EXCHANGES
= "^(TLS|SSL)_(NULL|ECDH_anon|DH_anon|DH_anon_EXPORT|DHE_RSA_EXPORT|DHE_DSS_EXPORT|"
+ "DSS_EXPORT|DH_DSS_EXPORT|DH_RSA_EXPORT|RSA_EXPORT|KRB5_EXPORT)_(.*)";
private static final String WEAK_CIPHERS
= "^(TLS|SSL)_(.*)_WITH_(NULL|DES_CBC|DES40_CBC|DES_CBC_40|3DES_EDE_CBC|RC4_128|RC4_40|RC2_CBC_40)_(.*)";
private static final List<Pattern> WEAK_CIPHER_SUITE_PATTERNS = Collections.unmodifiableList(Arrays.asList(
Pattern.compile(WEAK_KEY_EXCHANGES, Pattern.CASE_INSENSITIVE),
Pattern.compile(WEAK_CIPHERS, Pattern.CASE_INSENSITIVE)));
private final Log log = LogFactory.getLog(getClass());
/**
@ -183,6 +194,15 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor
return new SSLConnectionSocketFactory(SSLContexts.createDefault(), getDefaultHostnameVerifier());
}
static boolean isWeakCipherSuite(final String cipherSuite) {
for (final Pattern pattern : WEAK_CIPHER_SUITE_PATTERNS) {
if (pattern.matcher(cipherSuite).matches()) {
return true;
}
}
return false;
}
private static String[] split(final String s) {
if (TextUtils.isBlank(s)) {
return null;
@ -392,6 +412,18 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor
}
if (supportedCipherSuites != null) {
sslsock.setEnabledCipherSuites(supportedCipherSuites);
} else {
// If cipher suites are not explicitly set, remove all insecure ones
final String[] allCipherSuites = sslsock.getEnabledCipherSuites();
final List<String> enabledCipherSuites = new ArrayList<String>(allCipherSuites.length);
for (final String cipherSuite : allCipherSuites) {
if (!isWeakCipherSuite(cipherSuite)) {
enabledCipherSuites.add(cipherSuite);
}
}
if (!enabledCipherSuites.isEmpty()) {
sslsock.setEnabledCipherSuites(enabledCipherSuites.toArray(new String[enabledCipherSuites.size()]));
}
}
if (this.log.isDebugEnabled()) {

View File

@ -376,4 +376,79 @@ public class TestSSLSocketFactory {
inputStream.close();
}
}
@Test
public void testStrongCipherSuites() {
final String[] strongCipherSuites = {
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_GCM_SHA384"
};
for (final String cipherSuite : strongCipherSuites) {
Assert.assertFalse(SSLConnectionSocketFactory.isWeakCipherSuite(cipherSuite));
}
}
@Test
public void testWeakCiphersDisabledByDefault() {
final String[] weakCiphersSuites = {
"SSL_RSA_WITH_RC4_128_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_DH_anon_WITH_AES_128_CBC_SHA",
"SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_RSA_WITH_NULL_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_DH_anon_WITH_AES_256_GCM_SHA384",
"TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_NULL_SHA256",
"SSL_RSA_EXPORT_WITH_RC4_40_MD5",
"SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
"TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
"SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"
};
for (final String cipherSuite : weakCiphersSuites) {
Assert.assertTrue(SSLConnectionSocketFactory.isWeakCipherSuite(cipherSuite));
try {
testWeakCipherDisabledByDefault(cipherSuite);
Assert.fail("IOException expected");
} catch (final Exception e) {
Assert.assertTrue(e instanceof IOException || e instanceof IllegalArgumentException);
}
}
}
private void testWeakCipherDisabledByDefault(final String cipherSuite) throws Exception {
// @formatter:off
this.server = ServerBootstrap.bootstrap()
.setServerInfo(LocalServerTestBase.ORIGIN)
.setSslContext(SSLTestContexts.createServerSSLContext())
.setSslSetupHandler(new SSLServerSetupHandler() {
@Override
public void initialize(final SSLServerSocket socket) {
socket.setEnabledCipherSuites(new String[] {cipherSuite});
}
})
.create();
// @formatter:on
this.server.start();
final HttpContext context = new BasicHttpContext();
final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
SSLTestContexts.createClientSSLContext());
final Socket socket = socketFactory.createSocket(context);
try {
final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort());
final HttpHost target = new HttpHost("localhost", this.server.getLocalPort(), "https");
socketFactory.connectSocket(0, socket, target, remoteAddress, null, context);
} finally {
socket.close();
}
}
}