Skip to content

Commit

Permalink
Vert.x TCP client does configure the TLS hostname verification algori…
Browse files Browse the repository at this point in the history
…thm to the empty string which means no checks of the server identity shall be performed beyond checking the validity of the certificate.

This set the default algorithm to null instead of the empty string, any usage of the TCP client must set the verification algorithm when used over TLS, valid values are empty string, HTTPS or LDAPS.

This is a breaking change when using NetClient over TLS.
  • Loading branch information
vietj committed Feb 15, 2024
1 parent 939ea22 commit bee2f5d
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 14 deletions.
11 changes: 8 additions & 3 deletions src/main/asciidoc/net.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -536,16 +536,21 @@ be sure who you are connecting to. Use this with caution. Default value is false
If {@link io.vertx.core.net.ClientOptionsBase#setTrustAll trustAll} is not set then a client trust store must be
configured and should contain the certificates of the servers that the client trusts.

By default, host verification is disabled on the client.
To enable host verification, set the algorithm to use on your client (only HTTPS and LDAPS is currently supported):
By default, host verification is *not* configured on the client. This verifies the CN portion of the server certificate against the server hostname to avoid [Man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack).

You must configure it explicitly on your client

- `""` (empty string) disables host verification
- `"HTTPS"` enables HTTP over TLS [verification](https://datatracker.ietf.org/doc/html/rfc2818#section-3.1)
- `LDAPS` enables LDAP v3 extension for TLS [verification](https://datatracker.ietf.org/doc/html/rfc2830#section-3.6)
[source,$lang]
----
{@link examples.NetExamples#example46}
----

Likewise server configuration, the client trust can be configured in several ways:
NOTE: the Vert.x HTTP client uses the TCP client and configures with `"HTTPS"` the verification algorithm.

Like server configuration, the client trust can be configured in several ways:

The first method is by specifying the location of a Java trust-store which contains the certificate authority.

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/examples/NetExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -583,10 +583,10 @@ public void exampleSSLEngine(Vertx vertx, JksOptions keyStoreOptions) {
setOpenSslEngineOptions(new OpenSSLEngineOptions());
}

public void example46(Vertx vertx, JksOptions keyStoreOptions) {
public void example46(Vertx vertx, String verificationAlgorithm) {
NetClientOptions options = new NetClientOptions().
setSsl(true).
setHostnameVerificationAlgorithm("HTTPS");
setHostnameVerificationAlgorithm(verificationAlgorithm);
NetClient client = vertx.createNetClient(options);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public ClusteredEventBus(VertxInternal vertx, VertxOptions options, ClusterManag
this.nodeSelector = nodeSelector;
closeFuture = new CloseFuture(log);
ebContext = vertx.createEventLoopContext(null, closeFuture, null, Thread.currentThread().getContextClassLoader());
this.client = createNetClient(vertx, new NetClientOptions(this.options.toJson()), closeFuture);
this.client = createNetClient(vertx, new NetClientOptions(this.options.toJson()).setHostnameVerificationAlgorithm(""), closeFuture);
}

private NetClient createNetClient(VertxInternal vertx, NetClientOptions clientOptions, CloseFuture closeFuture) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/io/vertx/core/net/NetClientOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public class NetClientOptions extends ClientOptionsBase {
public static final long DEFAULT_RECONNECT_INTERVAL = 1000;

/**
* Default value to determine hostname verification algorithm hostname verification (for SSL/TLS) = ""
* Default value to determine hostname verification algorithm hostname verification (for SSL/TLS) = null
*/
public static final String DEFAULT_HOSTNAME_VERIFICATION_ALGORITHM = "";
public static final String DEFAULT_HOSTNAME_VERIFICATION_ALGORITHM = null;

/**
* Whether a write-handler should be registered by default = false.
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/io/vertx/core/net/impl/NetClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ public void connectInternal(ProxyOptions proxyOptions,
if (closeFuture.isClosed()) {
connectHandler.fail(new IllegalStateException("Client is closed"));
} else {
if (ssl && options.getHostnameVerificationAlgorithm() == null) {
connectHandler.fail("Missing hostname verification algorithm: you must set TCP client options host name" +
" verification algorithm");
return;
}
Future<SslContextUpdate> fut;
synchronized (NetClientImpl.this) {
fut = sslChannelProvider;
Expand Down Expand Up @@ -328,7 +333,7 @@ private void connectInternal2(ProxyOptions proxyOptions,
private void connected(ContextInternal context, Channel ch, Promise<NetSocket> connectHandler, SocketAddress remoteAddress, SslChannelProvider sslChannelProvider, String applicationLayerProtocol, boolean registerWriteHandlers) {
channelGroup.add(ch);
initChannel(ch.pipeline());
VertxHandler<NetSocketImpl> handler = VertxHandler.create(ctx -> new NetSocketImpl(context, ctx, remoteAddress, sslChannelProvider, metrics, applicationLayerProtocol, registerWriteHandlers));
VertxHandler<NetSocketImpl> handler = VertxHandler.create(ctx -> new NetSocketImpl(context, ctx, remoteAddress, sslChannelProvider, metrics, options.getHostnameVerificationAlgorithm(), applicationLayerProtocol, registerWriteHandlers));
handler.removeHandler(NetSocketImpl::unregisterEventBusHandler);
handler.addHandler(sock -> {
if (metrics != null) {
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/io/vertx/core/net/impl/NetSocketImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class NetSocketImpl extends ConnectionBase implements NetSocketInternal {
private final SocketAddress remoteAddress;
private final TCPMetrics metrics;
private final InboundBuffer<Object> pending;
private final String hostnameVerificationAlgorithm;
private final String negotiatedApplicationLayerProtocol;
private Handler<Void> endHandler;
private Handler<Void> drainHandler;
Expand All @@ -69,14 +70,15 @@ public class NetSocketImpl extends ConnectionBase implements NetSocketInternal {
private Handler<Object> eventHandler;

public NetSocketImpl(ContextInternal context, ChannelHandlerContext channel, SslChannelProvider sslChannelProvider, TCPMetrics metrics, boolean registerWriteHandler) {
this(context, channel, null, sslChannelProvider, metrics, null, registerWriteHandler);
this(context, channel, null, sslChannelProvider, metrics, null, null, registerWriteHandler);
}

public NetSocketImpl(ContextInternal context,
ChannelHandlerContext channel,
SocketAddress remoteAddress,
SslChannelProvider sslChannelProvider,
TCPMetrics metrics,
String hostnameVerificationAlgorithm,
String negotiatedApplicationLayerProtocol,
boolean registerWriteHandler) {
super(context, channel);
Expand All @@ -85,6 +87,7 @@ public NetSocketImpl(ContextInternal context,
this.remoteAddress = remoteAddress;
this.metrics = metrics;
this.messageHandler = new DataMessageHandler();
this.hostnameVerificationAlgorithm = hostnameVerificationAlgorithm;
this.negotiatedApplicationLayerProtocol = negotiatedApplicationLayerProtocol;
pending = new InboundBuffer<>(context);
pending.drainHandler(v -> doResume());
Expand Down Expand Up @@ -316,6 +319,13 @@ public Future<Void> upgradeToSsl() {
public Future<Void> upgradeToSsl(String serverName) {
PromiseInternal<Void> promise = context.promise();
if (chctx.pipeline().get("ssl") == null) {
if (remoteAddress != null && hostnameVerificationAlgorithm == null) {
promise.fail("Missing hostname verification algorithm: you must set TCP client options host name" +
" verification algorithm");
return promise.future();
}


ChannelPromise flush = chctx.newPromise();
flush(flush);
flush.addListener(fut -> {
Expand Down
71 changes: 67 additions & 4 deletions src/test/java/io/vertx/core/net/NetTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static io.vertx.core.http.HttpTestBase.DEFAULT_HTTPS_HOST;
import static io.vertx.core.http.HttpTestBase.DEFAULT_HTTPS_PORT;
Expand Down Expand Up @@ -237,7 +238,7 @@ public void testClientOptions() {
assertTrue(options.isTrustAll());

String randomAlphaString = TestUtils.randomAlphaString(10);
assertTrue(options.getHostnameVerificationAlgorithm().isEmpty());
assertNull(options.getHostnameVerificationAlgorithm());
assertEquals(options, options.setHostnameVerificationAlgorithm(randomAlphaString));
assertEquals(randomAlphaString, options.getHostnameVerificationAlgorithm());

Expand Down Expand Up @@ -1537,7 +1538,7 @@ public void testClientSniMultipleServerName() throws Exception {
startServer();
List<String> serverNames = Arrays.asList("host1", "host2.com", "fake");
List<String> cns = new ArrayList<>();
client = vertx.createNetClient(new NetClientOptions().setSsl(true).setTrustAll(true));
client = vertx.createNetClient(new NetClientOptions().setSsl(true).setTrustAll(true).setHostnameVerificationAlgorithm(""));
for (String serverName : serverNames) {
NetSocket so = client.connect(testAddress, serverName).toCompletionStage().toCompletableFuture().get();
String host = cnOf(so.peerCertificates().get(0));
Expand Down Expand Up @@ -1919,6 +1920,7 @@ void run(boolean shouldPass) {
for (String protocol : enabledSecureTransportProtocols) {
clientOptions.addEnabledSecureTransportProtocol(protocol);
}
clientOptions.setHostnameVerificationAlgorithm("");
client = vertx.createNetClient(clientOptions);
Future<Void> f = client.connect(connectAddress, serverName).compose(socket -> {
Promise<Void> result = Promise.promise();
Expand Down Expand Up @@ -3032,6 +3034,62 @@ public void testHostVerificationHttpsMatching() {
await();
}

@Test
public void testClientMissingHostnameVerificationAlgorithm1() {
NetClientOptions clientOptions = new NetClientOptions()
.setSsl(true)
.setTrustAll(true);
client.close();
client = vertx.createNetClient(clientOptions);
testClientMissingHostnameVerificationAlgorithm(() -> client.connect(1234, "localhost"), true);
}

@Test
public void testClientMissingHostnameVerificationAlgorithm2() {
NetClientOptions clientOptions = new NetClientOptions().setTrustAll(true);
client.close();
client = vertx.createNetClient(clientOptions);
testClientMissingHostnameVerificationAlgorithm(() -> client.connect(1234, "localhost"), false);
}

@Test
public void testClientMissingHostnameVerificationAlgorithm3() {
NetClientOptions clientOptions = new NetClientOptions().setTrustAll(true);
client.close();
client = vertx.createNetClient(clientOptions);
testClientMissingHostnameVerificationAlgorithm(() -> client
.connect(1234, "localhost")
.compose(NetSocket::upgradeToSsl), true);
}

private void testClientMissingHostnameVerificationAlgorithm(Supplier<Future<?>> consumer, boolean expectFailure) {
server.close();
NetServerOptions options = new NetServerOptions()
.setPort(1234)
.setHost("localhost")
.setSsl(true)
.setKeyCertOptions(new JksOptions().setPath("tls/server-keystore.jks").setPassword("wibble"));
NetServer server = vertx.createNetServer(options);

server.connectHandler(sock -> {

});
server.listen().onComplete(onSuccess(v -> {
Future<?> fut = consumer.get();
if (expectFailure) {
fut.onComplete(onFailure(err -> {
assertTrue(err.getMessage().contains("Missing hostname verification algorithm"));
testComplete();
}));
} else {
fut.onComplete(onSuccess(so -> {
testComplete();
}));
}
}));
await();
}

@Test
public void testNoLogging() throws Exception {
TestLoggerFactory factory = testLogging();
Expand Down Expand Up @@ -3433,11 +3491,13 @@ public void testSelfSignedCertificate() throws Exception {

NetClientOptions clientOptions = new NetClientOptions()
.setSsl(true)
.setHostnameVerificationAlgorithm("")
.setKeyCertOptions(certificate.keyCertOptions())
.setTrustOptions(certificate.trustOptions());

NetClientOptions clientTrustAllOptions = new NetClientOptions()
.setSsl(true)
.setHostnameVerificationAlgorithm("")
.setTrustAll(true);

server = vertx.createNetServer(serverOptions)
Expand Down Expand Up @@ -3601,7 +3661,7 @@ public void testNetClientInternal() throws Exception {
@Test
public void testNetClientInternalTLS() throws Exception {
client.close();
client = vertx.createNetClient(new NetClientOptions().setSsl(true).setTrustStoreOptions(Trust.SERVER_JKS.get()));
client = vertx.createNetClient(new NetClientOptions().setSsl(true).setHostnameVerificationAlgorithm("").setTrustStoreOptions(Trust.SERVER_JKS.get()));
testNetClientInternal_(new HttpServerOptions()
.setHost("localhost")
.setPort(1234)
Expand Down Expand Up @@ -3633,6 +3693,7 @@ public void testNetClientInternalTLSWithSuppliedSSLContext() throws Exception {
);

client = vertx.createNetClient(new NetClientOptions().setSsl(true)
.setHostnameVerificationAlgorithm("")
.setSslEngineOptions(new JdkSSLEngineOptions() {
@Override
public SslContextFactory sslContextFactory() {
Expand Down Expand Up @@ -4034,7 +4095,8 @@ public void testSslHandshakeTimeoutNotHappened() throws Exception {

NetClientOptions clientOptions = new NetClientOptions()
.setSsl(true)
.setTrustAll(true);
.setTrustAll(true)
.setHostnameVerificationAlgorithm("");
client = vertx.createNetClient(clientOptions);

server.connectHandler(s -> {
Expand All @@ -4061,6 +4123,7 @@ public void testSslHandshakeTimeoutHappenedWhenUpgradeSsl() {
NetClientOptions clientOptions = new NetClientOptions()
.setSsl(false)
.setTrustAll(true)
.setHostnameVerificationAlgorithm("")
.setSslHandshakeTimeout(200)
.setSslHandshakeTimeoutUnit(TimeUnit.MILLISECONDS);
client = vertx.createNetClient(clientOptions);
Expand Down
1 change: 1 addition & 0 deletions src/test/java/io/vertx/core/spi/metrics/MetricsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,7 @@ public void testHTTP2ConnectionCloseBeforePrefaceIsReceived() throws Exception {
.setUseAlpn(true)
.setSsl(true)
.setTrustStoreOptions(Trust.SERVER_JKS.get())
.setHostnameVerificationAlgorithm("HTTPS")
.setApplicationLayerProtocols(Collections.singletonList("h2")));
CountDownLatch latch = new CountDownLatch(1);
client.connect(HttpTestBase.DEFAULT_HTTP_PORT, HttpTestBase.DEFAULT_HTTP_HOST, onSuccess(so -> {
Expand Down

0 comments on commit bee2f5d

Please sign in to comment.