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 5e1cae2 commit 1b8a5bd
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 36 deletions.
11 changes: 11 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 @@ -100,6 +100,17 @@ public SSLHelper(SSLEngineOptions sslEngineOptions, int cacheMaxSize) {
this.useWorkerPool = sslEngineOptions.getUseWorkerThread();
}

public synchronized int sniEntrySize() {
int size = 0;
for (Future<SslChannelProvider> fut : sslChannelProviderMap.values()) {
SslChannelProvider result = fut.result();
if (result != null) {
size += result.sniEntrySize();
}
}
return size;
}

public SSLHelper(SSLEngineOptions sslEngineOptions) {
this(sslEngineOptions, 256);
}
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 @@ -53,6 +53,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 @@ -67,17 +71,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 @@ -122,6 +122,10 @@ private GlobalTrafficShapingHandler createTrafficShapingHandler(EventLoopGroup e
protected void configure(SSLOptions options) {
}

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

public Future<Boolean> updateSSLOptions(ServerSSLOptions options, boolean force) {
TCPServerBase server = actualServer;
if (server != null && server != this) {
Expand Down
9 changes: 6 additions & 3 deletions src/test/java/io/vertx/core/net/NetTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1481,14 +1481,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 = awaitFuture(client.connect(testAddress, serverName));
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 1b8a5bd

Please sign in to comment.