Skip to content

Commit 7069365

Browse files
authored
* Fix Tls - invalid reuse of SSLParameters (helidon-io#7622)
* Fix ServerLister - close sockets that are not going to be processed * Fix MutualTlsTest - intermittently failing test
1 parent ec4e534 commit 7069365

3 files changed

Lines changed: 63 additions & 28 deletions

File tree

common/tls/src/main/java/io/helidon/common/tls/Tls.java

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -184,24 +184,6 @@ public SSLServerSocket createServerSocket() {
184184
}
185185
}
186186

187-
/**
188-
* Create a socket for the chosen protocol.
189-
*
190-
* @param alpnProtocol protocol to use
191-
* @return a new socket ready for TLS communication
192-
*/
193-
public SSLSocket createSocket(String alpnProtocol) {
194-
checkEnabled();
195-
try {
196-
SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket();
197-
sslParameters.setApplicationProtocols(new String[] {alpnProtocol});
198-
socket.setSSLParameters(sslParameters);
199-
return socket;
200-
} catch (IOException e) {
201-
throw new UncheckedIOException(e);
202-
}
203-
}
204-
205187
/**
206188
* Create a SSLSocket for the chosen protocol and the given socket.
207189
*
@@ -215,7 +197,24 @@ public SSLSocket createSocket(List<String> alpnProtocols, Socket socket, InetSoc
215197
try {
216198
SSLSocket sslSocket = (SSLSocket) sslSocketFactory
217199
.createSocket(socket, address.getHostName(), address.getPort(), true);
218-
sslParameters.setApplicationProtocols(alpnProtocols.toArray(new String[0]));
200+
201+
// create a copy of SSLParameters, as otherwise we may overwrite alpnProtocols of requests in parallel
202+
SSLParameters parameters = new SSLParameters();
203+
parameters.setApplicationProtocols(alpnProtocols.toArray(new String[0]));
204+
parameters.setServerNames(this.sslParameters.getServerNames());
205+
parameters.setCipherSuites(this.sslParameters.getCipherSuites());
206+
parameters.setAlgorithmConstraints(this.sslParameters.getAlgorithmConstraints());
207+
parameters.setEnableRetransmissions(this.sslParameters.getEnableRetransmissions());
208+
parameters.setEndpointIdentificationAlgorithm(this.sslParameters.getEndpointIdentificationAlgorithm());
209+
parameters.setMaximumPacketSize(this.sslParameters.getMaximumPacketSize());
210+
parameters.setNamedGroups(this.sslParameters.getNamedGroups());
211+
parameters.setNeedClientAuth(this.sslParameters.getNeedClientAuth());
212+
parameters.setProtocols(this.sslParameters.getProtocols());
213+
parameters.setSignatureSchemes(this.sslParameters.getSignatureSchemes());
214+
parameters.setSNIMatchers(this.sslParameters.getSNIMatchers());
215+
parameters.setUseCipherSuitesOrder(this.sslParameters.getUseCipherSuitesOrder());
216+
parameters.setWantClientAuth(this.sslParameters.getWantClientAuth());
217+
219218
sslSocket.setSSLParameters(sslParameters);
220219
return sslSocket;
221220
} catch (IOException e) {
@@ -270,12 +269,6 @@ Optional<X509TrustManager> trustManager() {
270269
return tlsManager.trustManager();
271270
}
272271

273-
private void checkEnabled() {
274-
if (sslContext == null) {
275-
throw new IllegalStateException("TLS config is disabled, SSL related methods cannot be called.");
276-
}
277-
}
278-
279272
private static int hashCode(SSLParameters first) {
280273
int result = Objects.hash(first.getAlgorithmConstraints(),
281274
first.getEnableRetransmissions(),
@@ -307,4 +300,10 @@ private static boolean equals(SSLParameters first, SSLParameters second) {
307300
&& first.getServerNames().equals(second.getServerNames())
308301
&& first.getSNIMatchers().equals(second.getSNIMatchers());
309302
}
303+
304+
private void checkEnabled() {
305+
if (sslContext == null) {
306+
throw new IllegalStateException("TLS config is disabled, SSL related methods cannot be called.");
307+
}
308+
}
310309
}

webclient/tests/webclient/src/test/java/io/helidon/webclient/tests/MutualTlsTest.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
*/
1616
package io.helidon.webclient.tests;
1717

