From 7f58db7d0e9084881f4171a9cc5ac4ae19647598 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 2 Nov 2017 22:42:26 +0100 Subject: [PATCH] Apply TLS configuration to reactive web servers This commit applies the SSL configuration to the following reactive web servers: Jetty, Tomcat, Undertow, Reactor Netty. Closes gh-9431 --- .../jetty/JettyReactiveWebServerFactory.java | 6 +- .../jetty/JettyServletWebServerFactory.java | 128 +-------- .../embedded/jetty/SslServerCustomizer.java | 175 ++++++++++++ .../netty/NettyReactiveWebServerFactory.java | 4 + .../embedded/netty/SslServerCustomizer.java | 127 +++++++++ .../CompressionConnectorCustomizer.java | 60 +++++ .../tomcat/SslConnectorCustomizer.java | 157 +++++++++++ .../TomcatReactiveWebServerFactory.java | 4 + .../tomcat/TomcatServletWebServerFactory.java | 140 +--------- .../undertow/SslBuilderCustomizer.java | 250 ++++++++++++++++++ .../UndertowReactiveWebServerFactory.java | 9 +- .../UndertowServletWebServerFactory.java | 197 +------------- .../tomcat/SslConnectorCustomizerTests.java | 111 ++++++++ .../TomcatServletWebServerFactoryTests.java | 59 ----- .../undertow/SslBuilderCustomizerTests.java | 49 ++++ .../UndertowServletWebServerFactoryTests.java | 14 - ...AbstractReactiveWebServerFactoryTests.java | 51 +++- 17 files changed, 1009 insertions(+), 532 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/CompressionConnectorCustomizer.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizer.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizerTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizerTests.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java index ec84e579c7..d3354a08f1 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyReactiveWebServerFactory.java @@ -97,11 +97,13 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact ServletContextHandler contextHandler = new ServletContextHandler(server, "", false, false); contextHandler.addServlet(servletHolder, "/"); + this.logger.info("Server initialized with port: " + port); + SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(port, + getSsl(), getSslStoreProvider()); + sslServerCustomizer.customize(server); for (JettyServerCustomizer customizer : getServerCustomizers()) { customizer.customize(server); } - JettyReactiveWebServerFactory.logger - .info("Server initialized with port: " + port); return server; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java index bd596bd037..8f221dd72b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyServletWebServerFactory.java @@ -36,7 +36,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.ConnectionFactory; @@ -44,12 +43,9 @@ import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.ForwardedRequestCustomizer; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.handler.gzip.GzipHandler; @@ -62,7 +58,6 @@ import org.eclipse.jetty.servlet.ServletMapping; import org.eclipse.jetty.util.resource.JarResource; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceCollection; -import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.ThreadPool; import org.eclipse.jetty.webapp.AbstractConfiguration; import org.eclipse.jetty.webapp.Configuration; @@ -71,18 +66,13 @@ import org.eclipse.jetty.webapp.WebAppContext; import org.springframework.boot.web.server.Compression; import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.MimeMappings; -import org.springframework.boot.web.server.Ssl; -import org.springframework.boot.web.server.Ssl.ClientAuth; import org.springframework.boot.web.server.WebServer; -import org.springframework.boot.web.server.WebServerException; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** @@ -163,13 +153,9 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor configureWebAppContext(context, initializers); server.setHandler(addHandlerWrappers(context)); this.logger.info("Server initialized with port: " + port); - if (getSsl() != null && getSsl().isEnabled()) { - SslContextFactory sslContextFactory = new SslContextFactory(); - configureSsl(sslContextFactory, getSsl()); - AbstractConnector connector = createSslConnector(server, sslContextFactory, - port); - server.setConnectors(new Connector[] { connector }); - } + SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(port, + getSsl(), getSslStoreProvider()); + sslServerCustomizer.customize(server); for (JettyServerCustomizer customizer : getServerCustomizers()) { customizer.customize(server); } @@ -181,7 +167,7 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor private Server createServer(InetSocketAddress address) { Server server = new Server(getThreadPool()); - server.setConnectors(new Connector[] { createConnector(address, server) }); + server.setConnectors(new Connector[] {createConnector(address, server)}); return server; } @@ -199,20 +185,6 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor return connector; } - private AbstractConnector createSslConnector(Server server, - SslContextFactory sslContextFactory, int port) { - HttpConfiguration config = new HttpConfiguration(); - config.setSendServerVersion(false); - config.addCustomizer(new SecureRequestCustomizer()); - HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config); - SslConnectionFactory sslConnectionFactory = new SslConnectionFactory( - sslContextFactory, HttpVersion.HTTP_1_1.asString()); - ServerConnector serverConnector = new ServerConnector(server, - sslConnectionFactory, connectionFactory); - serverConnector.setPort(port); - return serverConnector; - } - private Handler addHandlerWrappers(Handler handler) { if (getCompression() != null && getCompression().getEnabled()) { handler = applyWrapper(handler, createGzipHandler()); @@ -242,96 +214,6 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor return handler; } - /** - * Configure the SSL connection. - * @param factory the Jetty {@link SslContextFactory}. - * @param ssl the ssl details. - */ - protected void configureSsl(SslContextFactory factory, Ssl ssl) { - factory.setProtocol(ssl.getProtocol()); - configureSslClientAuth(factory, ssl); - configureSslPasswords(factory, ssl); - factory.setCertAlias(ssl.getKeyAlias()); - if (!ObjectUtils.isEmpty(ssl.getCiphers())) { - factory.setIncludeCipherSuites(ssl.getCiphers()); - factory.setExcludeCipherSuites(); - } - if (ssl.getEnabledProtocols() != null) { - factory.setIncludeProtocols(ssl.getEnabledProtocols()); - } - if (getSslStoreProvider() != null) { - try { - factory.setKeyStore(getSslStoreProvider().getKeyStore()); - factory.setTrustStore(getSslStoreProvider().getTrustStore()); - } - catch (Exception ex) { - throw new IllegalStateException("Unable to set SSL store", ex); - } - } - else { - configureSslKeyStore(factory, ssl); - configureSslTrustStore(factory, ssl); - } - } - - private void configureSslClientAuth(SslContextFactory factory, Ssl ssl) { - if (ssl.getClientAuth() == ClientAuth.NEED) { - factory.setNeedClientAuth(true); - factory.setWantClientAuth(true); - } - else if (ssl.getClientAuth() == ClientAuth.WANT) { - factory.setWantClientAuth(true); - } - } - - private void configureSslPasswords(SslContextFactory factory, Ssl ssl) { - if (ssl.getKeyStorePassword() != null) { - factory.setKeyStorePassword(ssl.getKeyStorePassword()); - } - if (ssl.getKeyPassword() != null) { - factory.setKeyManagerPassword(ssl.getKeyPassword()); - } - } - - private void configureSslKeyStore(SslContextFactory factory, Ssl ssl) { - try { - URL url = ResourceUtils.getURL(ssl.getKeyStore()); - factory.setKeyStoreResource(Resource.newResource(url)); - } - catch (IOException ex) { - throw new WebServerException( - "Could not find key store '" + ssl.getKeyStore() + "'", ex); - } - if (ssl.getKeyStoreType() != null) { - factory.setKeyStoreType(ssl.getKeyStoreType()); - } - if (ssl.getKeyStoreProvider() != null) { - factory.setKeyStoreProvider(ssl.getKeyStoreProvider()); - } - } - - private void configureSslTrustStore(SslContextFactory factory, Ssl ssl) { - if (ssl.getTrustStorePassword() != null) { - factory.setTrustStorePassword(ssl.getTrustStorePassword()); - } - if (ssl.getTrustStore() != null) { - try { - URL url = ResourceUtils.getURL(ssl.getTrustStore()); - factory.setTrustStoreResource(Resource.newResource(url)); - } - catch (IOException ex) { - throw new WebServerException( - "Could not find trust store '" + ssl.getTrustStore() + "'", ex); - } - } - if (ssl.getTrustStoreType() != null) { - factory.setTrustStoreType(ssl.getTrustStoreType()); - } - if (ssl.getTrustStoreProvider() != null) { - factory.setTrustStoreProvider(ssl.getTrustStoreProvider()); - } - } - /** * Configure the given Jetty {@link WebAppContext} for use. * @param context the context to configure @@ -456,7 +338,7 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor context.getServletHandler().addServlet(holder); ServletMapping mapping = new ServletMapping(); mapping.setServletName("jsp"); - mapping.setPathSpecs(new String[] { "*.jsp", "*.jspx" }); + mapping.setPathSpecs(new String[] {"*.jsp", "*.jspx"}); context.getServletHandler().addServletMapping(mapping); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java new file mode 100644 index 0000000000..022799e750 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java @@ -0,0 +1,175 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.embedded.jetty; + +import java.io.IOException; +import java.net.URL; + +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.server.AbstractConnector; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import org.springframework.boot.web.server.Ssl; +import org.springframework.boot.web.server.SslStoreProvider; +import org.springframework.boot.web.server.WebServerException; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ResourceUtils; + +/** + * {@link JettyServerCustomizer} that configures SSL on the + * given Jetty server instance. + * + * @author Brian Clozel + */ +class SslServerCustomizer implements JettyServerCustomizer { + + private final int port; + + private final Ssl ssl; + + private final SslStoreProvider sslStoreProvider; + + SslServerCustomizer(int port, Ssl ssl, SslStoreProvider sslStoreProvider) { + this.port = port; + this.ssl = ssl; + this.sslStoreProvider = sslStoreProvider; + } + + @Override + public void customize(Server server) { + if (this.ssl != null && this.ssl.isEnabled()) { + SslContextFactory sslContextFactory = new SslContextFactory(); + configureSsl(sslContextFactory, this.ssl, this.sslStoreProvider); + AbstractConnector connector = createSslConnector(server, sslContextFactory, + this.port); + server.setConnectors(new Connector[] { connector }); + } + } + + private AbstractConnector createSslConnector(Server server, + SslContextFactory sslContextFactory, int port) { + HttpConfiguration config = new HttpConfiguration(); + config.setSendServerVersion(false); + config.addCustomizer(new SecureRequestCustomizer()); + HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config); + SslConnectionFactory sslConnectionFactory = new SslConnectionFactory( + sslContextFactory, HttpVersion.HTTP_1_1.asString()); + ServerConnector serverConnector = new ServerConnector(server, + sslConnectionFactory, connectionFactory); + serverConnector.setPort(port); + return serverConnector; + } + + /** + * Configure the SSL connection. + * @param factory the Jetty {@link SslContextFactory}. + * @param ssl the ssl details. + * @param sslStoreProvider the ssl store provider + */ + protected void configureSsl(SslContextFactory factory, Ssl ssl, SslStoreProvider sslStoreProvider) { + factory.setProtocol(ssl.getProtocol()); + configureSslClientAuth(factory, ssl); + configureSslPasswords(factory, ssl); + factory.setCertAlias(ssl.getKeyAlias()); + if (!ObjectUtils.isEmpty(ssl.getCiphers())) { + factory.setIncludeCipherSuites(ssl.getCiphers()); + factory.setExcludeCipherSuites(); + } + if (ssl.getEnabledProtocols() != null) { + factory.setIncludeProtocols(ssl.getEnabledProtocols()); + } + if (sslStoreProvider != null) { + try { + factory.setKeyStore(sslStoreProvider.getKeyStore()); + factory.setTrustStore(sslStoreProvider.getTrustStore()); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to set SSL store", ex); + } + } + else { + configureSslKeyStore(factory, ssl); + configureSslTrustStore(factory, ssl); + } + } + + private void configureSslClientAuth(SslContextFactory factory, Ssl ssl) { + if (ssl.getClientAuth() == Ssl.ClientAuth.NEED) { + factory.setNeedClientAuth(true); + factory.setWantClientAuth(true); + } + else if (ssl.getClientAuth() == Ssl.ClientAuth.WANT) { + factory.setWantClientAuth(true); + } + } + + private void configureSslPasswords(SslContextFactory factory, Ssl ssl) { + if (ssl.getKeyStorePassword() != null) { + factory.setKeyStorePassword(ssl.getKeyStorePassword()); + } + if (ssl.getKeyPassword() != null) { + factory.setKeyManagerPassword(ssl.getKeyPassword()); + } + } + + private void configureSslKeyStore(SslContextFactory factory, Ssl ssl) { + try { + URL url = ResourceUtils.getURL(ssl.getKeyStore()); + factory.setKeyStoreResource(Resource.newResource(url)); + } + catch (IOException ex) { + throw new WebServerException( + "Could not find key store '" + ssl.getKeyStore() + "'", ex); + } + if (ssl.getKeyStoreType() != null) { + factory.setKeyStoreType(ssl.getKeyStoreType()); + } + if (ssl.getKeyStoreProvider() != null) { + factory.setKeyStoreProvider(ssl.getKeyStoreProvider()); + } + } + + private void configureSslTrustStore(SslContextFactory factory, Ssl ssl) { + if (ssl.getTrustStorePassword() != null) { + factory.setTrustStorePassword(ssl.getTrustStorePassword()); + } + if (ssl.getTrustStore() != null) { + try { + URL url = ResourceUtils.getURL(ssl.getTrustStore()); + factory.setTrustStoreResource(Resource.newResource(url)); + } + catch (IOException ex) { + throw new WebServerException( + "Could not find trust store '" + ssl.getTrustStore() + "'", ex); + } + } + if (ssl.getTrustStoreType() != null) { + factory.setTrustStoreType(ssl.getTrustStoreType()); + } + if (ssl.getTrustStoreProvider() != null) { + factory.setTrustStoreProvider(ssl.getTrustStoreProvider()); + } + } +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java index c08ee9e052..bc018717ec 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java @@ -90,6 +90,10 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact private HttpServer createHttpServer() { return HttpServer.builder().options((options) -> { options.listenAddress(getListenAddress()); + if (getSsl() != null && getSsl().isEnabled()) { + SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(getSsl(), getSslStoreProvider()); + sslServerCustomizer.customize(options); + } applyCustomizers(options); }).build(); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java new file mode 100644 index 0000000000..33903d8737 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java @@ -0,0 +1,127 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.embedded.netty; + +import java.net.URL; +import java.security.KeyStore; +import java.util.Arrays; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; + +import io.netty.handler.ssl.SslContextBuilder; +import reactor.ipc.netty.http.server.HttpServerOptions; + +import org.springframework.boot.web.server.Ssl; +import org.springframework.boot.web.server.SslStoreProvider; +import org.springframework.util.ResourceUtils; + +/** + * {@link NettyServerCustomizer} that configures SSL for the + * given Reactor Netty server instance. + * + * @author Brian Clozel + */ +public class SslServerCustomizer implements NettyServerCustomizer { + + private final Ssl ssl; + + private final SslStoreProvider sslStoreProvider; + + public SslServerCustomizer(Ssl ssl, SslStoreProvider sslStoreProvider) { + this.ssl = ssl; + this.sslStoreProvider = sslStoreProvider; + } + + @Override + public void customize(HttpServerOptions.Builder builder) { + SslContextBuilder sslBuilder = SslContextBuilder + .forServer(getKeyManagerFactory(this.ssl, this.sslStoreProvider)) + .trustManager(getTrustManagerFactory(this.ssl, this.sslStoreProvider)); + if (this.ssl.getEnabledProtocols() != null) { + sslBuilder.protocols(this.ssl.getEnabledProtocols()); + } + if (this.ssl.getCiphers() != null) { + sslBuilder = sslBuilder.ciphers(Arrays.asList(this.ssl.getCiphers())); + } + try { + builder.sslContext(sslBuilder.build()); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + protected KeyManagerFactory getKeyManagerFactory(Ssl ssl, SslStoreProvider sslStoreProvider) { + try { + KeyStore keyStore = getKeyStore(ssl, sslStoreProvider); + KeyManagerFactory keyManagerFactory = KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()); + char[] keyPassword = (ssl.getKeyPassword() != null + ? ssl.getKeyPassword().toCharArray() : null); + if (keyPassword == null && ssl.getKeyStorePassword() != null) { + keyPassword = ssl.getKeyStorePassword().toCharArray(); + } + keyManagerFactory.init(keyStore, keyPassword); + return keyManagerFactory; + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + private KeyStore getKeyStore(Ssl ssl, SslStoreProvider sslStoreProvider) throws Exception { + if (sslStoreProvider != null) { + return sslStoreProvider.getKeyStore(); + } + return loadKeyStore(ssl.getKeyStoreType(), ssl.getKeyStore(), + ssl.getKeyStorePassword()); + } + + protected TrustManagerFactory getTrustManagerFactory(Ssl ssl, SslStoreProvider sslStoreProvider) { + try { + KeyStore store = getTrustStore(ssl, sslStoreProvider); + TrustManagerFactory trustManagerFactory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(store); + return trustManagerFactory; + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + private KeyStore getTrustStore(Ssl ssl, SslStoreProvider sslStoreProvider) throws Exception { + if (sslStoreProvider != null) { + return sslStoreProvider.getTrustStore(); + } + return loadKeyStore(ssl.getTrustStoreType(), ssl.getTrustStore(), + ssl.getTrustStorePassword()); + } + + private KeyStore loadKeyStore(String type, String resource, String password) + throws Exception { + type = (type == null ? "JKS" : type); + if (resource == null) { + return null; + } + KeyStore store = KeyStore.getInstance(type); + URL url = ResourceUtils.getURL(resource); + store.load(url.openStream(), password == null ? null : password.toCharArray()); + return store; + } +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/CompressionConnectorCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/CompressionConnectorCustomizer.java new file mode 100644 index 0000000000..81b93b0275 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/CompressionConnectorCustomizer.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.embedded.tomcat; + +import org.apache.catalina.connector.Connector; +import org.apache.coyote.ProtocolHandler; +import org.apache.coyote.http11.AbstractHttp11Protocol; + +import org.springframework.boot.web.server.Compression; +import org.springframework.util.StringUtils; + +/** + * {@link TomcatConnectorCustomizer} that configures compression + * support on the given Connector. + * + * @author Brian Clozel + */ +class CompressionConnectorCustomizer implements TomcatConnectorCustomizer { + + + private final Compression compression; + + CompressionConnectorCustomizer(Compression compression) { + this.compression = compression; + } + + @Override + public void customize(Connector connector) { + if (this.compression != null && this.compression.getEnabled()) { + ProtocolHandler handler = connector.getProtocolHandler(); + if (handler instanceof AbstractHttp11Protocol) { + AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; + Compression compression = this.compression; + protocol.setCompression("on"); + protocol.setCompressionMinSize(compression.getMinResponseSize()); + protocol.setCompressibleMimeType( + StringUtils.arrayToCommaDelimitedString(compression.getMimeTypes())); + if (this.compression.getExcludedUserAgents() != null) { + protocol.setNoCompressionUserAgents( + StringUtils.arrayToCommaDelimitedString( + this.compression.getExcludedUserAgents())); + } + } + } + } +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java new file mode 100644 index 0000000000..5b97e541e5 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java @@ -0,0 +1,157 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.embedded.tomcat; + +import java.io.FileNotFoundException; + +import org.apache.catalina.connector.Connector; +import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory; +import org.apache.coyote.ProtocolHandler; +import org.apache.coyote.http11.AbstractHttp11JsseProtocol; +import org.apache.coyote.http11.Http11NioProtocol; +import org.apache.tomcat.util.net.SSLHostConfig; + +import org.springframework.boot.web.server.Ssl; +import org.springframework.boot.web.server.SslStoreProvider; +import org.springframework.boot.web.server.WebServerException; +import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; + +/** + * {@link TomcatConnectorCustomizer} that configures SSL support + * on the given connector. + * + * @author Brian Clozel + */ +class SslConnectorCustomizer implements TomcatConnectorCustomizer { + + private final Ssl ssl; + + private final SslStoreProvider sslStoreProvider; + + SslConnectorCustomizer(Ssl ssl, SslStoreProvider sslStoreProvider) { + this.ssl = ssl; + this.sslStoreProvider = sslStoreProvider; + } + + @Override + public void customize(Connector connector) { + if (this.ssl != null && this.ssl.isEnabled()) { + ProtocolHandler handler = connector.getProtocolHandler(); + Assert.state(handler instanceof AbstractHttp11JsseProtocol, + "To use SSL, the connector's protocol handler must be an " + + "AbstractHttp11JsseProtocol subclass"); + configureSsl((AbstractHttp11JsseProtocol) handler, + this.ssl, this.sslStoreProvider); + connector.setScheme("https"); + connector.setSecure(true); + } + } + + /** + * Configure Tomcat's {@link AbstractHttp11JsseProtocol} for SSL. + * @param protocol the protocol + * @param ssl the ssl details + * @param sslStoreProvider the ssl store provider + */ + protected void configureSsl(AbstractHttp11JsseProtocol protocol, + Ssl ssl, SslStoreProvider sslStoreProvider) { + protocol.setSSLEnabled(true); + protocol.setSslProtocol(ssl.getProtocol()); + configureSslClientAuth(protocol, ssl); + protocol.setKeystorePass(ssl.getKeyStorePassword()); + protocol.setKeyPass(ssl.getKeyPassword()); + protocol.setKeyAlias(ssl.getKeyAlias()); + String ciphers = StringUtils.arrayToCommaDelimitedString(ssl.getCiphers()); + if (StringUtils.hasText(ciphers)) { + protocol.setCiphers(ciphers); + } + if (ssl.getEnabledProtocols() != null) { + for (SSLHostConfig sslHostConfig : protocol.findSslHostConfigs()) { + sslHostConfig.setProtocols(StringUtils + .arrayToCommaDelimitedString(ssl.getEnabledProtocols())); + } + } + if (sslStoreProvider != null) { + configureSslStoreProvider(protocol, sslStoreProvider); + } + else { + configureSslKeyStore(protocol, ssl); + configureSslTrustStore(protocol, ssl); + } + } + + private void configureSslClientAuth(AbstractHttp11JsseProtocol protocol, Ssl ssl) { + if (ssl.getClientAuth() == Ssl.ClientAuth.NEED) { + protocol.setClientAuth(Boolean.TRUE.toString()); + } + else if (ssl.getClientAuth() == Ssl.ClientAuth.WANT) { + protocol.setClientAuth("want"); + } + } + + protected void configureSslStoreProvider(AbstractHttp11JsseProtocol protocol, + SslStoreProvider sslStoreProvider) { + Assert.isInstanceOf(Http11NioProtocol.class, protocol, + "SslStoreProvider can only be used with Http11NioProtocol"); + TomcatURLStreamHandlerFactory instance = TomcatURLStreamHandlerFactory + .getInstance(); + instance.addUserFactory( + new SslStoreProviderUrlStreamHandlerFactory(sslStoreProvider)); + protocol.setKeystoreFile( + SslStoreProviderUrlStreamHandlerFactory.KEY_STORE_URL); + protocol.setTruststoreFile( + SslStoreProviderUrlStreamHandlerFactory.TRUST_STORE_URL); + } + + private void configureSslKeyStore(AbstractHttp11JsseProtocol protocol, Ssl ssl) { + try { + protocol.setKeystoreFile(ResourceUtils.getURL(ssl.getKeyStore()).toString()); + } + catch (FileNotFoundException ex) { + throw new WebServerException("Could not load key store: " + ex.getMessage(), + ex); + } + if (ssl.getKeyStoreType() != null) { + protocol.setKeystoreType(ssl.getKeyStoreType()); + } + if (ssl.getKeyStoreProvider() != null) { + protocol.setKeystoreProvider(ssl.getKeyStoreProvider()); + } + } + + private void configureSslTrustStore(AbstractHttp11JsseProtocol protocol, Ssl ssl) { + if (ssl.getTrustStore() != null) { + try { + protocol.setTruststoreFile( + ResourceUtils.getURL(ssl.getTrustStore()).toString()); + } + catch (FileNotFoundException ex) { + throw new WebServerException( + "Could not load trust store: " + ex.getMessage(), ex); + } + } + protocol.setTruststorePass(ssl.getTrustStorePassword()); + if (ssl.getTrustStoreType() != null) { + protocol.setTruststoreType(ssl.getTrustStoreType()); + } + if (ssl.getTrustStoreProvider() != null) { + protocol.setTruststoreProvider(ssl.getTrustStoreProvider()); + } + } +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java index e09544be97..72704b39f2 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java @@ -141,6 +141,10 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac // If ApplicationContext is slow to start we want Tomcat not to bind to the socket // prematurely... connector.setProperty("bindOnInit", "false"); + TomcatConnectorCustomizer ssl = new SslConnectorCustomizer(getSsl(), getSslStoreProvider()); + ssl.customize(connector); + TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression()); + compression.customize(connector); for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) { customizer.customize(connector); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java index 3805340e2d..d63758e7ae 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java @@ -17,7 +17,6 @@ package org.springframework.boot.web.embedded.tomcat; import java.io.File; -import java.io.FileNotFoundException; import java.io.InputStream; import java.lang.reflect.Method; import java.net.URL; @@ -59,22 +58,11 @@ import org.apache.catalina.util.LifecycleBase; import org.apache.catalina.webresources.AbstractResourceSet; import org.apache.catalina.webresources.EmptyResource; import org.apache.catalina.webresources.StandardRoot; -import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory; import org.apache.coyote.AbstractProtocol; -import org.apache.coyote.ProtocolHandler; -import org.apache.coyote.http11.AbstractHttp11JsseProtocol; -import org.apache.coyote.http11.AbstractHttp11Protocol; -import org.apache.coyote.http11.Http11NioProtocol; -import org.apache.tomcat.util.net.SSLHostConfig; -import org.springframework.boot.web.server.Compression; import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.MimeMappings; -import org.springframework.boot.web.server.Ssl; -import org.springframework.boot.web.server.Ssl.ClientAuth; -import org.springframework.boot.web.server.SslStoreProvider; import org.springframework.boot.web.server.WebServer; -import org.springframework.boot.web.server.WebServerException; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory; import org.springframework.context.ResourceLoaderAware; @@ -82,7 +70,6 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; -import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** @@ -310,12 +297,10 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto // prematurely... connector.setProperty("bindOnInit", "false"); - if (getSsl() != null && getSsl().isEnabled()) { - customizeSsl(connector); - } - if (getCompression() != null && getCompression().getEnabled()) { - customizeCompression(connector); - } + TomcatConnectorCustomizer ssl = new SslConnectorCustomizer(getSsl(), getSslStoreProvider()); + ssl.customize(connector); + TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression()); + compression.customize(connector); for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) { customizer.customize(connector); } @@ -327,123 +312,6 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto } } - private void customizeSsl(Connector connector) { - ProtocolHandler handler = connector.getProtocolHandler(); - Assert.state(handler instanceof AbstractHttp11JsseProtocol, - "To use SSL, the connector's protocol handler must be an " - + "AbstractHttp11JsseProtocol subclass"); - configureSsl((AbstractHttp11JsseProtocol) handler, getSsl()); - connector.setScheme("https"); - connector.setSecure(true); - } - - private void customizeCompression(Connector connector) { - ProtocolHandler handler = connector.getProtocolHandler(); - if (handler instanceof AbstractHttp11Protocol) { - AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; - Compression compression = getCompression(); - protocol.setCompression("on"); - protocol.setCompressionMinSize(compression.getMinResponseSize()); - protocol.setCompressibleMimeType( - StringUtils.arrayToCommaDelimitedString(compression.getMimeTypes())); - if (getCompression().getExcludedUserAgents() != null) { - protocol.setNoCompressionUserAgents( - StringUtils.arrayToCommaDelimitedString( - getCompression().getExcludedUserAgents())); - } - } - } - - /** - * Configure Tomcat's {@link AbstractHttp11JsseProtocol} for SSL. - * @param protocol the protocol - * @param ssl the ssl details - */ - protected void configureSsl(AbstractHttp11JsseProtocol protocol, Ssl ssl) { - protocol.setSSLEnabled(true); - protocol.setSslProtocol(ssl.getProtocol()); - configureSslClientAuth(protocol, ssl); - protocol.setKeystorePass(ssl.getKeyStorePassword()); - protocol.setKeyPass(ssl.getKeyPassword()); - protocol.setKeyAlias(ssl.getKeyAlias()); - String ciphers = StringUtils.arrayToCommaDelimitedString(ssl.getCiphers()); - if (StringUtils.hasText(ciphers)) { - protocol.setCiphers(ciphers); - } - if (ssl.getEnabledProtocols() != null) { - for (SSLHostConfig sslHostConfig : protocol.findSslHostConfigs()) { - sslHostConfig.setProtocols(StringUtils - .arrayToCommaDelimitedString(ssl.getEnabledProtocols())); - } - } - if (getSslStoreProvider() != null) { - TomcatURLStreamHandlerFactory instance = TomcatURLStreamHandlerFactory - .getInstance(); - instance.addUserFactory( - new SslStoreProviderUrlStreamHandlerFactory(getSslStoreProvider())); - protocol.setKeystoreFile( - SslStoreProviderUrlStreamHandlerFactory.KEY_STORE_URL); - protocol.setTruststoreFile( - SslStoreProviderUrlStreamHandlerFactory.TRUST_STORE_URL); - } - else { - configureSslKeyStore(protocol, ssl); - configureSslTrustStore(protocol, ssl); - } - } - - private void configureSslClientAuth(AbstractHttp11JsseProtocol protocol, Ssl ssl) { - if (ssl.getClientAuth() == ClientAuth.NEED) { - protocol.setClientAuth(Boolean.TRUE.toString()); - } - else if (ssl.getClientAuth() == ClientAuth.WANT) { - protocol.setClientAuth("want"); - } - } - - protected void configureSslStoreProvider(AbstractHttp11JsseProtocol protocol, - SslStoreProvider sslStoreProvider) { - Assert.isInstanceOf(Http11NioProtocol.class, protocol, - "SslStoreProvider can only be used with Http11NioProtocol"); - } - - private void configureSslKeyStore(AbstractHttp11JsseProtocol protocol, Ssl ssl) { - try { - protocol.setKeystoreFile(ResourceUtils.getURL(ssl.getKeyStore()).toString()); - } - catch (FileNotFoundException ex) { - throw new WebServerException("Could not load key store: " + ex.getMessage(), - ex); - } - if (ssl.getKeyStoreType() != null) { - protocol.setKeystoreType(ssl.getKeyStoreType()); - } - if (ssl.getKeyStoreProvider() != null) { - protocol.setKeystoreProvider(ssl.getKeyStoreProvider()); - } - } - - private void configureSslTrustStore(AbstractHttp11JsseProtocol protocol, Ssl ssl) { - - if (ssl.getTrustStore() != null) { - try { - protocol.setTruststoreFile( - ResourceUtils.getURL(ssl.getTrustStore()).toString()); - } - catch (FileNotFoundException ex) { - throw new WebServerException( - "Could not load trust store: " + ex.getMessage(), ex); - } - } - protocol.setTruststorePass(ssl.getTrustStorePassword()); - if (ssl.getTrustStoreType() != null) { - protocol.setTruststoreType(ssl.getTrustStoreType()); - } - if (ssl.getTrustStoreProvider() != null) { - protocol.setTruststoreProvider(ssl.getTrustStoreProvider()); - } - } - /** * Configure the Tomcat {@link Context}. * @param context the Tomcat context diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizer.java new file mode 100644 index 0000000000..964389638e --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizer.java @@ -0,0 +1,250 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.embedded.undertow; + +import java.net.InetAddress; +import java.net.Socket; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; + +import io.undertow.Undertow; +import org.xnio.Options; +import org.xnio.Sequence; +import org.xnio.SslClientAuthMode; + +import org.springframework.boot.web.server.Ssl; +import org.springframework.boot.web.server.SslStoreProvider; +import org.springframework.util.ResourceUtils; + +/** + * {@link UndertowBuilderCustomizer} that configures SSL + * on the given builder instance. + * + * @author Brian Clozel + */ +class SslBuilderCustomizer implements UndertowBuilderCustomizer { + + private final int port; + + private final InetAddress address; + + private final Ssl ssl; + + private final SslStoreProvider sslStoreProvider; + + SslBuilderCustomizer(int port, InetAddress address, + Ssl ssl, SslStoreProvider sslStoreProvider) { + this.port = port; + this.address = address; + this.ssl = ssl; + this.sslStoreProvider = sslStoreProvider; + } + + @Override + public void customize(Undertow.Builder builder) { + try { + SSLContext sslContext = SSLContext.getInstance(this.ssl.getProtocol()); + sslContext.init(getKeyManagers(this.ssl, this.sslStoreProvider), + getTrustManagers(this.ssl, this.sslStoreProvider), null); + builder.addHttpsListener(this.port, getListenAddress(this.address), sslContext); + builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, + getSslClientAuthMode(this.ssl)); + if (this.ssl.getEnabledProtocols() != null) { + builder.setSocketOption(Options.SSL_ENABLED_PROTOCOLS, + Sequence.of(this.ssl.getEnabledProtocols())); + } + if (this.ssl.getCiphers() != null) { + builder.setSocketOption(Options.SSL_ENABLED_CIPHER_SUITES, + Sequence.of(this.ssl.getCiphers())); + } + } + catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException(ex); + } + catch (KeyManagementException ex) { + throw new IllegalStateException(ex); + } + } + + private String getListenAddress(InetAddress address) { + if (this.address == null) { + return "0.0.0.0"; + } + return this.address.getHostAddress(); + } + + private SslClientAuthMode getSslClientAuthMode(Ssl ssl) { + if (ssl.getClientAuth() == Ssl.ClientAuth.NEED) { + return SslClientAuthMode.REQUIRED; + } + if (ssl.getClientAuth() == Ssl.ClientAuth.WANT) { + return SslClientAuthMode.REQUESTED; + } + return SslClientAuthMode.NOT_REQUESTED; + } + + private KeyManager[] getKeyManagers(Ssl ssl, SslStoreProvider sslStoreProvider) { + try { + KeyStore keyStore = getKeyStore(ssl, sslStoreProvider); + KeyManagerFactory keyManagerFactory = KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()); + char[] keyPassword = (ssl.getKeyPassword() != null + ? ssl.getKeyPassword().toCharArray() : null); + if (keyPassword == null && ssl.getKeyStorePassword() != null) { + keyPassword = ssl.getKeyStorePassword().toCharArray(); + } + keyManagerFactory.init(keyStore, keyPassword); + if (ssl.getKeyAlias() != null) { + return getConfigurableAliasKeyManagers(ssl, + keyManagerFactory.getKeyManagers()); + } + return keyManagerFactory.getKeyManagers(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + private KeyManager[] getConfigurableAliasKeyManagers(Ssl ssl, + KeyManager[] keyManagers) { + for (int i = 0; i < keyManagers.length; i++) { + if (keyManagers[i] instanceof X509ExtendedKeyManager) { + keyManagers[i] = new ConfigurableAliasKeyManager( + (X509ExtendedKeyManager) keyManagers[i], ssl.getKeyAlias()); + } + } + return keyManagers; + } + + private KeyStore getKeyStore(Ssl ssl, SslStoreProvider sslStoreProvider) throws Exception { + if (sslStoreProvider != null) { + return sslStoreProvider.getKeyStore(); + } + return loadKeyStore(ssl.getKeyStoreType(), ssl.getKeyStore(), + ssl.getKeyStorePassword()); + } + + private TrustManager[] getTrustManagers(Ssl ssl, SslStoreProvider sslStoreProvider) { + try { + KeyStore store = getTrustStore(ssl, sslStoreProvider); + TrustManagerFactory trustManagerFactory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(store); + return trustManagerFactory.getTrustManagers(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + private KeyStore getTrustStore(Ssl ssl, SslStoreProvider sslStoreProvider) throws Exception { + if (sslStoreProvider != null) { + return sslStoreProvider.getTrustStore(); + } + return loadKeyStore(ssl.getTrustStoreType(), ssl.getTrustStore(), + ssl.getTrustStorePassword()); + } + + private KeyStore loadKeyStore(String type, String resource, String password) + throws Exception { + type = (type == null ? "JKS" : type); + if (resource == null) { + return null; + } + KeyStore store = KeyStore.getInstance(type); + URL url = ResourceUtils.getURL(resource); + store.load(url.openStream(), password == null ? null : password.toCharArray()); + return store; + } + + /** + * {@link X509ExtendedKeyManager} that supports custom alias configuration. + */ + private static class ConfigurableAliasKeyManager extends X509ExtendedKeyManager { + + private final X509ExtendedKeyManager keyManager; + + private final String alias; + + ConfigurableAliasKeyManager(X509ExtendedKeyManager keyManager, String alias) { + this.keyManager = keyManager; + this.alias = alias; + } + + @Override + public String chooseEngineClientAlias(String[] strings, Principal[] principals, + SSLEngine sslEngine) { + return this.keyManager.chooseEngineClientAlias(strings, principals, + sslEngine); + } + + @Override + public String chooseEngineServerAlias(String s, Principal[] principals, + SSLEngine sslEngine) { + if (this.alias == null) { + return this.keyManager.chooseEngineServerAlias(s, principals, sslEngine); + } + return this.alias; + } + + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, + Socket socket) { + return this.keyManager.chooseClientAlias(keyType, issuers, socket); + } + + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, + Socket socket) { + return this.keyManager.chooseServerAlias(keyType, issuers, socket); + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + return this.keyManager.getCertificateChain(alias); + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return this.keyManager.getClientAliases(keyType, issuers); + } + + @Override + public PrivateKey getPrivateKey(String alias) { + return this.keyManager.getPrivateKey(alias); + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return this.keyManager.getServerAliases(keyType, issuers); + } + + } +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowReactiveWebServerFactory.java index de14b42dd5..07e33a5f1f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowReactiveWebServerFactory.java @@ -85,7 +85,14 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF if (this.directBuffers != null) { builder.setDirectBuffers(this.directBuffers); } - builder.addHttpListener(port, getListenAddress()); + if (getSsl() != null && getSsl().isEnabled()) { + SslBuilderCustomizer sslBuilderCustomizer = + new SslBuilderCustomizer(getPort(), getAddress(), getSsl(), getSslStoreProvider()); + sslBuilderCustomizer.customize(builder); + } + else { + builder.addHttpListener(port, getListenAddress()); + } for (UndertowBuilderCustomizer customizer : this.builderCustomizers) { customizer.customize(builder); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactory.java index d21cf08797..609ad8747f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactory.java @@ -19,15 +19,8 @@ package org.springframework.boot.web.embedded.undertow; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; -import java.net.Socket; import java.net.URL; import java.nio.charset.Charset; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.NoSuchAlgorithmException; -import java.security.Principal; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -37,13 +30,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedKeyManager; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -70,15 +56,11 @@ 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; import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.MimeMappings.Mapping; -import org.springframework.boot.web.server.Ssl; -import org.springframework.boot.web.server.Ssl.ClientAuth; import org.springframework.boot.web.server.WebServer; import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory; @@ -86,7 +68,6 @@ import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; -import org.springframework.util.ResourceUtils; /** * {@link ServletWebServerFactory} that can be used to create @@ -251,7 +232,9 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac builder.setDirectBuffers(this.directBuffers); } if (getSsl() != null && getSsl().isEnabled()) { - configureSsl(getSsl(), port, builder); + SslBuilderCustomizer sslBuilderCustomizer = + new SslBuilderCustomizer(getPort(), getAddress(), getSsl(), getSslStoreProvider()); + sslBuilderCustomizer.customize(builder); } else { builder.addHttpListener(port, getListenAddress()); @@ -262,30 +245,6 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac return builder; } - private void configureSsl(Ssl ssl, int port, Builder builder) { - try { - 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)); - if (ssl.getEnabledProtocols() != null) { - builder.setSocketOption(Options.SSL_ENABLED_PROTOCOLS, - Sequence.of(ssl.getEnabledProtocols())); - } - if (ssl.getCiphers() != null) { - builder.setSocketOption(Options.SSL_ENABLED_CIPHER_SUITES, - Sequence.of(ssl.getCiphers())); - } - } - catch (NoSuchAlgorithmException ex) { - throw new IllegalStateException(ex); - } - catch (KeyManagementException ex) { - throw new IllegalStateException(ex); - } - } - private String getListenAddress() { if (getAddress() == null) { return "0.0.0.0"; @@ -293,93 +252,6 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac return getAddress().getHostAddress(); } - private SslClientAuthMode getSslClientAuthMode(Ssl ssl) { - if (ssl.getClientAuth() == ClientAuth.NEED) { - return SslClientAuthMode.REQUIRED; - } - if (ssl.getClientAuth() == ClientAuth.WANT) { - return SslClientAuthMode.REQUESTED; - } - return SslClientAuthMode.NOT_REQUESTED; - } - - private KeyManager[] getKeyManagers() { - try { - KeyStore keyStore = getKeyStore(); - KeyManagerFactory keyManagerFactory = KeyManagerFactory - .getInstance(KeyManagerFactory.getDefaultAlgorithm()); - Ssl ssl = getSsl(); - char[] keyPassword = (ssl.getKeyPassword() != null - ? ssl.getKeyPassword().toCharArray() : null); - if (keyPassword == null && ssl.getKeyStorePassword() != null) { - keyPassword = ssl.getKeyStorePassword().toCharArray(); - } - keyManagerFactory.init(keyStore, keyPassword); - if (ssl.getKeyAlias() != null) { - return getConfigurableAliasKeyManagers(ssl, - keyManagerFactory.getKeyManagers()); - } - return keyManagerFactory.getKeyManagers(); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - private KeyManager[] getConfigurableAliasKeyManagers(Ssl ssl, - KeyManager[] keyManagers) { - for (int i = 0; i < keyManagers.length; i++) { - if (keyManagers[i] instanceof X509ExtendedKeyManager) { - keyManagers[i] = new ConfigurableAliasKeyManager( - (X509ExtendedKeyManager) keyManagers[i], ssl.getKeyAlias()); - } - } - return keyManagers; - } - - private KeyStore getKeyStore() throws Exception { - if (getSslStoreProvider() != null) { - return getSslStoreProvider().getKeyStore(); - } - Ssl ssl = getSsl(); - return loadKeyStore(ssl.getKeyStoreType(), ssl.getKeyStore(), - ssl.getKeyStorePassword()); - } - - private TrustManager[] getTrustManagers() { - try { - KeyStore store = getTrustStore(); - TrustManagerFactory trustManagerFactory = TrustManagerFactory - .getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(store); - return trustManagerFactory.getTrustManagers(); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - private KeyStore getTrustStore() throws Exception { - if (getSslStoreProvider() != null) { - return getSslStoreProvider().getTrustStore(); - } - Ssl ssl = getSsl(); - return loadKeyStore(ssl.getTrustStoreType(), ssl.getTrustStore(), - ssl.getTrustStorePassword()); - } - - private KeyStore loadKeyStore(String type, String resource, String password) - throws Exception { - type = (type == null ? "JKS" : type); - if (resource == null) { - return null; - } - KeyStore store = KeyStore.getInstance(type); - URL url = ResourceUtils.getURL(resource); - store.load(url.openStream(), password == null ? null : password.toCharArray()); - return store; - } - private DeploymentManager createDeploymentManager( ServletContextInitializer... initializers) { DeploymentInfo deployment = Servlets.deployment(); @@ -726,69 +598,6 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac } } - /** - * {@link X509ExtendedKeyManager} that supports custom alias configuration. - */ - private static class ConfigurableAliasKeyManager extends X509ExtendedKeyManager { - - private final X509ExtendedKeyManager keyManager; - - private final String alias; - - ConfigurableAliasKeyManager(X509ExtendedKeyManager keyManager, String alias) { - this.keyManager = keyManager; - this.alias = alias; - } - - @Override - public String chooseEngineClientAlias(String[] strings, Principal[] principals, - SSLEngine sslEngine) { - return this.keyManager.chooseEngineClientAlias(strings, principals, - sslEngine); - } - - @Override - public String chooseEngineServerAlias(String s, Principal[] principals, - SSLEngine sslEngine) { - if (this.alias == null) { - return this.keyManager.chooseEngineServerAlias(s, principals, sslEngine); - } - return this.alias; - } - - @Override - public String chooseClientAlias(String[] keyType, Principal[] issuers, - Socket socket) { - return this.keyManager.chooseClientAlias(keyType, issuers, socket); - } - - @Override - public String chooseServerAlias(String keyType, Principal[] issuers, - Socket socket) { - return this.keyManager.chooseServerAlias(keyType, issuers, socket); - } - - @Override - public X509Certificate[] getCertificateChain(String alias) { - return this.keyManager.getCertificateChain(alias); - } - - @Override - public String[] getClientAliases(String keyType, Principal[] issuers) { - return this.keyManager.getClientAliases(keyType, issuers); - } - - @Override - public PrivateKey getPrivateKey(String alias) { - return this.keyManager.getPrivateKey(alias); - } - - @Override - public String[] getServerAliases(String keyType, Principal[] issuers) { - return this.keyManager.getServerAliases(keyType, issuers); - } - - } private static final class LoaderHidingResourceManager implements ResourceManager { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizerTests.java new file mode 100644 index 0000000000..655797f3f7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizerTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.embedded.tomcat; + +import org.apache.catalina.connector.Connector; +import org.apache.catalina.startup.Tomcat; +import org.apache.tomcat.util.net.SSLHostConfig; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.boot.web.server.Ssl; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SslConnectorCustomizer} + * + * @author Brian Clozel + */ +public class SslConnectorCustomizerTests { + + private Tomcat tomcat; + + private Connector connector; + + @Before + public void setup() throws Exception { + this.tomcat = new Tomcat(); + this.connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); + this.connector.setPort(0); + this.tomcat.setConnector(this.connector); + } + + @After + public void stop() throws Exception { + this.tomcat.stop(); + } + + @Test + public void sslCiphersConfiguration() throws Exception { + Ssl ssl = new Ssl(); + ssl.setKeyStore("test.jks"); + ssl.setKeyStorePassword("secret"); + ssl.setCiphers(new String[] {"ALPHA", "BRAVO", "CHARLIE"}); + + SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, null); + Connector connector = this.tomcat.getConnector(); + customizer.customize(connector); + this.tomcat.start(); + + SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler() + .findSslHostConfigs(); + assertThat(sslHostConfigs[0].getCiphers()).isEqualTo("ALPHA:BRAVO:CHARLIE"); + } + + @Test + public void sslEnabledMultipleProtocolsConfiguration() throws Exception { + Ssl ssl = new Ssl(); + ssl.setKeyPassword("password"); + ssl.setKeyStore("src/test/resources/test.jks"); + + ssl.setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"}); + ssl.setCiphers(new String[] {"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO"}); + + SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, null); + Connector connector = this.tomcat.getConnector(); + customizer.customize(connector); + this.tomcat.start(); + + SSLHostConfig sslHostConfig = connector.getProtocolHandler() + .findSslHostConfigs()[0]; + assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS"); + assertThat(sslHostConfig.getEnabledProtocols()) + .containsExactlyInAnyOrder("TLSv1.1", "TLSv1.2"); + } + + @Test + public void sslEnabledProtocolsConfiguration() throws Exception { + Ssl ssl = new Ssl(); + ssl.setKeyPassword("password"); + ssl.setKeyStore("src/test/resources/test.jks"); + + ssl.setEnabledProtocols(new String[] {"TLSv1.2"}); + ssl.setCiphers(new String[] {"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO"}); + + SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, null); + Connector connector = this.tomcat.getConnector(); + customizer.customize(connector); + this.tomcat.start(); + + SSLHostConfig sslHostConfig = connector.getProtocolHandler() + .findSslHostConfigs()[0]; + assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS"); + assertThat(sslHostConfig.getEnabledProtocols()).containsExactly("TLSv1.2"); + } +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java index d1fdb47557..1fe22f331a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java @@ -43,14 +43,12 @@ import org.apache.catalina.startup.Tomcat; import org.apache.catalina.util.CharsetMapper; import org.apache.catalina.valves.RemoteIpValve; import org.apache.jasper.servlet.JspServlet; -import org.apache.tomcat.util.net.SSLHostConfig; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.mockito.InOrder; import org.springframework.boot.testsupport.rule.OutputCapture; -import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.server.WebServerException; import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory; import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactoryTests; @@ -265,63 +263,6 @@ public class TomcatServletWebServerFactoryTests assertThat(connector.getURIEncoding()).isEqualTo("UTF-8"); } - @Test - public void sslCiphersConfiguration() throws Exception { - Ssl ssl = new Ssl(); - ssl.setKeyStore("test.jks"); - ssl.setKeyStorePassword("secret"); - ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" }); - TomcatServletWebServerFactory factory = getFactory(); - factory.setSsl(ssl); - - Tomcat tomcat = getTomcat(factory); - Connector connector = ((TomcatWebServer) this.webServer).getServiceConnectors() - .get(tomcat.getService())[0]; - SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler() - .findSslHostConfigs(); - assertThat(sslHostConfigs[0].getCiphers()).isEqualTo("ALPHA:BRAVO:CHARLIE"); - } - - @Test - public void sslEnabledMultipleProtocolsConfiguration() throws Exception { - Ssl ssl = getSsl(null, "password", "src/test/resources/test.jks"); - ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" }); - ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" }); - - TomcatServletWebServerFactory factory = getFactory(); - factory.setSsl(ssl); - - this.webServer = factory.getWebServer(sessionServletRegistration()); - this.webServer.start(); - Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat(); - Connector connector = tomcat.getConnector(); - - SSLHostConfig sslHostConfig = connector.getProtocolHandler() - .findSslHostConfigs()[0]; - assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS"); - assertThat(sslHostConfig.getEnabledProtocols()) - .containsExactlyInAnyOrder("TLSv1.1", "TLSv1.2"); - } - - @Test - public void sslEnabledProtocolsConfiguration() throws Exception { - Ssl ssl = getSsl(null, "password", "src/test/resources/test.jks"); - ssl.setEnabledProtocols(new String[] { "TLSv1.2" }); - ssl.setCiphers(new String[] { "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "BRAVO" }); - - TomcatServletWebServerFactory factory = getFactory(); - factory.setSsl(ssl); - - this.webServer = factory.getWebServer(sessionServletRegistration()); - Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat(); - this.webServer.start(); - Connector connector = tomcat.getConnector(); - SSLHostConfig sslHostConfig = connector.getProtocolHandler() - .findSslHostConfigs()[0]; - assertThat(sslHostConfig.getSslProtocol()).isEqualTo("TLS"); - assertThat(sslHostConfig.getEnabledProtocols()).containsExactly("TLSv1.2"); - } - @Test public void primaryConnectorPortClashThrowsIllegalStateException() throws InterruptedException, IOException { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizerTests.java new file mode 100644 index 0000000000..55401eeeb1 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizerTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.embedded.undertow; + +import java.net.InetAddress; + +import javax.net.ssl.KeyManager; + +import org.junit.Test; + +import org.springframework.boot.web.server.Ssl; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SslBuilderCustomizer} + * + * @author Brian Clozel + */ +public class SslBuilderCustomizerTests { + + @Test + public void getKeyManagersWhenAliasIsNullShouldNotDecorate() throws Exception { + Ssl ssl = new Ssl(); + ssl.setKeyPassword("password"); + ssl.setKeyStore("src/test/resources/test.jks"); + SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null); + KeyManager[] keyManagers = ReflectionTestUtils.invokeMethod(customizer, + "getKeyManagers", ssl, null); + Class name = Class.forName("org.springframework.boot.web.embedded.undertow" + + ".SslBuilderCustomizer$ConfigurableAliasKeyManager"); + assertThat(keyManagers[0]).isNotInstanceOf(name); + } +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java index 324e32edab..e7ed1f3319 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServerFactoryTests.java @@ -28,7 +28,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -import javax.net.ssl.KeyManager; import javax.net.ssl.SSLHandshakeException; import io.undertow.Undertow.Builder; @@ -43,7 +42,6 @@ import org.springframework.boot.testsupport.web.servlet.ExampleServlet; import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.MimeMappings.Mapping; import org.springframework.boot.web.server.PortInUseException; -import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory; import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactoryTests; @@ -239,18 +237,6 @@ public class UndertowServletWebServerFactoryTests new String[] { "TLS_RSA_WITH_AES_128_CBC_SHA256" }); } - @Test - public void getKeyManagersWhenAliasIsNullShouldNotDecorate() throws Exception { - UndertowServletWebServerFactory factory = getFactory(); - Ssl ssl = getSsl(null, "password", "src/test/resources/test.jks"); - factory.setSsl(ssl); - KeyManager[] keyManagers = ReflectionTestUtils.invokeMethod(factory, - "getKeyManagers"); - Class name = Class.forName("org.springframework.boot.web.embedded.undertow" - + ".UndertowServletWebServerFactory$ConfigurableAliasKeyManager"); - assertThat(keyManagers[0]).isNotInstanceOf(name); - } - @Override protected JspServlet getJspServlet() { return null; // Undertow does not support JSPs diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java index 2c51c47c6f..bd6b0e30ba 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.web.reactive.server; +import io.netty.handler.ssl.SslProvider; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.junit.After; import org.junit.Rule; import org.junit.Test; @@ -24,9 +26,11 @@ import org.junit.rules.TemporaryFolder; import reactor.core.publisher.Mono; import org.springframework.boot.testsupport.rule.OutputCapture; +import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.server.WebServer; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -75,7 +79,7 @@ public abstract class AbstractReactiveWebServerFactoryTests { factory.setPort(specificPort); this.webServer = factory.getWebServer(new EchoHandler()); this.webServer.start(); - Mono result = WebClient.create("http://localhost:" + specificPort).post() + Mono result = getWebClient().build().post() .uri("/test").contentType(MediaType.TEXT_PLAIN) .body(BodyInserters.fromObject("Hello World")).exchange() .flatMap((response) -> response.bodyToMono(String.class)); @@ -83,8 +87,49 @@ public abstract class AbstractReactiveWebServerFactoryTests { assertThat(this.webServer.getPort()).isEqualTo(specificPort); } - protected WebClient getWebClient() { - return WebClient.create("http://localhost:" + this.webServer.getPort()); + @Test + public void basicSslFromClassPath() throws Exception { + testBasicSslWithKeyStore("classpath:test.jks"); + } + + @Test + public void basicSslFromFileSystem() throws Exception { + testBasicSslWithKeyStore("src/test/resources/test.jks"); + } + + + protected final void testBasicSslWithKeyStore(String keyStore) throws Exception { + AbstractReactiveWebServerFactory factory = getFactory(); + Ssl ssl = new Ssl(); + ssl.setKeyStore(keyStore); + ssl.setKeyPassword("password"); + factory.setSsl(ssl); + this.webServer = factory.getWebServer(new EchoHandler()); + this.webServer.start(); + + ReactorClientHttpConnector connector = buildTrustAllSslConnector(); + WebClient client = WebClient.builder() + .baseUrl("https://localhost:" + this.webServer.getPort()) + .clientConnector(connector).build(); + + Mono result = client.post() + .uri("/test").contentType(MediaType.TEXT_PLAIN) + .body(BodyInserters.fromObject("Hello World")).exchange() + .flatMap((response) -> response.bodyToMono(String.class)); + assertThat(result.block()).isEqualTo("Hello World"); + } + + protected ReactorClientHttpConnector buildTrustAllSslConnector() { + return new ReactorClientHttpConnector(options -> options + .sslSupport(sslContextBuilder -> { + sslContextBuilder + .sslProvider(SslProvider.JDK) + .trustManager(InsecureTrustManagerFactory.INSTANCE); + })); + } + + protected WebClient.Builder getWebClient() { + return WebClient.builder().baseUrl("http://localhost:" + this.webServer.getPort()); } protected static class EchoHandler implements HttpHandler {