Use Tomcat SSLHostConfig API for SSL configuration

Closes gh-30531
pull/30641/head
Scott Frederick 3 years ago
parent 1c71567c94
commit 103c2bdd7d

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2022 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.
@ -19,11 +19,12 @@ package org.springframework.boot.web.embedded.tomcat;
import java.io.FileNotFoundException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.apache.coyote.http11.Http11NioProtocol;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.SslStoreProvider;
@ -36,6 +37,8 @@ import org.springframework.util.StringUtils;
* {@link TomcatConnectorCustomizer} that configures SSL support on the given connector.
*
* @author Brian Clozel
* @author Andy Wilkinson
* @author Scott Frederick
*/
class SslConnectorCustomizer implements TomcatConnectorCustomizer {
@ -67,56 +70,63 @@ class SslConnectorCustomizer implements TomcatConnectorCustomizer {
*/
protected void configureSsl(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl, SslStoreProvider sslStoreProvider) {
protocol.setSSLEnabled(true);
protocol.setSslProtocol(ssl.getProtocol());
configureSslClientAuth(protocol, ssl);
SSLHostConfig sslHostConfig = new SSLHostConfig();
sslHostConfig.setHostName(protocol.getDefaultSSLHostConfigName());
sslHostConfig.setSslProtocol(ssl.getProtocol());
protocol.addSslHostConfig(sslHostConfig);
configureSslClientAuth(sslHostConfig, ssl);
SSLHostConfigCertificate certificate = new SSLHostConfigCertificate(sslHostConfig, Type.UNDEFINED);
if (ssl.getKeyStorePassword() != null) {
protocol.setKeystorePass(ssl.getKeyStorePassword());
certificate.setCertificateKeystorePassword(ssl.getKeyStorePassword());
}
if (ssl.getKeyPassword() != null) {
protocol.setKeyPass(ssl.getKeyPassword());
certificate.setCertificateKeyPassword(ssl.getKeyPassword());
}
protocol.setKeyAlias(ssl.getKeyAlias());
if (ssl.getKeyAlias() != null) {
certificate.setCertificateKeyAlias(ssl.getKeyAlias());
}
sslHostConfig.addCertificate(certificate);
String ciphers = StringUtils.arrayToCommaDelimitedString(ssl.getCiphers());
if (StringUtils.hasText(ciphers)) {
protocol.setCiphers(ciphers);
sslHostConfig.setCiphers(ciphers);
}
configureEnabledProtocols(protocol, ssl);
if (sslStoreProvider != null) {
configureSslStoreProvider(protocol, sslHostConfig, certificate, sslStoreProvider);
}
else {
configureSslKeyStore(certificate, ssl);
configureSslTrustStore(sslHostConfig, ssl);
}
}
private void configureEnabledProtocols(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
if (ssl.getEnabledProtocols() != null) {
for (SSLHostConfig sslHostConfig : protocol.findSslHostConfigs()) {
sslHostConfig.setProtocols(StringUtils.arrayToCommaDelimitedString(ssl.getEnabledProtocols()));
}
}
if (sslStoreProvider != null) {
configureSslStoreProvider(protocol, sslStoreProvider);
}
else {
configureSslKeyStore(protocol, ssl);
configureSslTrustStore(protocol, ssl);
}
}
private void configureSslClientAuth(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
private void configureSslClientAuth(SSLHostConfig config, Ssl ssl) {
if (ssl.getClientAuth() == Ssl.ClientAuth.NEED) {
protocol.setClientAuth(Boolean.TRUE.toString());
config.setCertificateVerification("required");
}
else if (ssl.getClientAuth() == Ssl.ClientAuth.WANT) {
protocol.setClientAuth("want");
config.setCertificateVerification("optional");
}
}
protected void configureSslStoreProvider(AbstractHttp11JsseProtocol<?> protocol,
SslStoreProvider sslStoreProvider) {
protected void configureSslStoreProvider(AbstractHttp11JsseProtocol<?> protocol, SSLHostConfig sslHostConfig,
SSLHostConfigCertificate certificate, SslStoreProvider sslStoreProvider) {
Assert.isInstanceOf(Http11NioProtocol.class, protocol,
"SslStoreProvider can only be used with Http11NioProtocol");
TomcatURLStreamHandlerFactory instance = TomcatURLStreamHandlerFactory.getInstance();
instance.addUserFactory(new SslStoreProviderUrlStreamHandlerFactory(sslStoreProvider));
try {
if (sslStoreProvider.getKeyStore() != null) {
protocol.setKeystorePass("");
protocol.setKeystoreFile(SslStoreProviderUrlStreamHandlerFactory.KEY_STORE_URL);
certificate.setCertificateKeystore(sslStoreProvider.getKeyStore());
}
if (sslStoreProvider.getTrustStore() != null) {
protocol.setTruststorePass("");
protocol.setTruststoreFile(SslStoreProviderUrlStreamHandlerFactory.TRUST_STORE_URL);
sslHostConfig.setTrustStore(sslStoreProvider.getTrustStore());
}
}
catch (Exception ex) {
@ -124,38 +134,38 @@ class SslConnectorCustomizer implements TomcatConnectorCustomizer {
}
}
private void configureSslKeyStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
private void configureSslKeyStore(SSLHostConfigCertificate certificate, Ssl ssl) {
try {
protocol.setKeystoreFile(ResourceUtils.getURL(ssl.getKeyStore()).toString());
certificate.setCertificateKeystoreFile(ResourceUtils.getURL(ssl.getKeyStore()).toString());
}
catch (Exception ex) {
throw new WebServerException("Could not load key store '" + ssl.getKeyStore() + "'", ex);
}
if (ssl.getKeyStoreType() != null) {
protocol.setKeystoreType(ssl.getKeyStoreType());
certificate.setCertificateKeystoreType(ssl.getKeyStoreType());
}
if (ssl.getKeyStoreProvider() != null) {
protocol.setKeystoreProvider(ssl.getKeyStoreProvider());
certificate.setCertificateKeystoreProvider(ssl.getKeyStoreProvider());
}
}
private void configureSslTrustStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
private void configureSslTrustStore(SSLHostConfig sslHostConfig, Ssl ssl) {
if (ssl.getTrustStore() != null) {
try {
protocol.setTruststoreFile(ResourceUtils.getURL(ssl.getTrustStore()).toString());
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) {
protocol.setTruststorePass(ssl.getTrustStorePassword());
sslHostConfig.setTruststorePassword(ssl.getTrustStorePassword());
}
if (ssl.getTrustStoreType() != null) {
protocol.setTruststoreType(ssl.getTrustStoreType());
sslHostConfig.setTruststoreType(ssl.getTrustStoreType());
}
if (ssl.getTrustStoreProvider() != null) {
protocol.setTruststoreProvider(ssl.getTrustStoreProvider());
sslHostConfig.setTruststoreProvider(ssl.getTrustStoreProvider());
}
}

@ -1,111 +0,0 @@
/*
* Copyright 2012-2019 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.embedded.tomcat;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.KeyStore;
import org.springframework.boot.web.server.SslStoreProvider;
/**
* A {@link URLStreamHandlerFactory} that provides a {@link URLStreamHandler} for
* accessing an {@link SslStoreProvider}'s key store and trust store from a URL.
*
* @author Andy Wilkinson
*/
class SslStoreProviderUrlStreamHandlerFactory implements URLStreamHandlerFactory {
private static final String PROTOCOL = "springbootssl";
private static final String KEY_STORE_PATH = "keyStore";
static final String KEY_STORE_URL = PROTOCOL + ":" + KEY_STORE_PATH;
private static final String TRUST_STORE_PATH = "trustStore";
static final String TRUST_STORE_URL = PROTOCOL + ":" + TRUST_STORE_PATH;
private final SslStoreProvider sslStoreProvider;
SslStoreProviderUrlStreamHandlerFactory(SslStoreProvider sslStoreProvider) {
this.sslStoreProvider = sslStoreProvider;
}
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
if (PROTOCOL.equals(protocol)) {
return new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL url) throws IOException {
try {
if (KEY_STORE_PATH.equals(url.getPath())) {
return new KeyStoreUrlConnection(url,
SslStoreProviderUrlStreamHandlerFactory.this.sslStoreProvider.getKeyStore());
}
if (TRUST_STORE_PATH.equals(url.getPath())) {
return new KeyStoreUrlConnection(url,
SslStoreProviderUrlStreamHandlerFactory.this.sslStoreProvider.getTrustStore());
}
}
catch (Exception ex) {
throw new IOException(ex);
}
throw new IOException("Invalid path: " + url.getPath());
}
};
}
return null;
}
private static final class KeyStoreUrlConnection extends URLConnection {
private final KeyStore keyStore;
private KeyStoreUrlConnection(URL url, KeyStore keyStore) {
super(url);
this.keyStore = keyStore;
}
@Override
public void connect() throws IOException {
}
@Override
public InputStream getInputStream() throws IOException {
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
this.keyStore.store(stream, new char[0]);
return new ByteArrayInputStream(stream.toByteArray());
}
catch (Exception ex) {
throw new IOException(ex);
}
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@ -23,13 +23,15 @@ import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
import org.apache.coyote.http11.Http11NioProtocol;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -53,6 +55,8 @@ import static org.mockito.Mockito.mock;
* Tests for {@link SslConnectorCustomizer}
*
* @author Brian Clozel
* @author Andy Wilkinson
* @author Scott Frederick
*/
@ExtendWith(OutputCaptureExtension.class)
class SslConnectorCustomizerTests {
@ -129,7 +133,8 @@ class SslConnectorCustomizerTests {
ssl.setKeyPassword("password");
ssl.setTrustStore("src/test/resources/test.jks");
SslStoreProvider sslStoreProvider = mock(SslStoreProvider.class);
given(sslStoreProvider.getKeyStore()).willReturn(loadStore());
KeyStore keyStore = loadStore();
given(sslStoreProvider.getKeyStore()).willReturn(keyStore);
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, sslStoreProvider);
Connector connector = this.tomcat.getConnector();
customizer.customize(connector);
@ -137,8 +142,9 @@ class SslConnectorCustomizerTests {
SSLHostConfig sslHostConfig = connector.getProtocolHandler().findSslHostConfigs()[0];
SSLHostConfig sslHostConfigWithDefaults = new SSLHostConfig();
assertThat(sslHostConfig.getTruststoreFile()).isEqualTo(sslHostConfigWithDefaults.getTruststoreFile());
assertThat(sslHostConfig.getCertificateKeystoreFile())
.isEqualTo(SslStoreProviderUrlStreamHandlerFactory.KEY_STORE_URL);
Set<SSLHostConfigCertificate> certificates = sslHostConfig.getCertificates();
assertThat(certificates).hasSize(1);
assertThat(certificates.iterator().next().getCertificateKeystore()).isEqualTo(keyStore);
}
@Test
@ -147,7 +153,8 @@ class SslConnectorCustomizerTests {
ssl.setKeyPassword("password");
ssl.setKeyStore("src/test/resources/test.jks");
SslStoreProvider sslStoreProvider = mock(SslStoreProvider.class);
given(sslStoreProvider.getTrustStore()).willReturn(loadStore());
KeyStore trustStore = loadStore();
given(sslStoreProvider.getTrustStore()).willReturn(trustStore);
SslConnectorCustomizer customizer = new SslConnectorCustomizer(ssl, sslStoreProvider);
Connector connector = this.tomcat.getConnector();
customizer.customize(connector);
@ -156,10 +163,11 @@ class SslConnectorCustomizerTests {
sslHostConfig.getCertificates(true);
SSLHostConfig sslHostConfigWithDefaults = new SSLHostConfig();
sslHostConfigWithDefaults.getCertificates(true);
assertThat(sslHostConfig.getTruststoreFile())
.isEqualTo(SslStoreProviderUrlStreamHandlerFactory.TRUST_STORE_URL);
assertThat(sslHostConfig.getCertificateKeystoreFile())
.contains(sslHostConfigWithDefaults.getCertificateKeystoreFile());
assertThat(sslHostConfig.getTruststore()).isEqualTo(trustStore);
System.out.println(sslHostConfig.getCertificates(false).stream()
.map(SSLHostConfigCertificate::getCertificateFile).collect(Collectors.toList()));
System.out.println(sslHostConfigWithDefaults.getCertificates(false).stream()
.map(SSLHostConfigCertificate::getCertificateFile).collect(Collectors.toList()));
}
@Test
@ -186,37 +194,6 @@ class SslConnectorCustomizerTests {
.withMessageContaining("Could not load key store 'null'");
}
@Test
void keyStorePasswordIsNotSetWhenNull() {
Http11NioProtocol protocol = (Http11NioProtocol) this.tomcat.getConnector().getProtocolHandler();
protocol.setKeystorePass("password");
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
new SslConnectorCustomizer(ssl, null).customize(this.tomcat.getConnector());
assertThat(protocol.getKeystorePass()).isEqualTo("password");
}
@Test
void keyPasswordIsNotSetWhenNull() {
Http11NioProtocol protocol = (Http11NioProtocol) this.tomcat.getConnector().getProtocolHandler();
protocol.setKeyPass("password");
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
new SslConnectorCustomizer(ssl, null).customize(this.tomcat.getConnector());
assertThat(protocol.getKeyPass()).isEqualTo("password");
}
@Test
void trustStorePasswordIsNotSetWhenNull() {
Http11NioProtocol protocol = (Http11NioProtocol) this.tomcat.getConnector().getProtocolHandler();
protocol.setTruststorePass("password");
Ssl ssl = new Ssl();
ssl.setKeyStore("src/test/resources/test.jks");
ssl.setTrustStore("src/test/resources/test.jks");
new SslConnectorCustomizer(ssl, null).customize(this.tomcat.getConnector());
assertThat(protocol.getTruststorePass()).isEqualTo("password");
}
private KeyStore loadStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore keyStore = KeyStore.getInstance("JKS");
Resource resource = new ClassPathResource("test.jks");

Loading…
Cancel
Save