Skip to content

Commit

Permalink
The SslChannelProvider class maintains a map of server name to Netty …
Browse files Browse the repository at this point in the history
…SslContext that is filled when a client provides a server name. When a server name does not resolve to a KeyManagerFactory or TrustManagerFactory, the default factories are used and the entry is stored in the map. Instead no specific factory is resolved the default Netty SslContext is used, since this can lead to a a memory leak when a client specifies spurious SNI server names. This affects only a TCP server when SNI is set in the HttpServerOptions.
  • Loading branch information
vietj committed Feb 6, 2024
1 parent ea749f6 commit 7ad34ea
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 40 deletions.
8 changes: 8 additions & 0 deletions src/main/java/io/vertx/core/net/impl/SSLHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ public SSLHelper(TCPSSLOptions options, List<String> applicationProtocols) {
this.applicationProtocols = applicationProtocols;
}

public synchronized int sniEntrySize() {
CachedProvider res = cachedProvider.result();
if (res != null) {
return res.sslChannelProvider.sniEntrySize();
}
return 0;
}

private static class CachedProvider {
final SSLOptions options;
final long id;
Expand Down
21 changes: 13 additions & 8 deletions src/main/java/io/vertx/core/net/impl/SslChannelProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public SslChannelProvider(SslContextProvider sslContextProvider,
this.sslContextProvider = sslContextProvider;
}

public int sniEntrySize() {
return sslContextMaps[0].size() + sslContextMaps[1].size();
}

public SslContextProvider sslContextProvider() {
return sslContextProvider;
}
Expand All @@ -83,17 +87,18 @@ public SslContext sslClientContext(String serverName, boolean useAlpn, boolean t

public SslContext sslContext(String serverName, boolean useAlpn, boolean server, boolean trustAll) throws Exception {
int idx = idx(useAlpn);
if (serverName == null) {
if (sslContexts[idx] == null) {
SslContext context = sslContextProvider.createContext(server, null, null, null, useAlpn, trustAll);
sslContexts[idx] = context;
}
return sslContexts[idx];
} else {
if (serverName != null) {
KeyManagerFactory kmf = sslContextProvider.resolveKeyManagerFactory(serverName);
TrustManager[] trustManagers = trustAll ? null : sslContextProvider.resolveTrustManagers(serverName);
return sslContextMaps[idx].computeIfAbsent(serverName, s -> sslContextProvider.createContext(server, kmf, trustManagers, s, useAlpn, trustAll));
if (kmf != null || trustManagers != null || !server) {
return sslContextMaps[idx].computeIfAbsent(serverName, s -> sslContextProvider.createContext(server, kmf, trustManagers, s, useAlpn, trustAll));
}
}
if (sslContexts[idx] == null) {
SslContext context = sslContextProvider.createContext(server, null, null, serverName, useAlpn, trustAll);
sslContexts[idx] = context;
}
return sslContexts[idx];
}

public SslContext sslServerContext(boolean useAlpn) {
Expand Down
32 changes: 7 additions & 25 deletions src/main/java/io/vertx/core/net/impl/SslContextProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,6 @@ protected void initEngine(SSLEngine engine) {
}
}

public KeyManagerFactory loadKeyManagerFactory(String serverName) throws Exception {
if (keyManagerFactoryMapper != null) {
return keyManagerFactoryMapper.apply(serverName);
}
return null;
}

public TrustManager[] defaultTrustManagers() {
return trustManagerFactory != null ? trustManagerFactory.getTrustManagers() : null;
}
Expand All @@ -174,8 +167,7 @@ public KeyManagerFactory defaultKeyManagerFactory() {
}

/**
* Resolve the {@link KeyManagerFactory} for the {@code serverName}, when a factory cannot be resolved, the default
* factory is returned.
* Resolve the {@link KeyManagerFactory} for the {@code serverName}, when a factory cannot be resolved, {@code null} is returned.
* <br/>
* This can block and should be executed on the appropriate thread.
*
Expand All @@ -184,23 +176,14 @@ public KeyManagerFactory defaultKeyManagerFactory() {
* @throws Exception anything that would prevent loading the factory
*/
public KeyManagerFactory resolveKeyManagerFactory(String serverName) throws Exception {
KeyManagerFactory kmf = loadKeyManagerFactory(serverName);
if (kmf == null) {
kmf = keyManagerFactory;
}
return kmf;
}

public TrustManager[] loadTrustManagers(String serverName) throws Exception {
if (trustManagerMapper != null) {
return trustManagerMapper.apply(serverName);
if (keyManagerFactoryMapper != null) {
return keyManagerFactoryMapper.apply(serverName);
}
return null;
}

/**
* Resolve the {@link TrustManager}[] for the {@code serverName}, when managers cannot be resolved, the default
* managers are returned.
* Resolve the {@link TrustManager}[] for the {@code serverName}, when managers cannot be resolved, {@code null} is returned.
* <br/>
* This can block and should be executed on the appropriate thread.
*
Expand All @@ -209,11 +192,10 @@ public TrustManager[] loadTrustManagers(String serverName) throws Exception {
* @throws Exception anything that would prevent loading the managers
*/
public TrustManager[] resolveTrustManagers(String serverName) throws Exception {
TrustManager[] trustManagers = loadTrustManagers(serverName);
if (trustManagers == null && trustManagerFactory != null) {
trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagerMapper != null) {
return trustManagerMapper.apply(serverName);
}
return trustManagers;
return null;
}

private VertxTrustManagerFactory buildVertxTrustManagerFactory(TrustManager[] mgrs) {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/io/vertx/core/net/impl/TCPServerBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ private GlobalTrafficShapingHandler createTrafficShapingHandler(EventLoopGroup e
return trafficShapingHandler;
}

public int sniEntrySize() {
return sslHelper.sniEntrySize();
}

public Future<Boolean> updateSSLOptions(SSLOptions options, boolean force) {
TCPServerBase server = actualServer;
if (server != null && server != this) {
Expand Down
14 changes: 7 additions & 7 deletions src/test/java/io/vertx/core/net/NetTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.impl.HAProxyMessageCompletionHandler;
import io.vertx.core.net.impl.NetServerImpl;
import io.vertx.core.net.impl.NetSocketInternal;
import io.vertx.core.net.impl.VertxHandler;
import io.vertx.core.net.impl.*;
import io.vertx.core.spi.tls.SslContextFactory;
import io.vertx.core.streams.ReadStream;
import io.vertx.test.core.CheckingSender;
Expand Down Expand Up @@ -1538,14 +1535,17 @@ public void testClientSniMultipleServerName() throws Exception {
receivedServerNames.add(so.indicatedServerName());
});
startServer();
List<String> serverNames = Arrays.asList("host1", "host2.com");
List<String> serverNames = Arrays.asList("host1", "host2.com", "fake");
List<String> cns = new ArrayList<>();
client = vertx.createNetClient(new NetClientOptions().setSsl(true).setTrustAll(true));
for (String serverName : serverNames) {
NetSocket so = client.connect(testAddress, serverName).toCompletionStage().toCompletableFuture().get();
String host = cnOf(so.peerCertificates().get(0));
assertEquals(serverName, host);
cns.add(host);
}
assertWaitUntil(() -> receivedServerNames.size() == 2);
assertEquals(Arrays.asList("host1", "host2.com", "localhost"), cns);
assertEquals(2, ((TCPServerBase)server).sniEntrySize());
assertWaitUntil(() -> receivedServerNames.size() == 3);
assertEquals(receivedServerNames, serverNames);
}

Expand Down

0 comments on commit 7ad34ea

Please sign in to comment.