Make TLS protocols and cipher suites configurable via the environemnt
Closes gh-4823
This commit is contained in:
parent
7512687cba
commit
766ccd753b
|
|
@ -41,6 +41,11 @@ public class Ssl {
|
|||
*/
|
||||
private String[] ciphers;
|
||||
|
||||
/**
|
||||
* Supported SSL protocols.
|
||||
*/
|
||||
private String[] protocols;
|
||||
|
||||
/**
|
||||
* Alias that identifies the key in the key store.
|
||||
*/
|
||||
|
|
@ -208,6 +213,14 @@ public class Ssl {
|
|||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public String[] getProtocols() {
|
||||
return this.protocols;
|
||||
}
|
||||
|
||||
public void setProtocols(String[] protocols) {
|
||||
this.protocols = protocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Client authentication types.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -207,7 +207,13 @@ public class JettyEmbeddedServletContainerFactory
|
|||
* @param ssl the ssl details.
|
||||
*/
|
||||
protected void configureSsl(SslContextFactory factory, Ssl ssl) {
|
||||
//Set the default TLS protocol
|
||||
factory.setProtocol(ssl.getProtocol());
|
||||
|
||||
//Assign the supported protocols, if provided
|
||||
if (ssl.getProtocols() != null) {
|
||||
factory.setIncludeProtocols(ssl.getProtocols());
|
||||
}
|
||||
configureSslClientAuth(factory, ssl);
|
||||
configureSslPasswords(factory, ssl);
|
||||
factory.setCertAlias(ssl.getKeyAlias());
|
||||
|
|
|
|||
|
|
@ -315,7 +315,15 @@ public class TomcatEmbeddedServletContainerFactory
|
|||
*/
|
||||
protected void configureSsl(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
|
||||
protocol.setSSLEnabled(true);
|
||||
//Set the default TLS protocol
|
||||
protocol.setSslProtocol(ssl.getProtocol());
|
||||
|
||||
//Assign the supported protocols, if provided
|
||||
if (ssl.getProtocols() != null) {
|
||||
String protocols = StringUtils.arrayToCommaDelimitedString(ssl.getProtocols());
|
||||
protocol.setProperty("sslEnabledProtocols", protocols);
|
||||
}
|
||||
|
||||
configureSslClientAuth(protocol, ssl);
|
||||
protocol.setKeystorePass(ssl.getKeyStorePassword());
|
||||
protocol.setKeyPass(ssl.getKeyPassword());
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ import io.undertow.servlet.handlers.DefaultServlet;
|
|||
import io.undertow.servlet.util.ImmediateInstanceFactory;
|
||||
import org.xnio.OptionMap;
|
||||
import org.xnio.Options;
|
||||
import org.xnio.Sequence;
|
||||
import org.xnio.SslClientAuthMode;
|
||||
import org.xnio.Xnio;
|
||||
import org.xnio.XnioWorker;
|
||||
|
|
@ -257,8 +258,15 @@ public class UndertowEmbeddedServletContainerFactory
|
|||
SSLContext sslContext = SSLContext.getInstance(ssl.getProtocol());
|
||||
sslContext.init(getKeyManagers(), getTrustManagers(), null);
|
||||
builder.addHttpsListener(port, getListenAddress(), sslContext);
|
||||
builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE,
|
||||
getSslClientAuthMode(ssl));
|
||||
builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, getSslClientAuthMode(ssl));
|
||||
|
||||
//Configure the supported TLS protocols and Cipher suites
|
||||
if (ssl.getProtocols() != null) {
|
||||
builder.setSocketOption(Options.SSL_ENABLED_PROTOCOLS, Sequence.of(ssl.getProtocols()));
|
||||
}
|
||||
if (ssl.getCiphers() != null) {
|
||||
builder.setSocketOption(Options.SSL_ENABLED_CIPHER_SUITES, Sequence.of(ssl.getCiphers()));
|
||||
}
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
|
|
|
|||
|
|
@ -404,7 +404,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
|||
AbstractEmbeddedServletContainerFactory factory = getFactory();
|
||||
addTestTxtFile(factory);
|
||||
factory.setSsl(getSsl(ClientAuth.NEED, null, "classpath:test.p12",
|
||||
"classpath:test.p12"));
|
||||
"classpath:test.p12", null, null));
|
||||
this.container = factory.getEmbeddedServletContainer();
|
||||
this.container.start();
|
||||
KeyStore keyStore = KeyStore.getInstance("pkcs12");
|
||||
|
|
@ -428,7 +428,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
|||
AbstractEmbeddedServletContainerFactory factory = getFactory();
|
||||
addTestTxtFile(factory);
|
||||
factory.setSsl(getSsl(ClientAuth.NEED, "password", "classpath:test.jks",
|
||||
"classpath:test.jks"));
|
||||
"classpath:test.jks", null, null));
|
||||
this.container = factory.getEmbeddedServletContainer();
|
||||
this.container.start();
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
|
|
@ -526,11 +526,11 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
|||
}
|
||||
|
||||
private Ssl getSsl(ClientAuth clientAuth, String keyPassword, String keyStore) {
|
||||
return getSsl(clientAuth, keyPassword, keyStore, null);
|
||||
return getSsl(clientAuth, keyPassword, keyStore, null, null, null);
|
||||
}
|
||||
|
||||
private Ssl getSsl(ClientAuth clientAuth, String keyPassword, String keyStore,
|
||||
String trustStore) {
|
||||
String trustStore, String[] protocols, String[] ciphers) {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setClientAuth(clientAuth);
|
||||
if (keyPassword != null) {
|
||||
|
|
@ -546,9 +546,42 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
|||
ssl.setTrustStorePassword("secret");
|
||||
ssl.setTrustStoreType(getStoreType(trustStore));
|
||||
}
|
||||
if (ciphers != null) {
|
||||
ssl.setCiphers(ciphers);
|
||||
}
|
||||
if (protocols != null) {
|
||||
ssl.setProtocols(protocols);
|
||||
}
|
||||
return ssl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see <a
|
||||
* href="http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SunJSSEProvider">
|
||||
* SunJSSE supported Cipher Suites</a>
|
||||
*/
|
||||
protected void testRestrictedSSLProtocolsAndCipherSuites(String[] protocols,
|
||||
String[] ciphers) throws Exception {
|
||||
AbstractEmbeddedServletContainerFactory factory = getFactory();
|
||||
factory.setSsl(getSsl(null, "password", "src/test/resources/test.jks", null,
|
||||
protocols, ciphers));
|
||||
this.container = factory.getEmbeddedServletContainer(
|
||||
new ServletRegistrationBean(new ExampleServlet(true, false), "/hello"));
|
||||
this.container.start();
|
||||
|
||||
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
|
||||
new SSLContextBuilder()
|
||||
.loadTrustMaterial(null, new TrustSelfSignedStrategy()).build());
|
||||
|
||||
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory)
|
||||
.build();
|
||||
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
|
||||
httpClient);
|
||||
|
||||
assertThat(getResponse(getLocalUrl("https", "/hello"), requestFactory))
|
||||
.contains("scheme=https");
|
||||
}
|
||||
|
||||
private String getStoreType(String keyStore) {
|
||||
return (keyStore.endsWith(".p12") ? "pkcs12" : null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,6 +152,56 @@ public class JettyEmbeddedServletContainerFactoryTests
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sslEnabledMultiProtocolsConfiguration() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("src/test/resources/test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
|
||||
ssl.setProtocols(new String[]{ "TLSv1.1", "TLSv1.2" });
|
||||
|
||||
JettyEmbeddedServletContainerFactory factory = getFactory();
|
||||
factory.setSsl(ssl);
|
||||
|
||||
this.container = factory.getEmbeddedServletContainer();
|
||||
this.container.start();
|
||||
|
||||
JettyEmbeddedServletContainer jettyContainer = (JettyEmbeddedServletContainer) this.container;
|
||||
ServerConnector connector = (ServerConnector) jettyContainer.getServer()
|
||||
.getConnectors()[0];
|
||||
SslConnectionFactory connectionFactory = connector
|
||||
.getConnectionFactory(SslConnectionFactory.class);
|
||||
|
||||
assertThat(connectionFactory.getSslContextFactory().getIncludeProtocols())
|
||||
.isEqualTo(new String[] { "TLSv1.1", "TLSv1.2" });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sslEnabledProtocolsConfiguration() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("src/test/resources/test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
|
||||
ssl.setProtocols(new String[]{ "TLSv1.1" });
|
||||
|
||||
JettyEmbeddedServletContainerFactory factory = getFactory();
|
||||
factory.setSsl(ssl);
|
||||
|
||||
this.container = factory.getEmbeddedServletContainer();
|
||||
this.container.start();
|
||||
|
||||
JettyEmbeddedServletContainer jettyContainer = (JettyEmbeddedServletContainer) this.container;
|
||||
ServerConnector connector = (ServerConnector) jettyContainer.getServer()
|
||||
.getConnectors()[0];
|
||||
SslConnectionFactory connectionFactory = connector
|
||||
.getConnectionFactory(SslConnectionFactory.class);
|
||||
|
||||
assertThat(connectionFactory.getSslContextFactory().getIncludeProtocols())
|
||||
.isEqualTo(new String[] { "TLSv1.1" });
|
||||
}
|
||||
|
||||
private void assertTimeout(JettyEmbeddedServletContainerFactory factory,
|
||||
int expected) {
|
||||
this.container = factory.getEmbeddedServletContainer();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.boot.context.embedded.tomcat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -41,11 +42,13 @@ import org.mockito.InOrder;
|
|||
|
||||
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
|
||||
import org.springframework.boot.context.embedded.Ssl;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
|
|
@ -260,6 +263,78 @@ public class TomcatEmbeddedServletContainerFactoryTests
|
|||
assertThat(jsseProtocol.getCiphers()).isEqualTo("ALPHA,BRAVO,CHARLIE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sslEnabledMultipleProtocolsConfiguration() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setProtocols(new String[]{ "TLSv1.1", "TLSv1.2" });
|
||||
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
|
||||
|
||||
TomcatEmbeddedServletContainerFactory factory = getFactory();
|
||||
factory.setSsl(ssl);
|
||||
|
||||
this.container = factory
|
||||
.getEmbeddedServletContainer(sessionServletRegistration());
|
||||
Tomcat tomcat = ((TomcatEmbeddedServletContainer) this.container).getTomcat();
|
||||
Connector connector = tomcat.getConnector();
|
||||
|
||||
AbstractHttp11JsseProtocol<?> jsseProtocol = (AbstractHttp11JsseProtocol<?>) connector
|
||||
.getProtocolHandler();
|
||||
assertThat(jsseProtocol.getSslProtocol()).isEqualTo("TLS");
|
||||
assertThat(jsseProtocol.getProperty("sslEnabledProtocols"))
|
||||
.isEqualTo("TLSv1.1,TLSv1.2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sslEnabledProtocolsConfiguration() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setProtocols(new String[]{"TLSv1.2"});
|
||||
ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" });
|
||||
|
||||
TomcatEmbeddedServletContainerFactory factory = getFactory();
|
||||
factory.setSsl(ssl);
|
||||
|
||||
this.container = factory
|
||||
.getEmbeddedServletContainer(sessionServletRegistration());
|
||||
Tomcat tomcat = ((TomcatEmbeddedServletContainer) this.container).getTomcat();
|
||||
Connector connector = tomcat.getConnector();
|
||||
|
||||
AbstractHttp11JsseProtocol<?> jsseProtocol = (AbstractHttp11JsseProtocol<?>) connector
|
||||
.getProtocolHandler();
|
||||
assertThat(jsseProtocol.getSslProtocol()).isEqualTo("TLS");
|
||||
assertThat(jsseProtocol.getProperty("sslEnabledProtocols")).isEqualTo("TLSv1.2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void primaryConnectorPortClashThrowsIllegalStateException()
|
||||
throws InterruptedException, IOException {
|
||||
final int port = SocketUtils.findAvailableTcpPort(40000);
|
||||
|
||||
doWithBlockedPort(port, new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
TomcatEmbeddedServletContainerFactory factory = getFactory();
|
||||
factory.setPort(port);
|
||||
|
||||
try {
|
||||
TomcatEmbeddedServletContainerFactoryTests.this.container = factory
|
||||
.getEmbeddedServletContainer();
|
||||
TomcatEmbeddedServletContainerFactoryTests.this.container.start();
|
||||
fail();
|
||||
}
|
||||
catch (EmbeddedServletContainerException ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConnector(int port,
|
||||
AbstractEmbeddedServletContainerFactory factory) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
import io.undertow.Undertow.Builder;
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import io.undertow.servlet.api.DeploymentManager;
|
||||
|
|
@ -201,6 +203,36 @@ public class UndertowEmbeddedServletContainerFactoryTests
|
|||
});
|
||||
}
|
||||
|
||||
@Test(expected = SSLHandshakeException.class)
|
||||
public void sslRestrictedProtocolsEmptyCipherFailure() throws Exception {
|
||||
testRestrictedSSLProtocolsAndCipherSuites(new String[] { "TLSv1.2" },
|
||||
new String[] { "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" });
|
||||
}
|
||||
|
||||
@Test(expected = SSLHandshakeException.class)
|
||||
public void sslRestrictedProtocolsECDHETLS1Failure() throws Exception {
|
||||
testRestrictedSSLProtocolsAndCipherSuites(new String[] { "TLSv1" },
|
||||
new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sslRestrictedProtocolsECDHESuccess() throws Exception {
|
||||
testRestrictedSSLProtocolsAndCipherSuites(new String[] { "TLSv1.2" },
|
||||
new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sslRestrictedProtocolsRSATLS12Success() throws Exception {
|
||||
testRestrictedSSLProtocolsAndCipherSuites(new String[] { "TLSv1.2" },
|
||||
new String[] { "TLS_RSA_WITH_AES_128_CBC_SHA256" });
|
||||
}
|
||||
|
||||
@Test(expected = SSLHandshakeException.class)
|
||||
public void sslRestrictedProtocolsRSATLS11Failure() throws Exception {
|
||||
testRestrictedSSLProtocolsAndCipherSuites(new String[] { "TLSv1.1" },
|
||||
new String[] { "TLS_RSA_WITH_AES_128_CBC_SHA256" });
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getJspServlet() {
|
||||
return null; // Undertow does not support JSPs
|
||||
|
|
|
|||
Loading…
Reference in New Issue