18+
import java.io.IOException;
1819
import java.io.UncheckedIOException;
20+
import java.net.SocketException;
1921
import java.util.concurrent.atomic.AtomicBoolean;
2022

23+
import javax.net.ssl.SSLHandshakeException;
24+
2125
import io.helidon.common.tls.Tls;
2226
import io.helidon.config.Config;
2327
import io.helidon.config.ConfigSources;
@@ -37,12 +41,13 @@
3741
import static org.hamcrest.CoreMatchers.is;
3842
import static org.hamcrest.MatcherAssert.assertThat;
3943
import static org.junit.jupiter.api.Assertions.assertThrows;
44+
import static org.junit.jupiter.api.Assertions.fail;
4045

4146
/**
4247
* This test is testing ability to set mutual TLS on WebClient and WebServer.
4348
*/
4449
@ServerTest
45-
public class MutualTlsTest {
50+
class MutualTlsTest {
4651

4752
private static final Config CONFIG = Config.just(() -> ConfigSources.classpath("application-test.yaml").build());
4853

@@ -86,8 +91,22 @@ public void testAccessSuccessful() {
8691
public void testNoClientCert() {
8792
Http1Client client = createWebClient(CONFIG.get("no-client-cert"));
8893

89-
RuntimeException ex = assertThrows(RuntimeException.class, () -> exec(client, "https", server.port("secured")));
90-
assertThat(ex.getMessage(), containsString("Received fatal alert: bad_certificate"));
94+
// as the client does not have client certificate configured, it is not aware it should wait for the application
95+
// data from server during SSL handshake, as a result, handshake is OK, but we fail when we try to communicate
96+
// (as the client becomes aware of the problem only when it tries to read data from TLS, and by that time the
97+
// server may have terminated the connection)
98+
// so sometimes we get correct fatal alert about certificate, sometimes connection closed (Broken pipe or similar)
99+
// it just must always fail with a socket exception or SSLHandshakeException
100+
UncheckedIOException ex = assertThrows(UncheckedIOException.class, () -> exec(client, "https", server.port("secured")));
101+
102+
IOException cause = ex.getCause();
103+
if (cause instanceof SSLHandshakeException) {
104+
assertThat(cause.getMessage(), containsString("Received fatal alert: bad_certificate"));
105+
} else {
106+
if (!(cause instanceof SocketException)) {
107+
fail("Call must fail with either SSLHandshakeException or SocketException", cause);
108+
}
109+
}
91110
}
92111

93112
@Test

webserver/webserver/src/main/java/io/helidon/webserver/ServerListener.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,11 +393,28 @@ private void listen() {
393393
readerExecutor.execute(handler);
394394
} catch (RejectedExecutionException e) {
395395
LOGGER.log(ERROR, "Executor rejected handler for new connection");
396+
397+
// the socket was never handled
398+
try {
399+
socket.close();
400+
} catch (IOException ex) {
401+
LOGGER.log(TRACE, "Failed to close socket that was rejected for execution", e);
402+
}
403+
396404
// we never started the handler, so we must release the semaphore here
397405
connectionSemaphore.release();
398406
} catch (Exception e) {
399407
// we may get an SSL handshake errors, which should only fail one socket, not the listener
400408
LOGGER.log(TRACE, "Failed to handle accepted socket", e);
409+
// the socket was never handled
410+
try {
411+
socket.close();
412+
} catch (IOException ex) {
413+
LOGGER.log(TRACE,
414+
"Failed to close socket that failed start execution (see previous trace for reason)",
415+
e);
416+
}
417+
401418
// we never started the handler, so we must release the semaphore here
402419
connectionSemaphore.release();
403420
}

0 commit comments

Comments
 (0)