Consolidate web server SSL configuration

pull/34437/head
Scott Frederick 2 years ago
parent 0b23ffd73a
commit 9f0108496c

@ -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.
@ -39,9 +39,9 @@ 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.web.server.CertificateFileSslStoreProvider;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.SslStoreProvider;
import org.springframework.boot.web.server.SslStoreProviderFactory;
import org.springframework.http.client.reactive.ReactorResourceFactory;
import org.springframework.util.Assert;
import org.springframework.util.unit.DataSize;
@ -202,7 +202,7 @@ public class NettyRSocketServerFactory implements RSocketServerFactory, Configur
if (this.sslStoreProvider != null) {
return this.sslStoreProvider;
}
return CertificateFileSslStoreProvider.from(this.ssl);
return SslStoreProviderFactory.from(this.ssl);
}
private InetSocketAddress getListenAddress() {

@ -16,9 +16,7 @@
package org.springframework.boot.web.embedded.jetty;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URL;
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.http.HttpVersion;
@ -32,19 +30,15 @@ import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.springframework.boot.web.server.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.WebServerException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* {@link JettyServerCustomizer} that configures SSL on the given Jetty server instance.
@ -53,6 +47,7 @@ import org.springframework.util.StringUtils;
* @author Olivier Lamy
* @author Chris Bono
* @author Cyril Dangerville
* @author Scott Frederick
*/
class SslServerCustomizer implements JettyServerCustomizer {
@ -193,13 +188,9 @@ class SslServerCustomizer implements JettyServerCustomizer {
factory.setTrustStore(sslStoreProvider.getTrustStore());
}
catch (Exception ex) {
throw new IllegalStateException("Unable to set SSL store", ex);
throw new IllegalStateException("Unable to set SSL store: " + ex.getMessage(), ex);
}
}
else {
configureSslKeyStore(factory, ssl);
configureSslTrustStore(factory, ssl);
}
}
private void configureSslClientAuth(SslContextFactory.Server factory, Ssl ssl) {
@ -221,49 +212,6 @@ class SslServerCustomizer implements JettyServerCustomizer {
}
}
private void configureSslKeyStore(SslContextFactory.Server factory, Ssl ssl) {
String keystoreType = (ssl.getKeyStoreType() != null) ? ssl.getKeyStoreType() : "JKS";
String keystoreLocation = ssl.getKeyStore();
if (keystoreType.equalsIgnoreCase("PKCS11")) {
Assert.state(!StringUtils.hasText(keystoreLocation),
() -> "Keystore location '" + keystoreLocation + "' must be empty or null for PKCS11 key stores");
}
else {
try {
URL url = ResourceUtils.getURL(keystoreLocation);
factory.setKeyStoreResource(Resource.newResource(url));
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + keystoreLocation + "'", ex);
}
}
factory.setKeyStoreType(keystoreType);
if (ssl.getKeyStoreProvider() != null) {
factory.setKeyStoreProvider(this.ssl.getKeyStoreProvider());
}
}
private void configureSslTrustStore(SslContextFactory.Server factory, Ssl ssl) {
if (ssl.getTrustStorePassword() != null) {
factory.setTrustStorePassword(ssl.getTrustStorePassword());
}
if (ssl.getTrustStore() != null) {
try {
URL url = ResourceUtils.getURL(ssl.getTrustStore());
factory.setTrustStoreResource(Resource.newResource(url));
}
catch (IOException ex) {
throw new WebServerException("Could not find trust store '" + ssl.getTrustStore() + "'", ex);
}
}
if (ssl.getTrustStoreType() != null) {
factory.setTrustStoreType(ssl.getTrustStoreType());
}
if (ssl.getTrustStoreProvider() != null) {
factory.setTrustStoreProvider(ssl.getTrustStoreProvider());
}
}
/**
* A {@link ServerConnector} that validates the ssl key alias on server startup.
*/

