Skip to content

Commit d40ec74

Browse files
committed
Upgrade to Tomcat 10.1.52
Tomcat 9.0.115, 10.1.52, and 11.0.18 include a breaking change [1] to how ciphers are configured when using HTTPS. Previously, a single setting was used but this has now been split in two; the existing ciphers setting for TLSv1.2 ciphers and a new ciperSuites setting for TLSv1.3. As part of this split, the behavior of the ciphers setting has been changed such that any TLSv1.3 ciphers are ignored and a warning is logged. This change in Tomcat is problematic without also making some changes in Boot. If we had done nothing, a user that had configured only TLSv1.3 cipers would have them all ignored, leaving their SSL connection unexpectedly using all of the default ciphers which may be less secure. This commit adapts to the breaking change in Tomcat by taking the user's list of ciphers and splitting into into TLSv1.2 and TLSv1.3 ciphers before passing them into Tomcat's two settings (ciphers and cipherSuites respectively). This is done defensively for backwards compatibility. If the methods to identify and configure the TLSv1.3 ciphers are not present, we assume that we're running with an earlier version of Tomcat and fall back to passing them all into the ciphers setting as we did previously. Closes spring-projectsgh-49084 [1] apache/tomcat@9abf6bd
1 parent 43363ed commit d40ec74

3 files changed

Lines changed: 99 additions & 6 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ nativeBuildToolsVersion=0.10.6
2121
snakeYamlVersion=2.4
2222
springFrameworkVersion=6.2.16-SNAPSHOT
2323
springFramework60xVersion=6.0.23
24-
tomcatVersion=10.1.50
24+
tomcatVersion=10.1.52
2525

2626
kotlin.stdlib.default.dependency=false

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

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.boot.web.embedded.tomcat;
1818

19+
import java.util.ArrayList;
20+
import java.util.List;
1921
import java.util.Map;
2022

2123
import org.apache.catalina.connector.Connector;
@@ -25,6 +27,7 @@
2527
import org.apache.tomcat.util.net.SSLHostConfig;
2628
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
2729
import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type;
30+
import org.apache.tomcat.util.net.openssl.ciphers.OpenSSLCipherConfigurationParser;
2831

2932
import org.springframework.boot.ssl.SslBundle;
3033
import org.springframework.boot.ssl.SslBundleKey;
@@ -111,14 +114,24 @@ private void applySslBundle(AbstractHttp11Protocol<?> protocol, SSLHostConfig ss
111114
certificate.setCertificateKeyAlias(key.getAlias());
112115
}
113116
sslHostConfig.addCertificate(certificate);
114-
if (options.getCiphers() != null) {
115-
String ciphers = StringUtils.arrayToCommaDelimitedString(options.getCiphers());
116-
sslHostConfig.setCiphers(ciphers);
117-
}
117+
configureCiphers(options, sslHostConfig);
118118
configureSslStores(sslHostConfig, certificate, stores);
119119
configureEnabledProtocols(sslHostConfig, options);
120120
}
121121

