Skip to content

Commit b567959

Browse files
committed
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 spring-projectsgh-5208
1 parent 2679a6f commit b567959

8 files changed

+290
-32
lines changed

spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java

+11
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ public abstract class AbstractConfigurableEmbeddedServletContainer
7171

7272
private Ssl ssl;
7373

74+
private SslStoreProvider sslStoreProvider;
75+
7476
private JspServlet jspServlet = new JspServlet();
7577

7678
private Compression compression;
@@ -287,6 +289,15 @@ public Ssl getSsl() {
287289
return this.ssl;
288290
}
289291

292+
@Override
293+
public void setSslStoreProvider(SslStoreProvider sslStoreProvider) {
294+
this.sslStoreProvider = sslStoreProvider;
295+
}
296+
297+
public SslStoreProvider getSslStoreProvider() {
298+
return this.sslStoreProvider;
299+
}
300+
290301
@Override
291302
public void setJspServlet(JspServlet jspServlet) {
292303
this.jspServlet = jspServlet;

spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java

+6
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ public interface ConfigurableEmbeddedServletContainer {
151151
*/
152152
void setSsl(Ssl ssl);
153153

154+
/**
155+
* Sets a provider that will be used to obtain SSL stores.
156+
* @param sslStoreProvider the SSL store provider
157+
*/
158+
void setSslStoreProvider(SslStoreProvider sslStoreProvider);
159+
154160
/**
155161
* Sets the configuration that will be applied to the container's JSP servlet.
156162
* @param jspServlet the JSP servlet configuration
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2012-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.embedded;
18+
19+
import java.security.KeyStore;
20+
21+
/**
22+
* Interface to provide SSL key stores for an {@link EmbeddedServletContainer} to use. Can
23+
* be used when file based key stores cannot be used.
24+
*
25+
* @author Phillip Webb
26+
* @since 1.4.0
27+
*/
28+
public interface SslStoreProvider {
29+
30+
/**
31+
* Return the key store that should be used.
32+
* @return the key store to use
33+
* @throws Exception on load error
34+
*/
35+
KeyStore getKeyStore() throws Exception;
36+
37+
/**
38+
* Return the trust store that should be used.
39+
* @return the trust store to use
40+
* @throws Exception on load error
41+
*/
42+
KeyStore getTrustStore() throws Exception;
43+
44+
}

spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -250,14 +250,25 @@ protected void configureSsl(SslContextFactory factory, Ssl ssl) {
250250
configureSslClientAuth(factory, ssl);
251251
configureSslPasswords(factory, ssl);
252252
factory.setCertAlias(ssl.getKeyAlias());
253-
configureSslKeyStore(factory, ssl);
254253
if (ssl.getCiphers() != null) {
255254
factory.setIncludeCipherSuites(ssl.getCiphers());
256255
}
257256
if (ssl.getEnabledProtocols() != null) {
258257
factory.setIncludeProtocols(ssl.getEnabledProtocols());
259258
}
260-
configureSslTrustStore(factory, ssl);
259+
if (getSslStoreProvider() != null) {
260+
try {
261+
factory.setKeyStore(getSslStoreProvider().getKeyStore());
262+
factory.setTrustStore(getSslStoreProvider().getKeyStore());
263+
}
264+
catch (Exception ex) {
265+
throw new IllegalStateException("Unable to set SSL store", ex);
266+
}
267+
}
268+
else {
269+
configureSslKeyStore(factory, ssl);
270+
configureSslTrustStore(factory, ssl);
271+
}
261272
}
262273

263274
private void configureSslClientAuth(SslContextFactory factory, Ssl ssl) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2012-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.embedded.tomcat;
18+
19+
import java.io.IOException;
20+
import java.security.KeyStore;
21+
22+
import org.apache.tomcat.util.net.AbstractEndpoint;
23+
import org.apache.tomcat.util.net.SSLUtil;
24+
import org.apache.tomcat.util.net.ServerSocketFactory;
25+
import org.apache.tomcat.util.net.jsse.JSSEImplementation;
26+
import org.apache.tomcat.util.net.jsse.JSSESocketFactory;
27+
28+
import org.springframework.boot.context.embedded.SslStoreProvider;
29+
30+
/**
31+
* {@link JSSEImplementation} for embedded Tomcat that supports {@link SslStoreProvider}.
32+
*
33+
* @author Phillip Webb
34+
* @author Venil Noronha
35+
* @since 1.4.0
36+
*/
37+
public class TomcatEmbeddedJSSEImplementation extends JSSEImplementation {
38+
39+
@Override
40+
public ServerSocketFactory getServerSocketFactory(AbstractEndpoint<?> endpoint) {
41+
return new SocketFactory(endpoint);
42+
}
43+
44+
@Override
45+
public SSLUtil getSSLUtil(AbstractEndpoint<?> endpoint) {
46+
return new SocketFactory(endpoint);
47+
}
48+
49+
/**
50+
* {@link JSSESocketFactory} that supports {@link SslStoreProvider}.
51+
*/
52+
static class SocketFactory extends JSSESocketFactory {
53+
54+
private final SslStoreProvider sslStoreProvider;
55+
56+
SocketFactory(AbstractEndpoint<?> endpoint) {
57+
super(endpoint);
58+
this.sslStoreProvider = (SslStoreProvider) endpoint
59+
.getAttribute("sslStoreProvider");
60+
}
61+
62+
@Override
63+
protected KeyStore getKeystore(String type, String provider, String pass)
64+
throws IOException {
65+
if (this.sslStoreProvider != null) {
66+
try {
67+
KeyStore store = this.sslStoreProvider.getKeyStore();
68+
if (store != null) {
69+
return store;
70+
}
71+
}
72+
catch (Exception ex) {
73+
throw new IOException(ex);
74+
}
75+
}
76+
return super.getKeystore(type, provider, pass);
77+
}
78+
79+
@Override
80+
protected KeyStore getTrustStore(String keystoreType, String keystoreProvider)
81+
throws IOException {
82+
if (this.sslStoreProvider != null) {
83+
try {
84+
KeyStore store = this.sslStoreProvider.getTrustStore();
85+
if (store != null) {
86+
return store;
87+
}
88+
}
89+
catch (Exception ex) {
90+
throw new IOException(ex);
91+
}
92+
}
93+
return super.getTrustStore(keystoreType, keystoreProvider);
94+
}
95+
96+
}
97+
98+
}

spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.apache.coyote.ProtocolHandler;
5252
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
5353
import org.apache.coyote.http11.AbstractHttp11Protocol;
54+
import org.apache.coyote.http11.Http11NioProtocol;
5455

5556
import org.springframework.beans.BeanUtils;
5657
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
@@ -63,6 +64,7 @@
6364
import org.springframework.boot.context.embedded.ServletContextInitializer;
6465
import org.springframework.boot.context.embedded.Ssl;
6566
import org.springframework.boot.context.embedded.Ssl.ClientAuth;
67+
import org.springframework.boot.context.embedded.SslStoreProvider;
6668
import org.springframework.context.ResourceLoaderAware;
6769
import org.springframework.core.io.ResourceLoader;
6870
import org.springframework.util.Assert;
@@ -320,13 +322,18 @@ protected void configureSsl(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
320322
protocol.setKeystorePass(ssl.getKeyStorePassword());
321323
protocol.setKeyPass(ssl.getKeyPassword());
322324
protocol.setKeyAlias(ssl.getKeyAlias());
323-
configureSslKeyStore(protocol, ssl);
324325
protocol.setCiphers(StringUtils.arrayToCommaDelimitedString(ssl.getCiphers()));
325326
if (ssl.getEnabledProtocols() != null) {
326327
protocol.setProperty("sslEnabledProtocols",
327328
StringUtils.arrayToCommaDelimitedString(ssl.getEnabledProtocols()));
328329
}
329-
configureSslTrustStore(protocol, ssl);
330+
if (getSslStoreProvider() != null) {
331+
configureSslStoreProvider(protocol, getSslStoreProvider());
332+
}
333+
else {
334+
configureSslKeyStore(protocol, ssl);
335+
configureSslTrustStore(protocol, ssl);
336+
}
330337
}
331338

332339
private void configureSslClientAuth(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
@@ -338,6 +345,16 @@ else if (ssl.getClientAuth() == ClientAuth.WANT) {
338345
}
339346
}
340347

348+
protected void configureSslStoreProvider(AbstractHttp11JsseProtocol<?> protocol,
349+
SslStoreProvider sslStoreProvider) {
350+
Assert.isInstanceOf(Http11NioProtocol.class, protocol,
351+
"SslStoreProvider can only be used with Http11NioProtocol");
352+
((Http11NioProtocol) protocol).getEndpoint().setAttribute("sslStoreProvider",
353+
sslStoreProvider);
354+
protocol.setSslImplementationName(
355+
TomcatEmbeddedJSSEImplementation.class.getName());
356+
}
357+
341358
private void configureSslKeyStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
342359
try {
343360
protocol.setKeystoreFile(ResourceUtils.getURL(ssl.getKeyStore()).toString());
@@ -355,6 +372,7 @@ private void configureSslKeyStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ss
355372
}
356373

357374
private void configureSslTrustStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
375+
358376
if (ssl.getTrustStore() != null) {
359377
try {
360378
protocol.setTruststoreFile(

spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java

+39-28
Original file line numberDiff line numberDiff line change
@@ -296,54 +296,65 @@ private SslClientAuthMode getSslClientAuthMode(Ssl ssl) {
296296

297297
private KeyManager[] getKeyManagers() {
298298
try {
299-
Ssl ssl = getSsl();
300-
String keyStoreType = ssl.getKeyStoreType();
301-
if (keyStoreType == null) {
302-
keyStoreType = "JKS";
303-
}
304-
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
305-
URL url = ResourceUtils.getURL(ssl.getKeyStore());
306-
keyStore.load(url.openStream(), ssl.getKeyStorePassword().toCharArray());
307-
308-
// Get key manager to provide client credentials.
299+
KeyStore keyStore = getKeyStore();
309300
KeyManagerFactory keyManagerFactory = KeyManagerFactory
310301
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
311-
char[] keyPassword = ssl.getKeyPassword() != null
312-
? ssl.getKeyPassword().toCharArray()
313-
: ssl.getKeyStorePassword().toCharArray();
314-
keyManagerFactory.init(keyStore, keyPassword);
302+
Ssl ssl = getSsl();
303+
String keyPassword = ssl.getKeyPassword();
304+
if (keyPassword == null) {
305+
keyPassword = ssl.getKeyStorePassword();
306+
}
307+
keyManagerFactory.init(keyStore, keyPassword.toCharArray());
315308
return keyManagerFactory.getKeyManagers();
316309
}
317310
catch (Exception ex) {
318311
throw new IllegalStateException(ex);
319312
}
320313
}
321314

315+
private KeyStore getKeyStore() throws Exception {
316+
if (getSslStoreProvider() != null) {
317+
return getSslStoreProvider().getKeyStore();
318+
}
319+
Ssl ssl = getSsl();
320+
return loadKeyStore(ssl.getKeyStoreType(), ssl.getKeyStore(),
321+
ssl.getKeyStorePassword());
322+
}
323+
322324
private TrustManager[] getTrustManagers() {
323325
try {
324-
Ssl ssl = getSsl();
325-
String trustStoreType = ssl.getTrustStoreType();
326-
if (trustStoreType == null) {
327-
trustStoreType = "JKS";
328-
}
329-
String trustStore = ssl.getTrustStore();
330-
if (trustStore == null) {
331-
return null;
332-
}
333-
KeyStore trustedKeyStore = KeyStore.getInstance(trustStoreType);
334-
URL url = ResourceUtils.getURL(trustStore);
335-
trustedKeyStore.load(url.openStream(),
336-
ssl.getTrustStorePassword().toCharArray());
326+
KeyStore store = getTrustStore();
337327
TrustManagerFactory trustManagerFactory = TrustManagerFactory
338328
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
339-
trustManagerFactory.init(trustedKeyStore);
329+
trustManagerFactory.init(store);
340330
return trustManagerFactory.getTrustManagers();
341331
}
342332
catch (Exception ex) {
343333
throw new IllegalStateException(ex);
344334
}
345335
}
346336

337+
private KeyStore getTrustStore() throws Exception {
338+
if (getSslStoreProvider() != null) {
339+
return getSslStoreProvider().getTrustStore();
340+
}
341+
Ssl ssl = getSsl();
342+
return loadKeyStore(ssl.getTrustStoreType(), ssl.getTrustStore(),
343+
ssl.getTrustStorePassword());
344+
}
345+
346+
private KeyStore loadKeyStore(String type, String resource, String password)
347+
throws Exception {
348+
type = (type == null ? "JKS" : type);
349+
if (resource == null) {
350+
return null;
351+
}
352+
KeyStore store = KeyStore.getInstance(type);
353+
URL url = ResourceUtils.getURL(resource);
354+
store.load(url.openStream(), password.toCharArray());
355+
return store;
356+
}
357+
347358
private DeploymentManager createDeploymentManager(
348359
ServletContextInitializer... initializers) {
349360
DeploymentInfo deployment = Servlets.deployment();

0 commit comments

Comments
 (0)