@ -16,9 +16,7 @@
package org.springframework.boot.web.embedded.netty;
import java.io.InputStream;
import java.net.Socket;
import java.net.URL;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@ -47,10 +45,6 @@ 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.WebServerException;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* {@link NettyServerCustomizer} that configures SSL for the given Reactor Netty server
@ -60,6 +54,7 @@ import org.springframework.util.StringUtils;
* @author Raheela Aslam
* @author Chris Bono
* @author Cyril Dangerville
* @author Scott Frederick
* @since 2.0.0
* @deprecated this class is meant for Spring Boot internal use only.
*/
@ -93,7 +88,7 @@ public class SslServerCustomizer implements NettyServerCustomizer {
sslContextSpec = Http11SslContextSpec.forServer(getKeyManagerFactory(this.ssl, this.sslStoreProvider));
}
sslContextSpec.configure((builder) -> {
builder.trustManager(getTrustManagerFactory(this.ssl, this.sslStoreProvider));
builder.trustManager(getTrustManagerFactory(this.sslStoreProvider));
if (this.ssl.getEnabledProtocols() != null) {
builder.protocols(this.ssl.getEnabledProtocols());
}
@ -112,13 +107,13 @@ public class SslServerCustomizer implements NettyServerCustomizer {
KeyManagerFactory getKeyManagerFactory(Ssl ssl, SslStoreProvider sslStoreProvider) {
try {
KeyStore keyStore = getKeyStore(ssl, sslStoreProvider);
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 != null) ? sslStoreProvider.getKeyPassword() : null;
String keyPassword = sslStoreProvider.getKeyPassword();
if (keyPassword == null) {
keyPassword = (ssl.getKeyPassword() != null) ? ssl.getKeyPassword() : ssl.getKeyStorePassword();
}
@ -126,74 +121,21 @@ public class SslServerCustomizer implements NettyServerCustomizer {
return keyManagerFactory;
}
catch (Exception ex) {
throw new IllegalStateException(ex);
throw new IllegalStateException("Could not load key manager factory: " + ex.getMessage(), ex);
}
}
private KeyStore getKeyStore(Ssl ssl, SslStoreProvider sslStoreProvider) throws Exception {
if (sslStoreProvider != null) {
return sslStoreProvider.getKeyStore();
}
return loadKeyStore(ssl.getKeyStoreType(), ssl.getKeyStoreProvider(), ssl.getKeyStore(),
ssl.getKeyStorePassword());
}
TrustManagerFactory getTrustManagerFactory(Ssl ssl, SslStoreProvider sslStoreProvider) {
TrustManagerFactory getTrustManagerFactory(SslStoreProvider sslStoreProvider) {
try {
KeyStore store = getTrustStore(ssl, sslStoreProvider);
KeyStore store = sslStoreProvider.getTrustStore();
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(store);
return trustManagerFactory;
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private KeyStore getTrustStore(Ssl ssl, SslStoreProvider sslStoreProvider) throws Exception {
if (sslStoreProvider != null) {
return sslStoreProvider.getTrustStore();
}
return loadTrustStore(ssl.getTrustStoreType(), ssl.getTrustStoreProvider(), ssl.getTrustStore(),
ssl.getTrustStorePassword());
}
private KeyStore loadKeyStore(String type, String provider, String resource, String password) throws Exception {
return loadStore(type, provider, resource, password);
}
private KeyStore loadTrustStore(String type, String provider, String resource, String password) throws Exception {
if (resource == null) {
return null;
}
return loadStore(type, provider, resource, password);
}
private KeyStore loadStore(String keystoreType, String provider, String keystoreLocation, String password)
throws Exception {
keystoreType = (keystoreType != null) ? keystoreType : "JKS";
char[] passwordChars = (password != null) ? password.toCharArray() : null;
KeyStore store = (provider != null) ? KeyStore.getInstance(keystoreType, provider)
: KeyStore.getInstance(keystoreType);
if (keystoreType.equalsIgnoreCase("PKCS11")) {
Assert.state(!StringUtils.hasText(keystoreLocation),
() -> "Keystore location '" + keystoreLocation + "' must be empty or null for PKCS11 key stores");
store.load(null, passwordChars);
}
else {
try {
URL url = ResourceUtils.getURL(keystoreLocation);
try (InputStream stream = url.openStream()) {
store.load(stream, passwordChars);
}
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + keystoreLocation + "'", ex);
}
throw new IllegalStateException("Could not load trust manager factory: " + ex.getMessage(), ex);
}
return store;
}
/**

@ -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,8 +16,6 @@
package org.springframework.boot.web.embedded.tomcat;
import java.io.FileNotFoundException;
import org.apache.catalina.connector.Connector;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
@ -28,9 +26,8 @@ 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.WebServerException;
import org.springframework.boot.web.server.SslStoreProviderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
@ -47,6 +44,10 @@ class SslConnectorCustomizer implements TomcatConnectorCustomizer {
private final SslStoreProvider sslStoreProvider;
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;
@ -99,10 +100,6 @@ class SslConnectorCustomizer implements TomcatConnectorCustomizer {
certificate.setCertificateKeyPassword(keyPassword);
}
}
else {
configureSslKeyStore(certificate, ssl);
configureSslTrustStore(sslHostConfig, ssl);
}
}
private void configureEnabledProtocols(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
@ -135,48 +132,7 @@ class SslConnectorCustomizer implements TomcatConnectorCustomizer {
}
}
catch (Exception ex) {
throw new WebServerException("Could not load store: " + ex.getMessage(), ex);
}
}
private void configureSslKeyStore(SSLHostConfigCertificate certificate, Ssl ssl) {
String keystoreType = (ssl.getKeyStoreType() != null) ? ssl.getKeyStoreType() : "JKS";
String keystoreLocation = ssl.getKeyStore();
if (keystoreType.equalsIgnoreCase("PKCS11")) {
Assert.state(!StringUtils.hasText(keystoreLocation),
() -> "Keystore location '" + keystoreLocation + "' must be empty or null for PKCS11 key stores");
}
else {
try {
certificate.setCertificateKeystoreFile(ResourceUtils.getURL(keystoreLocation).toString());
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + keystoreLocation + "'", ex);
}
}
certificate.setCertificateKeystoreType(keystoreType);
if (ssl.getKeyStoreProvider() != null) {
certificate.setCertificateKeystoreProvider(ssl.getKeyStoreProvider());
}
}
private void configureSslTrustStore(SSLHostConfig sslHostConfig, Ssl ssl) {
if (ssl.getTrustStore() != null) {
try {
sslHostConfig.setTruststoreFile(ResourceUtils.getURL(ssl.getTrustStore()).toString());
}
catch (FileNotFoundException ex) {
throw new WebServerException("Could not load trust store: " + ex.getMessage(), ex);
}
}
if (ssl.getTrustStorePassword() != null) {
sslHostConfig.setTruststorePassword(ssl.getTrustStorePassword());
}
if (ssl.getTrustStoreType() != null) {
sslHostConfig.setTruststoreType(ssl.getTrustStoreType());
}
if (ssl.getTrustStoreProvider() != null) {
sslHostConfig.setTruststoreProvider(ssl.getTrustStoreProvider());
throw new IllegalStateException("Could not load store: " + ex.getMessage(), ex);
}
}

@ -16,10 +16,8 @@
package org.springframework.boot.web.embedded.undertow;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
@ -43,8 +41,6 @@ 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.web.server.WebServerException;
import org.springframework.util.ResourceUtils;
/**
* {@link UndertowBuilderCustomizer} that configures SSL on the given builder instance.
@ -52,6 +48,7 @@ import org.springframework.util.ResourceUtils;
* @author Brian Clozel
* @author Raheela Aslam
* @author Cyril Dangerville
* @author Scott Frederick
*/
class SslBuilderCustomizer implements UndertowBuilderCustomizer {
@ -74,8 +71,8 @@ class SslBuilderCustomizer implements UndertowBuilderCustomizer {
public void customize(Undertow.Builder builder) {
try {
SSLContext sslContext = SSLContext.getInstance(this.ssl.getProtocol());
sslContext.init(getKeyManagers(this.ssl, this.sslStoreProvider),
getTrustManagers(this.ssl, this.sslStoreProvider), null);
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) {
@ -107,13 +104,13 @@ class SslBuilderCustomizer implements UndertowBuilderCustomizer {
return SslClientAuthMode.NOT_REQUESTED;
}
private KeyManager[] getKeyManagers(Ssl ssl, SslStoreProvider sslStoreProvider) {
KeyManager[] getKeyManagers(Ssl ssl, SslStoreProvider sslStoreProvider) {
try {
KeyStore keyStore = getKeyStore(ssl, sslStoreProvider);
KeyStore keyStore = sslStoreProvider.getKeyStore();
SslConfigurationValidator.validateKeyAlias(keyStore, ssl.getKeyAlias());
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
String keyPassword = (sslStoreProvider != null) ? sslStoreProvider.getKeyPassword() : null;
String keyPassword = sslStoreProvider.getKeyPassword();
if (keyPassword == null) {
keyPassword = (ssl.getKeyPassword() != null) ? ssl.getKeyPassword() : ssl.getKeyStorePassword();
}
@ -124,7 +121,7 @@ class SslBuilderCustomizer implements UndertowBuilderCustomizer {
return keyManagerFactory.getKeyManagers();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
throw new IllegalStateException("Could not load key managers: " + ex.getMessage(), ex);
}
}
@ -138,69 +135,17 @@ class SslBuilderCustomizer implements UndertowBuilderCustomizer {
return keyManagers;
}
private KeyStore getKeyStore(Ssl ssl, SslStoreProvider sslStoreProvider) throws Exception {
if (sslStoreProvider != null) {
return sslStoreProvider.getKeyStore();
}
return loadKeyStore(ssl.getKeyStoreType(), ssl.getKeyStoreProvider(), ssl.getKeyStore(),
ssl.getKeyStorePassword());
}
private TrustManager[] getTrustManagers(Ssl ssl, SslStoreProvider sslStoreProvider) {
TrustManager[] getTrustManagers(SslStoreProvider sslStoreProvider) {
try {
KeyStore store = getTrustStore(ssl, sslStoreProvider);
KeyStore store = sslStoreProvider.getTrustStore();
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(store);
return trustManagerFactory.getTrustManagers();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private KeyStore getTrustStore(Ssl ssl, SslStoreProvider sslStoreProvider) throws Exception {
if (sslStoreProvider != null) {
return sslStoreProvider.getTrustStore();
}
return loadTrustStore(ssl.getTrustStoreType(), ssl.getTrustStoreProvider(), ssl.getTrustStore(),
ssl.getTrustStorePassword());
}
private KeyStore loadKeyStore(String type, String provider, String resource, String password) throws Exception {
return loadStore(type, provider, resource, password);
}
private KeyStore loadTrustStore(String type, String provider, String resource, String password) throws Exception {
if (resource == null) {
return null;
}
return loadStore(type, provider, resource, password);
}
private KeyStore loadStore(String type, String provider, String resource, String password) throws Exception {
type = (type != null) ? type : "JKS";
KeyStore store = (provider != null) ? KeyStore.getInstance(type, provider) : KeyStore.getInstance(type);
if (type.equalsIgnoreCase("PKCS11")) {
if (resource != null && !resource.isEmpty()) {
throw new IllegalArgumentException("Input keystore location is not valid for keystore type 'PKCS11': '"
+ resource + "'. Must be undefined / null.");
}
store.load(null, (password != null) ? password.toCharArray() : null);
}
else {
try {
URL url = ResourceUtils.getURL(resource);
try (InputStream stream = url.openStream()) {
store.load(stream, (password != null) ? password.toCharArray() : null);
}
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + resource + "'", ex);
}
throw new IllegalStateException("Could not load trust managers: " + ex.getMessage(), ex);
}
return store;
}
/**

@ -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.
@ -189,7 +189,7 @@ public abstract class AbstractConfigurableWebServerFactory implements Configurab
if (this.sslStoreProvider != null) {
return this.sslStoreProvider;
}
return CertificateFileSslStoreProvider.from(this.ssl);
return SslStoreProviderFactory.from(this.ssl);
}
/**

@ -0,0 +1,97 @@
/*
* 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;
}
}

@ -0,0 +1,41 @@
/*
* 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));
}
}

@ -35,10 +35,9 @@ 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.WebServerException;
import org.springframework.boot.web.server.SslStoreProviderFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNoException;
@ -47,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThatNoException;
*
* @author Andy Wilkinson
* @author Cyril Dangerville
* @author Scott Frederick
*/
@MockPkcs11Security
class SslServerCustomizerTests {
@ -92,12 +92,9 @@ class SslServerCustomizerTests {
void configureSslWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() {
Ssl ssl = new Ssl();
SslServerCustomizer customizer = new SslServerCustomizer(null, ssl, null, null);
assertThatExceptionOfType(Exception.class)
.isThrownBy(() -> customizer.configureSsl(new SslContextFactory.Server(), ssl, null))
.satisfies((ex) -> {
assertThat(ex).isInstanceOf(WebServerException.class);
assertThat(ex).hasMessageContaining("Could not load key store 'null'");
});
assertThatIllegalStateException().isThrownBy(
() -> customizer.configureSsl(new SslContextFactory.Server(), ssl, SslStoreProviderFactory.from(ssl)))
.withMessageContaining("KeyStore location must not be empty or null");
}
@Test
@ -108,8 +105,8 @@ class SslServerCustomizerTests {
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, null))
assertThatIllegalStateException().isThrownBy(
() -> customizer.configureSsl(new SslContextFactory.Server(), ssl, SslStoreProviderFactory.from(ssl)))
.withMessageContaining("must be empty or null for PKCS11 key stores");
}

@ -23,7 +23,7 @@ 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.WebServerException;
import org.springframework.boot.web.server.SslStoreProviderFactory;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNoException;
@ -34,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThatNoException;
* @author Andy Wilkinson
* @author Raheela Aslam
* @author Cyril Dangerville
* @author Scott Frederick
*/
@SuppressWarnings("deprecation")
@MockPkcs11Security
@ -46,7 +47,8 @@ class SslServerCustomizerTests {
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyStoreProvider("com.example.KeyStoreProvider");
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null);
assertThatIllegalStateException().isThrownBy(() -> customizer.getKeyManagerFactory(ssl, null))
assertThatIllegalStateException()
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl)))
.withCauseInstanceOf(NoSuchProviderException.class)
.withMessageContaining("com.example.KeyStoreProvider");
}
@ -58,7 +60,8 @@ class SslServerCustomizerTests {
ssl.setTrustStore("src/test/resources/test.jks");
ssl.setTrustStoreProvider("com.example.TrustStoreProvider");
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null);
assertThatIllegalStateException().isThrownBy(() -> customizer.getTrustManagerFactory(ssl, null))
assertThatIllegalStateException()
.isThrownBy(() -> customizer.getTrustManagerFactory(SslStoreProviderFactory.from(ssl)))
.withCauseInstanceOf(NoSuchProviderException.class)
.withMessageContaining("com.example.TrustStoreProvider");
}
@ -67,9 +70,10 @@ class SslServerCustomizerTests {
void getKeyManagerFactoryWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() {
Ssl ssl = new Ssl();
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null);
assertThatIllegalStateException().isThrownBy(() -> customizer.getKeyManagerFactory(ssl, null))
.withCauseInstanceOf(WebServerException.class)
.withMessageContaining("Could not load key store 'null'");
assertThatIllegalStateException()
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl)))
.withCauseInstanceOf(IllegalStateException.class)
.withMessageContaining("KeyStore location must not be empty or null");
}
@Test
@ -80,7 +84,8 @@ class SslServerCustomizerTests {
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyPassword("password");
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null);
assertThatIllegalStateException().isThrownBy(() -> customizer.getKeyManagerFactory(ssl, null))
assertThatIllegalStateException()
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl)))
.withCauseInstanceOf(IllegalStateException.class)
.withMessageContaining("must be empty or null for PKCS11 key stores");
}
@ -92,7 +97,8 @@ class SslServerCustomizerTests {
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
ssl.setKeyStorePassword("1234");
SslServerCustomizer customizer = new SslServerCustomizer(ssl, null, null);
assertThatNoException().isThrownBy(() -> customizer.getKeyManagerFactory(ssl, null));
assertThatNoException()
.isThrownBy(() -> customizer.getKeyManagerFactory(ssl, SslStoreProviderFactory.from(ssl)));
}
}