122+
private void configureCiphers(SslOptions options, SSLHostConfig sslHostConfig) {
123+
CipherConfiguration cipherConfiguration = CipherConfiguration.from(options);
124+
if (cipherConfiguration != null) {
125+
sslHostConfig.setCiphers(cipherConfiguration.tls12Ciphers);
126+
try {
127+
sslHostConfig.setCipherSuites(cipherConfiguration.tls13Ciphers);
128+
}
129+
catch (Exception ex) {
130+
// Tomcat version without setCipherSuites method. Continue.
131+
}
132+
}
133+
}
134+
122135
private void configureEnabledProtocols(SSLHostConfig sslHostConfig, SslOptions options) {
123136
if (options.getEnabledProtocols() != null) {
124137
String enabledProtocols = StringUtils.arrayToDelimitedString(options.getEnabledProtocols(), "+");
@@ -145,4 +158,47 @@ private void configureSslStores(SSLHostConfig sslHostConfig, SSLHostConfigCertif
145158
}
146159
}
147160

161+
private static class CipherConfiguration {
162+
163+
private final String tls12Ciphers;
164+
165+
private final String tls13Ciphers;
166+
167+
CipherConfiguration(String tls12Ciphers, String tls13Ciphers) {
168+
this.tls12Ciphers = tls12Ciphers;
169+
this.tls13Ciphers = tls13Ciphers;
170+
}
171+
172+
static CipherConfiguration from(SslOptions options) {
173+
List<String> tls12Ciphers = new ArrayList<>();
174+
List<String> tls13Ciphers = new ArrayList<>();
175+
String[] ciphers = options.getCiphers();
176+
if (ciphers == null || ciphers.length == 0) {
177+
return null;
178+
}
179+
for (String cipher : ciphers) {
180+
if (isTls13(cipher)) {
181+
tls13Ciphers.add(cipher);
182+
}
183+
else {
184+
tls12Ciphers.add(cipher);
185+
}
186+
}
187+
return new CipherConfiguration(StringUtils.collectionToCommaDelimitedString(tls12Ciphers),
188+
StringUtils.collectionToCommaDelimitedString(tls13Ciphers));
189+
}
190+
191+
private static boolean isTls13(String cipher) {
192+
try {
193+
return OpenSSLCipherConfigurationParser.isTls13Cipher(cipher);
194+
}
195+
catch (Exception ex) {
196+
// Tomcat version without isTls13Cipher method. Continue, treating all
197+
// ciphers as TLSv1.2
198+
return false;
199+
}
200+
}
201+
202+
}
203+
148204
}

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.apache.commons.logging.Log;
2424
import org.apache.commons.logging.LogFactory;
2525
import org.apache.tomcat.util.net.SSLHostConfig;
26+
import org.apache.tomcat.util.net.openssl.ciphers.Cipher;
2627
import org.junit.jupiter.api.AfterEach;
2728
import org.junit.jupiter.api.BeforeEach;
2829
import org.junit.jupiter.api.Test;
@@ -73,7 +74,7 @@ void stop() throws Exception {
7374

7475
@Test
7576
@WithPackageResources("test.jks")
76-
void sslCiphersConfiguration() throws Exception {
77+
void tls12CiphersConfiguration() throws Exception {
7778
Ssl ssl = new Ssl();
7879
ssl.setKeyStore("classpath:test.jks");
7980
ssl.setKeyStorePassword("secret");
@@ -84,6 +85,42 @@ void sslCiphersConfiguration() throws Exception {
8485
this.tomcat.start();
8586
SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler().findSslHostConfigs();
8687
assertThat(sslHostConfigs[0].getCiphers()).isEqualTo("ALPHA:BRAVO:CHARLIE");
88+
assertThat(sslHostConfigs[0].getCipherSuites()).isEmpty();
89+
}
90+
91+
@Test
92+
@WithPackageResources("test.jks")
93+
void tls13CiphersConfiguration() throws Exception {
94+
Ssl ssl = new Ssl();
95+
ssl.setKeyStore("classpath:test.jks");
96+
ssl.setKeyStorePassword("secret");
97+
ssl.setCiphers(new String[] { Cipher.TLS_AES_128_CCM_SHA256.getOpenSSLAlias(),
98+
Cipher.TLS_AES_256_GCM_SHA384.getOpenSSLAlias() });
99+
Connector connector = this.tomcat.getConnector();
100+
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
101+
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
102+
this.tomcat.start();
103+
SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler().findSslHostConfigs();
104+
assertThat(sslHostConfigs[0].getCiphers()).isEmpty();
105+
assertThat(sslHostConfigs[0].getCipherSuites()).isEqualTo("TLS_AES_128_CCM_SHA256:TLS_AES_256_GCM_SHA384");
106+
}
107+
108+
@Test
109+
@WithPackageResources("test.jks")
110+
void mixedTls12AndTls13CiphersConfiguration() throws Exception {
111+
Ssl ssl = new Ssl();
112+
ssl.setKeyStore("classpath:test.jks");
113+
ssl.setKeyStorePassword("secret");
114+
ssl.setCiphers(new String[] { Cipher.TLS_AES_128_CCM_SHA256.getOpenSSLAlias(),
115+
Cipher.TLS_DH_DSS_WITH_AES_128_CBC_SHA256.getOpenSSLAlias(),
116+
Cipher.TLS_AES_256_GCM_SHA384.getOpenSSLAlias() });
117+
Connector connector = this.tomcat.getConnector();
118+
SslConnectorCustomizer customizer = new SslConnectorCustomizer(this.logger, connector, ssl.getClientAuth());
119+
customizer.customize(WebServerSslBundle.get(ssl), Collections.emptyMap());
120+
this.tomcat.start();
121+
SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler().findSslHostConfigs();
122+
assertThat(sslHostConfigs[0].getCiphers()).isEqualTo("DH-DSS-AES128-SHA256");
123+
assertThat(sslHostConfigs[0].getCipherSuites()).isEqualTo("TLS_AES_128_CCM_SHA256:TLS_AES_256_GCM_SHA384");
87124
}
88125

89126
@Test

0 commit comments

Comments
 (0)