Skip to content

Commit 98a657f

Browse files
committed
Merge branch '2.1.x' into 2.2.x
Closes gh-20132
2 parents 3bdd91a + 8bce270 commit 98a657f

File tree

10 files changed

+259
-20
lines changed

10 files changed

+259
-20
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/SslServerCustomizer.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -24,6 +24,7 @@
2424
import org.eclipse.jetty.http.HttpVersion;
2525
import org.eclipse.jetty.http2.HTTP2Cipher;
2626
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
27+
import org.eclipse.jetty.server.ConnectionFactory;
2728
import org.eclipse.jetty.server.Connector;
2829
import org.eclipse.jetty.server.HttpConfiguration;
2930
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -36,6 +37,7 @@
3637

3738
import org.springframework.boot.web.server.Http2;
3839
import org.springframework.boot.web.server.Ssl;
40+
import org.springframework.boot.web.server.SslConfigurationValidator;
3941
import org.springframework.boot.web.server.SslStoreProvider;
4042
import org.springframework.boot.web.server.WebServerException;
4143
import org.springframework.util.Assert;
@@ -48,6 +50,7 @@
4850
*
4951
* @author Brian Clozel
5052
* @author Olivier Lamy
53+
* @author Chris Bono
5154
*/
5255
class SslServerCustomizer implements JettyServerCustomizer {
5356

@@ -105,7 +108,8 @@ private ServerConnector createHttp11ServerConnector(Server server, HttpConfigura
105108
HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config);
106109
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory,
107110
HttpVersion.HTTP_1_1.asString());
108-
return new ServerConnector(server, sslConnectionFactory, connectionFactory);
111+
return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(), sslConnectionFactory,
112+
connectionFactory);
109113
}
110114