@ -41,12 +41,10 @@ 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.WebServerException;
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.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.BDDMockito.given;
@ -184,9 +182,9 @@ class SslConnectorCustomizerTests {
@Test
void customizeWhenSslIsEnabledWithNoKeyStoreAndNotPkcs11ThrowsException() {
assertThatExceptionOfType(WebServerException.class)
.isThrownBy(() -> new SslConnectorCustomizer(new Ssl(), null).customize(this.tomcat.getConnector()))
.withMessageContaining("Could not load key store 'null'");
assertThatIllegalStateException()
.isThrownBy(() -> new SslConnectorCustomizer(new Ssl()).customize(this.tomcat.getConnector()))
.withMessageContaining("KeyStore location must not be empty or null");
}
@Test
@ -196,7 +194,7 @@ class SslConnectorCustomizerTests {
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setKeyPassword("password");
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, null);
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl);
assertThatIllegalStateException().isThrownBy(() -> customizer.customize(this.tomcat.getConnector()))
.withMessageContaining("must be empty or null for PKCS11 key stores");
}

@ -26,8 +26,7 @@ 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.WebServerException;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.boot.web.server.SslStoreProviderFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@ -49,7 +48,7 @@ class SslBuilderCustomizerTests {
ssl.setKeyPassword("password");
ssl.setKeyStore("src/test/resources/test.jks");
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
KeyManager[] keyManagers = ReflectionTestUtils.invokeMethod(customizer, "getKeyManagers", ssl, null);
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);
@ -63,7 +62,7 @@ class SslBuilderCustomizerTests {
ssl.setKeyStoreProvider("com.example.KeyStoreProvider");
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
assertThatIllegalStateException()
.isThrownBy(() -> ReflectionTestUtils.invokeMethod(customizer, "getKeyManagers", ssl, null))
.isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl)))
.withCauseInstanceOf(NoSuchProviderException.class)
.withMessageContaining("com.example.KeyStoreProvider");
}
@ -76,8 +75,7 @@ class SslBuilderCustomizerTests {
ssl.setTrustStoreProvider("com.example.TrustStoreProvider");
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
assertThatIllegalStateException()
.isThrownBy(() -> ReflectionTestUtils.invokeMethod(customizer, "getTrustManagers", ssl, null))
.withCauseInstanceOf(NoSuchProviderException.class)
.isThrownBy(() -> customizer.getTrustManagers(SslStoreProviderFactory.from(ssl)))
.withMessageContaining("com.example.TrustStoreProvider");
}
@ -86,9 +84,9 @@ class SslBuilderCustomizerTests {
Ssl ssl = new Ssl();
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
assertThatIllegalStateException()
.isThrownBy(() -> ReflectionTestUtils.invokeMethod(customizer, "getKeyManagers", ssl, null))
.withCauseInstanceOf(WebServerException.class)
.withMessageContaining("Could not load key store 'null'");
.isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl)))
.withCauseInstanceOf(IllegalStateException.class)
.withMessageContaining("KeyStore location must not be empty or null");
}
@Test
@ -100,9 +98,9 @@ class SslBuilderCustomizerTests {
ssl.setKeyPassword("password");
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
assertThatIllegalStateException()
.isThrownBy(() -> ReflectionTestUtils.invokeMethod(customizer, "getKeyManagers", ssl, null))
.withCauseInstanceOf(IllegalArgumentException.class)
.withMessageContaining("Input keystore location is not valid for keystore type 'PKCS11'");
.isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl)))
.withCauseInstanceOf(IllegalStateException.class)
.withMessageContaining("must be empty or null for PKCS11 key stores");
}
@Test
@ -112,8 +110,7 @@ class SslBuilderCustomizerTests {
ssl.setKeyStoreProvider(MockPkcs11SecurityProvider.NAME);
ssl.setKeyStorePassword("1234");
SslBuilderCustomizer customizer = new SslBuilderCustomizer(8080, InetAddress.getLocalHost(), ssl, null);
assertThatNoException()
.isThrownBy(() -> ReflectionTestUtils.invokeMethod(customizer, "getKeyManagers", ssl, null));
assertThatNoException().isThrownBy(() -> customizer.getKeyManagers(ssl, SslStoreProviderFactory.from(ssl)));
}
}

@ -77,6 +77,7 @@ import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientRequestException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
@ -400,8 +401,8 @@ public abstract class AbstractReactiveWebServerFactoryTests {
@Test
void whenSslIsEnabledAndNoKeyStoreIsConfiguredThenServerFailsToStart() {
assertThatThrownBy(() -> testBasicSslWithKeyStore(null, null))
.hasMessageContaining("Could not load key store 'null'");
assertThatIllegalStateException().isThrownBy(() -> testBasicSslWithKeyStore(null, null))
.withMessageContaining("KeyStore location must not be empty or null");
}
@Test

@ -0,0 +1,142 @@
/*
* 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();
}
}
Loading…
Cancel
Save