Skip to content

Commit 103c2bd

Browse files
Use Tomcat SSLHostConfig API for SSL configuration
Closes gh-30531
1 parent 1c71567 commit 103c2bd

File tree

3 files changed

+63
-187
lines changed

3 files changed

+63
-187
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizer.java

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,11 +19,12 @@
1919
import java.io.FileNotFoundException;
2020

2121
import org.apache.catalina.connector.Connector;
22-
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
2322
import org.apache.coyote.ProtocolHandler;
2423
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
2524
import org.apache.coyote.http11.Http11NioProtocol;
2625
import org.apache.tomcat.util.net.SSLHostConfig;
26+
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
27+
import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type;
2728

2829
import org.springframework.boot.web.server.Ssl;
2930
import org.springframework.boot.web.server.SslStoreProvider;
@@ -36,6 +37,8 @@
3637
* {@link TomcatConnectorCustomizer} that configures SSL support on the given connector.
3738
*
3839
* @author Brian Clozel
40+
* @author Andy Wilkinson
41+
* @author Scott Frederick
3942
*/
4043
class SslConnectorCustomizer implements TomcatConnectorCustomizer {
4144

@@ -67,95 +70,102 @@ public void customize(Connector connector) {
6770
*/
6871
protected void configureSsl(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl, SslStoreProvider sslStoreProvider) {
6972
protocol.setSSLEnabled(true);
70-
protocol.setSslProtocol(ssl.getProtocol());
71-
configureSslClientAuth(protocol, ssl);
73+
SSLHostConfig sslHostConfig = new SSLHostConfig();
74+
sslHostConfig.setHostName(protocol.getDefaultSSLHostConfigName());
75+
sslHostConfig.setSslProtocol(ssl.getProtocol());
76+
protocol.addSslHostConfig(sslHostConfig);
77+
configureSslClientAuth(sslHostConfig, ssl);
78+
SSLHostConfigCertificate certificate = new SSLHostConfigCertificate(sslHostConfig, Type.UNDEFINED);
7279
if (ssl.getKeyStorePassword() != null) {
73-
protocol.setKeystorePass(ssl.getKeyStorePassword());
80+
certificate.setCertificateKeystorePassword(ssl.getKeyStorePassword());
7481
}
7582
if (ssl.getKeyPassword() != null) {
76-
protocol.setKeyPass(ssl.getKeyPassword());
83+
certificate.setCertificateKeyPassword(ssl.getKeyPassword());
7784
}
78-
protocol.setKeyAlias(ssl.getKeyAlias());
85+
if (ssl.getKeyAlias() != null) {
86+
certificate.setCertificateKeyAlias(ssl.getKeyAlias());
87+
}
88+
sslHostConfig.addCertificate(certificate);
7989
String ciphers = StringUtils.arrayToCommaDelimitedString(ssl.getCiphers());
8090
if (StringUtils.hasText(ciphers)) {
81-
protocol.setCiphers(ciphers);
91+
sslHostConfig.setCiphers(ciphers);
92+
}
93+
configureEnabledProtocols(protocol, ssl);
94+
if (sslStoreProvider != null) {
95+
configureSslStoreProvider(protocol, sslHostConfig, certificate, sslStoreProvider);
8296
}
97+
else {
98+
configureSslKeyStore(certificate, ssl);
99+
configureSslTrustStore(sslHostConfig, ssl);
100+
}
101+
}
102+
103+
private void configureEnabledProtocols(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
83104
if (ssl.getEnabledProtocols() != null) {
84105
for (SSLHostConfig sslHostConfig : protocol.findSslHostConfigs()) {
85106
sslHostConfig.setProtocols(StringUtils.arrayToCommaDelimitedString(ssl.getEnabledProtocols()));
86107
}
87108
}
88-
if (sslStoreProvider != null) {
89-
configureSslStoreProvider(protocol, sslStoreProvider);
90-
}
91-
else {
92-
configureSslKeyStore(protocol, ssl);
93-
configureSslTrustStore(protocol, ssl);
94-
}
95109
}
96110

97-
private void configureSslClientAuth(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
111+
private void configureSslClientAuth(SSLHostConfig config, Ssl ssl) {
98112
if (ssl.getClientAuth() == Ssl.ClientAuth.NEED) {
99-
protocol.setClientAuth(Boolean.TRUE.toString());
113+
config.setCertificateVerification("required");
100114
}
101115
else if (ssl.getClientAuth() == Ssl.ClientAuth.WANT) {
102-
protocol.setClientAuth("want");
116+
config.setCertificateVerification("optional");
103117
}
104118
}
105119

106-
protected void configureSslStoreProvider(AbstractHttp11JsseProtocol<?> protocol,
107-
SslStoreProvider sslStoreProvider) {
120+
protected void configureSslStoreProvider(AbstractHttp11JsseProtocol<?> protocol, SSLHostConfig sslHostConfig,
121+
SSLHostConfigCertificate certificate, SslStoreProvider sslStoreProvider) {
108122
Assert.isInstanceOf(Http11NioProtocol.class, protocol,
109123
"SslStoreProvider can only be used with Http11NioProtocol");
110-
TomcatURLStreamHandlerFactory instance = TomcatURLStreamHandlerFactory.getInstance();
111-
instance.addUserFactory(new SslStoreProviderUrlStreamHandlerFactory(sslStoreProvider));
112124
try {
113125
if (sslStoreProvider.getKeyStore() != null) {
114-
protocol.setKeystorePass("");
115-
protocol.setKeystoreFile(SslStoreProviderUrlStreamHandlerFactory.KEY_STORE_URL);
126+
certificate.setCertificateKeystore(sslStoreProvider.getKeyStore());
116127
}
117128
if (sslStoreProvider.getTrustStore() != null) {
118-
protocol.setTruststorePass("");
119-
protocol.setTruststoreFile(SslStoreProviderUrlStreamHandlerFactory.TRUST_STORE_URL);
129+
sslHostConfig.setTrustStore(sslStoreProvider.getTrustStore());
120130
}
121131
}
122132
catch (Exception ex) {
123133
throw new WebServerException("Could not load store: " + ex.getMessage(), ex);
124134
}
125135
}
126136

127-
private void configureSslKeyStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
137+
private void configureSslKeyStore(SSLHostConfigCertificate certificate, Ssl ssl) {
128138
try {
129-
protocol.setKeystoreFile(ResourceUtils.getURL(ssl.getKeyStore()).toString());
139+
certificate.setCertificateKeystoreFile(ResourceUtils.getURL(ssl.getKeyStore()).toString());
130140
}
131141
catch (Exception ex) {
132142
throw new WebServerException("Could not load key store '" + ssl.getKeyStore() + "'", ex);
133143
}
134144
if (ssl.getKeyStoreType() != null) {
135-
protocol.setKeystoreType(ssl.getKeyStoreType());
145+
certificate.setCertificateKeystoreType(ssl.getKeyStoreType());
136146
}
137147
if (ssl.getKeyStoreProvider() != null) {
138-
protocol.setKeystoreProvider(ssl.getKeyStoreProvider());
148+
certificate.setCertificateKeystoreProvider(ssl.getKeyStoreProvider());
139149
}
140150
}
141151

142-
private void configureSslTrustStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
152+
private void configureSslTrustStore(SSLHostConfig sslHostConfig, Ssl ssl) {
143153
if (ssl.getTrustStore() != null) {
144154
try {
145-
protocol.setTruststoreFile(ResourceUtils.getURL(ssl.getTrustStore()).toString());
155+
sslHostConfig.setTruststoreFile(ResourceUtils.getURL(ssl.getTrustStore()).toString());
146156
}
147157
catch (FileNotFoundException ex) {
148158
throw new WebServerException("Could not load trust store: " + ex.getMessage(), ex);
149159
}
150160
}
151161
if (ssl.getTrustStorePassword() != null) {
152-
protocol.setTruststorePass(ssl.getTrustStorePassword());
162+
sslHostConfig.setTruststorePassword(ssl.getTrustStorePassword());
153163
}
154164
if (ssl.getTrustStoreType() != null) {
155-
protocol.setTruststoreType(ssl.getTrustStoreType());
165+
sslHostConfig.setTruststoreType(ssl.getTrustStoreType());
156166
}
157167
if (ssl.getTrustStoreProvider() != null) {
158-
protocol.setTruststoreProvider(ssl.getTrustStoreProvider());
168+
sslHostConfig.setTruststoreProvider(ssl.getTrustStoreProvider());
159169
}
160170
}
161171

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/SslStoreProviderUrlStreamHandlerFactory.java

Lines changed: 0 additions & 111 deletions
This file was deleted.

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/SslConnectorCustomizerTests.java

Lines changed: 18 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,13 +23,15 @@
2323
import java.security.KeyStoreException;
2424
import java.security.NoSuchAlgorithmException;
2525
import java.security.cert.CertificateException;
26+
import java.util.Set;
27+
import java.util.stream.Collectors;
2628

2729
import org.apache.catalina.LifecycleState;
2830
import org.apache.catalina.connector.Connector;
2931
import org.apache.catalina.startup.Tomcat;
3032
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
31-
import org.apache.coyote.http11.Http11NioProtocol;
3233
import org.apache.tomcat.util.net.SSLHostConfig;
34+
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
3335
import org.junit.jupiter.api.AfterEach;
3436
import org.junit.jupiter.api.BeforeEach;
3537
import org.junit.jupiter.api.Test;
@@ -53,6 +55,8 @@
5355
* Tests for {@link SslConnectorCustomizer}
5456
*
5557
* @author Brian Clozel
58+
* @author Andy Wilkinson
59+
* @author Scott Frederick
5660
*/
5761
@ExtendWith(OutputCaptureExtension.class)
5862
class SslConnectorCustomizerTests {
@@ -129,16 +133,18 @@ void customizeWhenSslStoreProviderProvidesOnlyKeyStoreShouldUseDefaultTruststore
129133
ssl.setKeyPassword("password");
130134
ssl.setTrustStore("src/test/resources/test.jks");
131135
SslStoreProvider sslStoreProvider = mock(SslStoreProvider.class);
132-
given(sslStoreProvider.getKeyStore()).willReturn(loadStore());
136+
KeyStore keyStore = loadStore();
137+
given(sslStoreProvider.getKeyStore()).willReturn(keyStore);
133138
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, sslStoreProvider);
134139
Connector connector = this.tomcat.getConnector();
135140
customizer.customize(connector);
136141
this.tomcat.start();
137142
SSLHostConfig sslHostConfig = connector.getProtocolHandler().findSslHostConfigs()[0];
138143
SSLHostConfig sslHostConfigWithDefaults = new SSLHostConfig();
139144
assertThat(sslHostConfig.getTruststoreFile()).isEqualTo(sslHostConfigWithDefaults.getTruststoreFile());
140-
assertThat(sslHostConfig.getCertificateKeystoreFile())
141-
.isEqualTo(SslStoreProviderUrlStreamHandlerFactory.KEY_STORE_URL);
145+
Set<SSLHostConfigCertificate> certificates = sslHostConfig.getCertificates();
146+
assertThat(certificates).hasSize(1);
147+
assertThat(certificates.iterator().next().getCertificateKeystore()).isEqualTo(keyStore);
142148
}
143149

144150
@Test
@@ -147,7 +153,8 @@ void customizeWhenSslStoreProviderProvidesOnlyTrustStoreShouldUseDefaultKeystore
147153
ssl.setKeyPassword("password");
148154
ssl.setKeyStore("src/test/resources/test.jks");
149155
SslStoreProvider sslStoreProvider = mock(SslStoreProvider.class);
150-
given(sslStoreProvider.getTrustStore()).willReturn(loadStore());
156+
KeyStore trustStore = loadStore();
157+
given(sslStoreProvider.getTrustStore()).willReturn(trustStore);
151158
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, sslStoreProvider);
152159
Connector connector = this.tomcat.getConnector();
153160
customizer.customize(connector);
@@ -156,10 +163,11 @@ void customizeWhenSslStoreProviderProvidesOnlyTrustStoreShouldUseDefaultKeystore
156163
sslHostConfig.getCertificates(true);
157164
SSLHostConfig sslHostConfigWithDefaults = new SSLHostConfig();
158165
sslHostConfigWithDefaults.getCertificates(true);
159-
assertThat(sslHostConfig.getTruststoreFile())
160-
.isEqualTo(SslStoreProviderUrlStreamHandlerFactory.TRUST_STORE_URL);
161-
assertThat(sslHostConfig.getCertificateKeystoreFile())
162-
.contains(sslHostConfigWithDefaults.getCertificateKeystoreFile());
166+
assertThat(sslHostConfig.getTruststore()).isEqualTo(trustStore);
167+
System.out.println(sslHostConfig.getCertificates(false).stream()
168+
.map(SSLHostConfigCertificate::getCertificateFile).collect(Collectors.toList()));
169+
System.out.println(sslHostConfigWithDefaults.getCertificates(false).stream()
170+
.map(SSLHostConfigCertificate::getCertificateFile).collect(Collectors.toList()));
163171
}
164172

165173
@Test
@@ -186,37 +194,6 @@ void customizeWhenSslIsEnabledWithNoKeyStoreThrowsWebServerException() {
186194
.withMessageContaining("Could not load key store 'null'");
187195
}
188196

189-
@Test
190-
void keyStorePasswordIsNotSetWhenNull() {
191-
Http11NioProtocol protocol = (Http11NioProtocol) this.tomcat.getConnector().getProtocolHandler();
192-
protocol.setKeystorePass("password");
193-
Ssl ssl = new Ssl();
194-
ssl.setKeyStore("src/test/resources/test.jks");
195-
new SslConnectorCustomizer(ssl, null).customize(this.tomcat.getConnector());
196-
assertThat(protocol.getKeystorePass()).isEqualTo("password");
197-
}
198-
199-
@Test
200-
void keyPasswordIsNotSetWhenNull() {
201-
Http11NioProtocol protocol = (Http11NioProtocol) this.tomcat.getConnector().getProtocolHandler();
202-
protocol.setKeyPass("password");
203-
Ssl ssl = new Ssl();
204-
ssl.setKeyStore("src/test/resources/test.jks");
205-
new SslConnectorCustomizer(ssl, null).customize(this.tomcat.getConnector());
206-
assertThat(protocol.getKeyPass()).isEqualTo("password");
207-
}
208-
209-
@Test
210-
void trustStorePasswordIsNotSetWhenNull() {
211-
Http11NioProtocol protocol = (Http11NioProtocol) this.tomcat.getConnector().getProtocolHandler();
212-
protocol.setTruststorePass("password");
213-
Ssl ssl = new Ssl();
214-
ssl.setKeyStore("src/test/resources/test.jks");
215-
ssl.setTrustStore("src/test/resources/test.jks");
216-
new SslConnectorCustomizer(ssl, null).customize(this.tomcat.getConnector());
217-
assertThat(protocol.getTruststorePass()).isEqualTo("password");
218-
}
219-
220197
private KeyStore loadStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
221198
KeyStore keyStore = KeyStore.getInstance("JKS");
222199
Resource resource = new ClassPathResource("test.jks");

0 commit comments

Comments
 (0)