Refactor web server support to use SslBundles
Update Tomcat, Jetty, Undertow and Netty servers so that an SslBundle is used to apply SSL configuration. Existing `Ssl` properties are internally adapted to an `SslBundle` using the `WebServerSslBundle` class. Additionally, if `Ssl.getBundle()` returns a non-null value the the `SslBundles` bean will be used to find a registered bundle by name. See gh-34814
This commit is contained in:
parent
8e1f24f98f
commit
66db13b962
|
@ -2064,6 +2064,10 @@
|
|||
"level": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "management.server.ssl.bundle",
|
||||
"description": "The name of a configured SSL bundle."
|
||||
},
|
||||
{
|
||||
"name": "management.server.ssl.certificate",
|
||||
"description": "Path to a PEM-encoded SSL certificate file."
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.springframework.boot.rsocket.context.RSocketServerBootstrap;
|
|||
import org.springframework.boot.rsocket.netty.NettyRSocketServerFactory;
|
||||
import org.springframework.boot.rsocket.server.RSocketServerCustomizer;
|
||||
import org.springframework.boot.rsocket.server.RSocketServerFactory;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
@ -54,6 +55,7 @@ import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHa
|
|||
* server port is configured, a new standalone RSocket server is created.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@AutoConfiguration(after = RSocketStrategiesAutoConfiguration.class)
|
||||
|
@ -85,7 +87,7 @@ public class RSocketServerAutoConfiguration {
|
|||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
RSocketServerFactory rSocketServerFactory(RSocketProperties properties, ReactorResourceFactory resourceFactory,
|
||||
ObjectProvider<RSocketServerCustomizer> customizers) {
|
||||
ObjectProvider<RSocketServerCustomizer> customizers, ObjectProvider<SslBundles> sslBundles) {
|
||||
NettyRSocketServerFactory factory = new NettyRSocketServerFactory();
|
||||
factory.setResourceFactory(resourceFactory);
|
||||
factory.setTransport(properties.getServer().getTransport());
|
||||
|
@ -94,6 +96,7 @@ public class RSocketServerAutoConfiguration {
|
|||
map.from(properties.getServer().getPort()).to(factory::setPort);
|
||||
map.from(properties.getServer().getFragmentSize()).to(factory::setFragmentSize);
|
||||
map.from(properties.getServer().getSsl()).to(factory::setSsl);
|
||||
factory.setSslBundles(sslBundles.getIfAvailable());
|
||||
factory.setRSocketServerCustomizers(customizers.orderedStream().toList());
|
||||
return factory;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.web.reactive;
|
|||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
|
@ -31,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
@ -45,6 +47,7 @@ import org.springframework.web.server.adapter.ForwardedHeaderTransformer;
|
|||
* {@link EnableAutoConfiguration Auto-configuration} for a reactive web server.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
|
||||
|
@ -60,8 +63,9 @@ import org.springframework.web.server.adapter.ForwardedHeaderTransformer;
|
|||
public class ReactiveWebServerFactoryAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public ReactiveWebServerFactoryCustomizer reactiveWebServerFactoryCustomizer(ServerProperties serverProperties) {
|
||||
return new ReactiveWebServerFactoryCustomizer(serverProperties);
|
||||
public ReactiveWebServerFactoryCustomizer reactiveWebServerFactoryCustomizer(ServerProperties serverProperties,
|
||||
ObjectProvider<SslBundles> sslBundles) {
|
||||
return new ReactiveWebServerFactoryCustomizer(serverProperties, sslBundles.getIfAvailable());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.web.reactive;
|
|||
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.core.Ordered;
|
||||
|
@ -28,6 +29,7 @@ import org.springframework.core.Ordered;
|
|||
*
|
||||
* @author Brian Clozel
|
||||
* @author Yunkun Huang
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ReactiveWebServerFactoryCustomizer
|
||||
|
@ -35,8 +37,25 @@ public class ReactiveWebServerFactoryCustomizer
|
|||
|
||||
private final ServerProperties serverProperties;
|
||||
|
||||
private final SslBundles sslBundles;
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveWebServerFactoryCustomizer} instance.
|
||||
* @param serverProperties the server properties
|
||||
*/
|
||||
public ReactiveWebServerFactoryCustomizer(ServerProperties serverProperties) {
|
||||
this(serverProperties, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveWebServerFactoryCustomizer} instance.
|
||||
* @param serverProperties the server properties
|
||||
* @param sslBundles the SSL bundles
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public ReactiveWebServerFactoryCustomizer(ServerProperties serverProperties, SslBundles sslBundles) {
|
||||
this.serverProperties = serverProperties;
|
||||
this.sslBundles = sslBundles;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,6 +72,7 @@ public class ReactiveWebServerFactoryCustomizer
|
|||
map.from(this.serverProperties::getCompression).to(factory::setCompression);
|
||||
map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
|
||||
map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
|
||||
map.from(() -> this.sslBundles).to(factory::setSslBundles);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,8 +33,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
|
@ -57,9 +59,10 @@ import org.springframework.web.filter.ForwardedHeaderFilter;
|
|||
* @author Ivan Sopov
|
||||
* @author Brian Clozel
|
||||
* @author Stephane Nicoll
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@AutoConfiguration(after = SslAutoConfiguration.class)
|
||||
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
|
||||
@ConditionalOnClass(ServletRequest.class)
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
|
@ -73,9 +76,9 @@ public class ServletWebServerFactoryAutoConfiguration {
|
|||
@Bean
|
||||
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
|
||||
ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
|
||||
ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
|
||||
ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers, ObjectProvider<SslBundles> sslBundles) {
|
||||
return new ServletWebServerFactoryCustomizer(serverProperties, webListenerRegistrars.orderedStream().toList(),
|
||||
cookieSameSiteSuppliers.orderedStream().toList());
|
||||
cookieSameSiteSuppliers.orderedStream().toList(), sslBundles.getIfAvailable());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -21,6 +21,7 @@ import java.util.List;
|
|||
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.boot.web.servlet.WebListenerRegistrar;
|
||||
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
|
||||
|
@ -36,6 +37,7 @@ import org.springframework.util.CollectionUtils;
|
|||
* @author Stephane Nicoll
|
||||
* @author Olivier Lamy
|
||||
* @author Yunkun Huang
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ServletWebServerFactoryCustomizer
|
||||
|
@ -47,20 +49,24 @@ public class ServletWebServerFactoryCustomizer
|
|||
|
||||
private final List<CookieSameSiteSupplier> cookieSameSiteSuppliers;
|
||||
|
||||
private final SslBundles sslBundles;
|
||||
|
||||
public ServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
|
||||
this(serverProperties, Collections.emptyList());
|
||||
}
|
||||
|
||||
public ServletWebServerFactoryCustomizer(ServerProperties serverProperties,
|
||||
List<WebListenerRegistrar> webListenerRegistrars) {
|
||||
this(serverProperties, webListenerRegistrars, null);
|
||||
this(serverProperties, webListenerRegistrars, null, null);
|
||||
}
|
||||
|
||||
ServletWebServerFactoryCustomizer(ServerProperties serverProperties,
|
||||
List<WebListenerRegistrar> webListenerRegistrars, List<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
|
||||
List<WebListenerRegistrar> webListenerRegistrars, List<CookieSameSiteSupplier> cookieSameSiteSuppliers,
|
||||
SslBundles sslBundles) {
|
||||
this.serverProperties = serverProperties;
|
||||
this.webListenerRegistrars = webListenerRegistrars;
|
||||
this.cookieSameSiteSuppliers = cookieSameSiteSuppliers;
|
||||
this.sslBundles = sslBundles;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,12 +90,11 @@ public class ServletWebServerFactoryCustomizer
|
|||
map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
|
||||
map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
|
||||
map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
|
||||
for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
|
||||
registrar.register(factory);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(this.cookieSameSiteSuppliers)) {
|
||||
factory.setCookieSameSiteSuppliers(this.cookieSameSiteSuppliers);
|
||||
}
|
||||
map.from(() -> this.sslBundles).to(factory::setSslBundles);
|
||||
map.from(() -> this.cookieSameSiteSuppliers)
|
||||
.whenNot(CollectionUtils::isEmpty)
|
||||
.to(factory::setCookieSameSiteSuppliers);
|
||||
this.webListenerRegistrars.forEach((registrar) -> registrar.register(factory));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2639,6 +2639,10 @@
|
|||
"level": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.rsocket.server.ssl.bundle",
|
||||
"description": "The name of a configured SSL bundle."
|
||||
},
|
||||
{
|
||||
"name": "spring.rsocket.server.ssl.certificate",
|
||||
"description": "Path to a PEM-encoded SSL certificate file."
|
||||
|
|
|
@ -16,13 +16,16 @@
|
|||
|
||||
package org.springframework.boot.autoconfigure.rsocket;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer;
|
||||
import org.springframework.boot.rsocket.context.RSocketServerBootstrap;
|
||||
import org.springframework.boot.rsocket.server.RSocketServerCustomizer;
|
||||
import org.springframework.boot.rsocket.server.RSocketServerFactory;
|
||||
import org.springframework.boot.ssl.NoSuchSslBundleException;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
|
@ -44,6 +47,7 @@ import static org.mockito.Mockito.mock;
|
|||
*
|
||||
* @author Brian Clozel
|
||||
* @author Verónica Vásquez
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class RSocketServerAutoConfigurationTests {
|
||||
|
||||
|
@ -134,6 +138,32 @@ class RSocketServerAutoConfigurationTests {
|
|||
.hasFieldOrPropertyWithValue("ssl.keyPassword", "password"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void shouldUseSslWhenRocketServerSslIsConfiguredWithSslBundle() {
|
||||
reactiveWebContextRunner()
|
||||
.withPropertyValues("spring.rsocket.server.port=0", "spring.rsocket.server.ssl.bundle=test-bundle",
|
||||
"spring.ssl.bundle.jks.test-bundle.keystore.location=classpath:rsocket/test.jks",
|
||||
"spring.ssl.bundle.jks.test-bundle.key.password=password")
|
||||
.run((context) -> assertThat(context).hasSingleBean(RSocketServerFactory.class)
|
||||
.hasSingleBean(RSocketServerBootstrap.class)
|
||||
.hasSingleBean(RSocketServerCustomizer.class)
|
||||
.getBean(RSocketServerFactory.class)
|
||||
.hasFieldOrPropertyWithValue("sslBundle.details.keyStore", "classpath:rsocket/test.jks")
|
||||
.hasFieldOrPropertyWithValue("sslBundle.details.keyPassword", "password"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWhenSslIsConfiguredWithMissingBundle() {
|
||||
reactiveWebContextRunner()
|
||||
.withPropertyValues("spring.rsocket.server.port=0", "spring.rsocket.server.ssl.bundle=test-bundle")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure()).hasRootCauseInstanceOf(NoSuchSslBundleException.class)
|
||||
.withFailMessage("SSL bundle name 'test-bundle' is not valid");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustomServerBootstrap() {
|
||||
contextRunner().withUserConfiguration(CustomServerBootstrapConfig.class)
|
||||
|
@ -164,7 +194,7 @@ class RSocketServerAutoConfigurationTests {
|
|||
|
||||
private ReactiveWebApplicationContextRunner reactiveWebContextRunner() {
|
||||
return new ReactiveWebApplicationContextRunner().withUserConfiguration(BaseConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(RSocketServerAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(RSocketServerAutoConfiguration.class, SslAutoConfiguration.class));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
|
|
@ -26,6 +26,8 @@ import org.junit.jupiter.api.Test;
|
|||
import reactor.netty.http.server.HttpServer;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.ssl.NoSuchSslBundleException;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories;
|
||||
|
@ -64,13 +66,15 @@ import static org.mockito.Mockito.mock;
|
|||
* @author Brian Clozel
|
||||
* @author Raheela Aslam
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@DirtiesUrlFactories
|
||||
class ReactiveWebServerFactoryAutoConfigurationTests {
|
||||
|
||||
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner(
|
||||
AnnotationConfigReactiveWebServerApplicationContext::new)
|
||||
.withConfiguration(AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class));
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class, SslAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void createFromConfigClass() {
|
||||
|
@ -118,6 +122,17 @@ class ReactiveWebServerFactoryAutoConfigurationTests {
|
|||
.isInstanceOf(TomcatReactiveWebServerFactory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void webServerFailsWithInvalidSslBundle() {
|
||||
this.contextRunner.withUserConfiguration(HttpHandlerConfiguration.class)
|
||||
.withPropertyValues("server.port=0", "server.ssl.bundle=test-bundle")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure().getCause()).isInstanceOf(NoSuchSslBundleException.class)
|
||||
.withFailMessage("test");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void tomcatConnectorCustomizerBeanIsAddedToFactory() {
|
||||
ReactiveWebApplicationContextRunner runner = new ReactiveWebApplicationContextRunner(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -23,6 +23,8 @@ import org.junit.jupiter.api.Test;
|
|||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.Shutdown;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
|
@ -36,16 +38,19 @@ import static org.mockito.Mockito.mock;
|
|||
*
|
||||
* @author Brian Clozel
|
||||
* @author Yunkun Huang
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class ReactiveWebServerFactoryCustomizerTests {
|
||||
|
||||
private final ServerProperties properties = new ServerProperties();
|
||||
|
||||
private final SslBundles sslBundles = new DefaultSslBundleRegistry();
|
||||
|
||||
private ReactiveWebServerFactoryCustomizer customizer;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.customizer = new ReactiveWebServerFactoryCustomizer(this.properties);
|
||||
this.customizer = new ReactiveWebServerFactoryCustomizer(this.properties, this.sslBundles);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -72,6 +77,7 @@ class ReactiveWebServerFactoryCustomizerTests {
|
|||
this.properties.setSsl(ssl);
|
||||
this.customizer.customize(factory);
|
||||
then(factory).should().setSsl(ssl);
|
||||
then(factory).should().setSslBundles(this.sslBundles);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -39,9 +39,12 @@ import org.springframework.boot.rsocket.server.ConfigurableRSocketServerFactory;
|
|||
import org.springframework.boot.rsocket.server.RSocketServer;
|
||||
import org.springframework.boot.rsocket.server.RSocketServerCustomizer;
|
||||
import org.springframework.boot.rsocket.server.RSocketServerFactory;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.web.embedded.netty.SslServerCustomizer;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.SslStoreProvider;
|
||||
import org.springframework.boot.web.server.SslStoreProviderFactory;
|
||||
import org.springframework.boot.web.server.WebServerSslBundle;
|
||||
import org.springframework.http.client.reactive.ReactorResourceFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
@ -55,6 +58,7 @@ import org.springframework.util.unit.DataSize;
|
|||
* @author Scott Frederick
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public class NettyRSocketServerFactory implements RSocketServerFactory, ConfigurableRSocketServerFactory {
|
||||
|
||||
private int port = 9898;
|
||||
|
@ -75,6 +79,8 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
|||
|
||||
private SslStoreProvider sslStoreProvider;
|
||||
|
||||
private SslBundles sslBundles;
|
||||
|
||||
@Override
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
|
@ -105,6 +111,11 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
|||
this.sslStoreProvider = sslStoreProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSslBundles(SslBundles sslBundles) {
|
||||
this.sslBundles = sslBundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ReactorResourceFactory} to get the shared resources from.
|
||||
* @param resourceFactory the server resources
|
||||
|
@ -172,17 +183,14 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
|||
if (this.resourceFactory != null) {
|
||||
httpServer = httpServer.runOn(this.resourceFactory.getLoopResources());
|
||||
}
|
||||
if (this.ssl != null && this.ssl.isEnabled()) {
|
||||
if (Ssl.isEnabled(this.ssl)) {
|
||||
httpServer = customizeSslConfiguration(httpServer);
|
||||
}
|
||||
return WebsocketServerTransport.create(httpServer.bindAddress(this::getListenAddress));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private HttpServer customizeSslConfiguration(HttpServer httpServer) {
|
||||
org.springframework.boot.web.embedded.netty.SslServerCustomizer sslServerCustomizer = new org.springframework.boot.web.embedded.netty.SslServerCustomizer(
|
||||
this.ssl, null, getOrCreateSslStoreProvider());
|
||||
return sslServerCustomizer.apply(httpServer);
|
||||
return new SslServerCustomizer(null, this.ssl.getClientAuth(), getSslBundle()).apply(httpServer);
|
||||
}
|
||||
|
||||
private ServerTransport<CloseableChannel> createTcpTransport() {
|
||||
|
@ -190,19 +198,15 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
|||
if (this.resourceFactory != null) {
|
||||
tcpServer = tcpServer.runOn(this.resourceFactory.getLoopResources());
|
||||
}
|
||||
if (this.ssl != null && this.ssl.isEnabled()) {
|
||||
TcpSslServerCustomizer sslServerCustomizer = new TcpSslServerCustomizer(this.ssl,
|
||||
getOrCreateSslStoreProvider());
|
||||
tcpServer = sslServerCustomizer.apply(tcpServer);
|
||||
if (Ssl.isEnabled(this.ssl)) {
|
||||
tcpServer = new TcpSslServerCustomizer(this.ssl.getClientAuth(), getSslBundle()).apply(tcpServer);
|
||||
}
|
||||
return TcpServerTransport.create(tcpServer.bindAddress(this::getListenAddress));
|
||||
}
|
||||
|
||||
private SslStoreProvider getOrCreateSslStoreProvider() {
|
||||
if (this.sslStoreProvider != null) {
|
||||
return this.sslStoreProvider;
|
||||
}
|
||||
return SslStoreProviderFactory.from(this.ssl);
|
||||
@SuppressWarnings("deprecation")
|
||||
private SslBundle getSslBundle() {
|
||||
return WebServerSslBundle.get(this.ssl, this.sslBundles, this.sslStoreProvider);
|
||||
}
|
||||
|
||||
private InetSocketAddress getListenAddress() {
|
||||
|
@ -212,12 +216,11 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
|
|||
return new InetSocketAddress(this.port);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static final class TcpSslServerCustomizer
|
||||
extends org.springframework.boot.web.embedded.netty.SslServerCustomizer {
|
||||
|
||||
private TcpSslServerCustomizer(Ssl ssl, SslStoreProvider sslStoreProvider) {
|
||||
super(ssl, null, sslStoreProvider);
|
||||
private TcpSslServerCustomizer(Ssl.ClientAuth clientAuth, SslBundle sslBundle) {
|
||||
super(null, clientAuth, sslBundle);
|
||||
}
|
||||
|
||||
private TcpServer apply(TcpServer server) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -18,6 +18,7 @@ package org.springframework.boot.rsocket.server;
|
|||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.SslStoreProvider;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
@ -26,6 +27,7 @@ import org.springframework.util.unit.DataSize;
|
|||
* A configurable {@link RSocketServerFactory}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public interface ConfigurableRSocketServerFactory {
|
||||
|
@ -66,7 +68,18 @@ public interface ConfigurableRSocketServerFactory {
|
|||
/**
|
||||
* Sets a provider that will be used to obtain SSL stores.
|
||||
* @param sslStoreProvider the SSL store provider
|
||||
* @deprecated since 3.1.0 for removal in 3.3.0 in favor of
|
||||
* {@link #setSslBundles(SslBundles)}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
void setSslStoreProvider(SslStoreProvider sslStoreProvider);
|
||||
|
||||
/**
|
||||
* Sets an SSL bundle that can be used to get SSL configuration.
|
||||
* @param sslBundles the SSL bundles
|
||||
* @since 3.1.0
|
||||
*/
|
||||
void setSslBundles(SslBundles sslBundles);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -43,6 +43,7 @@ import org.eclipse.jetty.util.thread.ThreadPool;
|
|||
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.Shutdown;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.http.client.reactive.JettyResourceFactory;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
|
@ -179,7 +180,7 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
|||
contextHandler.addServlet(servletHolder, "/");
|
||||
server.setHandler(addHandlerWrappers(contextHandler));
|
||||
JettyReactiveWebServerFactory.logger.info("Server initialized with port: " + port);
|
||||
if (getSsl() != null && getSsl().isEnabled()) {
|
||||
if (Ssl.isEnabled(getSsl())) {
|
||||
customizeSsl(server, address);
|
||||
}
|
||||
for (JettyServerCustomizer customizer : getServerCustomizers()) {
|
||||
|
@ -236,7 +237,7 @@ public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
|||
}
|
||||
|
||||
private void customizeSsl(Server server, InetSocketAddress address) {
|
||||
new SslServerCustomizer(address, getSsl(), getOrCreateSslStoreProvider(), getHttp2()).customize(server);
|
||||
new SslServerCustomizer(getHttp2(), address, getSsl().getClientAuth(), getSslBundle()).customize(server);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ import org.springframework.boot.web.server.Cookie.SameSite;
|
|||
import org.springframework.boot.web.server.ErrorPage;
|
||||
import org.springframework.boot.web.server.MimeMappings;
|
||||
import org.springframework.boot.web.server.Shutdown;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
|
||||
|
@ -162,7 +163,7 @@ 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()) {
|
||||
if (Ssl.isEnabled(getSsl())) {
|
||||
customizeSsl(server, address);
|
||||
}
|
||||
for (JettyServerCustomizer customizer : getServerCustomizers()) {
|
||||
|
@ -220,7 +221,7 @@ public class JettyServletWebServerFactory extends AbstractServletWebServerFactor
|
|||
}
|
||||
|
||||
private void customizeSsl(Server server, InetSocketAddress address) {
|
||||
new SslServerCustomizer(address, getSsl(), getOrCreateSslStoreProvider(), getHttp2()).customize(server);
|
||||
new SslServerCustomizer(getHttp2(), address, getSsl().getClientAuth(), getSslBundle()).customize(server);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -32,12 +32,15 @@ import org.eclipse.jetty.server.ServerConnector;
|
|||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundleKey;
|
||||
import org.springframework.boot.ssl.SslOptions;
|
||||
import org.springframework.boot.ssl.SslStoreBundle;
|
||||
import org.springframework.boot.web.server.Http2;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.SslConfigurationValidator;
|
||||
import org.springframework.boot.web.server.SslStoreProvider;
|
||||
import org.springframework.boot.web.server.Ssl.ClientAuth;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
|
@ -51,18 +54,18 @@ import org.springframework.util.ObjectUtils;
|
|||
*/
|
||||
class SslServerCustomizer implements JettyServerCustomizer {
|
||||
|
||||
private final InetSocketAddress address;
|
||||
|
||||
private final Ssl ssl;
|
||||
|
||||
private final SslStoreProvider sslStoreProvider;
|
||||
|
||||
private final Http2 http2;
|
||||
|
||||
SslServerCustomizer(InetSocketAddress address, Ssl ssl, SslStoreProvider sslStoreProvider, Http2 http2) {
|
||||
private final InetSocketAddress address;
|
||||
|
||||
private final ClientAuth clientAuth;
|
||||
|
||||
private final SslBundle sslBundle;
|
||||
|
||||
SslServerCustomizer(Http2 http2, InetSocketAddress address, ClientAuth clientAuth, SslBundle sslBundle) {
|
||||
this.address = address;
|
||||
this.ssl = ssl;
|
||||
this.sslStoreProvider = sslStoreProvider;
|
||||
this.clientAuth = clientAuth;
|
||||
this.sslBundle = sslBundle;
|
||||
this.http2 = http2;
|
||||
}
|
||||
|
||||
|
@ -70,41 +73,42 @@ class SslServerCustomizer implements JettyServerCustomizer {
|
|||
public void customize(Server server) {
|
||||
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||
sslContextFactory.setEndpointIdentificationAlgorithm(null);
|
||||
configureSsl(sslContextFactory, this.ssl, this.sslStoreProvider);
|
||||
ServerConnector connector = createConnector(server, sslContextFactory, this.address);
|
||||
configureSsl(sslContextFactory, this.clientAuth);
|
||||
ServerConnector connector = createConnector(server, sslContextFactory);
|
||||
server.setConnectors(new Connector[] { connector });
|
||||
}
|
||||
|
||||
private ServerConnector createConnector(Server server, SslContextFactory.Server sslContextFactory,
|
||||
InetSocketAddress address) {
|
||||
private ServerConnector createConnector(Server server, SslContextFactory.Server sslContextFactory) {
|
||||
HttpConfiguration config = new HttpConfiguration();
|
||||
config.setSendServerVersion(false);
|
||||
config.setSecureScheme("https");
|
||||
config.setSecurePort(address.getPort());
|
||||
config.setSecurePort(this.address.getPort());
|
||||
config.addCustomizer(new SecureRequestCustomizer());
|
||||
ServerConnector connector = createServerConnector(server, sslContextFactory, config);
|
||||
connector.setPort(address.getPort());
|
||||
connector.setHost(address.getHostString());
|
||||
connector.setPort(this.address.getPort());
|
||||
connector.setHost(this.address.getHostString());
|
||||
return connector;
|
||||
}
|
||||
|
||||
private ServerConnector createServerConnector(Server server, SslContextFactory.Server sslContextFactory,
|
||||
HttpConfiguration config) {
|
||||
if (this.http2 == null || !this.http2.isEnabled()) {
|
||||
return createHttp11ServerConnector(server, config, sslContextFactory);
|
||||
return createHttp11ServerConnector(config, sslContextFactory, server);
|
||||
}
|
||||
Assert.state(isJettyAlpnPresent(),
|
||||
() -> "An 'org.eclipse.jetty:jetty-alpn-*-server' dependency is required for HTTP/2 support.");
|
||||
Assert.state(isJettyHttp2Present(),
|
||||
() -> "The 'org.eclipse.jetty.http2:http2-server' dependency is required for HTTP/2 support.");
|
||||
return createHttp2ServerConnector(server, config, sslContextFactory);
|
||||
return createHttp2ServerConnector(config, sslContextFactory, server);
|
||||
}
|
||||
|
||||
private ServerConnector createHttp11ServerConnector(Server server, HttpConfiguration config,
|
||||
SslContextFactory.Server sslContextFactory) {
|
||||
private ServerConnector createHttp11ServerConnector(HttpConfiguration config,
|
||||
SslContextFactory.Server sslContextFactory, Server server) {
|
||||
SslConnectionFactory sslConnectionFactory = createSslConnectionFactory(sslContextFactory,
|
||||
HttpVersion.HTTP_1_1.asString());
|
||||
HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config);
|
||||
return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(),
|
||||
createSslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), connectionFactory);
|
||||
return new SslValidatingServerConnector(this.sslBundle.getKey(), sslContextFactory, server,
|
||||
sslConnectionFactory, connectionFactory);
|
||||
}
|
||||
|
||||
private SslConnectionFactory createSslConnectionFactory(SslContextFactory.Server sslContextFactory,
|
||||
|
@ -132,8 +136,8 @@ class SslServerCustomizer implements JettyServerCustomizer {
|
|||
return ClassUtils.isPresent("org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory", null);
|
||||
}
|
||||
|
||||
private ServerConnector createHttp2ServerConnector(Server server, HttpConfiguration config,
|
||||
SslContextFactory.Server sslContextFactory) {
|
||||
private ServerConnector createHttp2ServerConnector(HttpConfiguration config,
|
||||
SslContextFactory.Server sslContextFactory, Server server) {
|
||||
HttpConnectionFactory http = new HttpConnectionFactory(config);
|
||||
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(config);
|
||||
ALPNServerConnectionFactory alpn = createAlpnServerConnectionFactory();
|
||||
|
@ -141,8 +145,9 @@ class SslServerCustomizer implements JettyServerCustomizer {
|
|||
if (isConscryptPresent()) {
|
||||
sslContextFactory.setProvider("Conscrypt");
|
||||
}
|
||||
SslConnectionFactory ssl = createSslConnectionFactory(sslContextFactory, alpn.getProtocol());
|
||||
return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(), ssl, alpn, h2, http);
|
||||
SslConnectionFactory sslConnectionFactory = createSslConnectionFactory(sslContextFactory, alpn.getProtocol());
|
||||
return new SslValidatingServerConnector(this.sslBundle.getKey(), sslContextFactory, server,
|
||||
sslConnectionFactory, alpn, h2, http);
|
||||
}
|
||||
|
||||
private ALPNServerConnectionFactory createAlpnServerConnectionFactory() {
|
||||
|
@ -163,53 +168,40 @@ class SslServerCustomizer implements JettyServerCustomizer {
|
|||
/**
|
||||
* Configure the SSL connection.
|
||||
* @param factory the Jetty {@link Server SslContextFactory.Server}.
|
||||
* @param ssl the ssl details.
|
||||
* @param sslStoreProvider the ssl store provider
|
||||
* @param clientAuth the client authentication mode
|
||||
*/
|
||||
protected void configureSsl(SslContextFactory.Server 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());
|
||||
protected void configureSsl(SslContextFactory.Server factory, ClientAuth clientAuth) {
|
||||
SslBundleKey key = this.sslBundle.getKey();
|
||||
SslOptions options = this.sslBundle.getOptions();
|
||||
SslStoreBundle stores = this.sslBundle.getStores();
|
||||
factory.setProtocol(this.sslBundle.getProtocol());
|
||||
configureSslClientAuth(factory, clientAuth);
|
||||
if (stores.getKeyStorePassword() != null) {
|
||||
factory.setKeyStorePassword(stores.getKeyStorePassword());
|
||||
}
|
||||
factory.setCertAlias(key.getAlias());
|
||||
if (!ObjectUtils.isEmpty(options.getCiphers())) {
|
||||
factory.setIncludeCipherSuites(options.getCiphers().toArray(String[]::new));
|
||||
factory.setExcludeCipherSuites();
|
||||
}
|
||||
if (ssl.getEnabledProtocols() != null) {
|
||||
factory.setIncludeProtocols(ssl.getEnabledProtocols());
|
||||
if (!CollectionUtils.isEmpty(options.getEnabledProtocols())) {
|
||||
factory.setIncludeProtocols(options.getEnabledProtocols().toArray(String[]::new));
|
||||
}
|
||||
if (sslStoreProvider != null) {
|
||||
try {
|
||||
String keyPassword = sslStoreProvider.getKeyPassword();
|
||||
if (keyPassword != null) {
|
||||
factory.setKeyManagerPassword(keyPassword);
|
||||
}
|
||||
factory.setKeyStore(sslStoreProvider.getKeyStore());
|
||||
factory.setTrustStore(sslStoreProvider.getTrustStore());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Unable to set SSL store: " + ex.getMessage(), ex);
|
||||
try {
|
||||
if (key.getPassword() != null) {
|
||||
factory.setKeyManagerPassword(key.getPassword());
|
||||
}
|
||||
factory.setKeyStore(stores.getKeyStore());
|
||||
factory.setTrustStore(stores.getTrustStore());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Unable to set SSL store: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureSslClientAuth(SslContextFactory.Server 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.Server factory, Ssl ssl) {
|
||||
if (ssl.getKeyStorePassword() != null) {
|
||||
factory.setKeyStorePassword(ssl.getKeyStorePassword());
|
||||
}
|
||||
if (ssl.getKeyPassword() != null) {
|
||||
factory.setKeyManagerPassword(ssl.getKeyPassword());
|
||||
}
|
||||
private void configureSslClientAuth(SslContextFactory.Server factory, ClientAuth clientAuth) {
|
||||
factory.setWantClientAuth(clientAuth == ClientAuth.WANT || clientAuth == ClientAuth.NEED);
|
||||
factory.setNeedClientAuth(clientAuth == ClientAuth.NEED);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -217,28 +209,28 @@ class SslServerCustomizer implements JettyServerCustomizer {
|
|||
*/
|
||||
static class SslValidatingServerConnector extends ServerConnector {
|
||||
|
||||
private final SslBundleKey key;
|
||||
|
||||
private final SslContextFactory sslContextFactory;
|
||||
|
||||
private final String keyAlias;
|
||||
|
||||
SslValidatingServerConnector(Server server, SslContextFactory sslContextFactory, String keyAlias,
|
||||
SslValidatingServerConnector(SslBundleKey key, SslContextFactory sslContextFactory, Server server,
|
||||
SslConnectionFactory sslConnectionFactory, HttpConnectionFactory connectionFactory) {
|
||||
super(server, sslConnectionFactory, connectionFactory);
|
||||
this.key = key;
|
||||
this.sslContextFactory = sslContextFactory;
|
||||
this.keyAlias = keyAlias;
|
||||
}
|
||||
|
||||
SslValidatingServerConnector(Server server, SslContextFactory sslContextFactory, String keyAlias,
|
||||
SslValidatingServerConnector(SslBundleKey keyAlias, SslContextFactory sslContextFactory, Server server,
|
||||
ConnectionFactory... factories) {
|
||||
super(server, factories);
|
||||
this.key = keyAlias;
|
||||
this.sslContextFactory = sslContextFactory;
|
||||
this.keyAlias = keyAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception {
|
||||
super.doStart();
|
||||
SslConfigurationValidator.validateKeyAlias(this.sslContextFactory.getKeyStore(), this.keyAlias);
|
||||
this.key.assertContainsAlias(this.sslContextFactory.getKeyStore());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -32,6 +32,7 @@ import reactor.netty.resources.LoopResources;
|
|||
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.Shutdown;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.http.client.reactive.ReactorResourceFactory;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
|
@ -166,7 +167,7 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
|||
else {
|
||||
server = server.bindAddress(this::getListenAddress);
|
||||
}
|
||||
if (getSsl() != null && getSsl().isEnabled()) {
|
||||
if (Ssl.isEnabled(getSsl())) {
|
||||
server = customizeSslConfiguration(server);
|
||||
}
|
||||
if (getCompression() != null && getCompression().getEnabled()) {
|
||||
|
@ -177,11 +178,8 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
|||
return applyCustomizers(server);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private HttpServer customizeSslConfiguration(HttpServer httpServer) {
|
||||
SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(getSsl(), getHttp2(),
|
||||
getOrCreateSslStoreProvider());
|
||||
return sslServerCustomizer.apply(httpServer);
|
||||
return new SslServerCustomizer(getHttp2(), getSsl().getClientAuth(), getSslBundle()).apply(httpServer);
|
||||
}
|
||||
|
||||
private HttpProtocol[] listProtocols() {
|
||||
|
|
|
@ -16,35 +16,17 @@
|
|||
|
||||
package org.springframework.boot.web.embedded.netty;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.KeyManagerFactorySpi;
|
||||
import javax.net.ssl.ManagerFactoryParameters;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import reactor.netty.http.Http11SslContextSpec;
|
||||
import reactor.netty.http.Http2SslContextSpec;
|
||||
import reactor.netty.http.server.HttpServer;
|
||||
import reactor.netty.tcp.AbstractProtocolSslContextSpec;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslOptions;
|
||||
import org.springframework.boot.web.server.Http2;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.SslConfigurationValidator;
|
||||
import org.springframework.boot.web.server.SslStoreProvider;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* {@link NettyServerCustomizer} that configures SSL for the given Reactor Netty server
|
||||
|
@ -56,21 +38,19 @@ import org.springframework.boot.web.server.SslStoreProvider;
|
|||
* @author Cyril Dangerville
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
* @deprecated this class is meant for Spring Boot internal use only.
|
||||
*/
|
||||
@Deprecated(since = "2.0.0", forRemoval = false)
|
||||
public class SslServerCustomizer implements NettyServerCustomizer {
|
||||
|
||||
private final Ssl ssl;
|
||||
|
||||
private final Http2 http2;
|
||||
|
||||
private final SslStoreProvider sslStoreProvider;
|
||||
private final Ssl.ClientAuth clientAuth;
|
||||
|
||||
public SslServerCustomizer(Ssl ssl, Http2 http2, SslStoreProvider sslStoreProvider) {
|
||||
this.ssl = ssl;
|
||||
private final SslBundle sslBundle;
|
||||
|
||||
public SslServerCustomizer(Http2 http2, Ssl.ClientAuth clientAuth, SslBundle sslBundle) {
|
||||
this.http2 = http2;
|
||||
this.sslStoreProvider = sslStoreProvider;
|
||||
this.clientAuth = clientAuth;
|
||||
this.sslBundle = sslBundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -80,172 +60,22 @@ public class SslServerCustomizer implements NettyServerCustomizer {
|
|||
}
|
||||
|
||||
protected AbstractProtocolSslContextSpec<?> createSslContextSpec() {
|
||||
AbstractProtocolSslContextSpec<?> sslContextSpec;
|
||||
if (this.http2 != null && this.http2.isEnabled()) {
|
||||
sslContextSpec = Http2SslContextSpec.forServer(getKeyManagerFactory(this.ssl, this.sslStoreProvider));
|
||||
}
|
||||
else {
|
||||
sslContextSpec = Http11SslContextSpec.forServer(getKeyManagerFactory(this.ssl, this.sslStoreProvider));
|
||||
}
|
||||
AbstractProtocolSslContextSpec<?> sslContextSpec = (this.http2 != null && this.http2.isEnabled())
|
||||
? Http2SslContextSpec.forServer(this.sslBundle.getManagers().getKeyManagerFactory())
|
||||
: Http11SslContextSpec.forServer(this.sslBundle.getManagers().getKeyManagerFactory());
|
||||
sslContextSpec.configure((builder) -> {
|
||||
builder.trustManager(getTrustManagerFactory(this.sslStoreProvider));
|
||||
if (this.ssl.getEnabledProtocols() != null) {
|
||||
builder.protocols(this.ssl.getEnabledProtocols());
|
||||
builder.trustManager(this.sslBundle.getManagers().getTrustManagerFactory());
|
||||
SslOptions options = this.sslBundle.getOptions();
|
||||
if (!CollectionUtils.isEmpty(options.getEnabledProtocols())) {
|
||||
builder.protocols(options.getEnabledProtocols());
|
||||
}
|
||||
if (this.ssl.getCiphers() != null) {
|
||||
builder.ciphers(Arrays.asList(this.ssl.getCiphers()));
|
||||
}
|
||||
if (this.ssl.getClientAuth() == Ssl.ClientAuth.NEED) {
|
||||
builder.clientAuth(ClientAuth.REQUIRE);
|
||||
}
|
||||
else if (this.ssl.getClientAuth() == Ssl.ClientAuth.WANT) {
|
||||
builder.clientAuth(ClientAuth.OPTIONAL);
|
||||
if (!CollectionUtils.isEmpty(options.getCiphers())) {
|
||||
builder.ciphers(options.getCiphers());
|
||||
}
|
||||
builder.clientAuth(org.springframework.boot.web.server.Ssl.ClientAuth.map(this.clientAuth, ClientAuth.NONE,
|
||||
ClientAuth.OPTIONAL, ClientAuth.REQUIRE));
|
||||
});
|
||||
return sslContextSpec;
|
||||
}
|
||||
|
||||
KeyManagerFactory getKeyManagerFactory(Ssl ssl, SslStoreProvider sslStoreProvider) {
|
||||
try {
|
||||
KeyStore keyStore = sslStoreProvider.getKeyStore();
|
||||
SslConfigurationValidator.validateKeyAlias(keyStore, ssl.getKeyAlias());
|
||||
KeyManagerFactory keyManagerFactory = (ssl.getKeyAlias() == null)
|
||||
? KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
|
||||
: new ConfigurableAliasKeyManagerFactory(ssl.getKeyAlias(),
|
||||
KeyManagerFactory.getDefaultAlgorithm());
|
||||
String keyPassword = sslStoreProvider.getKeyPassword();
|
||||
if (keyPassword == null) {
|
||||
keyPassword = (ssl.getKeyPassword() != null) ? ssl.getKeyPassword() : ssl.getKeyStorePassword();
|
||||
}
|
||||
keyManagerFactory.init(keyStore, (keyPassword != null) ? keyPassword.toCharArray() : null);
|
||||
return keyManagerFactory;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not load key manager factory: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
TrustManagerFactory getTrustManagerFactory(SslStoreProvider sslStoreProvider) {
|
||||
try {
|
||||
KeyStore store = sslStoreProvider.getTrustStore();
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory
|
||||
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init(store);
|
||||
return trustManagerFactory;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not load trust manager factory: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link KeyManagerFactory} that allows a configurable key alias to be used. Due to
|
||||
* the fact that the actual calls to retrieve the key by alias are done at request
|
||||
* time the approach is to wrap the actual key managers with a
|
||||
* {@link ConfigurableAliasKeyManager}. The actual SPI has to be wrapped as well due
|
||||
* to the fact that {@link KeyManagerFactory#getKeyManagers()} is final.
|
||||
*/
|
||||
private static final class ConfigurableAliasKeyManagerFactory extends KeyManagerFactory {
|
||||
|
||||
private ConfigurableAliasKeyManagerFactory(String alias, String algorithm) throws NoSuchAlgorithmException {
|
||||
this(KeyManagerFactory.getInstance(algorithm), alias, algorithm);
|
||||
}
|
||||
|
||||
private ConfigurableAliasKeyManagerFactory(KeyManagerFactory delegate, String alias, String algorithm) {
|
||||
super(new ConfigurableAliasKeyManagerFactorySpi(delegate, alias), delegate.getProvider(), algorithm);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class ConfigurableAliasKeyManagerFactorySpi extends KeyManagerFactorySpi {
|
||||
|
||||
private final KeyManagerFactory delegate;
|
||||
|
||||
private final String alias;
|
||||
|
||||
private ConfigurableAliasKeyManagerFactorySpi(KeyManagerFactory delegate, String alias) {
|
||||
this.delegate = delegate;
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInit(KeyStore keyStore, char[] chars)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
this.delegate.init(keyStore, chars);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
|
||||
throws InvalidAlgorithmParameterException {
|
||||
throw new InvalidAlgorithmParameterException("Unsupported ManagerFactoryParameters");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyManager[] engineGetKeyManagers() {
|
||||
return Arrays.stream(this.delegate.getKeyManagers())
|
||||
.filter(X509ExtendedKeyManager.class::isInstance)
|
||||
.map(X509ExtendedKeyManager.class::cast)
|
||||
.map(this::wrap)
|
||||
.toArray(KeyManager[]::new);
|
||||
}
|
||||
|
||||
private ConfigurableAliasKeyManager wrap(X509ExtendedKeyManager keyManager) {
|
||||
return new ConfigurableAliasKeyManager(keyManager, this.alias);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class ConfigurableAliasKeyManager extends X509ExtendedKeyManager {
|
||||
|
||||
private final X509ExtendedKeyManager delegate;
|
||||
|
||||
private final String alias;
|
||||
|
||||
private ConfigurableAliasKeyManager(X509ExtendedKeyManager keyManager, String alias) {
|
||||
this.delegate = keyManager;
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseEngineClientAlias(String[] strings, Principal[] principals, SSLEngine sslEngine) {
|
||||
return this.delegate.chooseEngineClientAlias(strings, principals, sslEngine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseEngineServerAlias(String s, Principal[] principals, SSLEngine sslEngine) {
|
||||
return (this.alias != null) ? this.alias : this.delegate.chooseEngineServerAlias(s, principals, sslEngine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
|
||||
return this.delegate.chooseClientAlias(keyType, issuers, socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
|
||||
return this.delegate.chooseServerAlias(keyType, issuers, socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain(String alias) {
|
||||
return this.delegate.getCertificateChain(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||
return this.delegate.getClientAliases(keyType, issuers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey getPrivateKey(String alias) {
|
||||
return this.delegate.getPrivateKey(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||
return this.delegate.getServerAliases(keyType, issuers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,10 +24,13 @@ import org.apache.tomcat.util.net.SSLHostConfig;
|
|||
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
|
||||
import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type;
|
||||
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.SslStoreProvider;
|
||||
import org.springframework.boot.web.server.SslStoreProviderFactory;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundleKey;
|
||||
import org.springframework.boot.ssl.SslOptions;
|
||||
import org.springframework.boot.ssl.SslStoreBundle;
|
||||
import org.springframework.boot.web.server.Ssl.ClientAuth;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
@ -40,18 +43,13 @@ import org.springframework.util.StringUtils;
|
|||
*/
|
||||
class SslConnectorCustomizer implements TomcatConnectorCustomizer {
|
||||
|
||||
private final Ssl ssl;
|
||||
private final ClientAuth clientAuth;
|
||||
|
||||
private final SslStoreProvider sslStoreProvider;
|
||||
private final SslBundle sslBundle;
|
||||
|
||||
SslConnectorCustomizer(Ssl ssl) {
|
||||
this(ssl, SslStoreProviderFactory.from(ssl));
|
||||
}
|
||||
|
||||
SslConnectorCustomizer(Ssl ssl, SslStoreProvider sslStoreProvider) {
|
||||
Assert.notNull(ssl, "Ssl configuration should not be null");
|
||||
this.ssl = ssl;
|
||||
this.sslStoreProvider = sslStoreProvider;
|
||||
SslConnectorCustomizer(ClientAuth clientAuth, SslBundle sslBundle) {
|
||||
this.clientAuth = clientAuth;
|
||||
this.sslBundle = sslBundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -59,7 +57,7 @@ class SslConnectorCustomizer implements TomcatConnectorCustomizer {
|
|||
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);
|
||||
configureSsl((AbstractHttp11JsseProtocol<?>) handler);
|
||||
connector.setScheme("https");
|
||||
connector.setSecure(true);
|
||||
}
|
||||
|
@ -67,68 +65,59 @@ class SslConnectorCustomizer implements TomcatConnectorCustomizer {
|
|||
/**
|
||||
* 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) {
|
||||
void configureSsl(AbstractHttp11JsseProtocol<?> protocol) {
|
||||
SslBundleKey key = this.sslBundle.getKey();
|
||||
SslStoreBundle stores = this.sslBundle.getStores();
|
||||
SslOptions options = this.sslBundle.getOptions();
|
||||
protocol.setSSLEnabled(true);
|
||||
SSLHostConfig sslHostConfig = new SSLHostConfig();
|
||||
sslHostConfig.setHostName(protocol.getDefaultSSLHostConfigName());
|
||||
sslHostConfig.setSslProtocol(ssl.getProtocol());
|
||||
sslHostConfig.setSslProtocol(this.sslBundle.getProtocol());
|
||||
protocol.addSslHostConfig(sslHostConfig);
|
||||
configureSslClientAuth(sslHostConfig, ssl);
|
||||
configureSslClientAuth(sslHostConfig);
|
||||
SSLHostConfigCertificate certificate = new SSLHostConfigCertificate(sslHostConfig, Type.UNDEFINED);
|
||||
if (ssl.getKeyStorePassword() != null) {
|
||||
certificate.setCertificateKeystorePassword(ssl.getKeyStorePassword());
|
||||
String keystorePassword = (stores.getKeyStorePassword() != null) ? stores.getKeyStorePassword() : "";
|
||||
certificate.setCertificateKeystorePassword(keystorePassword);
|
||||
if (key.getPassword() != null) {
|
||||
certificate.setCertificateKeyPassword(key.getPassword());
|
||||
}
|
||||
if (ssl.getKeyPassword() != null) {
|
||||
certificate.setCertificateKeyPassword(ssl.getKeyPassword());
|
||||
}
|
||||
if (ssl.getKeyAlias() != null) {
|
||||
certificate.setCertificateKeyAlias(ssl.getKeyAlias());
|
||||
if (key.getAlias() != null) {
|
||||
certificate.setCertificateKeyAlias(key.getAlias());
|
||||
}
|
||||
sslHostConfig.addCertificate(certificate);
|
||||
String ciphers = StringUtils.arrayToCommaDelimitedString(ssl.getCiphers());
|
||||
if (StringUtils.hasText(ciphers)) {
|
||||
if (!CollectionUtils.isEmpty(options.getCiphers())) {
|
||||
String ciphers = StringUtils.collectionToCommaDelimitedString(options.getCiphers());
|
||||
sslHostConfig.setCiphers(ciphers);
|
||||
}
|
||||
configureEnabledProtocols(protocol, ssl);
|
||||
if (sslStoreProvider != null) {
|
||||
configureSslStoreProvider(protocol, sslHostConfig, certificate, sslStoreProvider);
|
||||
String keyPassword = sslStoreProvider.getKeyPassword();
|
||||
if (keyPassword != null) {
|
||||
certificate.setCertificateKeyPassword(keyPassword);
|
||||
}
|
||||
}
|
||||
configureEnabledProtocols(protocol);
|
||||
configureSslStoreProvider(protocol, sslHostConfig, certificate);
|
||||
}
|
||||
|
||||
private void configureEnabledProtocols(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
|
||||
if (ssl.getEnabledProtocols() != null) {
|
||||
private void configureEnabledProtocols(AbstractHttp11JsseProtocol<?> protocol) {
|
||||
SslOptions options = this.sslBundle.getOptions();
|
||||
if (!CollectionUtils.isEmpty(options.getEnabledProtocols())) {
|
||||
for (SSLHostConfig sslHostConfig : protocol.findSslHostConfigs()) {
|
||||
sslHostConfig.setProtocols(StringUtils.arrayToCommaDelimitedString(ssl.getEnabledProtocols()));
|
||||
sslHostConfig.setProtocols(StringUtils.collectionToCommaDelimitedString(options.getEnabledProtocols()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void configureSslClientAuth(SSLHostConfig config, Ssl ssl) {
|
||||
if (ssl.getClientAuth() == Ssl.ClientAuth.NEED) {
|
||||
config.setCertificateVerification("required");
|
||||
}
|
||||
else if (ssl.getClientAuth() == Ssl.ClientAuth.WANT) {
|
||||
config.setCertificateVerification("optional");
|
||||
}
|
||||
private void configureSslClientAuth(SSLHostConfig config) {
|
||||
config.setCertificateVerification(ClientAuth.map(this.clientAuth, "none", "optional", "required"));
|
||||
}
|
||||
|
||||
protected void configureSslStoreProvider(AbstractHttp11JsseProtocol<?> protocol, SSLHostConfig sslHostConfig,
|
||||
SSLHostConfigCertificate certificate, SslStoreProvider sslStoreProvider) {
|
||||
SSLHostConfigCertificate certificate) {
|
||||
Assert.isInstanceOf(Http11NioProtocol.class, protocol,
|
||||
"SslStoreProvider can only be used with Http11NioProtocol");
|
||||
try {
|
||||
if (sslStoreProvider.getKeyStore() != null) {
|
||||
certificate.setCertificateKeystore(sslStoreProvider.getKeyStore());
|
||||
SslStoreBundle stores = this.sslBundle.getStores();
|
||||
if (stores.getKeyStore() != null) {
|
||||
certificate.setCertificateKeystore(stores.getKeyStore());
|
||||
}
|
||||
if (sslStoreProvider.getTrustStore() != null) {
|
||||
sslHostConfig.setTrustStore(sslStoreProvider.getTrustStore());
|
||||
if (stores.getTrustStore() != null) {
|
||||
sslHostConfig.setTrustStore(stores.getTrustStore());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.apache.tomcat.util.scan.StandardJarScanFilter;
|
|||
import org.springframework.boot.util.LambdaSafe;
|
||||
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.WebServer;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.http.server.reactive.TomcatHttpHandlerAdapter;
|
||||
|
@ -199,7 +200,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
|
|||
if (getHttp2() != null && getHttp2().isEnabled()) {
|
||||
connector.addUpgradeProtocol(new Http2Protocol());
|
||||
}
|
||||
if (getSsl() != null && getSsl().isEnabled()) {
|
||||
if (Ssl.isEnabled(getSsl())) {
|
||||
customizeSsl(connector);
|
||||
}
|
||||
TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
|
||||
|
@ -223,7 +224,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
|
|||
}
|
||||
|
||||
private void customizeSsl(Connector connector) {
|
||||
new SslConnectorCustomizer(getSsl(), getOrCreateSslStoreProvider()).customize(connector);
|
||||
new SslConnectorCustomizer(getSsl().getClientAuth(), getSslBundle()).customize(connector);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -71,6 +71,7 @@ import org.springframework.boot.util.LambdaSafe;
|
|||
import org.springframework.boot.web.server.Cookie.SameSite;
|
||||
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.WebServer;
|
||||
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
|
||||
|
@ -339,7 +340,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
|
|||
if (getHttp2() != null && getHttp2().isEnabled()) {
|
||||
connector.addUpgradeProtocol(new Http2Protocol());
|
||||
}
|
||||
if (getSsl() != null && getSsl().isEnabled()) {
|
||||
if (Ssl.isEnabled(getSsl())) {
|
||||
customizeSsl(connector);
|
||||
}
|
||||
TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
|
||||
|
@ -363,7 +364,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
|
|||
}
|
||||
|
||||
private void customizeSsl(Connector connector) {
|
||||
new SslConnectorCustomizer(getSsl(), getOrCreateSslStoreProvider()).customize(connector);
|
||||
new SslConnectorCustomizer(getSsl().getClientAuth(), getSslBundle()).customize(connector);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,30 +17,18 @@
|
|||
package org.springframework.boot.web.embedded.undertow;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
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.SslConfigurationValidator;
|
||||
import org.springframework.boot.web.server.SslStoreProvider;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslOptions;
|
||||
import org.springframework.boot.web.server.Ssl.ClientAuth;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* {@link UndertowBuilderCustomizer} that configures SSL on the given builder instance.
|
||||
|
@ -56,34 +44,29 @@ class SslBuilderCustomizer implements UndertowBuilderCustomizer {
|
|||
|
||||
private final InetAddress address;
|
||||
|
||||
private final Ssl ssl;
|
||||
private final ClientAuth clientAuth;
|
||||
|
||||
private final SslStoreProvider sslStoreProvider;
|
||||
private final SslBundle sslBundle;
|
||||
|
||||
SslBuilderCustomizer(int port, InetAddress address, Ssl ssl, SslStoreProvider sslStoreProvider) {
|
||||
SslBuilderCustomizer(int port, InetAddress address, ClientAuth clientAuth, SslBundle sslBundle) {
|
||||
this.port = port;
|
||||
this.address = address;
|
||||
this.ssl = ssl;
|
||||
this.sslStoreProvider = sslStoreProvider;
|
||||
this.clientAuth = clientAuth;
|
||||
this.sslBundle = sslBundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(Undertow.Builder builder) {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance(this.ssl.getProtocol());
|
||||
sslContext.init(getKeyManagers(this.ssl, this.sslStoreProvider), getTrustManagers(this.sslStoreProvider),
|
||||
null);
|
||||
builder.addHttpsListener(this.port, getListenAddress(), 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()));
|
||||
}
|
||||
SslOptions options = this.sslBundle.getOptions();
|
||||
SSLContext sslContext = this.sslBundle.createSslContext();
|
||||
builder.addHttpsListener(this.port, getListenAddress(), sslContext);
|
||||
builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, ClientAuth.map(this.clientAuth,
|
||||
SslClientAuthMode.NOT_REQUESTED, SslClientAuthMode.REQUESTED, SslClientAuthMode.REQUIRED));
|
||||
if (!CollectionUtils.isEmpty(options.getEnabledProtocols())) {
|
||||
builder.setSocketOption(Options.SSL_ENABLED_PROTOCOLS, Sequence.of(options.getEnabledProtocols()));
|
||||
}
|
||||
catch (NoSuchAlgorithmException | KeyManagementException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
if (!CollectionUtils.isEmpty(options.getCiphers())) {
|
||||
builder.setSocketOption(Options.SSL_ENABLED_CIPHER_SUITES, Sequence.of(options.getCiphers()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,117 +77,4 @@ class SslBuilderCustomizer implements UndertowBuilderCustomizer {
|
|||
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;
|
||||
}
|
||||
|
||||
KeyManager[] getKeyManagers(Ssl ssl, SslStoreProvider sslStoreProvider) {
|
||||
try {
|
||||
KeyStore keyStore = sslStoreProvider.getKeyStore();
|
||||
SslConfigurationValidator.validateKeyAlias(keyStore, ssl.getKeyAlias());
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory
|
||||
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
String keyPassword = sslStoreProvider.getKeyPassword();
|
||||
if (keyPassword == null) {
|
||||
keyPassword = (ssl.getKeyPassword() != null) ? ssl.getKeyPassword() : ssl.getKeyStorePassword();
|
||||
}
|
||||
keyManagerFactory.init(keyStore, (keyPassword != null) ? keyPassword.toCharArray() : null);
|
||||
if (ssl.getKeyAlias() != null) {
|
||||
return getConfigurableAliasKeyManagers(ssl, keyManagerFactory.getKeyManagers());
|
||||
}
|
||||
return keyManagerFactory.getKeyManagers();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not load key managers: " + ex.getMessage(), 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;
|
||||
}
|
||||
|
||||
TrustManager[] getTrustManagers(SslStoreProvider sslStoreProvider) {
|
||||
try {
|
||||
KeyStore store = sslStoreProvider.getTrustStore();
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory
|
||||
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init(store);
|
||||
return trustManagerFactory.getTrustManagers();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not load trust managers: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -137,7 +137,7 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
|
|||
|
||||
@Override
|
||||
public WebServer getWebServer(org.springframework.http.server.reactive.HttpHandler httpHandler) {
|
||||
Undertow.Builder builder = this.delegate.createBuilder(this);
|
||||
Undertow.Builder builder = this.delegate.createBuilder(this, this::getSslBundle);
|
||||
List<HttpHandlerFactory> httpHandlerFactories = this.delegate.createHttpHandlerFactories(this,
|
||||
(next) -> new UndertowHttpHandlerAdapter(httpHandler));
|
||||
return new UndertowWebServer(builder, httpHandlerFactories, getPort() >= 0);
|
||||
|
|
|
@ -294,7 +294,7 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
|
|||
|
||||
@Override
|
||||
public WebServer getWebServer(ServletContextInitializer... initializers) {
|
||||
Builder builder = this.delegate.createBuilder(this);
|
||||
Builder builder = this.delegate.createBuilder(this, this::getSslBundle);
|
||||
DeploymentManager manager = createManager(initializers);
|
||||
return getUndertowWebServer(builder, manager, getPort());
|
||||
}
|
||||
|
|
|
@ -24,12 +24,14 @@ import java.util.Collection;
|
|||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.undertow.Handlers;
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.Undertow.Builder;
|
||||
import io.undertow.UndertowOptions;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory;
|
||||
import org.springframework.boot.web.server.Compression;
|
||||
import org.springframework.boot.web.server.Http2;
|
||||
|
@ -141,8 +143,7 @@ class UndertowWebServerFactoryDelegate {
|
|||
return this.useForwardHeaders;
|
||||
}
|
||||
|
||||
Builder createBuilder(AbstractConfigurableWebServerFactory factory) {
|
||||
Ssl ssl = factory.getSsl();
|
||||
Builder createBuilder(AbstractConfigurableWebServerFactory factory, Supplier<SslBundle> sslBundleSupplier) {
|
||||
InetAddress address = factory.getAddress();
|
||||
int port = factory.getPort();
|
||||
Builder builder = Undertow.builder();
|
||||
|
@ -162,8 +163,9 @@ class UndertowWebServerFactoryDelegate {
|
|||
if (http2 != null) {
|
||||
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, http2.isEnabled());
|
||||
}
|
||||
if (ssl != null && ssl.isEnabled()) {
|
||||
new SslBuilderCustomizer(factory.getPort(), address, ssl, factory.getOrCreateSslStoreProvider())
|
||||
Ssl ssl = factory.getSsl();
|
||||
if (Ssl.isEnabled(ssl)) {
|
||||
new SslBuilderCustomizer(factory.getPort(), address, ssl.getClientAuth(), sslBundleSupplier.get())
|
||||
.customize(builder);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -24,6 +24,8 @@ import java.util.Arrays;
|
|||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -49,8 +51,11 @@ public abstract class AbstractConfigurableWebServerFactory implements Configurab
|
|||
|
||||
private Ssl ssl;
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private SslStoreProvider sslStoreProvider;
|
||||
|
||||
private SslBundles sslBundles;
|
||||
|
||||
private Http2 http2;
|
||||
|
||||
private Compression compression;
|
||||
|
@ -130,15 +135,22 @@ public abstract class AbstractConfigurableWebServerFactory implements Configurab
|
|||
this.ssl = ssl;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public SslStoreProvider getSslStoreProvider() {
|
||||
return this.sslStoreProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("removal")
|
||||
public void setSslStoreProvider(SslStoreProvider sslStoreProvider) {
|
||||
this.sslStoreProvider = sslStoreProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSslBundles(SslBundles sslBundles) {
|
||||
this.sslBundles = sslBundles;
|
||||
}
|
||||
|
||||
public Http2 getHttp2() {
|
||||
return this.http2;
|
||||
}
|
||||
|
@ -184,12 +196,24 @@ public abstract class AbstractConfigurableWebServerFactory implements Configurab
|
|||
* Return the provided {@link SslStoreProvider} or create one using {@link Ssl}
|
||||
* properties.
|
||||
* @return the {@code SslStoreProvider}
|
||||
* @deprecated since 3.1.0 for removal in 3.3.0 in favor of {@link #getSslBundle()}
|
||||
*/
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public final SslStoreProvider getOrCreateSslStoreProvider() {
|
||||
if (this.sslStoreProvider != null) {
|
||||
return this.sslStoreProvider;
|
||||
}
|
||||
return SslStoreProviderFactory.from(this.ssl);
|
||||
return CertificateFileSslStoreProvider.from(this.ssl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link SslBundle} that should be used with this server.
|
||||
* @return the SSL bundle
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
protected final SslBundle getSslBundle() {
|
||||
return WebServerSslBundle.get(this.ssl, this.sslBundles, this.sslStoreProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -16,12 +16,10 @@
|
|||
|
||||
package org.springframework.boot.web.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
|
||||
|
||||
/**
|
||||
* An {@link SslStoreProvider} that creates key and trust stores from certificate and
|
||||
|
@ -29,82 +27,32 @@ import java.security.cert.X509Certificate;
|
|||
*
|
||||
* @author Scott Frederick
|
||||
* @since 2.7.0
|
||||
* @deprecated since 3.1.0 for removal in 3.3.0 in favor of registering a
|
||||
* {@link SslBundle} backed by a {@link PemSslStoreBundle}.
|
||||
*/
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
@SuppressWarnings({ "deprecation", "removal" })
|
||||
public final class CertificateFileSslStoreProvider implements SslStoreProvider {
|
||||
|
||||
/**
|
||||
* The password of the private key entry in the {@link #getKeyStore provided
|
||||
* KeyStore}.
|
||||
*/
|
||||
private static final String KEY_PASSWORD = "";
|
||||
private final SslBundle delegate;
|
||||
|
||||
private static final String DEFAULT_KEY_ALIAS = "spring-boot-web";
|
||||
|
||||
private final Ssl ssl;
|
||||
|
||||
private CertificateFileSslStoreProvider(Ssl ssl) {
|
||||
this.ssl = ssl;
|
||||
private CertificateFileSslStoreProvider(SslBundle delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getKeyStore() throws Exception {
|
||||
return createKeyStore(this.ssl.getCertificate(), this.ssl.getCertificatePrivateKey(),
|
||||
this.ssl.getKeyStoreType(), this.ssl.getKeyAlias());
|
||||
return this.delegate.getStores().getKeyStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getTrustStore() throws Exception {
|
||||
if (this.ssl.getTrustCertificate() == null) {
|
||||
return null;
|
||||
}
|
||||
return createKeyStore(this.ssl.getTrustCertificate(), this.ssl.getTrustCertificatePrivateKey(),
|
||||
this.ssl.getTrustStoreType(), this.ssl.getKeyAlias());
|
||||
return this.delegate.getStores().getTrustStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyPassword() {
|
||||
return KEY_PASSWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link KeyStore} populated with the certificate stored at the
|
||||
* specified file path and an optional private key.
|
||||
* @param certPath the path to the certificate authority file
|
||||
* @param keyPath the path to the private file
|
||||
* @param storeType the {@code KeyStore} type to create
|
||||
* @param keyAlias the alias to use when adding keys to the {@code KeyStore}
|
||||
* @return the {@code KeyStore}
|
||||
*/
|
||||
private KeyStore createKeyStore(String certPath, String keyPath, String storeType, String keyAlias) {
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance((storeType != null) ? storeType : KeyStore.getDefaultType());
|
||||
keyStore.load(null);
|
||||
X509Certificate[] certificates = CertificateParser.parse(certPath);
|
||||
PrivateKey privateKey = (keyPath != null) ? PrivateKeyParser.parse(keyPath) : null;
|
||||
try {
|
||||
addCertificates(keyStore, certificates, privateKey, keyAlias);
|
||||
}
|
||||
catch (KeyStoreException ex) {
|
||||
throw new IllegalStateException("Error adding certificates to KeyStore: " + ex.getMessage(), ex);
|
||||
}
|
||||
return keyStore;
|
||||
}
|
||||
catch (GeneralSecurityException | IOException ex) {
|
||||
throw new IllegalStateException("Error creating KeyStore: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void addCertificates(KeyStore keyStore, X509Certificate[] certificates, PrivateKey privateKey,
|
||||
String keyAlias) throws KeyStoreException {
|
||||
String alias = (keyAlias != null) ? keyAlias : DEFAULT_KEY_ALIAS;
|
||||
if (privateKey != null) {
|
||||
keyStore.setKeyEntry(alias, privateKey, KEY_PASSWORD.toCharArray(), certificates);
|
||||
}
|
||||
else {
|
||||
for (int index = 0; index < certificates.length; index++) {
|
||||
keyStore.setCertificateEntry(alias + "-" + index, certificates[index]);
|
||||
}
|
||||
}
|
||||
return this.delegate.getKey().getPassword();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,12 +62,8 @@ public final class CertificateFileSslStoreProvider implements SslStoreProvider {
|
|||
* @return an {@code SslStoreProvider} or {@code null}
|
||||
*/
|
||||
public static SslStoreProvider from(Ssl ssl) {
|
||||
if (ssl != null && ssl.isEnabled()) {
|
||||
if (ssl.getCertificate() != null && ssl.getCertificatePrivateKey() != null) {
|
||||
return new CertificateFileSslStoreProvider(ssl);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
SslBundle delegate = WebServerSslBundle.createCertificateFileSslStoreProviderDelegate(ssl);
|
||||
return (delegate != null) ? new CertificateFileSslStoreProvider(delegate) : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.server;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Parser for X.509 certificates in PEM format.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
final class CertificateParser {
|
||||
|
||||
private static final String HEADER = "-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+";
|
||||
|
||||
private static final String BASE64_TEXT = "([a-z0-9+/=\\r\\n]+)";
|
||||
|
||||
private static final String FOOTER = "-+END\\s+.*CERTIFICATE[^-]*-+";
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile(HEADER + BASE64_TEXT + FOOTER, Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private CertificateParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load certificates from the specified resource.
|
||||
* @param path the certificate to parse
|
||||
* @return the parsed certificates
|
||||
*/
|
||||
static X509Certificate[] parse(String path) {
|
||||
CertificateFactory factory = getCertificateFactory();
|
||||
List<X509Certificate> certificates = new ArrayList<>();
|
||||
readCertificates(path, factory, certificates::add);
|
||||
return certificates.toArray(new X509Certificate[0]);
|
||||
}
|
||||
|
||||
private static CertificateFactory getCertificateFactory() {
|
||||
try {
|
||||
return CertificateFactory.getInstance("X.509");
|
||||
}
|
||||
catch (CertificateException ex) {
|
||||
throw new IllegalStateException("Unable to get X.509 certificate factory", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void readCertificates(String resource, CertificateFactory factory,
|
||||
Consumer<X509Certificate> consumer) {
|
||||
try {
|
||||
String text = readText(resource);
|
||||
Matcher matcher = PATTERN.matcher(text);
|
||||
while (matcher.find()) {
|
||||
String encodedText = matcher.group(1);
|
||||
byte[] decodedBytes = decodeBase64(encodedText);
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedBytes);
|
||||
while (inputStream.available() > 0) {
|
||||
consumer.accept((X509Certificate) factory.generateCertificate(inputStream));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (CertificateException | IOException ex) {
|
||||
throw new IllegalStateException("Error reading certificate from '" + resource + "' : " + ex.getMessage(),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static String readText(String resource) throws IOException {
|
||||
URL url = ResourceUtils.getURL(resource);
|
||||
try (Reader reader = new InputStreamReader(url.openStream())) {
|
||||
return FileCopyUtils.copyToString(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] decodeBase64(String content) {
|
||||
byte[] bytes = content.replaceAll("\r", "").replaceAll("\n", "").getBytes();
|
||||
return Base64.getDecoder().decode(bytes);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -19,11 +19,14 @@ package org.springframework.boot.web.server;
|
|||
import java.net.InetAddress;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
|
||||
/**
|
||||
* A configurable {@link WebServerFactory}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Brian Clozel
|
||||
* @author Scott Frederick
|
||||
* @since 2.0.0
|
||||
* @see ErrorPageRegistry
|
||||
*/
|
||||
|
@ -58,9 +61,20 @@ public interface ConfigurableWebServerFactory extends WebServerFactory, ErrorPag
|
|||
/**
|
||||
* Sets a provider that will be used to obtain SSL stores.
|
||||
* @param sslStoreProvider the SSL store provider
|
||||
* @deprecated since 3.1.0 for removal in 3.3.0, in favor of
|
||||
* {@link #setSslBundles(SslBundles)}
|
||||
*/
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
void setSslStoreProvider(SslStoreProvider sslStoreProvider);
|
||||
|
||||
/**
|
||||
* Sets the SSL bundles that can be used to configure SSL connections.
|
||||
* @param sslBundles the SSL bundles
|
||||
* @since 3.1.0
|
||||
*/
|
||||
void setSslBundles(SslBundles sslBundles);
|
||||
|
||||
/**
|
||||
* Sets the HTTP/2 configuration that will be applied to the server.
|
||||
* @param http2 the HTTP/2 configuration
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.server;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link SslStoreProvider} that creates key and trust stores from Java keystore files.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
final class JavaKeyStoreSslStoreProvider implements SslStoreProvider {
|
||||
|
||||
private final Ssl ssl;
|
||||
|
||||
private JavaKeyStoreSslStoreProvider(Ssl ssl) {
|
||||
this.ssl = ssl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getKeyStore() throws Exception {
|
||||
return createKeyStore(this.ssl.getKeyStoreType(), this.ssl.getKeyStoreProvider(), this.ssl.getKeyStore(),
|
||||
this.ssl.getKeyStorePassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getTrustStore() throws Exception {
|
||||
if (this.ssl.getTrustStore() == null) {
|
||||
return null;
|
||||
}
|
||||
return createKeyStore(this.ssl.getTrustStoreType(), this.ssl.getTrustStoreProvider(), this.ssl.getTrustStore(),
|
||||
this.ssl.getTrustStorePassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyPassword() {
|
||||
return this.ssl.getKeyPassword();
|
||||
}
|
||||
|
||||
private KeyStore createKeyStore(String type, String provider, String location, String password) throws Exception {
|
||||
type = (type != null) ? type : "JKS";
|
||||
char[] passwordChars = (password != null) ? password.toCharArray() : null;
|
||||
KeyStore store = (provider != null) ? KeyStore.getInstance(type, provider) : KeyStore.getInstance(type);
|
||||
if (type.equalsIgnoreCase("PKCS11")) {
|
||||
Assert.state(!StringUtils.hasText(location),
|
||||
() -> "KeyStore location is '" + location + "', but must be empty or null for PKCS11 key stores");
|
||||
store.load(null, passwordChars);
|
||||
}
|
||||
else {
|
||||
Assert.state(StringUtils.hasText(location), () -> "KeyStore location must not be empty or null");
|
||||
try {
|
||||
URL url = ResourceUtils.getURL(location);
|
||||
try (InputStream stream = url.openStream()) {
|
||||
store.load(stream, passwordChars);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not load key store '" + location + "'", ex);
|
||||
}
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link SslStoreProvider} if the appropriate SSL properties are
|
||||
* configured.
|
||||
* @param ssl the SSL properties
|
||||
* @return an {@code SslStoreProvider} or {@code null}
|
||||
*/
|
||||
static SslStoreProvider from(Ssl ssl) {
|
||||
if (ssl != null && ssl.isEnabled()) {
|
||||
return new JavaKeyStoreSslStoreProvider(ssl);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,256 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.server;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Parser for PKCS private key files in PEM format.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
final class PrivateKeyParser {
|
||||
|
||||
private static final String PKCS1_HEADER = "-+BEGIN\\s+RSA\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+";
|
||||
|
||||
private static final String PKCS1_FOOTER = "-+END\\s+RSA\\s+PRIVATE\\s+KEY[^-]*-+";
|
||||
|
||||
private static final String PKCS8_HEADER = "-+BEGIN\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+";
|
||||
|
||||
private static final String PKCS8_FOOTER = "-+END\\s+PRIVATE\\s+KEY[^-]*-+";
|
||||
|
||||
private static final String EC_HEADER = "-+BEGIN\\s+EC\\s+PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+";
|
||||
|
||||
private static final String EC_FOOTER = "-+END\\s+EC\\s+PRIVATE\\s+KEY[^-]*-+";
|
||||
|
||||
private static final String BASE64_TEXT = "([a-z0-9+/=\\r\\n]+)";
|
||||
|
||||
private static final List<PemParser> PEM_PARSERS;
|
||||
static {
|
||||
List<PemParser> parsers = new ArrayList<>();
|
||||
parsers.add(new PemParser(PKCS1_HEADER, PKCS1_FOOTER, "RSA", PrivateKeyParser::createKeySpecForPkcs1));
|
||||
parsers.add(new PemParser(EC_HEADER, EC_FOOTER, "EC", PrivateKeyParser::createKeySpecForEc));
|
||||
parsers.add(new PemParser(PKCS8_HEADER, PKCS8_FOOTER, "RSA", PKCS8EncodedKeySpec::new));
|
||||
PEM_PARSERS = Collections.unmodifiableList(parsers);
|
||||
}
|
||||
|
||||
/**
|
||||
* ASN.1 encoded object identifier {@literal 1.2.840.113549.1.1.1}.
|
||||
*/
|
||||
private static final int[] RSA_ALGORITHM = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
|
||||
|
||||
/**
|
||||
* ASN.1 encoded object identifier {@literal 1.2.840.10045.2.1}.
|
||||
*/
|
||||
private static final int[] EC_ALGORITHM = { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 };
|
||||
|
||||
/**
|
||||
* ASN.1 encoded object identifier {@literal 1.3.132.0.34}.
|
||||
*/
|
||||
private static final int[] EC_PARAMETERS = { 0x2b, 0x81, 0x04, 0x00, 0x22 };
|
||||
|
||||
private PrivateKeyParser() {
|
||||
}
|
||||
|
||||
private static PKCS8EncodedKeySpec createKeySpecForPkcs1(byte[] bytes) {
|
||||
return createKeySpecForAlgorithm(bytes, RSA_ALGORITHM, null);
|
||||
}
|
||||
|
||||
private static PKCS8EncodedKeySpec createKeySpecForEc(byte[] bytes) {
|
||||
return createKeySpecForAlgorithm(bytes, EC_ALGORITHM, EC_PARAMETERS);
|
||||
}
|
||||
|
||||
private static PKCS8EncodedKeySpec createKeySpecForAlgorithm(byte[] bytes, int[] algorithm, int[] parameters) {
|
||||
try {
|
||||
DerEncoder encoder = new DerEncoder();
|
||||
encoder.integer(0x00); // Version 0
|
||||
DerEncoder algorithmIdentifier = new DerEncoder();
|
||||
algorithmIdentifier.objectIdentifier(algorithm);
|
||||
algorithmIdentifier.objectIdentifier(parameters);
|
||||
byte[] byteArray = algorithmIdentifier.toByteArray();
|
||||
encoder.sequence(byteArray);
|
||||
encoder.octetString(bytes);
|
||||
return new PKCS8EncodedKeySpec(encoder.toSequence());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a private key from the specified resource.
|
||||
* @param resource the private key to parse
|
||||
* @return the parsed private key
|
||||
*/
|
||||
static PrivateKey parse(String resource) {
|
||||
try {
|
||||
String text = readText(resource);
|
||||
for (PemParser pemParser : PEM_PARSERS) {
|
||||
PrivateKey privateKey = pemParser.parse(text);
|
||||
if (privateKey != null) {
|
||||
return privateKey;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Unrecognized private key format");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Error loading private key file " + resource, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static String readText(String resource) throws IOException {
|
||||
URL url = ResourceUtils.getURL(resource);
|
||||
try (Reader reader = new InputStreamReader(url.openStream())) {
|
||||
return FileCopyUtils.copyToString(reader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parser for a specific PEM format.
|
||||
*/
|
||||
private static class PemParser {
|
||||
|
||||
private final Pattern pattern;
|
||||
|
||||
private final String algorithm;
|
||||
|
||||
private final Function<byte[], PKCS8EncodedKeySpec> keySpecFactory;
|
||||
|
||||
PemParser(String header, String footer, String algorithm,
|
||||
Function<byte[], PKCS8EncodedKeySpec> keySpecFactory) {
|
||||
this.pattern = Pattern.compile(header + BASE64_TEXT + footer, Pattern.CASE_INSENSITIVE);
|
||||
this.algorithm = algorithm;
|
||||
this.keySpecFactory = keySpecFactory;
|
||||
}
|
||||
|
||||
PrivateKey parse(String text) {
|
||||
Matcher matcher = this.pattern.matcher(text);
|
||||
return (!matcher.find()) ? null : parse(decodeBase64(matcher.group(1)));
|
||||
}
|
||||
|
||||
private static byte[] decodeBase64(String content) {
|
||||
byte[] contentBytes = content.replaceAll("\r", "").replaceAll("\n", "").getBytes();
|
||||
return Base64.getDecoder().decode(contentBytes);
|
||||
}
|
||||
|
||||
private PrivateKey parse(byte[] bytes) {
|
||||
try {
|
||||
PKCS8EncodedKeySpec keySpec = this.keySpecFactory.apply(bytes);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(this.algorithm);
|
||||
return keyFactory.generatePrivate(keySpec);
|
||||
}
|
||||
catch (GeneralSecurityException ex) {
|
||||
throw new IllegalArgumentException("Unexpected key format", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple ASN.1 DER encoder.
|
||||
*/
|
||||
static class DerEncoder {
|
||||
|
||||
private final ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
void objectIdentifier(int... encodedObjectIdentifier) throws IOException {
|
||||
int code = (encodedObjectIdentifier != null) ? 0x06 : 0x05;
|
||||
codeLengthBytes(code, bytes(encodedObjectIdentifier));
|
||||
}
|
||||
|
||||
void integer(int... encodedInteger) throws IOException {
|
||||
codeLengthBytes(0x02, bytes(encodedInteger));
|
||||
}
|
||||
|
||||
void octetString(byte[] bytes) throws IOException {
|
||||
codeLengthBytes(0x04, bytes);
|
||||
}
|
||||
|
||||
void sequence(int... elements) throws IOException {
|
||||
sequence(bytes(elements));
|
||||
}
|
||||
|
||||
void sequence(byte[] bytes) throws IOException {
|
||||
codeLengthBytes(0x30, bytes);
|
||||
}
|
||||
|
||||
void codeLengthBytes(int code, byte[] bytes) throws IOException {
|
||||
this.stream.write(code);
|
||||
int length = (bytes != null) ? bytes.length : 0;
|
||||
if (length <= 127) {
|
||||
this.stream.write(length & 0xFF);
|
||||
}
|
||||
else {
|
||||
ByteArrayOutputStream lengthStream = new ByteArrayOutputStream();
|
||||
while (length != 0) {
|
||||
lengthStream.write(length & 0xFF);
|
||||
length = length >> 8;
|
||||
}
|
||||
byte[] lengthBytes = lengthStream.toByteArray();
|
||||
this.stream.write(0x80 | lengthBytes.length);
|
||||
for (int i = lengthBytes.length - 1; i >= 0; i--) {
|
||||
this.stream.write(lengthBytes[i]);
|
||||
}
|
||||
}
|
||||
if (bytes != null) {
|
||||
this.stream.write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] bytes(int... elements) {
|
||||
if (elements == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] result = new byte[elements.length];
|
||||
for (int i = 0; i < elements.length; i++) {
|
||||
result[i] = (byte) elements[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
byte[] toSequence() throws IOException {
|
||||
DerEncoder sequenceEncoder = new DerEncoder();
|
||||
sequenceEncoder.sequence(toByteArray());
|
||||
return sequenceEncoder.toByteArray();
|
||||
}
|
||||
|
||||
byte[] toByteArray() {
|
||||
return this.stream.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -29,6 +29,8 @@ public class Ssl {
|
|||
|
||||
private boolean enabled = true;
|
||||
|
||||
private String bundle;
|
||||
|
||||
private ClientAuth clientAuth;
|
||||
|
||||
private String[] ciphers;
|
||||
|
@ -77,6 +79,24 @@ public class Ssl {
|
|||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the SSL bundle to use.
|
||||
* @return the SSL bundle name
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public String getBundle() {
|
||||
return this.bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the SSL bundle to use.
|
||||
* @param bundle the SSL bundle name
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public void setBundle(String bundle) {
|
||||
this.bundle = bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Whether client authentication is not wanted ("none"), wanted ("want") or
|
||||
* needed ("need"). Requires a trust store.
|
||||
|
@ -295,6 +315,28 @@ public class Ssl {
|
|||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if SSL is enabled for the given instance.
|
||||
* @param ssl the {@link Ssl SSL} instance or {@code null}
|
||||
* @return {@code true} is SSL is enabled
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static boolean isEnabled(Ssl ssl) {
|
||||
return (ssl != null) && ssl.isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create an {@link Ssl} instance for a specific bundle name.
|
||||
* @param bundle the name of the bundle
|
||||
* @return a new {@link Ssl} instance with the bundle set
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static Ssl forBundle(String bundle) {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setBundle(bundle);
|
||||
return ssl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Client authentication types.
|
||||
*/
|
||||
|
@ -313,7 +355,25 @@ public class Ssl {
|
|||
/**
|
||||
* Client authentication is needed and mandatory.
|
||||
*/
|
||||
NEED
|
||||
NEED;
|
||||
|
||||
/**
|
||||
* Map an optional {@link ClientAuth} value to a different type.
|
||||
* @param <R> the result type
|
||||
* @param clientAuth the client auth to map (may be {@code null})
|
||||
* @param none the value for {@link ClientAuth#NONE} or {@code null}
|
||||
* @param want the value for {@link ClientAuth#WANT}
|
||||
* @param need the value for {@link ClientAuth#NEED}
|
||||
* @return the mapped value
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static <R> R map(ClientAuth clientAuth, R none, R want, R need) {
|
||||
return switch ((clientAuth != null) ? clientAuth : NONE) {
|
||||
case NONE -> none;
|
||||
case WANT -> want;
|
||||
case NEED -> need;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -17,33 +17,25 @@
|
|||
package org.springframework.boot.web.server;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.boot.ssl.SslBundleKey;
|
||||
|
||||
/**
|
||||
* Provides utilities around SSL.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @since 2.1.13
|
||||
* @deprecated since 3.1.0 for removal in 3.3.0 in favor of
|
||||
* {@link SslBundleKey#assertContainsAlias(KeyStore)}
|
||||
*/
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
public final class SslConfigurationValidator {
|
||||
|
||||
private SslConfigurationValidator() {
|
||||
}
|
||||
|
||||
public static void validateKeyAlias(KeyStore keyStore, String keyAlias) {
|
||||
if (StringUtils.hasLength(keyAlias)) {
|
||||
try {
|
||||
Assert.state(keyStore.containsAlias(keyAlias),
|
||||
() -> String.format("Keystore does not contain specified alias '%s'", keyAlias));
|
||||
}
|
||||
catch (KeyStoreException ex) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Could not determine if keystore contains alias '%s'", keyAlias), ex);
|
||||
}
|
||||
}
|
||||
SslBundleKey.of(null, keyAlias).assertContainsAlias(keyStore);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -18,13 +18,18 @@ package org.springframework.boot.web.server;
|
|||
|
||||
import java.security.KeyStore;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
|
||||
/**
|
||||
* Interface to provide SSL key stores for an {@link WebServer} to use. Can be used when
|
||||
* file based key stores cannot be used.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
* @deprecated since 3.1.0 for removal in 3.3.0 in favor of registering an
|
||||
* {@link SslBundle}.
|
||||
*/
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
public interface SslStoreProvider {
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.server;
|
||||
|
||||
/**
|
||||
* Creates an {@link SslStoreProvider} based on SSL configuration properties.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public final class SslStoreProviderFactory {
|
||||
|
||||
private SslStoreProviderFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link SslStoreProvider} if the appropriate SSL properties are
|
||||
* configured.
|
||||
* @param ssl the SSL properties
|
||||
* @return an {@code SslStoreProvider} or {@code null}
|
||||
*/
|
||||
public static SslStoreProvider from(Ssl ssl) {
|
||||
SslStoreProvider sslStoreProvider = CertificateFileSslStoreProvider.from(ssl);
|
||||
return ((sslStoreProvider != null) ? sslStoreProvider : JavaKeyStoreSslStoreProvider.from(ssl));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.server;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
import org.springframework.boot.ssl.NoSuchSslBundleException;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundleKey;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.ssl.SslManagerBundle;
|
||||
import org.springframework.boot.ssl.SslOptions;
|
||||
import org.springframework.boot.ssl.SslStoreBundle;
|
||||
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
|
||||
import org.springframework.boot.ssl.jks.JksSslStoreDetails;
|
||||
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
|
||||
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.function.ThrowingSupplier;
|
||||
|
||||
/**
|
||||
* {@link SslBundle} backed by {@link Ssl} or an {@link SslStoreProvider}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public final class WebServerSslBundle implements SslBundle {
|
||||
|
||||
private final SslStoreBundle stores;
|
||||
|
||||
private final SslBundleKey key;
|
||||
|
||||
private final SslOptions options;
|
||||
|
||||
private final String protocol;
|
||||
|
||||
private final SslManagerBundle managers;
|
||||
|
||||
private WebServerSslBundle(SslStoreBundle stores, String keyPassword, Ssl ssl) {
|
||||
this.stores = stores;
|
||||
this.key = SslBundleKey.of(keyPassword, ssl.getKeyAlias());
|
||||
this.protocol = ssl.getProtocol();
|
||||
this.options = SslOptions.of(ssl.getCiphers(), ssl.getEnabledProtocols());
|
||||
this.managers = SslManagerBundle.from(this.stores, this.key);
|
||||
}
|
||||
|
||||
private static SslStoreBundle createPemStoreBundle(Ssl ssl) {
|
||||
PemSslStoreDetails keyStoreDetails = new PemSslStoreDetails(ssl.getKeyStoreType(), ssl.getCertificate(),
|
||||
ssl.getCertificatePrivateKey());
|
||||
PemSslStoreDetails trustStoreDetails = new PemSslStoreDetails(ssl.getTrustStoreType(),
|
||||
ssl.getTrustCertificate(), ssl.getTrustCertificatePrivateKey());
|
||||
return new PemSslStoreBundle(keyStoreDetails, trustStoreDetails, ssl.getKeyAlias());
|
||||
}
|
||||
|
||||
private static SslStoreBundle createJksStoreBundle(Ssl ssl) {
|
||||
JksSslStoreDetails keyStoreDetails = new JksSslStoreDetails(ssl.getKeyStoreType(), ssl.getKeyStoreProvider(),
|
||||
ssl.getKeyStore(), ssl.getKeyStorePassword());
|
||||
JksSslStoreDetails trustStoreDetails = new JksSslStoreDetails(ssl.getTrustStoreType(),
|
||||
ssl.getTrustStoreProvider(), ssl.getTrustStore(), ssl.getTrustStorePassword());
|
||||
return new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslStoreBundle getStores() {
|
||||
return this.stores;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslBundleKey getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslOptions getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return this.protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslManagerBundle getManagers() {
|
||||
return this.managers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link SslBundle} that should be used for the given {@link Ssl} instance.
|
||||
* @param ssl the source ssl instance
|
||||
* @return a {@link SslBundle} instance
|
||||
* @throws NoSuchSslBundleException if a bundle lookup fails
|
||||
*/
|
||||
public static SslBundle get(Ssl ssl) throws NoSuchSslBundleException {
|
||||
return get(ssl, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link SslBundle} that should be used for the given {@link Ssl} instance.
|
||||
* @param ssl the source ssl instance
|
||||
* @param sslBundles the bundles that should be used when {@link Ssl#getBundle()} is
|
||||
* set
|
||||
* @return a {@link SslBundle} instance
|
||||
* @throws NoSuchSslBundleException if a bundle lookup fails
|
||||
*/
|
||||
public static SslBundle get(Ssl ssl, SslBundles sslBundles) throws NoSuchSslBundleException {
|
||||
return get(ssl, sslBundles, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link SslBundle} that should be used for the given {@link Ssl} and
|
||||
* {@link SslStoreProvider} instances.
|
||||
* @param ssl the source {@link Ssl} instance
|
||||
* @param sslBundles the bundles that should be used when {@link Ssl#getBundle()} is
|
||||
* set
|
||||
* @param sslStoreProvider the {@link SslStoreProvider} to use or {@code null}
|
||||
* @return a {@link SslBundle} instance
|
||||
* @throws NoSuchSslBundleException if a bundle lookup fails
|
||||
* @deprecated since 3.1.0 for removal in 3.3.0 along with {@link SslStoreProvider}
|
||||
*/
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public static SslBundle get(Ssl ssl, SslBundles sslBundles, SslStoreProvider sslStoreProvider) {
|
||||
Assert.state(Ssl.isEnabled(ssl), "SSL is not enabled");
|
||||
String keyPassword = (sslStoreProvider != null) ? sslStoreProvider.getKeyPassword() : null;
|
||||
keyPassword = (keyPassword != null) ? keyPassword : ssl.getKeyPassword();
|
||||
if (sslStoreProvider != null) {
|
||||
SslStoreBundle stores = new SslStoreProviderBundleAdapter(sslStoreProvider);
|
||||
return new WebServerSslBundle(stores, keyPassword, ssl);
|
||||
}
|
||||
String bundleName = ssl.getBundle();
|
||||
if (StringUtils.hasText(bundleName)) {
|
||||
Assert.state(sslBundles != null,
|
||||
() -> "SSL bundle '%s' was requested but no SslBundles instance was provided"
|
||||
.formatted(bundleName));
|
||||
return sslBundles.getBundle(bundleName);
|
||||
}
|
||||
SslStoreBundle stores = createStoreBundle(ssl);
|
||||
return new WebServerSslBundle(stores, keyPassword, ssl);
|
||||
}
|
||||
|
||||
private static SslStoreBundle createStoreBundle(Ssl ssl) {
|
||||
if (hasCertificateProperties(ssl)) {
|
||||
return createPemStoreBundle(ssl);
|
||||
}
|
||||
if (hasJavaKeyStoreProperties(ssl)) {
|
||||
return createJksStoreBundle(ssl);
|
||||
}
|
||||
throw new IllegalStateException("SSL is enabled but no trust material is configured");
|
||||
}
|
||||
|
||||
static SslBundle createCertificateFileSslStoreProviderDelegate(Ssl ssl) {
|
||||
if (!hasCertificateProperties(ssl)) {
|
||||
return null;
|
||||
}
|
||||
SslStoreBundle stores = createPemStoreBundle(ssl);
|
||||
return new WebServerSslBundle(stores, ssl.getKeyPassword(), ssl);
|
||||
}
|
||||
|
||||
private static boolean hasCertificateProperties(Ssl ssl) {
|
||||
return Ssl.isEnabled(ssl) && ssl.getCertificate() != null && ssl.getCertificatePrivateKey() != null;
|
||||
}
|
||||
|
||||
private static boolean hasJavaKeyStoreProperties(Ssl ssl) {
|
||||
return Ssl.isEnabled(ssl) && ssl.getKeyStore() != null
|
||||
|| (ssl.getKeyStoreType() != null && ssl.getKeyStoreType().equals("PKCS11"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to adapt a {@link SslStoreProvider} into a {@link SslStoreBundle}.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
private static class SslStoreProviderBundleAdapter implements SslStoreBundle {
|
||||
|
||||
private final SslStoreProvider sslStoreProvider;
|
||||
|
||||
SslStoreProviderBundleAdapter(SslStoreProvider sslStoreProvider) {
|
||||
this.sslStoreProvider = sslStoreProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getKeyStore() {
|
||||
return ThrowingSupplier.of(this.sslStoreProvider::getKeyStore).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyStorePassword() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getTrustStore() {
|
||||
return ThrowingSupplier.of(this.sslStoreProvider::getTrustStore).get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -44,6 +44,13 @@ import reactor.test.StepVerifier;
|
|||
import org.springframework.boot.rsocket.server.RSocketServer;
|
||||
import org.springframework.boot.rsocket.server.RSocketServer.Transport;
|
||||
import org.springframework.boot.rsocket.server.RSocketServerCustomizer;
|
||||
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundleKey;
|
||||
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
|
||||
import org.springframework.boot.ssl.jks.JksSslStoreDetails;
|
||||
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
|
||||
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
|
@ -191,6 +198,50 @@ class NettyRSocketServerFactoryTests {
|
|||
"src/test/resources/test-cert.pem", Transport.WEBSOCKET);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tcpTransportBasicSslFromClassPathWithBundle() {
|
||||
testBasicSslWithKeyStoreFromBundle("classpath:test.jks", "password", Transport.TCP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tcpTransportBasicSslFromFileSystemWithBundle() {
|
||||
testBasicSslWithKeyStoreFromBundle("src/test/resources/test.jks", "password", Transport.TCP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void websocketTransportBasicSslFromClassPathWithBundle() {
|
||||
testBasicSslWithKeyStoreFromBundle("classpath:test.jks", "password", Transport.WEBSOCKET);
|
||||
}
|
||||
|
||||
@Test
|
||||
void websocketTransportBasicSslFromFileSystemWithBundle() {
|
||||
testBasicSslWithKeyStoreFromBundle("src/test/resources/test.jks", "password", Transport.WEBSOCKET);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tcpTransportBasicSslCertificateFromClassPathWithBundle() {
|
||||
testBasicSslWithPemCertificateFromBundle("classpath:test-cert.pem", "classpath:test-key.pem",
|
||||
"classpath:test-cert.pem", Transport.TCP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tcpTransportBasicSslCertificateFromFileSystemWithBundle() {
|
||||
testBasicSslWithPemCertificateFromBundle("src/test/resources/test-cert.pem", "src/test/resources/test-key.pem",
|
||||
"src/test/resources/test-cert.pem", Transport.TCP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void websocketTransportBasicSslCertificateFromClassPathWithBundle() {
|
||||
testBasicSslWithPemCertificateFromBundle("classpath:test-cert.pem", "classpath:test-key.pem",
|
||||
"classpath:test-cert.pem", Transport.WEBSOCKET);
|
||||
}
|
||||
|
||||
@Test
|
||||
void websocketTransportBasicSslCertificateFromFileSystemWithBundle() {
|
||||
testBasicSslWithPemCertificateFromBundle("src/test/resources/test-cert.pem", "src/test/resources/test-key.pem",
|
||||
"src/test/resources/test-cert.pem", Transport.WEBSOCKET);
|
||||
}
|
||||
|
||||
private void checkEchoRequest() {
|
||||
String payload = "test payload";
|
||||
Mono<String> response = this.requester.route("test").data(payload).retrieveMono(String.class);
|
||||
|
@ -228,6 +279,39 @@ class NettyRSocketServerFactoryTests {
|
|||
checkEchoRequest();
|
||||
}
|
||||
|
||||
private void testBasicSslWithKeyStoreFromBundle(String keyStore, String keyPassword, Transport transport) {
|
||||
NettyRSocketServerFactory factory = getFactory();
|
||||
factory.setTransport(transport);
|
||||
JksSslStoreDetails keyStoreDetails = JksSslStoreDetails.forLocation(keyStore);
|
||||
JksSslStoreDetails trustStoreDetails = null;
|
||||
SslBundle sslBundle = SslBundle.of(new JksSslStoreBundle(keyStoreDetails, trustStoreDetails),
|
||||
SslBundleKey.of(keyPassword));
|
||||
factory.setSsl(Ssl.forBundle("test"));
|
||||
factory.setSslBundles(new DefaultSslBundleRegistry("test", sslBundle));
|
||||
this.server = factory.create(new EchoRequestResponseAcceptor());
|
||||
this.server.start();
|
||||
this.requester = (transport == Transport.TCP) ? createSecureRSocketTcpClient()
|
||||
: createSecureRSocketWebSocketClient();
|
||||
checkEchoRequest();
|
||||
}
|
||||
|
||||
private void testBasicSslWithPemCertificateFromBundle(String certificate, String certificatePrivateKey,
|
||||
String trustCertificate, Transport transport) {
|
||||
NettyRSocketServerFactory factory = getFactory();
|
||||
factory.setTransport(transport);
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate(certificate)
|
||||
.withPrivateKey(certificatePrivateKey);
|
||||
PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate(trustCertificate);
|
||||
SslBundle sslBundle = SslBundle.of(new PemSslStoreBundle(keyStoreDetails, trustStoreDetails));
|
||||
factory.setSsl(Ssl.forBundle("test"));
|
||||
factory.setSslBundles(new DefaultSslBundleRegistry("test", sslBundle));
|
||||
this.server = factory.create(new EchoRequestResponseAcceptor());
|
||||
this.server.start();
|
||||
this.requester = (transport == Transport.TCP) ? createSecureRSocketTcpClient()
|
||||
: createSecureRSocketWebSocketClient();
|
||||
checkEchoRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void tcpTransportSslRejectsInsecureClient() {
|
||||
NettyRSocketServerFactory factory = getFactory();
|
||||
|
|
|
@ -35,7 +35,7 @@ import org.springframework.boot.web.embedded.test.MockPkcs11Security;
|
|||
import org.springframework.boot.web.embedded.test.MockPkcs11SecurityProvider;
|
||||
import org.springframework.boot.web.server.Http2;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.SslStoreProviderFactory;
|
||||
import org.springframework.boot.web.server.WebServerSslBundle;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
@ -91,10 +91,10 @@ class SslServerCustomizerTests {
|
|||
@Test
|
||||
void configureSslWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() {
|
||||
Ssl ssl = new Ssl();
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(null, ssl, null, null);
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
() -> customizer.configureSsl(new SslContextFactory.Server(), ssl, SslStoreProviderFactory.from(ssl)))
|
||||
.withMessageContaining("KeyStore location must not be empty or null");
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(null, null, null, WebServerSslBundle.get(ssl));
|
||||
customizer.configureSsl(new SslContextFactory.Server(), ssl.getClientAuth());
|
||||
}).withMessageContaining("SSL is enabled but no trust material is configured");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -104,10 +104,10 @@ class SslServerCustomizerTests {
|
|||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||
ssl.setKeyStore("src/test/resources/test.jks");
|
||||
ssl.setKeyPassword("password");
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(null, ssl, null, null);
|
||||
assertThatIllegalStateException().isThrownBy(
|
||||
() -> customizer.configureSsl(new SslContextFactory.Server(), ssl, SslStoreProviderFactory.from(ssl)))
|
||||
.withMessageContaining("must be empty or null for PKCS11 key stores");
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(null, null, null, WebServerSslBundle.get(ssl));
|
||||
customizer.configureSsl(new SslContextFactory.Server(), ssl.getClientAuth());
|
||||
}).withMessageContaining("must be empty or null for PKCS11 hardware key stores");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -116,8 +116,10 @@ class SslServerCustomizerTests {
|
|||
ssl.setKeyStoreType("PKCS11");
|
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||
ssl.setKeyStorePassword("1234");
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(null, ssl, null, null);
|
||||
assertThatNoException().isThrownBy(() -> customizer.configureSsl(new SslContextFactory.Server(), ssl, null));
|
||||
assertThatNoException().isThrownBy(() -> {
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(null, null, null, WebServerSslBundle.get(ssl));
|
||||
customizer.configureSsl(new SslContextFactory.Server(), ssl.getClientAuth());
|
||||
});
|
||||
}
|
||||
|
||||
private Server createCustomizedServer() {
|
||||
|
@ -132,7 +134,8 @@ class SslServerCustomizerTests {
|
|||
|
||||
private Server createCustomizedServer(Ssl ssl, Http2 http2) {
|
||||
Server server = new Server();
|
||||
new SslServerCustomizer(new InetSocketAddress(0), ssl, null, http2).customize(server);
|
||||
new SslServerCustomizer(http2, new InetSocketAddress(0), ssl.getClientAuth(), WebServerSslBundle.get(ssl))
|
||||
.customize(server);
|
||||
return server;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.security.NoSuchProviderException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.web.embedded.test.MockPkcs11Security;
|
||||
import org.springframework.boot.web.embedded.test.MockPkcs11SecurityProvider;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.SslStoreProviderFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link SslServerCustomizer}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Raheela Aslam
|
||||
* @author Cyril Dangerville
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@MockPkcs11Security
|
||||
class SslServerCustomizerTests {
|
||||
|
||||
@Test
|
||||
void keyStoreProviderIsUsedWhenCreatingKeyStore() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setKeyStore("src/test/resources/test.jks");
|
||||
ssl.setKeyStoreProvider("com.example.KeyStoreProvider");
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl)))
|
||||
.withCauseInstanceOf(NoSuchProviderException.class)
|
||||
.withMessageContaining("com.example.KeyStoreProvider");
|
||||
}
|
||||
|
||||
@Test
|
||||
void trustStoreProviderIsUsedWhenCreatingTrustStore() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setTrustStorePassword("password");
|
||||
ssl.setTrustStore("src/test/resources/test.jks");
|
||||
ssl.setTrustStoreProvider("com.example.TrustStoreProvider");
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> customizer.getTrustManagerFactory(SslStoreProviderFactory.from(ssl)))
|
||||
.withCauseInstanceOf(NoSuchProviderException.class)
|
||||
.withMessageContaining("com.example.TrustStoreProvider");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeyManagerFactoryWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() {
|
||||
Ssl ssl = new Ssl();
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl)))
|
||||
.withCauseInstanceOf(IllegalStateException.class)
|
||||
.withMessageContaining("KeyStore location must not be empty or null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeyManagerFactoryWhenSslIsEnabledWithPkcs11AndKeyStoreThrowsException() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStoreType("PKCS11");
|
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||
ssl.setKeyStore("src/test/resources/test.jks");
|
||||
ssl.setKeyPassword("password");
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl)))
|
||||
.withCauseInstanceOf(IllegalStateException.class)
|
||||
.withMessageContaining("must be empty or null for PKCS11 key stores");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeyManagerFactoryWhenSslIsEnabledWithPkcs11AndKeyStoreProvider() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStoreType("PKCS11");
|
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||
ssl.setKeyStorePassword("1234");
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null);
|
||||
assertThatNoException()
|
||||
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl)));
|
||||
}
|
||||
|
||||
}
|
|
@ -41,6 +41,7 @@ import org.springframework.boot.web.embedded.test.MockPkcs11Security;
|
|||
import org.springframework.boot.web.embedded.test.MockPkcs11SecurityProvider;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.SslStoreProvider;
|
||||
import org.springframework.boot.web.server.WebServerSslBundle;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
|
@ -58,6 +59,7 @@ import static org.mockito.Mockito.mock;
|
|||
* @author Scott Frederick
|
||||
* @author Cyril Dangerville
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
@DirtiesUrlFactories
|
||||
@MockPkcs11Security
|
||||
|
@ -84,10 +86,11 @@ class SslConnectorCustomizerTests {
|
|||
@Test
|
||||
void sslCiphersConfiguration() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("test.jks");
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setCiphers(new String[] { "ALPHA", "BRAVO", "CHARLIE" });
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, null);
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl.getClientAuth(),
|
||||
WebServerSslBundle.get(ssl));
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
customizer.customize(connector);
|
||||
this.tomcat.start();
|
||||
|
@ -102,7 +105,8 @@ class SslConnectorCustomizerTests {
|
|||
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);
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl.getClientAuth(),
|
||||
WebServerSslBundle.get(ssl));
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
customizer.customize(connector);
|
||||
this.tomcat.start();
|
||||
|
@ -118,7 +122,8 @@ class SslConnectorCustomizerTests {
|
|||
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);
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl.getClientAuth(),
|
||||
WebServerSslBundle.get(ssl));
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
customizer.customize(connector);
|
||||
this.tomcat.start();
|
||||
|
@ -128,6 +133,7 @@ class SslConnectorCustomizerTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
void customizeWhenSslStoreProviderProvidesOnlyKeyStoreShouldUseDefaultTruststore() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyPassword("password");
|
||||
|
@ -135,7 +141,8 @@ class SslConnectorCustomizerTests {
|
|||
SslStoreProvider sslStoreProvider = mock(SslStoreProvider.class);
|
||||
KeyStore keyStore = loadStore();
|
||||
given(sslStoreProvider.getKeyStore()).willReturn(keyStore);
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, sslStoreProvider);
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl.getClientAuth(),
|
||||
WebServerSslBundle.get(ssl, null, sslStoreProvider));
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
customizer.customize(connector);
|
||||
this.tomcat.start();
|
||||
|
@ -148,6 +155,7 @@ class SslConnectorCustomizerTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
void customizeWhenSslStoreProviderProvidesOnlyTrustStoreShouldUseDefaultKeystore() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyPassword("password");
|
||||
|
@ -155,7 +163,8 @@ class SslConnectorCustomizerTests {
|
|||
SslStoreProvider sslStoreProvider = mock(SslStoreProvider.class);
|
||||
KeyStore trustStore = loadStore();
|
||||
given(sslStoreProvider.getTrustStore()).willReturn(trustStore);
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, sslStoreProvider);
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl.getClientAuth(),
|
||||
WebServerSslBundle.get(ssl, null, sslStoreProvider));
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
customizer.customize(connector);
|
||||
this.tomcat.start();
|
||||
|
@ -164,6 +173,7 @@ class SslConnectorCustomizerTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
void customizeWhenSslStoreProviderPresentShouldIgnorePasswordFromSsl(CapturedOutput output) throws Exception {
|
||||
System.setProperty("javax.net.ssl.trustStorePassword", "trustStoreSecret");
|
||||
Ssl ssl = new Ssl();
|
||||
|
@ -172,7 +182,8 @@ class SslConnectorCustomizerTests {
|
|||
SslStoreProvider sslStoreProvider = mock(SslStoreProvider.class);
|
||||
given(sslStoreProvider.getTrustStore()).willReturn(loadStore());
|
||||
given(sslStoreProvider.getKeyStore()).willReturn(loadStore());
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, sslStoreProvider);
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl.getClientAuth(),
|
||||
WebServerSslBundle.get(ssl, null, sslStoreProvider));
|
||||
Connector connector = this.tomcat.getConnector();
|
||||
customizer.customize(connector);
|
||||
this.tomcat.start();
|
||||
|
@ -182,9 +193,11 @@ class SslConnectorCustomizerTests {
|
|||
|
||||
@Test
|
||||
void customizeWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> new SslConnectorCustomizer(new Ssl()).customize(this.tomcat.getConnector()))
|
||||
.withMessageContaining("KeyStore location must not be empty or null");
|
||||
assertThatIllegalStateException().isThrownBy(() -> {
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(Ssl.ClientAuth.NONE,
|
||||
WebServerSslBundle.get(new Ssl()));
|
||||
customizer.customize(this.tomcat.getConnector());
|
||||
}).withMessageContaining("SSL is enabled but no trust material is configured");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -194,9 +207,10 @@ class SslConnectorCustomizerTests {
|
|||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||
ssl.setKeyStore("src/test/resources/test.jks");
|
||||
ssl.setKeyPassword("password");
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl);
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl.getClientAuth(),
|
||||
WebServerSslBundle.get(ssl));
|
||||
assertThatIllegalStateException().isThrownBy(() -> customizer.customize(this.tomcat.getConnector()))
|
||||
.withMessageContaining("must be empty or null for PKCS11 key stores");
|
||||
.withMessageContaining("must be empty or null for PKCS11 hardware key stores");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -205,7 +219,8 @@ class SslConnectorCustomizerTests {
|
|||
ssl.setKeyStoreType("PKCS11");
|
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||
ssl.setKeyStorePassword("1234");
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, null);
|
||||
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl.getClientAuth(),
|
||||
WebServerSslBundle.get(ssl));
|
||||
assertThatNoException().isThrownBy(() -> customizer.customize(this.tomcat.getConnector()));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.security.NoSuchProviderException;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.web.embedded.test.MockPkcs11Security;
|
||||
import org.springframework.boot.web.embedded.test.MockPkcs11SecurityProvider;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.SslStoreProviderFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link SslBuilderCustomizer}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Raheela Aslam
|
||||
* @author Cyril Dangerville
|
||||
*/
|
||||
@MockPkcs11Security
|
||||
class SslBuilderCustomizerTests {
|
||||
|
||||
@Test
|
||||
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 = customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl));
|
||||
Class<?> name = Class
|
||||
.forName("org.springframework.boot.web.embedded.undertow.SslBuilderCustomizer$ConfigurableAliasKeyManager");
|
||||
assertThat(keyManagers[0]).isNotInstanceOf(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
void keyStoreProviderIsUsedWhenCreatingKeyStore() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setKeyStore("src/test/resources/test.jks");
|
||||
ssl.setKeyStoreProvider("com.example.KeyStoreProvider");
|
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl)))
|
||||
.withCauseInstanceOf(NoSuchProviderException.class)
|
||||
.withMessageContaining("com.example.KeyStoreProvider");
|
||||
}
|
||||
|
||||
@Test
|
||||
void trustStoreProviderIsUsedWhenCreatingTrustStore() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setTrustStorePassword("password");
|
||||
ssl.setTrustStore("src/test/resources/test.jks");
|
||||
ssl.setTrustStoreProvider("com.example.TrustStoreProvider");
|
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> customizer.getTrustManagers(SslStoreProviderFactory.from(ssl)))
|
||||
.withMessageContaining("com.example.TrustStoreProvider");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeyManagersWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl)))
|
||||
.withCauseInstanceOf(IllegalStateException.class)
|
||||
.withMessageContaining("KeyStore location must not be empty or null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureSslWhenSslIsEnabledWithPkcs11AndKeyStoreThrowsException() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStoreType("PKCS11");
|
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||
ssl.setKeyStore("src/test/resources/test.jks");
|
||||
ssl.setKeyPassword("password");
|
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl)))
|
||||
.withCauseInstanceOf(IllegalStateException.class)
|
||||
.withMessageContaining("must be empty or null for PKCS11 key stores");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeWhenSslIsEnabledWithPkcs11AndKeyStoreProvider() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStoreType("PKCS11");
|
||||
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
|
||||
ssl.setKeyStorePassword("1234");
|
||||
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
|
||||
assertThatNoException().isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl)));
|
||||
}
|
||||
|
||||
}
|
|
@ -212,7 +212,7 @@ public abstract class AbstractReactiveWebServerFactoryTests {
|
|||
}
|
||||
|
||||
protected void assertThatSslWithInvalidAliasCallFails(ThrowingCallable call) {
|
||||
assertThatThrownBy(call).hasStackTraceContaining("Keystore does not contain specified alias 'test-alias-404'");
|
||||
assertThatThrownBy(call).hasStackTraceContaining("Keystore does not contain alias 'test-alias-404'");
|
||||
}
|
||||
|
||||
protected ReactorClientHttpConnector buildTrustAllSslConnector() {
|
||||
|
@ -402,7 +402,7 @@ public abstract class AbstractReactiveWebServerFactoryTests {
|
|||
@Test
|
||||
void whenSslIsEnabledAndNoKeyStoreIsConfiguredThenServerFailsToStart() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> testBasicSslWithKeyStore(null, null))
|
||||
.withMessageContaining("KeyStore location must not be empty or null");
|
||||
.withMessageContaining("SSL is enabled but no trust material is configured");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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.server;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CertificateFileSslStoreProvider}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class CertificateFileSslStoreProviderTests {
|
||||
|
||||
@Test
|
||||
void fromSslWhenNullReturnsNull() {
|
||||
assertThat(CertificateFileSslStoreProvider.from(null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSslWhenDisabledReturnsNull() {
|
||||
assertThat(CertificateFileSslStoreProvider.from(new Ssl())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSslWithCertAndKeyReturnsStoreProvider() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setEnabled(true);
|
||||
ssl.setCertificate("classpath:test-cert.pem");
|
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem");
|
||||
SslStoreProvider storeProvider = CertificateFileSslStoreProvider.from(ssl);
|
||||
assertThat(storeProvider).isNotNull();
|
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), KeyStore.getDefaultType(), "spring-boot-web");
|
||||
assertThat(storeProvider.getTrustStore()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSslWithCertAndKeyAndTrustCertReturnsStoreProvider() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setEnabled(true);
|
||||
ssl.setCertificate("classpath:test-cert.pem");
|
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem");
|
||||
ssl.setTrustCertificate("classpath:test-cert.pem");
|
||||
SslStoreProvider storeProvider = CertificateFileSslStoreProvider.from(ssl);
|
||||
assertThat(storeProvider).isNotNull();
|
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), KeyStore.getDefaultType(), "spring-boot-web");
|
||||
assertStoreContainsCert(storeProvider.getTrustStore(), KeyStore.getDefaultType(), "spring-boot-web-0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSslWithCertAndKeyAndTrustCertAndTrustKeyReturnsStoreProvider() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setEnabled(true);
|
||||
ssl.setCertificate("classpath:test-cert.pem");
|
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem");
|
||||
ssl.setTrustCertificate("classpath:test-cert.pem");
|
||||
ssl.setTrustCertificatePrivateKey("classpath:test-key.pem");
|
||||
SslStoreProvider storeProvider = CertificateFileSslStoreProvider.from(ssl);
|
||||
assertThat(storeProvider).isNotNull();
|
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), KeyStore.getDefaultType(), "spring-boot-web");
|
||||
assertStoreContainsCertAndKey(storeProvider.getTrustStore(), KeyStore.getDefaultType(), "spring-boot-web");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSslWithKeyAliasReturnsStoreProvider() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setEnabled(true);
|
||||
ssl.setKeyAlias("test-alias");
|
||||
ssl.setCertificate("classpath:test-cert.pem");
|
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem");
|
||||
ssl.setTrustCertificate("classpath:test-cert.pem");
|
||||
ssl.setTrustCertificatePrivateKey("classpath:test-key.pem");
|
||||
SslStoreProvider storeProvider = CertificateFileSslStoreProvider.from(ssl);
|
||||
assertThat(storeProvider).isNotNull();
|
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), KeyStore.getDefaultType(), "test-alias");
|
||||
assertStoreContainsCertAndKey(storeProvider.getTrustStore(), KeyStore.getDefaultType(), "test-alias");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSslWithStoreTypeReturnsStoreProvider() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setEnabled(true);
|
||||
ssl.setKeyStoreType("PKCS12");
|
||||
ssl.setTrustStoreType("PKCS12");
|
||||
ssl.setCertificate("classpath:test-cert.pem");
|
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem");
|
||||
ssl.setTrustCertificate("classpath:test-cert.pem");
|
||||
ssl.setTrustCertificatePrivateKey("classpath:test-key.pem");
|
||||
SslStoreProvider storeProvider = CertificateFileSslStoreProvider.from(ssl);
|
||||
assertThat(storeProvider).isNotNull();
|
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), "PKCS12", "spring-boot-web");
|
||||
assertStoreContainsCertAndKey(storeProvider.getTrustStore(), "PKCS12", "spring-boot-web");
|
||||
}
|
||||
|
||||
private void assertStoreContainsCertAndKey(KeyStore keyStore, String keyStoreType, String keyAlias)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
assertThat(keyStore).isNotNull();
|
||||
assertThat(keyStore.getType()).isEqualTo(keyStoreType);
|
||||
assertThat(keyStore.containsAlias(keyAlias)).isTrue();
|
||||
assertThat(keyStore.getCertificate(keyAlias)).isNotNull();
|
||||
assertThat(keyStore.getKey(keyAlias, new char[] {})).isNotNull();
|
||||
}
|
||||
|
||||
private void assertStoreContainsCert(KeyStore keyStore, String keyStoreType, String keyAlias)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
assertThat(keyStore).isNotNull();
|
||||
assertThat(keyStore.getType()).isEqualTo(keyStoreType);
|
||||
assertThat(keyStore.containsAlias(keyAlias)).isTrue();
|
||||
assertThat(keyStore.getCertificate(keyAlias)).isNotNull();
|
||||
assertThat(keyStore.getKey(keyAlias, new char[] {})).isNull();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.server;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link CertificateParser}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class CertificateParserTests {
|
||||
|
||||
@Test
|
||||
void parseCertificate() {
|
||||
X509Certificate[] certificates = CertificateParser.parse("classpath:test-cert.pem");
|
||||
assertThat(certificates).isNotNull();
|
||||
assertThat(certificates).hasSize(1);
|
||||
assertThat(certificates[0].getType()).isEqualTo("X.509");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCertificateChain() {
|
||||
X509Certificate[] certificates = CertificateParser.parse("classpath:test-cert-chain.pem");
|
||||
assertThat(certificates).isNotNull();
|
||||
assertThat(certificates).hasSize(2);
|
||||
assertThat(certificates[0].getType()).isEqualTo("X.509");
|
||||
assertThat(certificates[1].getType()).isEqualTo("X.509");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseWithInvalidPathWillThrowException() {
|
||||
String path = "file:///bad/path/cert.pem";
|
||||
assertThatIllegalStateException().isThrownBy(() -> CertificateParser.parse("file:///bad/path/cert.pem"))
|
||||
.withMessageContaining(path);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.server;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.web.embedded.test.MockPkcs11Security;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link JavaKeyStoreSslStoreProvider}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@MockPkcs11Security
|
||||
class JavaKeyStoreSslStoreProviderTests {
|
||||
|
||||
@Test
|
||||
void fromSslWhenNullReturnsNull() {
|
||||
assertThat(JavaKeyStoreSslStoreProvider.from(null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSslWhenDisabledReturnsNull() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setEnabled(false);
|
||||
assertThat(JavaKeyStoreSslStoreProvider.from(ssl)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeyStoreWithNoLocationThrowsException() {
|
||||
Ssl ssl = new Ssl();
|
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl);
|
||||
assertThatIllegalStateException().isThrownBy(storeProvider::getKeyStore)
|
||||
.withMessageContaining("KeyStore location must not be empty or null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeyStoreWithTypePKCS11AndLocationThrowsException() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("test.jks");
|
||||
ssl.setKeyStoreType("PKCS11");
|
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl);
|
||||
assertThatIllegalStateException().isThrownBy(storeProvider::getKeyStore)
|
||||
.withMessageContaining("KeyStore location is 'test.jks', but must be empty or null for PKCS11 key stores");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeyStoreWithLocationReturnsKeyStore() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl);
|
||||
assertThat(storeProvider).isNotNull();
|
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), "JKS", "test-alias", "password");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTrustStoreWithLocationsReturnsTrustStore() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setTrustStore("classpath:test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl);
|
||||
assertThat(storeProvider).isNotNull();
|
||||
assertStoreContainsCertAndKey(storeProvider.getTrustStore(), "JKS", "test-alias", "password");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeyStoreWithTypeUsesType() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setKeyStoreType("PKCS12");
|
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl);
|
||||
assertThat(storeProvider).isNotNull();
|
||||
assertStoreContainsCertAndKey(storeProvider.getKeyStore(), "PKCS12", "test-alias", "password");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTrustStoreWithTypeUsesType() throws Exception {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setTrustStore("classpath:test.jks");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setTrustStoreType("PKCS12");
|
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl);
|
||||
assertThat(storeProvider).isNotNull();
|
||||
assertStoreContainsCertAndKey(storeProvider.getTrustStore(), "PKCS12", "test-alias", "password");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getKeyStoreWithProviderUsesProvider() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("classpath:test.jks");
|
||||
ssl.setKeyStoreProvider("com.example.KeyStoreProvider");
|
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl);
|
||||
assertThatExceptionOfType(NoSuchProviderException.class).isThrownBy(storeProvider::getKeyStore)
|
||||
.withMessageContaining("com.example.KeyStoreProvider");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTrustStoreWithProviderUsesProvider() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setTrustStore("classpath:test.jks");
|
||||
ssl.setTrustStoreProvider("com.example.TrustStoreProvider");
|
||||
SslStoreProvider storeProvider = JavaKeyStoreSslStoreProvider.from(ssl);
|
||||
assertThatExceptionOfType(NoSuchProviderException.class).isThrownBy(storeProvider::getTrustStore)
|
||||
.withMessageContaining("com.example.TrustStoreProvider");
|
||||
}
|
||||
|
||||
private void assertStoreContainsCertAndKey(KeyStore keyStore, String keyStoreType, String keyAlias,
|
||||
String keyPassword) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
assertThat(keyStore).isNotNull();
|
||||
assertThat(keyStore.getType()).isEqualTo(keyStoreType);
|
||||
assertThat(keyStore.containsAlias(keyAlias)).isTrue();
|
||||
assertThat(keyStore.getCertificate(keyAlias)).isNotNull();
|
||||
assertThat(keyStore.getKey(keyAlias, keyPassword.toCharArray())).isNotNull();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.server;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link PrivateKeyParser}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class PrivateKeyParserTests {
|
||||
|
||||
@Test
|
||||
void parsePkcs8KeyFile() {
|
||||
PrivateKey privateKey = PrivateKeyParser.parse("classpath:test-key.pem");
|
||||
assertThat(privateKey).isNotNull();
|
||||
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
|
||||
assertThat(privateKey.getAlgorithm()).isEqualTo("RSA");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parsePkcs8KeyFileWithEcdsa() {
|
||||
PrivateKey privateKey = PrivateKeyParser.parse("classpath:test-ec-key.pem");
|
||||
assertThat(privateKey).isNotNull();
|
||||
assertThat(privateKey.getFormat()).isEqualTo("PKCS#8");
|
||||
assertThat(privateKey.getAlgorithm()).isEqualTo("EC");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseWithNonKeyFileWillThrowException() {
|
||||
String path = "classpath:test-banner.txt";
|
||||
assertThatIllegalStateException().isThrownBy(() -> PrivateKeyParser.parse("file://" + path))
|
||||
.withMessageContaining(path);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseWithInvalidPathWillThrowException() {
|
||||
String path = "file:///bad/path/key.pem";
|
||||
assertThatIllegalStateException().isThrownBy(() -> PrivateKeyParser.parse(path)).withMessageContaining(path);
|
||||
}
|
||||
|
||||
}
|
|
@ -31,7 +31,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
class SslConfigurationValidatorTests {
|
||||
|
||||
private static final String VALID_ALIAS = "test-alias";
|
||||
|
@ -67,7 +68,7 @@ class SslConfigurationValidatorTests {
|
|||
void validateKeyAliasWhenAliasNotFoundShouldThrowException() {
|
||||
assertThatThrownBy(() -> SslConfigurationValidator.validateKeyAlias(this.keyStore, INVALID_ALIAS))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessage("Keystore does not contain specified alias '" + INVALID_ALIAS + "'");
|
||||
.hasMessage("Keystore does not contain alias '" + INVALID_ALIAS + "'");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.server;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundleKey;
|
||||
import org.springframework.boot.ssl.SslOptions;
|
||||
import org.springframework.boot.ssl.SslStoreBundle;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link WebServerSslBundle}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class WebServerSslBundleTests {
|
||||
|
||||
@Test
|
||||
void whenSslDisabledThrowsException() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setEnabled(false);
|
||||
assertThatIllegalStateException().isThrownBy(() -> WebServerSslBundle.get(ssl))
|
||||
.withMessage("SSL is not enabled");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenFromJksProperties() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStore("classpath:test.p12");
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setKeyStoreType("PKCS12");
|
||||
ssl.setTrustStore("classpath:test.p12");
|
||||
ssl.setTrustStorePassword("secret");
|
||||
ssl.setTrustStoreType("PKCS12");
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setKeyAlias("alias");
|
||||
ssl.setClientAuth(Ssl.ClientAuth.NONE);
|
||||
ssl.setCiphers(new String[] { "ONE", "TWO", "THREE" });
|
||||
ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" });
|
||||
ssl.setProtocol("TestProtocol");
|
||||
SslBundle bundle = WebServerSslBundle.get(ssl);
|
||||
assertThat(bundle).isNotNull();
|
||||
assertThat(bundle.getProtocol()).isEqualTo("TestProtocol");
|
||||
SslBundleKey key = bundle.getKey();
|
||||
assertThat(key.getPassword()).isEqualTo("password");
|
||||
assertThat(key.getAlias()).isEqualTo("alias");
|
||||
SslStoreBundle stores = bundle.getStores();
|
||||
assertThat(stores.getKeyStorePassword()).isEqualTo("secret");
|
||||
assertThat(stores.getKeyStore()).isNotNull();
|
||||
assertThat(stores.getTrustStore()).isNotNull();
|
||||
SslOptions options = bundle.getOptions();
|
||||
assertThat(options.getCiphers()).containsExactly("ONE", "TWO", "THREE");
|
||||
assertThat(options.getEnabledProtocols()).containsExactly("TLSv1.1", "TLSv1.2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenFromJksPropertiesWithPkcs11StoreType() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStorePassword("secret");
|
||||
ssl.setKeyStoreType("PKCS11");
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setClientAuth(Ssl.ClientAuth.NONE);
|
||||
SslBundle bundle = WebServerSslBundle.get(ssl);
|
||||
assertThat(bundle).isNotNull();
|
||||
assertThat(bundle.getStores().getKeyStorePassword()).isEqualTo("secret");
|
||||
assertThat(bundle.getKey().getPassword()).isEqualTo("password");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenFromPemProperties() {
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setCertificate("classpath:test-cert.pem");
|
||||
ssl.setCertificatePrivateKey("classpath:test-key.pem");
|
||||
ssl.setTrustCertificate("classpath:test-cert-chain.pem");
|
||||
ssl.setKeyStoreType("PKCS12");
|
||||
ssl.setTrustStoreType("PKCS12");
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setClientAuth(Ssl.ClientAuth.NONE);
|
||||
ssl.setCiphers(new String[] { "ONE", "TWO", "THREE" });
|
||||
ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" });
|
||||
ssl.setProtocol("TLSv1.1");
|
||||
SslBundle bundle = WebServerSslBundle.get(ssl);
|
||||
assertThat(bundle).isNotNull();
|
||||
SslBundleKey key = bundle.getKey();
|
||||
assertThat(key.getAlias()).isNull();
|
||||
assertThat(key.getPassword()).isEqualTo("password");
|
||||
SslStoreBundle stores = bundle.getStores();
|
||||
assertThat(stores.getKeyStorePassword()).isNull();
|
||||
assertThat(stores.getKeyStore()).isNotNull();
|
||||
assertThat(stores.getTrustStore()).isNotNull();
|
||||
SslOptions options = bundle.getOptions();
|
||||
assertThat(options.getCiphers()).containsExactly("ONE", "TWO", "THREE");
|
||||
assertThat(options.getEnabledProtocols()).containsExactly("TLSv1.1", "TLSv1.2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
void whenFromCustomSslStoreProvider() throws Exception {
|
||||
SslStoreProvider sslStoreProvider = mock(SslStoreProvider.class);
|
||||
KeyStore keyStore = loadStore();
|
||||
given(sslStoreProvider.getKeyStore()).willReturn(keyStore);
|
||||
given(sslStoreProvider.getTrustStore()).willReturn(keyStore);
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setKeyStoreType("PKCS12");
|
||||
ssl.setTrustStoreType("PKCS12");
|
||||
ssl.setKeyPassword("password");
|
||||
ssl.setClientAuth(Ssl.ClientAuth.NONE);
|
||||
ssl.setCiphers(new String[] { "ONE", "TWO", "THREE" });
|
||||
ssl.setEnabledProtocols(new String[] { "TLSv1.1", "TLSv1.2" });
|
||||
ssl.setProtocol("TLSv1.1");
|
||||
SslBundle bundle = WebServerSslBundle.get(ssl, null, sslStoreProvider);
|
||||
assertThat(bundle).isNotNull();
|
||||
SslBundleKey key = bundle.getKey();
|
||||
assertThat(key.getPassword()).isEqualTo("password");
|
||||
assertThat(key.getAlias()).isNull();
|
||||
SslStoreBundle stores = bundle.getStores();
|
||||
assertThat(stores.getKeyStore()).isNotNull();
|
||||
assertThat(stores.getTrustStore()).isNotNull();
|
||||
SslOptions options = bundle.getOptions();
|
||||
assertThat(options.getCiphers()).containsExactly("ONE", "TWO", "THREE");
|
||||
assertThat(options.getEnabledProtocols()).containsExactly("TLSv1.1", "TLSv1.2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenMissingPropertiesThrowsException() {
|
||||
Ssl ssl = new Ssl();
|
||||
assertThatIllegalStateException().isThrownBy(() -> WebServerSslBundle.get(ssl))
|
||||
.withMessageContaining("SSL is enabled but no trust material is configured");
|
||||
}
|
||||
|
||||
private KeyStore loadStore() throws Exception {
|
||||
Resource resource = new ClassPathResource("test.p12");
|
||||
try (InputStream stream = resource.getInputStream()) {
|
||||
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
||||
keyStore.load(stream, "secret".toCharArray());
|
||||
return keyStore;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -111,6 +111,13 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.boot.ssl.DefaultSslBundleRegistry;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslStoreBundle;
|
||||
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
|
||||
import org.springframework.boot.ssl.jks.JksSslStoreDetails;
|
||||
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
|
||||
import org.springframework.boot.ssl.pem.PemSslStoreDetails;
|
||||
import org.springframework.boot.system.ApplicationHome;
|
||||
import org.springframework.boot.system.ApplicationTemp;
|
||||
import org.springframework.boot.testsupport.system.CapturedOutput;
|
||||
|
@ -170,6 +177,7 @@ import static org.mockito.Mockito.mock;
|
|||
* @author Raja Kolli
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
@DirtiesUrlFactories
|
||||
public abstract class AbstractServletWebServerFactoryTests {
|
||||
|
@ -473,7 +481,7 @@ public abstract class AbstractServletWebServerFactoryTests {
|
|||
}
|
||||
|
||||
protected void assertThatSslWithInvalidAliasCallFails(ThrowingCallable call) {
|
||||
assertThatThrownBy(call).hasStackTraceContaining("Keystore does not contain specified alias 'test-alias-404'");
|
||||
assertThatThrownBy(call).hasStackTraceContaining("Keystore does not contain alias 'test-alias-404'");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -557,6 +565,44 @@ public abstract class AbstractServletWebServerFactoryTests {
|
|||
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory)).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pkcs12KeyStoreAndTrustStoreFromBundle() throws Exception {
|
||||
AbstractServletWebServerFactory factory = getFactory();
|
||||
addTestTxtFile(factory);
|
||||
factory.setSsl(Ssl.forBundle("test"));
|
||||
factory.setSslBundles(
|
||||
new DefaultSslBundleRegistry("test", createJksSslBundle("classpath:test.p12", "classpath:test.p12")));
|
||||
this.webServer = factory.getWebServer();
|
||||
this.webServer.start();
|
||||
KeyStore keyStore = KeyStore.getInstance("pkcs12");
|
||||
loadStore(keyStore, new FileSystemResource("src/test/resources/test.p12"));
|
||||
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
|
||||
new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy())
|
||||
.loadKeyMaterial(keyStore, "secret".toCharArray())
|
||||
.build());
|
||||
HttpComponentsClientHttpRequestFactory requestFactory = createHttpComponentsRequestFactory(socketFactory);
|
||||
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory)).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pemKeyStoreAndTrustStoreFromBundle() throws Exception {
|
||||
AbstractServletWebServerFactory factory = getFactory();
|
||||
addTestTxtFile(factory);
|
||||
factory.setSsl(Ssl.forBundle("test"));
|
||||
factory.setSslBundles(new DefaultSslBundleRegistry("test",
|
||||
createPemSslBundle("classpath:test-cert.pem", "classpath:test-key.pem")));
|
||||
this.webServer = factory.getWebServer();
|
||||
this.webServer.start();
|
||||
KeyStore keyStore = KeyStore.getInstance("pkcs12");
|
||||
loadStore(keyStore, new FileSystemResource("src/test/resources/test.p12"));
|
||||
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
|
||||
new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy())
|
||||
.loadKeyMaterial(keyStore, "secret".toCharArray())
|
||||
.build());
|
||||
HttpComponentsClientHttpRequestFactory requestFactory = createHttpComponentsRequestFactory(socketFactory);
|
||||
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory)).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sslNeedsClientAuthenticationSucceedsWithClientCertificate() throws Exception {
|
||||
AbstractServletWebServerFactory factory = getFactory();
|
||||
|
@ -621,6 +667,7 @@ public abstract class AbstractServletWebServerFactoryTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Deprecated(since = "3.1.0", forRemoval = true)
|
||||
void sslWithCustomSslStoreProvider() throws Exception {
|
||||
AbstractServletWebServerFactory factory = getFactory();
|
||||
addTestTxtFile(factory);
|
||||
|
@ -715,6 +762,24 @@ public abstract class AbstractServletWebServerFactoryTests {
|
|||
return ssl;
|
||||
}
|
||||
|
||||
private SslBundle createJksSslBundle(String keyStore, String trustStore) {
|
||||
JksSslStoreDetails keyStoreDetails = getJksStoreDetails(keyStore);
|
||||
JksSslStoreDetails trustStoreDetails = getJksStoreDetails(trustStore);
|
||||
SslStoreBundle stores = new JksSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
return SslBundle.of(stores);
|
||||
}
|
||||
|
||||
private JksSslStoreDetails getJksStoreDetails(String location) {
|
||||
return new JksSslStoreDetails(getStoreType(location), null, location, "secret");
|
||||
}
|
||||
|
||||
private SslBundle createPemSslBundle(String cert, String privateKey) {
|
||||
PemSslStoreDetails keyStoreDetails = PemSslStoreDetails.forCertificate(cert).withPrivateKey(privateKey);
|
||||
PemSslStoreDetails trustStoreDetails = PemSslStoreDetails.forCertificate(cert);
|
||||
SslStoreBundle stores = new PemSslStoreBundle(keyStoreDetails, trustStoreDetails);
|
||||
return SslBundle.of(stores);
|
||||
}
|
||||
|
||||
protected void testRestrictedSSLProtocolsAndCipherSuites(String[] protocols, String[] ciphers) throws Exception {
|
||||
AbstractServletWebServerFactory factory = getFactory();
|
||||
factory.setSsl(getSsl(null, "password", "src/test/resources/restricted.jks", null, protocols, ciphers));
|
||||
|
|
Loading…
Reference in New Issue