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
pull/5453/merge
Phillip Webb 9 years ago
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,14 +250,25 @@ 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());
}
configureSslTrustStore(factory, ssl);
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) {

@ -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,13 +322,18 @@ 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()));
}
configureSslTrustStore(protocol, ssl);
if (getSslStoreProvider() != null) {
configureSslStoreProvider(protocol, getSslStoreProvider());
}
else {
configureSslKeyStore(protocol, ssl);
configureSslTrustStore(protocol, ssl);
}
}
private void configureSslClientAuth(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
@ -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…
Cancel
Save