SSL/TLS: Only use TLS protocols by default

Only enables TLSv1, TLSv1.1, and TLSv1.2 protocols for transport, http, and ldaps. The supported
protocols are configurable in case one of these protocols is found to be insecure in the future.

Closes elastic/elasticsearch#594

Original commit: elastic/x-pack-elasticsearch@d4556091ef
This commit is contained in:
jaymode 2015-01-20 13:40:58 -05:00
parent 1f8189fa12
commit ef979e4939
5 changed files with 92 additions and 24 deletions

View File

@ -78,5 +78,7 @@ public abstract class AbstractLdapSslSocketFactory extends SocketFactory {
* @param sslSocket
*/
protected void configureSSLSocket(SSLSocket sslSocket) {
sslSocket.setEnabledProtocols(sslService.supportedProtocols());
sslSocket.setEnabledCipherSuites(sslService.ciphers());
}
}

View File

@ -63,6 +63,7 @@ public class HostnameVerifyingLdapSslSocketFactory extends AbstractLdapSslSocket
*/
@Override
protected void configureSSLSocket(SSLSocket sslSocket) {
super.configureSSLSocket(sslSocket);
sslSocket.setSSLParameters(sslParameters);
}
}

View File

@ -26,6 +26,7 @@ import java.util.Map;
public class SSLService extends AbstractComponent {
static final String[] DEFAULT_CIPHERS = new String[]{ "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" };
static final String[] DEFAULT_SUPPORTED_PROTOCOLS = new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"};
private Map<String, SSLContext> sslContexts = ConcurrentCollections.newConcurrentMap();
@ -41,6 +42,14 @@ public class SSLService extends AbstractComponent {
return getSslContext(ImmutableSettings.EMPTY).getSocketFactory();
}
public String[] supportedProtocols() {
return componentSettings.getAsArray("supported_protocols", DEFAULT_SUPPORTED_PROTOCOLS);
}
public String[] ciphers() {
return componentSettings.getAsArray("ciphers", DEFAULT_CIPHERS);
}
public SSLEngine createSSLEngine() {
return createSSLEngine(ImmutableSettings.EMPTY);
}
@ -50,8 +59,9 @@ public class SSLService extends AbstractComponent {
}
public SSLEngine createSSLEngine(Settings settings, String host, int port) {
String[] ciphers = settings.getAsArray("ciphers", componentSettings.getAsArray("ciphers", DEFAULT_CIPHERS));
return createSSLEngine(getSslContext(settings), ciphers, host, port);
String[] ciphers = settings.getAsArray("ciphers", ciphers());
String[] supportedProtocols = settings.getAsArray("supported_protocols", supportedProtocols());
return createSSLEngine(getSslContext(settings), ciphers, supportedProtocols, host, port);
}
public SSLContext getSslContext() {
@ -103,13 +113,19 @@ public class SSLService extends AbstractComponent {
return sslContext;
}
private SSLEngine createSSLEngine(SSLContext sslContext, String[] ciphers, String host, int port) {
private SSLEngine createSSLEngine(SSLContext sslContext, String[] ciphers, String[] supportedProtocols, String host, int port) {
SSLEngine sslEngine = sslContext.createSSLEngine(host, port);
try {
sslEngine.setEnabledCipherSuites(ciphers);
} catch (Throwable t) {
throw new ElasticsearchSSLException("failed loading cipher suites [" + Arrays.asList(ciphers) + "]", t);
}
try {
sslEngine.setEnabledProtocols(supportedProtocols);
} catch (IllegalArgumentException e) {
throw new ElasticsearchSSLException("failed setting supported protocols [" + Arrays.asList(supportedProtocols) + "]", e);
}
return sslEngine;
}

View File

@ -14,6 +14,7 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.hamcrest.Matchers.*;
@ -89,4 +90,14 @@ public class SSLServiceTests extends ElasticsearchTestCase {
.put("shield.ssl.keystore.password", "testnode")
.build()).createSSLEngine();
}
@Test
public void testThatSSLv3IsNotEnabled() throws Exception {
SSLService sslService = new SSLService(settingsBuilder()
.put("shield.ssl.keystore.path", testnodeStore)
.put("shield.ssl.keystore.password", "testnode")
.build());
SSLEngine engine = sslService.createSSLEngine();
assertThat(Arrays.asList(engine.getEnabledProtocols()), not(hasItem("SSLv3")));
}
}

View File

@ -6,6 +6,10 @@
package org.elasticsearch.shield.transport.ssl;
import com.google.common.base.Charsets;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.elasticsearch.client.transport.NoNodeAvailableException;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.io.Streams;
@ -25,6 +29,8 @@ import javax.net.ssl.*;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Locale;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
@ -35,6 +41,20 @@ import static org.hamcrest.Matchers.*;
@ClusterScope(scope = Scope.SUITE)
public class SslIntegrationTests extends ShieldIntegrationTest {
private TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return ImmutableSettings.builder().put(super.nodeSettings(nodeOrdinal))
@ -64,27 +84,28 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
}
}
// no SSL exception as this is the exception is returned when connecting
@Test(expected = NoNodeAvailableException.class)
public void testThatTransportClientUsingSSLv3ProtocolIsRejected() {
try(TransportClient transportClient = new TransportClient(settingsBuilder()
.put(transportClientSettings())
.put("name", "programmatic_transport_client")
.put("cluster.name", internalCluster().getClusterName())
.putArray("shield.ssl.supported_protocols", new String[]{"SSLv3"})
.build())) {
TransportAddress transportAddress = internalCluster().getInstance(Transport.class).boundAddress().boundAddress();
transportClient.addTransportAddress(transportAddress);
transportClient.admin().cluster().prepareHealth().get();
}
}
@Test
public void testThatConnectionToHTTPWorks() throws Exception {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// totally secure
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@ -94,10 +115,7 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
}
});
TransportAddress transportAddress = internalCluster().getInstance(HttpServerTransport.class).boundAddress().boundAddress();
assertThat(transportAddress, is(instanceOf(InetSocketTransportAddress.class)));
InetSocketTransportAddress inetSocketTransportAddress = (InetSocketTransportAddress) transportAddress;
String url = String.format(Locale.ROOT, "https://%s:%s/", InetAddresses.toUriString(inetSocketTransportAddress.address().getAddress()), inetSocketTransportAddress.address().getPort());
String url = getNodeUrl();
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestProperty(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(nodeClientUsername(), nodeClientPassword()));
@ -107,4 +125,24 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
String data = Streams.copyToString(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8));
assertThat(data, containsString("You Know, for Search"));
}
@Test
public void testThatHttpUsingSSLv3IsRejected() throws Exception {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new SecureRandom());
SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(sslContext, new String[]{"SSLv3"}, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
try (CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sf).build()) {
client.execute(new HttpGet(getNodeUrl()));
fail("Expected a connection error due to SSLv3 not being supported by default");
} catch (Exception e) {
assertThat(e, is(instanceOf(SSLHandshakeException.class)));
}
}
private String getNodeUrl() {
TransportAddress transportAddress = internalCluster().getInstance(HttpServerTransport.class).boundAddress().boundAddress();
assertThat(transportAddress, is(instanceOf(InetSocketTransportAddress.class)));
InetSocketTransportAddress inetSocketTransportAddress = (InetSocketTransportAddress) transportAddress;
return String.format(Locale.ROOT, "https://%s:%s/", InetAddresses.toUriString(inetSocketTransportAddress.address().getAddress()), inetSocketTransportAddress.address().getPort());
}
}