111115
private boolean isAlpnPresent() {
@@ -123,7 +127,8 @@ private ServerConnector createHttp2ServerConnector(Server server, HttpConfigurat
123127
sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
124128
sslContextFactory.setProvider("Conscrypt");
125129
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
126-
return new ServerConnector(server, ssl, alpn, h2, new HttpConnectionFactory(config));
130+
return new SslValidatingServerConnector(server, sslContextFactory, this.ssl.getKeyAlias(), ssl, alpn, h2,
131+
new HttpConnectionFactory(config));
127132
}
128133

129134
/**
@@ -215,4 +220,35 @@ private void configureSslTrustStore(SslContextFactory.Server factory, Ssl ssl) {
215220
}
216221
}
217222

223+
/**
224+
* A {@link ServerConnector} that validates the ssl key alias on server startup.
225+
*/
226+
static class SslValidatingServerConnector extends ServerConnector {
227+
228+
private SslContextFactory sslContextFactory;
229+
230+
private String keyAlias;
231+
232+
SslValidatingServerConnector(Server server, SslContextFactory sslContextFactory, String keyAlias,
233+
SslConnectionFactory sslConnectionFactory, HttpConnectionFactory connectionFactory) {
234+
super(server, sslConnectionFactory, connectionFactory);
235+
this.sslContextFactory = sslContextFactory;
236+
this.keyAlias = keyAlias;
237+
}
238+
239+
SslValidatingServerConnector(Server server, SslContextFactory sslContextFactory, String keyAlias,
240+
ConnectionFactory... factories) {
241+
super(server, factories);
242+
this.sslContextFactory = sslContextFactory;
243+
this.keyAlias = keyAlias;
244+
}
245+
246+
@Override
247+
protected void doStart() throws Exception {
248+
super.doStart();
249+
SslConfigurationValidator.validateKeyAlias(this.sslContextFactory.getKeyStore(), this.keyAlias);
250+
}
251+
252+
}
253+
218254
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/SslServerCustomizer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -43,6 +43,7 @@
4343

4444
import org.springframework.boot.web.server.Http2;
4545
import org.springframework.boot.web.server.Ssl;
46+
import org.springframework.boot.web.server.SslConfigurationValidator;
4647
import org.springframework.boot.web.server.SslStoreProvider;
4748
import org.springframework.boot.web.server.WebServerException;
4849
import org.springframework.util.ResourceUtils;
@@ -106,6 +107,7 @@ else if (this.ssl.getClientAuth() == Ssl.ClientAuth.WANT) {
106107
protected KeyManagerFactory getKeyManagerFactory(Ssl ssl, SslStoreProvider sslStoreProvider) {
107108
try {
108109
KeyStore keyStore = getKeyStore(ssl, sslStoreProvider);
110+
SslConfigurationValidator.validateKeyAlias(keyStore, ssl.getKeyAlias());
109111
KeyManagerFactory keyManagerFactory = (ssl.getKeyAlias() == null)
110112
? KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
111113
: new ConfigurableAliasKeyManagerFactory(ssl.getKeyAlias(),

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/SslBuilderCustomizer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -40,6 +40,7 @@
4040
import org.xnio.SslClientAuthMode;
4141

4242
import org.springframework.boot.web.server.Ssl;
43+
import org.springframework.boot.web.server.SslConfigurationValidator;
4344
import org.springframework.boot.web.server.SslStoreProvider;
4445
import org.springframework.boot.web.server.WebServerException;
4546
import org.springframework.util.ResourceUtils;
@@ -107,6 +108,7 @@ private SslClientAuthMode getSslClientAuthMode(Ssl ssl) {
107108
private KeyManager[] getKeyManagers(Ssl ssl, SslStoreProvider sslStoreProvider) {
108109
try {
109110
KeyStore keyStore = getKeyStore(ssl, sslStoreProvider);
111+
SslConfigurationValidator.validateKeyAlias(keyStore, ssl.getKeyAlias());
110112
KeyManagerFactory keyManagerFactory = KeyManagerFactory
111113
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
112114
char[] keyPassword = (ssl.getKeyPassword() != null) ? ssl.getKeyPassword().toCharArray() : null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2012-2020 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+
* https://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.web.server;
18+
19+
import java.security.KeyStore;
20+
import java.security.KeyStoreException;
21+
22+
import org.springframework.util.Assert;
23+
import org.springframework.util.StringUtils;
24+
25+
/**
26+
* Provides utilities around SSL.
27+
*
28+
* @author Chris Bono
29+
* @since 2.1.13
30+
*/
31+
public final class SslConfigurationValidator {
32+
33+
private SslConfigurationValidator() {
34+
}
35+
36+
public static void validateKeyAlias(KeyStore keyStore, String keyAlias) {
37+
if (!StringUtils.isEmpty(keyAlias)) {
38+
try {
39+
Assert.state(keyStore.containsAlias(keyAlias),
40+
() -> String.format("Keystore does not contain specified alias '%s'", keyAlias));
41+
}
42+
catch (KeyStoreException ex) {
43+
throw new IllegalStateException(
44+
String.format("Could not determine if keystore contains alias '%s'", keyAlias), ex);
45+
}
46+
}
47+
}
48+
49+
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactoryTests.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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,8 +19,6 @@
1919
import java.time.Duration;
2020
import java.util.Arrays;
2121

22-
import javax.net.ssl.SSLHandshakeException;
23-
2422
import org.junit.jupiter.api.Test;
2523
import org.mockito.InOrder;
2624
import reactor.core.publisher.Mono;
@@ -101,14 +99,6 @@ void whenSslIsConfiguredWithAValidAliasARequestSucceeds() {
10199
StepVerifier.create(result).expectNext("Hello World").verifyComplete();
102100
}
103101

104-
@Test
105-
void whenSslIsConfiguredWithAnInvalidAliasTheSslHandshakeFails() {
106-
Mono<String> result = testSslWithAlias("test-alias-bad");
107-
StepVerifier.setDefaultTimeout(Duration.ofSeconds(30));
108-
StepVerifier.create(result).expectErrorMatches((throwable) -> throwable instanceof SSLHandshakeException
109-
&& throwable.getMessage().contains("HANDSHAKE_FAILURE")).verify();
110-
}
111-
112102
protected Mono<String> testSslWithAlias(String alias) {
113103
String keyStore = "classpath:test.jks";
114104
String keyPassword = "password";

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,15 @@
4141
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
4242
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests;
4343
import org.springframework.boot.web.server.PortInUseException;
44+
import org.springframework.boot.web.server.Ssl;
45+
import org.springframework.boot.web.server.WebServerException;
4446
import org.springframework.http.server.reactive.HttpHandler;
4547
import org.springframework.util.SocketUtils;
4648

4749
import static org.assertj.core.api.Assertions.assertThat;
4850
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4951
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
52+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5053
import static org.mockito.ArgumentMatchers.any;
5154
import static org.mockito.Mockito.inOrder;
5255
import static org.mockito.Mockito.mock;
@@ -239,6 +242,20 @@ void portClashOfPrimaryConnectorResultsInPortInUseException() throws IOException
239242
});
240243
}
241244

245+
@Test
246+
void sslWithInvalidAliasFailsDuringStartup() {
247+
String keyStore = "classpath:test.jks";
248+
String keyPassword = "password";
249+
AbstractReactiveWebServerFactory factory = getFactory();
250+
Ssl ssl = new Ssl();
251+
ssl.setKeyStore(keyStore);
252+
ssl.setKeyPassword(keyPassword);
253+
ssl.setKeyAlias("test-alias-404");
254+
factory.setSsl(ssl);
255+
assertThatThrownBy(() -> factory.getWebServer(new EchoHandler()).start())
256+
.isInstanceOf(WebServerException.class);
257+
}
258+
242259
private void doWithBlockedPort(BlockedPortAction action) throws IOException {
243260
int port = SocketUtils.findAvailableTcpPort(40000);
244261
ServerSocket serverSocket = new ServerSocket();

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -64,8 +64,11 @@
6464
import org.mockito.InOrder;
6565

6666
import org.springframework.boot.testsupport.system.CapturedOutput;
67+
import org.springframework.boot.testsupport.web.servlet.ExampleServlet;
6768
import org.springframework.boot.web.server.PortInUseException;
69+
import org.springframework.boot.web.server.Ssl;
6870
import org.springframework.boot.web.server.WebServerException;
71+
import org.springframework.boot.web.servlet.ServletRegistrationBean;
6972
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
7073
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactoryTests;
7174
import org.springframework.core.io.ByteArrayResource;
@@ -82,6 +85,7 @@
8285
import static org.assertj.core.api.Assertions.assertThat;
8386
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
8487
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
88+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
8589
import static org.mockito.ArgumentMatchers.any;
8690
import static org.mockito.Mockito.inOrder;
8791
import static org.mockito.Mockito.mock;
@@ -543,6 +547,16 @@ void registerJspServletWithDefaultLoadOnStartup() {
543547
this.webServer.start();
544548
}
545549

550+
@Test
551+
void sslWithInvalidAliasFailsDuringStartup() {
552+
AbstractServletWebServerFactory factory = getFactory();
553+
Ssl ssl = getSsl(null, "password", "test-alias-404", "src/test/resources/test.jks");
554+
factory.setSsl(ssl);
555+
ServletRegistrationBean<ExampleServlet> registration = new ServletRegistrationBean<>(
556+
new ExampleServlet(true, false), "/hello");
557+
assertThatThrownBy(() -> factory.getWebServer(registration).start()).isInstanceOf(WebServerException.class);
558+
}
559+
546560
@Override
547561
protected JspServlet getJspServlet() throws ServletException {
548562
Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat();

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -132,6 +132,44 @@ protected final void testBasicSslWithKeyStore(String keyStore, String keyPasswor
132132
assertThat(result.block(Duration.ofSeconds(30))).isEqualTo("Hello World");
133133
}
134134

135+
@Test
136+
void sslWithValidAlias() {
137+
String keyStore = "classpath:test.jks";
138+
String keyPassword = "password";
139+
AbstractReactiveWebServerFactory factory = getFactory();
140+
Ssl ssl = new Ssl();
141+
ssl.setKeyStore(keyStore);
142+
ssl.setKeyPassword(keyPassword);
143+
ssl.setKeyAlias("test-alias");
144+
factory.setSsl(ssl);
145+
this.webServer = factory.getWebServer(new EchoHandler());
146+
this.webServer.start();
147+
ReactorClientHttpConnector connector = buildTrustAllSslConnector();
148+
WebClient client = WebClient.builder().baseUrl("https://localhost:" + this.webServer.getPort())
149+
.clientConnector(connector).build();
150+
151+
Mono<String> result = client.post().uri("/test").contentType(MediaType.TEXT_PLAIN)
152+
.body(BodyInserters.fromObject("Hello World")).exchange()
153+
.flatMap((response) -> response.bodyToMono(String.class));
154+
155+
StepVerifier.setDefaultTimeout(Duration.ofSeconds(30));
156+
StepVerifier.create(result).expectNext("Hello World").verifyComplete();
157+
}
158+
159+
@Test
160+
void sslWithInvalidAliasFailsDuringStartup() {
161+
String keyStore = "classpath:test.jks";
162+
String keyPassword = "password";
163+
AbstractReactiveWebServerFactory factory = getFactory();
164+
Ssl ssl = new Ssl();
165+
ssl.setKeyStore(keyStore);
166+
ssl.setKeyPassword(keyPassword);
167+
ssl.setKeyAlias("test-alias-404");
168+
factory.setSsl(ssl);
169+
assertThatThrownBy(() -> factory.getWebServer(new EchoHandler()).start())
170+
.hasStackTraceContaining("Keystore does not contain specified alias 'test-alias-404'");
171+
}
172+
135173
protected ReactorClientHttpConnector buildTrustAllSslConnector() {
136174
SslContextBuilder builder = SslContextBuilder.forClient().sslProvider(SslProvider.JDK)
137175
.trustManager(InsecureTrustManagerFactory.INSTANCE);

0 commit comments

Comments
 (0)