Add support for dynamic SSL key stores
Add a SslStoreProvider interface that can be used to load key and trust stores from non file locations. Fixes gh-5208
This commit is contained in:
parent
2679a6f0c6
commit
b567959482
|
|
@ -71,6 +71,8 @@ public abstract class AbstractConfigurableEmbeddedServletContainer
|
|||
|
||||
private Ssl ssl;
|
||||
|
||||
private SslStoreProvider sslStoreProvider;
|
||||
|
||||
private JspServlet jspServlet = new JspServlet();
|
||||
|
||||
private Compression compression;
|
||||
|
|
@ -287,6 +289,15 @@ public abstract class AbstractConfigurableEmbeddedServletContainer
|
|||
return this.ssl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSslStoreProvider(SslStoreProvider sslStoreProvider) {
|
||||
this.sslStoreProvider = sslStoreProvider;
|
||||
}
|
||||
|
||||
public SslStoreProvider getSslStoreProvider() {
|
||||
return this.sslStoreProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJspServlet(JspServlet jspServlet) {
|
||||
this.jspServlet = jspServlet;
|
||||
|
|
|
|||
|
|
@ -151,6 +151,12 @@ public interface ConfigurableEmbeddedServletContainer {
|
|||
*/
|
||||
void setSsl(Ssl ssl);
|
||||
|
||||
/**
|
||||
* Sets a provider that will be used to obtain SSL stores.
|
||||
* @param sslStoreProvider the SSL store provider
|
||||
*/
|
||||
void setSslStoreProvider(SslStoreProvider sslStoreProvider);
|
||||
|
||||
/**
|
||||
* Sets the configuration that will be applied to the container's JSP servlet.
|
||||
* @param jspServlet the JSP servlet configuration
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
/**
|
||||
* Interface to provide SSL key stores for an {@link EmbeddedServletContainer} to use. Can
|
||||
* be used when file based key stores cannot be used.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface SslStoreProvider {
|
||||
|
||||
/**
|
||||
* Return the key store that should be used.
|
||||
* @return the key store to use
|
||||
* @throws Exception on load error
|
||||
*/
|
||||
KeyStore getKeyStore() throws Exception;
|
||||
|
||||
/**
|
||||
* Return the trust store that should be used.
|
||||
* @return the trust store to use
|
||||
* @throws Exception on load error
|
||||
*/
|
||||
KeyStore getTrustStore() throws Exception;
|
||||
|
||||
}
|
||||
|
|
@ -250,15 +250,26 @@ public class JettyEmbeddedServletContainerFactory
|
|||
configureSslClientAuth(factory, ssl);
|
||||
configureSslPasswords(factory, ssl);
|
||||
factory.setCertAlias(ssl.getKeyAlias());
|
||||
configureSslKeyStore(factory, ssl);
|
||||
if (ssl.getCiphers() != null) {
|
||||
factory.setIncludeCipherSuites(ssl.getCiphers());
|
||||
}
|
||||
if (ssl.getEnabledProtocols() != null) {
|
||||
factory.setIncludeProtocols(ssl.getEnabledProtocols());
|
||||
}
|
||||
if (getSslStoreProvider() != null) {
|
||||
try {
|
||||
factory.setKeyStore(getSslStoreProvider().getKeyStore());
|
||||
factory.setTrustStore(getSslStoreProvider().getKeyStore());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Unable to set SSL store", ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
configureSslKeyStore(factory, ssl);
|
||||
configureSslTrustStore(factory, ssl);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureSslClientAuth(SslContextFactory factory, Ssl ssl) {
|
||||
if (ssl.getClientAuth() == ClientAuth.NEED) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded.tomcat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import org.apache.tomcat.util.net.AbstractEndpoint;
|
||||
import org.apache.tomcat.util.net.SSLUtil;
|
||||
import org.apache.tomcat.util.net.ServerSocketFactory;
|
||||
import org.apache.tomcat.util.net.jsse.JSSEImplementation;
|
||||
import org.apache.tomcat.util.net.jsse.JSSESocketFactory;
|
||||
|
||||
import org.springframework.boot.context.embedded.SslStoreProvider;
|
||||
|
||||
/**
|
||||
* {@link JSSEImplementation} for embedded Tomcat that supports {@link SslStoreProvider}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Venil Noronha
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class TomcatEmbeddedJSSEImplementation extends JSSEImplementation {
|
||||
|
||||
@Override
|
||||
public ServerSocketFactory getServerSocketFactory(AbstractEndpoint<?> endpoint) {
|
||||
return new SocketFactory(endpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLUtil getSSLUtil(AbstractEndpoint<?> endpoint) {
|
||||
return new SocketFactory(endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JSSESocketFactory} that supports {@link SslStoreProvider}.
|
||||
*/
|
||||
static class SocketFactory extends JSSESocketFactory {
|
||||
|
||||
private final SslStoreProvider sslStoreProvider;
|
||||
|
||||
SocketFactory(AbstractEndpoint<?> endpoint) {
|
||||
super(endpoint);
|
||||
this.sslStoreProvider = (SslStoreProvider) endpoint
|
||||
.getAttribute("sslStoreProvider");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyStore getKeystore(String type, String provider, String pass)
|
||||
throws IOException {
|
||||
if (this.sslStoreProvider != null) {
|
||||
try {
|
||||
KeyStore store = this.sslStoreProvider.getKeyStore();
|
||||
if (store != null) {
|
||||
return store;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
return super.getKeystore(type, provider, pass);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyStore getTrustStore(String keystoreType, String keystoreProvider)
|
||||
throws IOException {
|
||||
if (this.sslStoreProvider != null) {
|
||||
try {
|
||||
KeyStore store = this.sslStoreProvider.getTrustStore();
|
||||
if (store != null) {
|
||||
return store;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
return super.getTrustStore(keystoreType, keystoreProvider);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ import org.apache.coyote.AbstractProtocol;
|
|||
import org.apache.coyote.ProtocolHandler;
|
||||
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
|
||||
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||
import org.apache.coyote.http11.Http11NioProtocol;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
|
||||
|
|
@ -63,6 +64,7 @@ import org.springframework.boot.context.embedded.MimeMappings;
|
|||
import org.springframework.boot.context.embedded.ServletContextInitializer;
|
||||
import org.springframework.boot.context.embedded.Ssl;
|
||||
import org.springframework.boot.context.embedded.Ssl.ClientAuth;
|
||||
import org.springframework.boot.context.embedded.SslStoreProvider;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.Assert;
|
||||
|
|
@ -320,14 +322,19 @@ public class TomcatEmbeddedServletContainerFactory
|
|||
protocol.setKeystorePass(ssl.getKeyStorePassword());
|
||||
protocol.setKeyPass(ssl.getKeyPassword());
|
||||
protocol.setKeyAlias(ssl.getKeyAlias());
|
||||
configureSslKeyStore(protocol, ssl);
|
||||
protocol.setCiphers(StringUtils.arrayToCommaDelimitedString(ssl.getCiphers()));
|
||||
if (ssl.getEnabledProtocols() != null) {
|
||||
protocol.setProperty("sslEnabledProtocols",
|
||||
StringUtils.arrayToCommaDelimitedString(ssl.getEnabledProtocols()));
|
||||
}
|
||||
if (getSslStoreProvider() != null) {
|
||||
configureSslStoreProvider(protocol, getSslStoreProvider());
|
||||
}
|
||||
else {
|
||||
configureSslKeyStore(protocol, ssl);
|
||||
configureSslTrustStore(protocol, ssl);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureSslClientAuth(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
|
||||
if (ssl.getClientAuth() == ClientAuth.NEED) {
|
||||
|
|
@ -338,6 +345,16 @@ public class TomcatEmbeddedServletContainerFactory
|
|||
}
|
||||
}
|
||||
|
||||
protected void configureSslStoreProvider(AbstractHttp11JsseProtocol<?> protocol,
|
||||
SslStoreProvider sslStoreProvider) {
|
||||
Assert.isInstanceOf(Http11NioProtocol.class, protocol,
|
||||
"SslStoreProvider can only be used with Http11NioProtocol");
|
||||
((Http11NioProtocol) protocol).getEndpoint().setAttribute("sslStoreProvider",
|
||||
sslStoreProvider);
|
||||
protocol.setSslImplementationName(
|
||||
TomcatEmbeddedJSSEImplementation.class.getName());
|
||||
}
|
||||
|
||||
private void configureSslKeyStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
|
||||
try {
|
||||
protocol.setKeystoreFile(ResourceUtils.getURL(ssl.getKeyStore()).toString());
|
||||
|
|
@ -355,6 +372,7 @@ public class TomcatEmbeddedServletContainerFactory
|
|||
}
|
||||
|
||||
private void configureSslTrustStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
|
||||
|
||||
if (ssl.getTrustStore() != null) {
|
||||
try {
|
||||
protocol.setTruststoreFile(
|
||||
|
|
|
|||
|
|
@ -296,22 +296,15 @@ public class UndertowEmbeddedServletContainerFactory
|
|||
|
||||
private KeyManager[] getKeyManagers() {
|
||||
try {
|
||||
Ssl ssl = getSsl();
|
||||
String keyStoreType = ssl.getKeyStoreType();
|
||||
if (keyStoreType == null) {
|
||||
keyStoreType = "JKS";
|
||||
}
|
||||
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
|
||||
URL url = ResourceUtils.getURL(ssl.getKeyStore());
|
||||
keyStore.load(url.openStream(), ssl.getKeyStorePassword().toCharArray());
|
||||
|
||||
// Get key manager to provide client credentials.
|
||||
KeyStore keyStore = getKeyStore();
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory
|
||||
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
char[] keyPassword = ssl.getKeyPassword() != null
|
||||
? ssl.getKeyPassword().toCharArray()
|
||||
: ssl.getKeyStorePassword().toCharArray();
|
||||
keyManagerFactory.init(keyStore, keyPassword);
|
||||
Ssl ssl = getSsl();
|
||||
String keyPassword = ssl.getKeyPassword();
|
||||
if (keyPassword == null) {
|
||||
keyPassword = ssl.getKeyStorePassword();
|
||||
}
|
||||
keyManagerFactory.init(keyStore, keyPassword.toCharArray());
|
||||
return keyManagerFactory.getKeyManagers();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
|
@ -319,24 +312,21 @@ public class UndertowEmbeddedServletContainerFactory
|
|||
}
|
||||
}
|
||||
|
||||
private KeyStore getKeyStore() throws Exception {
|
||||
if (getSslStoreProvider() != null) {
|
||||
return getSslStoreProvider().getKeyStore();
|
||||
}
|
||||
Ssl ssl = getSsl();
|
||||
return loadKeyStore(ssl.getKeyStoreType(), ssl.getKeyStore(),
|
||||
ssl.getKeyStorePassword());
|
||||
}
|
||||
|
||||
private TrustManager[] getTrustManagers() {
|
||||
try {
|
||||
Ssl ssl = getSsl();
|
||||
String trustStoreType = ssl.getTrustStoreType();
|
||||
if (trustStoreType == null) {
|
||||
trustStoreType = "JKS";
|
||||
}
|
||||
String trustStore = ssl.getTrustStore();
|
||||
if (trustStore == null) {
|
||||
return null;
|
||||
}
|
||||
KeyStore trustedKeyStore = KeyStore.getInstance(trustStoreType);
|
||||
URL url = ResourceUtils.getURL(trustStore);
|
||||
trustedKeyStore.load(url.openStream(),
|
||||
ssl.getTrustStorePassword().toCharArray());
|
||||
KeyStore store = getTrustStore();
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory
|
||||
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init(trustedKeyStore);
|
||||
trustManagerFactory.init(store);
|
||||
return trustManagerFactory.getTrustManagers();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
|
@ -344,6 +334,27 @@ public class UndertowEmbeddedServletContainerFactory
|
|||
}
|
||||
}
|
||||
|
||||
private KeyStore getTrustStore() throws Exception {
|
||||
if (getSslStoreProvider() != null) {
|
||||
return getSslStoreProvider().getTrustStore();
|
||||
}
|
||||
Ssl ssl = getSsl();
|
||||
return loadKeyStore(ssl.getTrustStoreType(), ssl.getTrustStore(),
|
||||
ssl.getTrustStorePassword());
|
||||
}
|
||||
|
||||
private KeyStore loadKeyStore(String type, String resource, String password)
|
||||
throws Exception {
|
||||
type = (type == null ? "JKS" : type);
|
||||
if (resource == null) {
|
||||
return null;
|
||||
}
|
||||
KeyStore store = KeyStore.getInstance(type);
|
||||
URL url = ResourceUtils.getURL(resource);
|
||||
store.load(url.openStream(), password.toCharArray());
|
||||
return store;
|
||||
}
|
||||
|
||||
private DeploymentManager createDeploymentManager(
|
||||
ServletContextInitializer... initializers) {
|
||||
DeploymentInfo deployment = Servlets.deployment();
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ import java.net.URISyntaxException;
|
|||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
|
@ -71,6 +74,8 @@ import org.mockito.InOrder;
|
|||
import org.springframework.boot.ApplicationHome;
|
||||
import org.springframework.boot.ApplicationTemp;
|
||||
import org.springframework.boot.context.embedded.Ssl.ClientAuth;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
|
|
@ -519,6 +524,32 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
|||
.isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sslWithCustomSslStoreProvider() throws Exception {
|
||||
AbstractEmbeddedServletContainerFactory factory = getFactory();
|
||||
addTestTxtFile(factory);
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setClientAuth(ClientAuth.NEED);
|
||||
ssl.setKeyPassword("password");
|
||||
factory.setSsl(ssl);
|
||||
factory.setSslStoreProvider(new CustomSslStoreProvider());
|
||||
this.container = factory.getEmbeddedServletContainer();
|
||||
this.container.start();
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(new FileInputStream(new File("src/test/resources/test.jks")),
|
||||
"secret".toCharArray());
|
||||
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
|
||||
new SSLContextBuilder()
|
||||
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
|
||||
.loadKeyMaterial(keyStore, "password".toCharArray()).build());
|
||||
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory)
|
||||
.build();
|
||||
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
|
||||
httpClient);
|
||||
assertThat(getResponse(getLocalUrl("https", "/test.txt"), requestFactory))
|
||||
.isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableJspServletRegistration() throws Exception {
|
||||
AbstractEmbeddedServletContainerFactory factory = getFactory();
|
||||
|
|
@ -1056,4 +1087,32 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
|||
|
||||
}
|
||||
|
||||
public static class CustomSslStoreProvider implements SslStoreProvider {
|
||||
|
||||
@Override
|
||||
public KeyStore getKeyStore() throws Exception {
|
||||
return loadStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getTrustStore() throws Exception {
|
||||
return loadStore();
|
||||
}
|
||||
|
||||
private KeyStore loadStore() throws KeyStoreException, IOException,
|
||||
NoSuchAlgorithmException, CertificateException {
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
Resource resource = new ClassPathResource("test.jks");
|
||||
InputStream inputStream = resource.getInputStream();
|
||||
try {
|
||||
keyStore.load(inputStream, "secret".toCharArray());
|
||||
return keyStore;
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue