diff --git a/src/main/java/io/vertx/core/Vertx.java b/src/main/java/io/vertx/core/Vertx.java index 56f769dc1ed..14644d7512c 100644 --- a/src/main/java/io/vertx/core/Vertx.java +++ b/src/main/java/io/vertx/core/Vertx.java @@ -63,6 +63,7 @@ * Please see the user manual for more detailed usage information. * * @author Tim Fox + * @author Julien Viet */ @VertxGen public interface Vertx extends Measured { diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java b/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java index 6eb2b70b9c3..fa60e3e1317 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java @@ -19,6 +19,7 @@ import io.vertx.core.impl.VertxInternal; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.net.*; +import io.vertx.core.net.impl.NetClientBuilder; import io.vertx.core.net.impl.NetClientImpl; import io.vertx.core.net.impl.ProxyFilter; import io.vertx.core.net.impl.pool.ConnectionManager; @@ -146,17 +147,16 @@ public HttpClientImpl(VertxInternal vertx, HttpClientOptions options, CloseFutur throw new IllegalStateException("Cannot have pipelining with no keep alive"); } this.proxyFilter = options.getNonProxyHosts() != null ? ProxyFilter.nonProxyHosts(options.getNonProxyHosts()) : ProxyFilter.DEFAULT_PROXY_FILTER; - this.netClient = new NetClientImpl( - vertx, - metrics, - new NetClientOptions(options) - .setHostnameVerificationAlgorithm(options.isVerifyHost() ? "HTTPS": "") - .setProxyOptions(null) - .setApplicationLayerProtocols(alpnVersions - .stream() - .map(HttpVersion::alpnName) - .collect(Collectors.toList())), - closeFuture); + this.netClient = (NetClientImpl) new NetClientBuilder(vertx, new NetClientOptions(options) + .setHostnameVerificationAlgorithm(options.isVerifyHost() ? "HTTPS": "") + .setProxyOptions(null) + .setApplicationLayerProtocols(alpnVersions + .stream() + .map(HttpVersion::alpnName) + .collect(Collectors.toList()))) + .metrics(metrics) + .closeFuture(closeFuture) + .build(); webSocketCM = webSocketConnectionManager(); httpCM = httpConnectionManager(); if (options.getPoolCleanerPeriod() > 0 && (options.getKeepAliveTimeout() > 0L || options.getHttp2KeepAliveTimeout() > 0L)) { diff --git a/src/main/java/io/vertx/core/http/impl/HttpServerImpl.java b/src/main/java/io/vertx/core/http/impl/HttpServerImpl.java index 47b500d0363..7d379cd6dda 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpServerImpl.java +++ b/src/main/java/io/vertx/core/http/impl/HttpServerImpl.java @@ -200,13 +200,11 @@ protected Handler childHandler(ContextInternal context, SocketAddress a @Override protected SSLHelper createSSLHelper() { - return super.createSSLHelper() - .setApplicationProtocols(options - .getAlpnVersions() - .stream() - .map(HttpVersion::alpnName) - .collect(Collectors.toList()) - ); + return new SSLHelper(options, options + .getAlpnVersions() + .stream() + .map(HttpVersion::alpnName) + .collect(Collectors.toList())); } @Override diff --git a/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java b/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java index af496c2596e..1cabb7ed838 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java +++ b/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java @@ -10,6 +10,7 @@ */ package io.vertx.core.http.impl; +import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.handler.codec.compression.CompressionOptions; @@ -19,6 +20,7 @@ import io.netty.handler.codec.http.HttpContentDecompressor; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SniHandler; +import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.timeout.IdleState; @@ -134,14 +136,7 @@ public void handle(Channel ch) { private void configurePipeline(Channel ch) { ChannelPipeline pipeline = ch.pipeline(); if (sslHelper.isSSL()) { - if (options.isSni()) { - SniHandler sniHandler = new SniHandler(sslHelper.serverNameMapper(vertx)); - pipeline.addLast(sniHandler); - } else { - SslHandler handler = new SslHandler(sslHelper.createEngine(vertx)); - handler.setHandshakeTimeout(sslHelper.getSslHandshakeTimeout(), sslHelper.getSslHandshakeTimeoutUnit()); - pipeline.addLast("ssl", handler); - } + pipeline.addLast("ssl", sslHelper.createHandler(vertx)); ChannelPromise p = ch.newPromise(); pipeline.addLast("handshaker", new SslHandshakeCompletionHandler(p)); p.addListener(future -> { diff --git a/src/main/java/io/vertx/core/impl/VertxImpl.java b/src/main/java/io/vertx/core/impl/VertxImpl.java index 8d07939bf27..13c0ffdb49b 100644 --- a/src/main/java/io/vertx/core/impl/VertxImpl.java +++ b/src/main/java/io/vertx/core/impl/VertxImpl.java @@ -31,6 +31,7 @@ import io.vertx.core.eventbus.impl.clustered.ClusteredEventBus; import io.vertx.core.file.FileSystem; import io.vertx.core.impl.btc.BlockedThreadChecker; +import io.vertx.core.net.impl.NetClientBuilder; import io.vertx.core.spi.file.FileResolver; import io.vertx.core.file.impl.FileSystemImpl; import io.vertx.core.file.impl.WindowsFileSystem; @@ -296,19 +297,14 @@ public NetServer createNetServer(NetServerOptions options) { return new NetServerImpl(this, options); } - @Override - public NetClient createNetClient(NetClientOptions options, CloseFuture closeFuture) { - NetClientImpl client = new NetClientImpl(this, options, closeFuture); - closeFuture.add(client); - return client; - } - public NetClient createNetClient(NetClientOptions options) { CloseFuture closeFuture = new CloseFuture(log); - NetClient client = createNetClient(options, closeFuture); CloseFuture fut = resolveCloseFuture(); fut.add(closeFuture); - return client; + NetClientBuilder builder = new NetClientBuilder(this, options); + builder.metrics(metricsSPI() != null ? metricsSPI().createNetClientMetrics(options) : null); + builder.closeFuture(closeFuture); + return builder.build(); } @Override diff --git a/src/main/java/io/vertx/core/impl/VertxInternal.java b/src/main/java/io/vertx/core/impl/VertxInternal.java index 36da3b9dffc..4ecbc7c8314 100644 --- a/src/main/java/io/vertx/core/impl/VertxInternal.java +++ b/src/main/java/io/vertx/core/impl/VertxInternal.java @@ -21,8 +21,8 @@ import io.vertx.core.http.impl.HttpServerImpl; import io.vertx.core.impl.btc.BlockedThreadChecker; import io.vertx.core.impl.future.PromiseInternal; -import io.vertx.core.net.NetClient; import io.vertx.core.net.NetClientOptions; +import io.vertx.core.net.impl.NetClientBuilder; import io.vertx.core.net.impl.NetServerImpl; import io.vertx.core.net.impl.ServerID; import io.vertx.core.net.impl.TCPServerBase; @@ -46,6 +46,7 @@ * developers creating vert.x applications * * @author Tim Fox + * @author Julien Viet */ public interface VertxInternal extends Vertx { @@ -85,15 +86,6 @@ public interface VertxInternal extends Vertx { Transport transport(); - /** - * Create a TCP/SSL client using the specified options and close future - * - * @param options the options to use - * @param closeFuture the close future - * @return the client - */ - NetClient createNetClient(NetClientOptions options, CloseFuture closeFuture); - /** * Create a HTTP/HTTPS client using the specified options and close future * diff --git a/src/main/java/io/vertx/core/impl/VertxWrapper.java b/src/main/java/io/vertx/core/impl/VertxWrapper.java index b563d67bf84..ebdd4ed9b98 100644 --- a/src/main/java/io/vertx/core/impl/VertxWrapper.java +++ b/src/main/java/io/vertx/core/impl/VertxWrapper.java @@ -414,11 +414,6 @@ public Transport transport() { return delegate.transport(); } - @Override - public NetClient createNetClient(NetClientOptions options, CloseFuture closeFuture) { - return delegate.createNetClient(options, closeFuture); - } - @Override public HttpClient createHttpClient(HttpClientOptions options, CloseFuture closeFuture) { return delegate.createHttpClient(options, closeFuture); diff --git a/src/main/java/io/vertx/core/net/JdkSSLEngineOptions.java b/src/main/java/io/vertx/core/net/JdkSSLEngineOptions.java index b68c90593b1..30ea6abae16 100644 --- a/src/main/java/io/vertx/core/net/JdkSSLEngineOptions.java +++ b/src/main/java/io/vertx/core/net/JdkSSLEngineOptions.java @@ -11,10 +11,12 @@ package io.vertx.core.net; +import io.netty.handler.ssl.SslProvider; import io.vertx.codegen.annotations.DataObject; import io.vertx.core.json.JsonObject; +import io.vertx.core.spi.tls.DefaultSslContextFactory; +import io.vertx.core.spi.tls.SslContextFactory; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; /** @@ -70,4 +72,9 @@ public JsonObject toJson() { public JdkSSLEngineOptions copy() { return new JdkSSLEngineOptions(); } + + @Override + public SslContextFactory sslContextFactory() { + return new DefaultSslContextFactory(SslProvider.JDK, false); + } } diff --git a/src/main/java/io/vertx/core/net/OpenSSLEngineOptions.java b/src/main/java/io/vertx/core/net/OpenSSLEngineOptions.java index d59842cac0e..b1d36d51694 100644 --- a/src/main/java/io/vertx/core/net/OpenSSLEngineOptions.java +++ b/src/main/java/io/vertx/core/net/OpenSSLEngineOptions.java @@ -12,8 +12,11 @@ package io.vertx.core.net; import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.SslProvider; import io.vertx.codegen.annotations.DataObject; import io.vertx.core.json.JsonObject; +import io.vertx.core.spi.tls.DefaultSslContextFactory; +import io.vertx.core.spi.tls.SslContextFactory; /** * Configures a {@link TCPSSLOptions} to use OpenSsl. @@ -86,4 +89,9 @@ public JsonObject toJson() { public OpenSSLEngineOptions copy() { return new OpenSSLEngineOptions(this); } + + @Override + public SslContextFactory sslContextFactory() { + return new DefaultSslContextFactory(SslProvider.OPENSSL, sessionCacheEnabled); + } } diff --git a/src/main/java/io/vertx/core/net/SSLEngineOptions.java b/src/main/java/io/vertx/core/net/SSLEngineOptions.java index adde61d6ff8..34bad418226 100644 --- a/src/main/java/io/vertx/core/net/SSLEngineOptions.java +++ b/src/main/java/io/vertx/core/net/SSLEngineOptions.java @@ -11,6 +11,8 @@ package io.vertx.core.net; +import io.vertx.core.spi.tls.SslContextFactory; + /** * The SSL engine implementation to use in a Vert.x server or client. * @@ -20,4 +22,9 @@ public abstract class SSLEngineOptions { public abstract SSLEngineOptions copy(); + /** + * @return a {@link SslContextFactory} that will be used to produce the Netty {@code SslContext} + */ + public abstract SslContextFactory sslContextFactory(); + } diff --git a/src/main/java/io/vertx/core/net/impl/ChannelProvider.java b/src/main/java/io/vertx/core/net/impl/ChannelProvider.java index a0aa7b02395..7aca6f0c32f 100644 --- a/src/main/java/io/vertx/core/net/impl/ChannelProvider.java +++ b/src/main/java/io/vertx/core/net/impl/ChannelProvider.java @@ -12,8 +12,10 @@ package io.vertx.core.net.impl; import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBufAllocator; import io.netty.channel.*; import io.netty.handler.proxy.*; +import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.resolver.NoopAddressResolverGroup; @@ -107,8 +109,7 @@ private void connect(Handler handler, SocketAddress remoteAddress, Sock private void initSSL(Handler handler, SocketAddress peerAddress, String serverName, boolean ssl, boolean useAlpn, Channel ch, Promise channelHandler) { if (ssl) { - SslHandler sslHandler = new SslHandler(sslHelper.createEngine(context.owner(), peerAddress, serverName, useAlpn)); - sslHandler.setHandshakeTimeout(sslHelper.getSslHandshakeTimeout(), sslHelper.getSslHandshakeTimeoutUnit()); + SslHandler sslHandler = sslHelper.createSslHandler(context.owner(), peerAddress, serverName, useAlpn); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("ssl", sslHandler); pipeline.addLast(new ChannelInboundHandlerAdapter() { diff --git a/src/main/java/io/vertx/core/net/impl/NetClientBuilder.java b/src/main/java/io/vertx/core/net/impl/NetClientBuilder.java new file mode 100644 index 00000000000..9a2fe3ab18e --- /dev/null +++ b/src/main/java/io/vertx/core/net/impl/NetClientBuilder.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2011-2022 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.net.impl; + +import io.vertx.core.impl.CloseFuture; +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.net.NetClient; +import io.vertx.core.net.NetClientOptions; +import io.vertx.core.spi.metrics.TCPMetrics; + +/** + * A builder to configure NetClient plugins. + */ +public class NetClientBuilder { + + private VertxInternal vertx; + private CloseFuture closeFuture; + private NetClientOptions options; + private TCPMetrics metrics; + + public NetClientBuilder(VertxInternal vertx, NetClientOptions options) { + this.vertx = vertx; + this.options = options; + } + + public NetClientBuilder closeFuture(CloseFuture closeFuture) { + this.closeFuture = closeFuture; + return this; + } + + public NetClientBuilder metrics(TCPMetrics metrics) { + this.metrics = metrics; + return this; + } + + public NetClient build() { + NetClientImpl client = new NetClientImpl(vertx, metrics, options, closeFuture); + closeFuture.add(client); + return client; + } +} diff --git a/src/main/java/io/vertx/core/net/impl/NetClientImpl.java b/src/main/java/io/vertx/core/net/impl/NetClientImpl.java index 9c1b07cf77c..37bb6acb3bf 100644 --- a/src/main/java/io/vertx/core/net/impl/NetClientImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetClientImpl.java @@ -44,7 +44,6 @@ import io.vertx.core.spi.metrics.MetricsProvider; import io.vertx.core.spi.metrics.TCPMetrics; -import javax.net.ssl.SSLContext; import java.io.FileNotFoundException; import java.net.ConnectException; import java.util.Objects; @@ -74,15 +73,11 @@ public class NetClientImpl implements MetricsProvider, NetClient, Closeable { private final CloseFuture closeFuture; private final Predicate proxyFilter; - public NetClientImpl(VertxInternal vertx, NetClientOptions options, CloseFuture closeFuture) { - this(vertx, vertx.metricsSPI() != null ? vertx.metricsSPI().createNetClientMetrics(options) : null, options, closeFuture); - } - public NetClientImpl(VertxInternal vertx, TCPMetrics metrics, NetClientOptions options, CloseFuture closeFuture) { this.vertx = vertx; this.channelGroup = new DefaultChannelGroup(vertx.getAcceptorEventLoopGroup().next()); this.options = new NetClientOptions(options); - this.sslHelper = new SSLHelper(options, options.getKeyCertOptions(), options.getTrustOptions()).setApplicationProtocols(options.getApplicationLayerProtocols()); + this.sslHelper = new SSLHelper(options, options.getApplicationLayerProtocols()); this.metrics = metrics; this.logEnabled = options.getLogActivity(); this.idleTimeout = options.getIdleTimeout(); @@ -91,8 +86,6 @@ public NetClientImpl(VertxInternal vertx, TCPMetrics metrics, NetClientOptions o this.idleTimeoutUnit = options.getIdleTimeoutUnit(); this.closeFuture = closeFuture; this.proxyFilter = options.getNonProxyHosts() != null ? ProxyFilter.nonProxyHosts(options.getNonProxyHosts()) : ProxyFilter.DEFAULT_PROXY_FILTER; - - sslHelper.validate(vertx); } protected void initChannel(ChannelPipeline pipeline) { @@ -177,13 +170,6 @@ public boolean isMetricsEnabled() { return metrics != null; } - /** - * Must be called before calling connect(). - */ - public void setSuppliedSSLContext(SSLContext suppliedSSLContext) { - sslHelper.setSuppliedSslContext(suppliedSSLContext); - } - @Override public Metrics getMetrics() { return metrics; @@ -225,7 +211,41 @@ private void connect(SocketAddress remoteAddress, String serverName, Promise connectHandler, + ContextInternal context, + int remainingAttempts) { + checkClosed(); + sslHelper.init(context).onComplete(ar -> { + if (ar.succeeded()) { + connectInternal2(proxyOptions, remoteAddress, peerAddress, serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts); + } else { + connectHandler.fail(ar.cause()); + } + }); + } + + private void connectInternal2(ProxyOptions proxyOptions, SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, @@ -235,8 +255,6 @@ public void connectInternal(ProxyOptions proxyOptions, Promise connectHandler, ContextInternal context, int remainingAttempts) { - checkClosed(); - EventLoop eventLoop = context.nettyEventLoop(); if (eventLoop.inEventLoop()) { @@ -272,7 +290,7 @@ public void connectInternal(ProxyOptions proxyOptions, } }); } else { - eventLoop.execute(() -> connectInternal(proxyOptions, remoteAddress, peerAddress, serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts)); + eventLoop.execute(() -> connectInternal2(proxyOptions, remoteAddress, peerAddress, serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts)); } } diff --git a/src/main/java/io/vertx/core/net/impl/NetServerImpl.java b/src/main/java/io/vertx/core/net/impl/NetServerImpl.java index 4a49d79ebb6..38503082d0f 100644 --- a/src/main/java/io/vertx/core/net/impl/NetServerImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetServerImpl.java @@ -11,12 +11,14 @@ package io.vertx.core.net.impl; +import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SniHandler; +import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.timeout.IdleStateHandler; @@ -252,14 +254,7 @@ public void handle(Channel ch) { private void configurePipeline(Channel ch) { if (sslHelper.isSSL()) { - if (options.isSni()) { - SniHandler sniHandler = new SniHandler(sslHelper.serverNameMapper(vertx)); - ch.pipeline().addLast("ssl", sniHandler); - } else { - SslHandler sslHandler = new SslHandler(sslHelper.createEngine(vertx)); - sslHandler.setHandshakeTimeout(sslHelper.getSslHandshakeTimeout(), sslHelper.getSslHandshakeTimeoutUnit()); - ch.pipeline().addLast("ssl", sslHandler); - } + ch.pipeline().addLast("ssl", sslHelper.createHandler(vertx)); ChannelPromise p = ch.newPromise(); ch.pipeline().addLast("handshaker", new SslHandshakeCompletionHandler(p)); p.addListener(future -> { diff --git a/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java b/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java index 989686d7d9c..71e80c2da9f 100644 --- a/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java @@ -12,9 +12,11 @@ package io.vertx.core.net.impl; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.handler.ssl.SniHandler; +import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCounted; @@ -337,7 +339,7 @@ public NetSocket upgradeToSsl(Handler> handler) { @Override public NetSocket upgradeToSsl(String serverName, Handler> handler) { - ChannelOutboundHandler sslHandler = (ChannelOutboundHandler) chctx.pipeline().get("ssl"); + ChannelHandler sslHandler = chctx.pipeline().get("ssl"); if (sslHandler == null) { ChannelPromise p = chctx.newPromise(); chctx.pipeline().addFirst("handshaker", new SslHandshakeCompletionHandler(p)); @@ -353,15 +355,9 @@ public NetSocket upgradeToSsl(String serverName, Handler> hand } }); if (remoteAddress != null) { - sslHandler = new SslHandler(helper.createEngine(vertx, remoteAddress, serverName, false)); - ((SslHandler) sslHandler).setHandshakeTimeout(helper.getSslHandshakeTimeout(), helper.getSslHandshakeTimeoutUnit()); + sslHandler = helper.createSslHandler(vertx, remoteAddress, serverName, false); } else { - if (helper.isSNI()) { - sslHandler = new SniHandler(helper.serverNameMapper(vertx)); - } else { - sslHandler = new SslHandler(helper.createEngine(vertx)); - ((SslHandler) sslHandler).setHandshakeTimeout(helper.getSslHandshakeTimeout(), helper.getSslHandshakeTimeoutUnit()); - } + sslHandler = helper.createHandler(vertx); } chctx.pipeline().addFirst("ssl", sslHandler); } diff --git a/src/main/java/io/vertx/core/net/impl/SSLHelper.java b/src/main/java/io/vertx/core/net/impl/SSLHelper.java index bdb8f6cd433..8935955c050 100755 --- a/src/main/java/io/vertx/core/net/impl/SSLHelper.java +++ b/src/main/java/io/vertx/core/net/impl/SSLHelper.java @@ -12,13 +12,21 @@ package io.vertx.core.net.impl; import io.netty.buffer.ByteBufAllocator; -import io.netty.handler.ssl.*; +import io.netty.channel.ChannelHandler; +import io.netty.handler.ssl.DelegatingSslContext; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.SniHandler; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslHandler; import io.netty.util.Mapping; +import io.vertx.core.Future; +import io.vertx.core.Promise; import io.vertx.core.VertxException; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.ClientAuth; -import io.vertx.core.http.HttpClientOptions; +import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.VertxInternal; +import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; import io.vertx.core.net.ClientOptionsBase; @@ -31,11 +39,12 @@ import io.vertx.core.net.SocketAddress; import io.vertx.core.net.TCPSSLOptions; import io.vertx.core.net.TrustOptions; +import io.vertx.core.spi.tls.SslContextFactory; import javax.net.ssl.*; import java.io.ByteArrayInputStream; +import java.security.KeyStore; import java.security.cert.CRL; -import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -43,24 +52,30 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; /** - * - * This is a pretty sucky class - could do with a refactoring - * * @author Tim Fox + * @author Julien Viet */ public class SSLHelper { + private static final EnumMap CLIENT_AUTH_MAPPING = new EnumMap<>(ClientAuth.class); + + static { + CLIENT_AUTH_MAPPING.put(ClientAuth.REQUIRED, io.netty.handler.ssl.ClientAuth.REQUIRE); + CLIENT_AUTH_MAPPING.put(ClientAuth.REQUEST, io.netty.handler.ssl.ClientAuth.OPTIONAL); + CLIENT_AUTH_MAPPING.put(ClientAuth.NONE, io.netty.handler.ssl.ClientAuth.NONE); + } + /** * Resolve the ssl engine options to use for properly running the configured options. */ - public static SSLEngineOptions resolveEngineOptions(TCPSSLOptions options) { - SSLEngineOptions engineOptions = options.getSslEngineOptions(); + public static SSLEngineOptions resolveEngineOptions(SSLEngineOptions engineOptions, boolean useAlpn) { if (engineOptions == null) { - if (options.isUseAlpn()) { + if (useAlpn) { if (JdkSSLEngineOptions.isAlpnAvailable()) { engineOptions = new JdkSSLEngineOptions(); } else if (OpenSSLEngineOptions.isAlpnAvailable()) { @@ -81,7 +96,7 @@ public static SSLEngineOptions resolveEngineOptions(TCPSSLOptions options) { } } - if (options.isUseAlpn()) { + if (useAlpn) { if (engineOptions instanceof JdkSSLEngineOptions) { if (!JdkSSLEngineOptions.isAlpnAvailable()) { throw new VertxException("ALPN not available for JDK SSL/TLS engine"); @@ -98,216 +113,255 @@ public static SSLEngineOptions resolveEngineOptions(TCPSSLOptions options) { private static final Logger log = LoggerFactory.getLogger(SSLHelper.class); - private boolean ssl; - private boolean sni; - private long sslHandshakeTimeout; - private TimeUnit sslHandshakeTimeoutUnit; - private KeyCertOptions keyCertOptions; - private TrustOptions trustOptions; - private boolean trustAll; - private ArrayList crlPaths; - private ArrayList crlValues; - private ClientAuth clientAuth = ClientAuth.NONE; - private Set enabledCipherSuites; - private boolean openSsl; - private boolean client; - private boolean useAlpn; - private List applicationProtocols; - private Set enabledProtocols; - - private String endpointIdentificationAlgorithm = ""; - + private final boolean ssl; + private final boolean sni; + private final long sslHandshakeTimeout; + private final TimeUnit sslHandshakeTimeoutUnit; + private final boolean trustAll; + private final ClientAuth clientAuth; + private final boolean client; + private final boolean useAlpn; + private final Set enabledProtocols; + private final String endpointIdentificationAlgorithm; + private final SSLEngineOptions sslEngineOptions; + private final KeyCertOptions keyCertOptions; + private final TrustOptions trustOptions; + private final ArrayList crlPaths; + private final ArrayList crlValues; + private final Set enabledCipherSuites; + private final List applicationProtocols; + + private Future> sslProvider; private SslContext[] sslContexts = new SslContext[2]; - private Map[] sslContextMaps = new Map[] { + private Map[] sslContextMaps = new Map[] { new ConcurrentHashMap<>(), new ConcurrentHashMap<>() }; - private boolean openSslSessionCacheEnabled = true; - private volatile SSLContext suppliedSslContext; - private SSLHelper(TCPSSLOptions options, KeyCertOptions keyCertOptions, TrustOptions trustOptions) { - SSLEngineOptions sslEngineOptions = resolveEngineOptions(options); + public SSLHelper(TCPSSLOptions options, List applicationProtocols) { + this.sslEngineOptions = options.getSslEngineOptions(); + this.crlPaths = new ArrayList<>(options.getCrlPaths()); + this.crlValues = new ArrayList<>(options.getCrlValues()); + this.enabledCipherSuites = new HashSet<>(options.getEnabledCipherSuites()); this.ssl = options.isSsl(); this.sslHandshakeTimeout = options.getSslHandshakeTimeout(); this.sslHandshakeTimeoutUnit = options.getSslHandshakeTimeoutUnit(); - this.keyCertOptions = keyCertOptions; - this.trustOptions = trustOptions; - this.crlPaths = new ArrayList<>(options.getCrlPaths()); - this.crlValues = new ArrayList<>(options.getCrlValues()); - this.enabledCipherSuites = options.getEnabledCipherSuites(); - this.openSsl = sslEngineOptions instanceof OpenSSLEngineOptions; this.useAlpn = options.isUseAlpn(); this.enabledProtocols = options.getEnabledSecureTransportProtocols(); - this.openSslSessionCacheEnabled = (sslEngineOptions instanceof OpenSSLEngineOptions) && ((OpenSSLEngineOptions) sslEngineOptions).isSessionCacheEnabled(); - } - - private SSLHelper(ClientOptionsBase options, KeyCertOptions keyCertOptions, TrustOptions trustOptions) { - this((TCPSSLOptions) options, keyCertOptions, trustOptions); - this.client = true; - this.trustAll = options.isTrustAll(); + this.client = options instanceof ClientOptionsBase; + this.trustAll = options instanceof ClientOptionsBase && ((ClientOptionsBase)options).isTrustAll(); + this.keyCertOptions = options.getKeyCertOptions() != null ? options.getKeyCertOptions().copy() : null; + this.trustOptions = options.getTrustOptions() != null ? options.getTrustOptions().copy() : null; + this.clientAuth = options instanceof NetServerOptions ? ((NetServerOptions)options).getClientAuth() : ClientAuth.NONE; + this.endpointIdentificationAlgorithm = options instanceof NetClientOptions ? ((NetClientOptions)options).getHostnameVerificationAlgorithm() : ""; + this.sni = options instanceof NetServerOptions && ((NetServerOptions) options).isSni(); + this.applicationProtocols = applicationProtocols; } - public SSLHelper(HttpClientOptions options, KeyCertOptions keyCertOptions, TrustOptions trustOptions) { - this((ClientOptionsBase) options, keyCertOptions, trustOptions); + public boolean isSSL() { + return ssl; } - public SSLHelper(NetClientOptions options, KeyCertOptions keyCertOptions, TrustOptions trustOptions) { - this((ClientOptionsBase) options, keyCertOptions, trustOptions); - this.endpointIdentificationAlgorithm = options.getHostnameVerificationAlgorithm(); + public boolean isSNI() { + return sni; } - public SSLHelper(NetServerOptions options, KeyCertOptions keyCertOptions, TrustOptions trustOptions) { - this((TCPSSLOptions) options, keyCertOptions, trustOptions); - this.clientAuth = options.getClientAuth(); - this.client = false; - this.sni = options.isSni(); + private void configureEngine(SSLEngine engine, String serverName) { + Set protocols = new LinkedHashSet<>(enabledProtocols); + protocols.retainAll(Arrays.asList(engine.getSupportedProtocols())); + if (protocols.isEmpty()) { + log.warn("no SSL/TLS protocols are enabled due to configuration restrictions"); + } + engine.setEnabledProtocols(protocols.toArray(new String[protocols.size()])); + if (client && !endpointIdentificationAlgorithm.isEmpty()) { + SSLParameters sslParameters = engine.getSSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm); + engine.setSSLParameters(sslParameters); + } + if (serverName != null) { + SSLParameters sslParameters = engine.getSSLParameters(); + sslParameters.setServerNames(Collections.singletonList(new SNIHostName(serverName))); + engine.setSSLParameters(sslParameters); + } } /** - * Copy constructor, only configuration field are copied. + * Initialize the helper, this loads and validates the configuration. + * + * @param ctx the context + * @return a future resolved when the helper is initialized */ - public SSLHelper(SSLHelper that) { - this.ssl = that.ssl; - this.sni = that.sni; - this.sslHandshakeTimeout = that.sslHandshakeTimeout; - this.sslHandshakeTimeoutUnit = that.sslHandshakeTimeoutUnit; - this.keyCertOptions = that.keyCertOptions; - this.trustOptions = that.trustOptions; - this.trustAll = that.trustAll; - this.crlPaths = that.crlPaths; - this.crlValues = that.crlValues; - this.clientAuth = that.clientAuth; - this.enabledCipherSuites = that.enabledCipherSuites; - this.openSsl = that.openSsl; - this.client = that.client; - this.useAlpn = that.useAlpn; - this.applicationProtocols = that.applicationProtocols; - this.enabledProtocols = that.enabledProtocols; - this.endpointIdentificationAlgorithm = that.endpointIdentificationAlgorithm; - this.openSslSessionCacheEnabled = that.openSslSessionCacheEnabled; - this.suppliedSslContext = that.suppliedSslContext; + public synchronized Future init(ContextInternal ctx) { + Future> fut = sslProvider; + if (fut == null) { + if (keyCertOptions != null || trustOptions != null || trustAll || ssl) { + Promise> promise = Promise.promise(); + fut = promise.future(); + ctx.executeBlockingInternal(p -> { + KeyManagerFactory kmf; + try { + getTrustMgrFactory(ctx.owner(), null, false); + kmf = getKeyMgrFactory(ctx.owner()); + } catch (Exception e) { + p.fail(e); + return; + } + if (client || kmf != null) { + p.complete(); + } else { + p.fail("Key/certificate is mandatory for SSL"); + } + }).compose(v2 -> ctx.>executeBlockingInternal(p -> { + Supplier sslProvider; + try { + SSLEngineOptions resolvedEngineOptions = resolveEngineOptions(sslEngineOptions, useAlpn); + sslProvider = resolvedEngineOptions::sslContextFactory; + } catch (Exception e) { + p.fail(e); + return; + } + p.complete(sslProvider); + })).onComplete(promise); + } else { + fut = Future.succeededFuture(); + } + sslProvider = fut; + } + PromiseInternal promise = ctx.promise(); + fut.mapEmpty().onComplete(promise); + return promise.future(); + } + + public Mapping serverNameMapper(VertxInternal vertx) { + return serverName -> { + SslContext ctx = createContext(vertx, serverName, useAlpn, client, trustAll); + if (ctx != null) { + ctx = new DelegatingSslContext(ctx) { + @Override + protected void initEngine(SSLEngine engine) { + configureEngine(engine, serverName); + } + }; + } + return ctx; + }; } - public boolean isUseAlpn() { - return useAlpn; + public SSLEngine createEngine(VertxInternal vertx) { + SSLEngine engine = createContext(vertx).newEngine(ByteBufAllocator.DEFAULT); + configureEngine(engine, null); + return engine; } - public SSLHelper setUseAlpn(boolean useAlpn) { - this.useAlpn = useAlpn; - return this; + public SslContext createContext(VertxInternal vertx) { + return createContext(vertx, null, useAlpn, client, trustAll); } - public boolean isSSL() { - return ssl; + public SslContext createContext(VertxInternal vertx, String serverName, boolean useAlpn, boolean client, boolean trustAll) { + int idx = useAlpn ? 0 : 1; + if (serverName == null) { + if (sslContexts[idx] == null) { + sslContexts[idx] = createContext2(vertx, serverName, useAlpn, client, trustAll); + } + return sslContexts[idx]; + } else { + return sslContextMaps[idx].computeIfAbsent(serverName, s -> createContext2(vertx, serverName, useAlpn, client, trustAll)); + } } - public boolean isSNI() { - return sni; + public SslContext sslContext(VertxInternal vertx, String serverName, boolean useAlpn) { + SslContext context = createContext(vertx, null, useAlpn, client, trustAll); + return new DelegatingSslContext(context) { + @Override + protected void initEngine(SSLEngine engine) { + configureEngine(engine, serverName); + } + }; } - public long getSslHandshakeTimeout() { - return sslHandshakeTimeout; + private SslContext createContext2(VertxInternal vertx, String serverName, boolean useAlpn, boolean client, boolean trustAll) { + try { + TrustManagerFactory tmf = getTrustMgrFactory(vertx, serverName, trustAll); + KeyManagerFactory kmf = getKeyMgrFactory(vertx, serverName); + SslContextFactory factory = sslProvider.result().get() + .useAlpn(useAlpn) + .forClient(client) + .enabledCipherSuites(enabledCipherSuites) + .applicationProtocols(applicationProtocols); + if (!client) { + factory.clientAuth(CLIENT_AUTH_MAPPING.get(clientAuth)); + } + if (kmf != null) { + factory.keyMananagerFactory(kmf); + } + if (tmf != null) { + factory.trustManagerFactory(tmf); + } + if (serverName != null) { + factory.serverName(serverName); + } + return factory.create(); + } catch (Exception e) { + throw new VertxException(e); + } } - public TimeUnit getSslHandshakeTimeoutUnit() { - return sslHandshakeTimeoutUnit; + public SslHandler createSslHandler(VertxInternal vertx, String serverName) { + return createSslHandler(vertx, null, serverName); } - public ClientAuth getClientAuth() { - return clientAuth; + public SslHandler createSslHandler(VertxInternal vertx, SocketAddress remoteAddress, String serverName) { + return createSslHandler(vertx, remoteAddress, serverName, useAlpn); } - public List getApplicationProtocols() { - return applicationProtocols; + public SslHandler createSslHandler(VertxInternal vertx, SocketAddress remoteAddress, String serverName, boolean useAlpn) { + SslContext sslContext = sslContext(vertx, serverName, useAlpn); + SslHandler sslHandler; + if (remoteAddress == null || remoteAddress.isDomainSocket()) { + sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT); + } else { + sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, remoteAddress.host(), remoteAddress.port()); + } + sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); + return sslHandler; } - public SSLHelper setApplicationProtocols(List applicationProtocols) { - this.applicationProtocols = applicationProtocols; - return this; + public SniHandler createSniHandler(VertxInternal vertx) { + return new SniHandler(serverNameMapper(vertx)); } - /** - * Must be called before createEngine() - */ - public void setSuppliedSslContext(SSLContext suppliedSslContext) { - if (this.suppliedSslContext != null) { - throw new IllegalArgumentException("suppliedSslContext already set"); + public ChannelHandler createHandler(VertxInternal vertx) { + if (sni) { + return createSniHandler(vertx); + } else { + return createSslHandler(vertx, null); } - Objects.requireNonNull(suppliedSslContext, "suppliedSslContext should not be null"); - this.suppliedSslContext = suppliedSslContext; } - /* - If you don't specify a trust store, and you haven't set system properties, the system will try to use either a file - called jsssecacerts or cacerts in the JDK/JRE security directory. - You can override this by specifying the javax.echo.ssl.trustStore system property - - If you don't specify a key store, and don't specify a system property no key store will be used - You can override this by specifying the javax.echo.ssl.keyStore system property - */ - private SslContext createContext(VertxInternal vertx, boolean useAlpn, X509KeyManager mgr, TrustManagerFactory trustMgrFactory) { - try { - SslContextBuilder builder; - if (client) { - builder = SslContextBuilder.forClient(); - KeyManagerFactory keyMgrFactory = getKeyMgrFactory(vertx); - if (keyMgrFactory != null) { - builder.keyManager(keyMgrFactory); - } - } else { - if (mgr != null) { - builder = SslContextBuilder.forServer(mgr.getPrivateKey(null), null, mgr.getCertificateChain(null)); - } else { - KeyManagerFactory keyMgrFactory = getKeyMgrFactory(vertx); - if (keyMgrFactory == null) { - throw new VertxException("Key/certificate is mandatory for SSL"); - } - builder = SslContextBuilder.forServer(keyMgrFactory); - } - } - Collection cipherSuites = enabledCipherSuites; - if (openSsl) { - builder.sslProvider(SslProvider.OPENSSL); - if (cipherSuites == null || cipherSuites.isEmpty()) { - cipherSuites = OpenSsl.availableOpenSslCipherSuites(); - } - } else { - builder.sslProvider(SslProvider.JDK); - if (cipherSuites == null || cipherSuites.isEmpty()) { - cipherSuites = DefaultJDKCipherSuite.get(); - } - } - if (trustMgrFactory != null) { - builder.trustManager(trustMgrFactory); - } - if (cipherSuites != null && cipherSuites.size() > 0) { - builder.ciphers(cipherSuites); - } - if (useAlpn && applicationProtocols != null && applicationProtocols.size() > 0) { - builder.applicationProtocolConfig(new ApplicationProtocolConfig( - ApplicationProtocolConfig.Protocol.ALPN, - ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, - ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, - applicationProtocols - )); - } - SslContext ctx = builder.build(); - if (ctx instanceof OpenSslServerContext){ - SSLSessionContext sslSessionContext = ctx.sessionContext(); - if (sslSessionContext instanceof OpenSslServerSessionContext){ - ((OpenSslServerSessionContext)sslSessionContext).setSessionCacheEnabled(openSslSessionCacheEnabled); - } + private KeyManagerFactory getKeyMgrFactory(VertxInternal vertx, String serverName) throws Exception { + KeyManagerFactory kmf = null; + if (serverName != null) { + X509KeyManager mgr = keyCertOptions.keyManagerMapper(vertx).apply(serverName); + if (mgr != null) { + String keyStoreType = KeyStore.getDefaultType(); + KeyStore ks = KeyStore.getInstance(keyStoreType); + ks.load(null, null); + ks.setKeyEntry("key", mgr.getPrivateKey(null), new char[0], mgr.getCertificateChain(null)); + String keyAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); + kmf = KeyManagerFactory.getInstance(keyAlgorithm); + kmf.init(ks, new char[0]); } - return ctx; - } catch (Exception e) { - throw new VertxException(e); } + if (kmf == null) { + kmf = getKeyMgrFactory(vertx); + } + return kmf; } private KeyManagerFactory getKeyMgrFactory(VertxInternal vertx) throws Exception { return keyCertOptions == null ? null : keyCertOptions.getKeyManagerFactory(vertx); } - private TrustManagerFactory getTrustMgrFactory(VertxInternal vertx, String serverName) throws Exception { + private TrustManagerFactory getTrustMgrFactory(VertxInternal vertx, String serverName, boolean trustAll) throws Exception { TrustManager[] mgrs = null; if (trustAll) { mgrs = new TrustManager[]{createTrustAllTrustManager()}; @@ -335,9 +389,9 @@ private TrustManagerFactory getTrustMgrFactory(VertxInternal vertx, String serve } if (crlPaths != null && crlValues != null && (crlPaths.size() > 0 || crlValues.size() > 0)) { Stream tmp = crlPaths. - stream(). - map(path -> vertx.resolveFile(path).getAbsolutePath()). - map(vertx.fileSystem()::readFileBlocking); + stream(). + map(path -> vertx.resolveFile(path).getAbsolutePath()). + map(vertx.fileSystem()::readFileBlocking); tmp = Stream.concat(tmp, crlValues.stream()); CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509"); ArrayList crls = new ArrayList<>(); @@ -406,160 +460,4 @@ public X509Certificate[] getAcceptedIssuers() { } }; } - - public Mapping serverNameMapper(VertxInternal vertx) { - return serverName -> { - SslContext ctx = getContext(vertx, serverName); - if (ctx != null) { - ctx = new DelegatingSslContext(ctx) { - @Override - protected void initEngine(SSLEngine engine) { - configureEngine(engine, serverName); - } - }; - } - return ctx; - }; - } - - public void configureEngine(SSLEngine engine, String serverName) { - if (enabledCipherSuites != null && !enabledCipherSuites.isEmpty()) { - String[] toUse = enabledCipherSuites.toArray(new String[enabledCipherSuites.size()]); - engine.setEnabledCipherSuites(toUse); - } - engine.setUseClientMode(client); - Set protocols = new LinkedHashSet<>(enabledProtocols); - protocols.retainAll(Arrays.asList(engine.getSupportedProtocols())); - if (protocols.isEmpty()) { - log.warn("no SSL/TLS protocols are enabled due to configuration restrictions"); - } - engine.setEnabledProtocols(protocols.toArray(new String[protocols.size()])); - if (!client) { - switch (getClientAuth()) { - case REQUEST: { - engine.setWantClientAuth(true); - break; - } - case REQUIRED: { - engine.setNeedClientAuth(true); - break; - } - case NONE: { - engine.setNeedClientAuth(false); - break; - } - } - } else if (!endpointIdentificationAlgorithm.isEmpty()) { - SSLParameters sslParameters = engine.getSSLParameters(); - sslParameters.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm); - engine.setSSLParameters(sslParameters); - } - if (serverName != null) { - SSLParameters sslParameters = engine.getSSLParameters(); - sslParameters.setServerNames(Collections.singletonList(new SNIHostName(serverName))); - engine.setSSLParameters(sslParameters); - } - } - - public SslContext getContext(VertxInternal vertx) { - return getContext(vertx, null); - } - - public SslContext getContext(VertxInternal vertx, String serverName) { - return getContext(vertx, serverName, useAlpn); - } - - public SslContext getContext(VertxInternal vertx, String serverName, boolean useAlpn) { - int idx = useAlpn ? 0 : 1; - if (serverName == null) { - if (sslContexts[idx] == null) { - TrustManagerFactory trustMgrFactory; - try { - trustMgrFactory = getTrustMgrFactory(vertx, null); - } catch (Exception e) { - throw new VertxException(e); - } - sslContexts[idx] = createContext(vertx, useAlpn, null, trustMgrFactory); - } - return sslContexts[idx]; - } else { - X509KeyManager mgr; - try { - mgr = keyCertOptions.keyManagerMapper(vertx).apply(serverName); - } catch (Exception e) { - throw new RuntimeException(e); - } - if (mgr == null) { - return sslContexts[idx]; - } - try { - TrustManagerFactory trustMgrFactory = getTrustMgrFactory(vertx, serverName); - return sslContextMaps[idx].computeIfAbsent(mgr.getCertificateChain(null)[0], s -> createContext(vertx, useAlpn, mgr, trustMgrFactory)); - } catch (Exception e) { - throw new VertxException(e); - } - } - } - - // This is called to validate some of the SSL params as that only happens when the context is created - public synchronized void validate(VertxInternal vertx) { - if (ssl) { - getContext(vertx, null); - } - } - - public SSLEngine createEngine(SslContext sslContext) { - SSLEngine engine = sslContext.newEngine(ByteBufAllocator.DEFAULT); - configureEngine(engine, null); - return engine; - } - - public SSLEngine createEngine(VertxInternal vertx, SocketAddress socketAddress, String serverName, boolean useAlpn) { - if (suppliedSslContext == null) { - SslContext context = getContext(vertx, null, useAlpn); - SSLEngine engine; - if (socketAddress.isDomainSocket()) { - engine = context.newEngine(ByteBufAllocator.DEFAULT); - } else { - engine = context.newEngine(ByteBufAllocator.DEFAULT, socketAddress.host(), socketAddress.port()); - } - configureEngine(engine, serverName); - return engine; - } else { - if (useAlpn) { - throw new IllegalStateException("Can not use useAlpn=true together with a supplied SSLContext"); - } - if (!client) { - throw new IllegalStateException("Can only use a supplied SSLContext with clients"); - } - SSLEngine engine; - if (socketAddress.isDomainSocket()) { - engine = suppliedSslContext.createSSLEngine(); - } else { - engine = suppliedSslContext.createSSLEngine(socketAddress.host(), socketAddress.port()); - } - - configureEngine(engine, serverName); - return engine; - - } - } - - public SSLEngine createEngine(VertxInternal vertx, String host, int port, boolean forceSNI) { - SSLEngine engine = getContext(vertx, null).newEngine(ByteBufAllocator.DEFAULT, host, port); - configureEngine(engine, forceSNI ? host : null); - return engine; - } - - public SSLEngine createEngine(VertxInternal vertx, String host, int port) { - SSLEngine engine = getContext(vertx, null).newEngine(ByteBufAllocator.DEFAULT, host, port); - configureEngine(engine, null); - return engine; - } - - public SSLEngine createEngine(VertxInternal vertx) { - SSLEngine engine = getContext(vertx, null).newEngine(ByteBufAllocator.DEFAULT); - configureEngine(engine, null); - return engine; - } } diff --git a/src/main/java/io/vertx/core/net/impl/TCPServerBase.java b/src/main/java/io/vertx/core/net/impl/TCPServerBase.java index 44dac3704dc..7848e9e0dbe 100644 --- a/src/main/java/io/vertx/core/net/impl/TCPServerBase.java +++ b/src/main/java/io/vertx/core/net/impl/TCPServerBase.java @@ -18,9 +18,7 @@ import io.netty.channel.ChannelOption; import io.netty.channel.EventLoop; import io.netty.util.concurrent.GenericFutureListener; -import io.vertx.core.AsyncResult; import io.vertx.core.Closeable; -import io.vertx.core.CompositeFuture; import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.Handler; @@ -37,12 +35,9 @@ import io.vertx.core.spi.metrics.TCPMetrics; import java.net.InetSocketAddress; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; /** * Base class for TCP servers @@ -68,7 +63,7 @@ public abstract class TCPServerBase implements Closeable, MetricsProvider { // Main private SSLHelper sslHelper; private ServerChannelLoadBalancer channelBalancer; - private io.netty.util.concurrent.Future bindFuture; + private Future bindFuture; private Set servers; private TCPMetrics metrics; private volatile int actualPort; @@ -87,7 +82,7 @@ public int actualPort() { protected abstract Handler childHandler(ContextInternal context, SocketAddress socketAddress, SSLHelper sslHelper); protected SSLHelper createSSLHelper() { - return new SSLHelper(options, options.getKeyCertOptions(), options.getTrustOptions()); + return new SSLHelper(options, null); } public synchronized SSLHelper sslHelper() { @@ -96,21 +91,10 @@ public synchronized SSLHelper sslHelper() { public Future bind(SocketAddress address) { ContextInternal listenContext = vertx.getOrCreateContext(); - - io.netty.util.concurrent.Future bindFuture = listen(address, listenContext); - - Promise promise = listenContext.promise(); - bindFuture.addListener(res -> { - if (res.isSuccess()) { - promise.complete(this); - } else { - promise.fail(res.cause()); - } - }); - return promise.future(); + return listen(address, listenContext).map(this); } - private synchronized io.netty.util.concurrent.Future listen(SocketAddress localAddress, ContextInternal context) { + private synchronized Future listen(SocketAddress localAddress, ContextInternal context) { if (listening) { throw new IllegalStateException("Listen already called"); } @@ -145,62 +129,78 @@ private synchronized io.netty.util.concurrent.Future listen(SocketAddre bindAddress = localAddress; } } + PromiseInternal promise = listenContext.promise(); if (main == null) { - try { - sslHelper = createSSLHelper(); - sslHelper.validate(vertx); - worker = childHandler(listenContext, localAddress, sslHelper); - servers = new HashSet<>(); - servers.add(this); - channelBalancer = new ServerChannelLoadBalancer(vertx.getAcceptorEventLoopGroup().next()); - channelBalancer.addWorker(eventLoop, worker); - - ServerBootstrap bootstrap = new ServerBootstrap(); - bootstrap.group(vertx.getAcceptorEventLoopGroup(), channelBalancer.workers()); - if (sslHelper.isSSL()) { - bootstrap.childOption(ChannelOption.ALLOCATOR, PartialPooledByteBufAllocator.INSTANCE); - } else { - bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); - } + // The first server binds the socket + actualServer = this; + bindFuture = promise; + sslHelper = createSSLHelper(); + worker = childHandler(listenContext, localAddress, sslHelper); + servers = new HashSet<>(); + servers.add(this); + channelBalancer = new ServerChannelLoadBalancer(vertx.getAcceptorEventLoopGroup().next()); - bootstrap.childHandler(channelBalancer); - applyConnectionOptions(localAddress.isDomainSocket(), bootstrap); - - bindFuture = AsyncResolveConnectHelper.doBind(vertx, bindAddress, bootstrap); - bindFuture.addListener((GenericFutureListener>) res -> { - if (res.isSuccess()) { - Channel ch = res.getNow(); - log.trace("Net server listening on " + hostOrPath + ":" + ch.localAddress()); - if (shared) { - ch.closeFuture().addListener((ChannelFutureListener) channelFuture -> { - synchronized (sharedNetServers) { - sharedNetServers.remove(id); - } - }); - } - // Update port to actual port when it is not a domain socket as wildcard port 0 might have been used - if (bindAddress.isInetSocket()) { - actualPort = ((InetSocketAddress)ch.localAddress()).getPort(); - } - listenContext.addCloseHook(this); - metrics = createMetrics(localAddress); + // Register the server in the shared server list + if (shared) { + sharedNetServers.put(id, this); + } + + // Initialize SSL before binding + sslHelper.init(listenContext).onComplete(ar -> { + if (ar.succeeded()) { + + // Socket bind + channelBalancer.addWorker(eventLoop, worker); + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(vertx.getAcceptorEventLoopGroup(), channelBalancer.workers()); + if (sslHelper.isSSL()) { + bootstrap.childOption(ChannelOption.ALLOCATOR, PartialPooledByteBufAllocator.INSTANCE); } else { - if (shared) { - synchronized (sharedNetServers) { - sharedNetServers.remove(id); + bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + } + + bootstrap.childHandler(channelBalancer); + applyConnectionOptions(localAddress.isDomainSocket(), bootstrap); + + // Actual bind + io.netty.util.concurrent.Future bindFuture = AsyncResolveConnectHelper.doBind(vertx, bindAddress, bootstrap); + bindFuture.addListener((GenericFutureListener>) res -> { + if (res.isSuccess()) { + Channel ch = res.getNow(); + log.trace("Net server listening on " + hostOrPath + ":" + ch.localAddress()); + if (shared) { + ch.closeFuture().addListener((ChannelFutureListener) channelFuture -> { + synchronized (sharedNetServers) { + sharedNetServers.remove(id); + } + }); + } + // Update port to actual port when it is not a domain socket as wildcard port 0 might have been used + if (bindAddress.isInetSocket()) { + actualPort = ((InetSocketAddress)ch.localAddress()).getPort(); } + listenContext.addCloseHook(this); + metrics = createMetrics(localAddress); + promise.complete(ch); + } else { + promise.fail(res.cause()); } - listening = false; + }); + } else { + promise.fail(ar.cause()); + } + }); + + bindFuture.onFailure(err -> { + if (shared) { + synchronized (sharedNetServers) { + sharedNetServers.remove(id); } - }); - } catch (Throwable t) { + } listening = false; - return vertx.getAcceptorEventLoopGroup().next().newFailedFuture(t); - } - if (shared) { - sharedNetServers.put(id, this); - } - actualServer = this; + }); + + return bindFuture; } else { // Server already exists with that host/port - we will use that actualServer = main; @@ -210,10 +210,10 @@ private synchronized io.netty.util.concurrent.Future listen(SocketAddre actualServer.servers.add(this); actualServer.channelBalancer.addWorker(eventLoop, worker); listenContext.addCloseHook(this); + main.bindFuture.onComplete(promise); + return promise.future(); } } - - return actualServer.bindFuture; } public boolean isListening() { @@ -268,9 +268,9 @@ public synchronized void close(Promise completion) { private void actualClose(Promise done) { channelBalancer.close(); - bindFuture.addListener((GenericFutureListener>) fut -> { - if (fut.isSuccess()) { - Channel channel = fut.getNow(); + bindFuture.onComplete(ar -> { + if (ar.succeeded()) { + Channel channel = ar.result(); ChannelFuture a = channel.close(); if (metrics != null) { a.addListener(cg -> metrics.close()); diff --git a/src/main/java/io/vertx/core/net/impl/DefaultJDKCipherSuite.java b/src/main/java/io/vertx/core/spi/tls/DefaultJDKCipherSuite.java similarity index 97% rename from src/main/java/io/vertx/core/net/impl/DefaultJDKCipherSuite.java rename to src/main/java/io/vertx/core/spi/tls/DefaultJDKCipherSuite.java index 2a87005a600..1e528304a9d 100644 --- a/src/main/java/io/vertx/core/net/impl/DefaultJDKCipherSuite.java +++ b/src/main/java/io/vertx/core/spi/tls/DefaultJDKCipherSuite.java @@ -8,7 +8,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.net.impl; +package io.vertx.core.spi.tls; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; diff --git a/src/main/java/io/vertx/core/spi/tls/DefaultSslContextFactory.java b/src/main/java/io/vertx/core/spi/tls/DefaultSslContextFactory.java new file mode 100755 index 00000000000..f71885b4f1d --- /dev/null +++ b/src/main/java/io/vertx/core/spi/tls/DefaultSslContextFactory.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.core.spi.tls; + +import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.OpenSslServerContext; +import io.netty.handler.ssl.OpenSslServerSessionContext; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.TrustManagerFactory; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * The default implementation of {@link SslContextFactory} that creates and configures a Netty {@link SslContext} using a + * {@link SslContextBuilder}. + * + * @author Tim Fox + * @author Julien Viet + */ +public class DefaultSslContextFactory implements SslContextFactory { + + private final SslProvider sslProvider; + private final boolean sslSessionCacheEnabled; + + public DefaultSslContextFactory(SslProvider sslProvider, + boolean sslSessionCacheEnabled) { + this.sslProvider = sslProvider; + this.sslSessionCacheEnabled = sslSessionCacheEnabled; + } + + private Set enabledCipherSuites; + private List applicationProtocols; + private boolean useAlpn; + private ClientAuth clientAuth; + private boolean forClient; + private KeyManagerFactory kmf; + private TrustManagerFactory tmf; + + @Override + public SslContextFactory useAlpn(boolean useAlpn) { + this.useAlpn = useAlpn; + return this; + } + + @Override + public SslContextFactory clientAuth(ClientAuth clientAuth) { + this.clientAuth = clientAuth; + return this; + } + + @Override + public SslContextFactory forClient(boolean forClient) { + this.forClient = forClient; + return this; + } + + @Override + public SslContextFactory keyMananagerFactory(KeyManagerFactory kmf) { + this.kmf = kmf; + return this; + } + + @Override + public SslContextFactory trustManagerFactory(TrustManagerFactory tmf) { + this.tmf = tmf; + return this; + } + + @Override + public SslContext create() throws SSLException { + return createContext(useAlpn, forClient, kmf, tmf); + } + + @Override + public SslContextFactory enabledCipherSuites(Set enabledCipherSuites) { + this.enabledCipherSuites = enabledCipherSuites; + return this; + } + + @Override + public SslContextFactory applicationProtocols(List applicationProtocols) { + this.applicationProtocols = applicationProtocols; + return this; + } + + /* + If you don't specify a trust store, and you haven't set system properties, the system will try to use either a file + called jsssecacerts or cacerts in the JDK/JRE security directory. + You can override this by specifying the javax.echo.ssl.trustStore system property + + If you don't specify a key store, and don't specify a system property no key store will be used + You can override this by specifying the javax.echo.ssl.keyStore system property + */ + private SslContext createContext(boolean useAlpn, boolean client, KeyManagerFactory kmf, TrustManagerFactory tmf) throws SSLException { + SslContextBuilder builder; + if (client) { + builder = SslContextBuilder.forClient(); + if (kmf != null) { + builder.keyManager(kmf); + } + } else { + builder = SslContextBuilder.forServer(kmf); + } + Collection cipherSuites = enabledCipherSuites; + switch (sslProvider) { + case OPENSSL: + builder.sslProvider(SslProvider.OPENSSL); + if (cipherSuites == null || cipherSuites.isEmpty()) { + cipherSuites = OpenSsl.availableOpenSslCipherSuites(); + } + break; + case JDK: + builder.sslProvider(SslProvider.JDK); + if (cipherSuites == null || cipherSuites.isEmpty()) { + cipherSuites = DefaultJDKCipherSuite.get(); + } + break; + default: + throw new UnsupportedOperationException(); + } + if (tmf != null) { + builder.trustManager(tmf); + } + if (cipherSuites != null && cipherSuites.size() > 0) { + builder.ciphers(cipherSuites); + } + if (useAlpn && applicationProtocols != null && applicationProtocols.size() > 0) { + builder.applicationProtocolConfig(new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + applicationProtocols + )); + } + if (clientAuth != null) { + builder.clientAuth(clientAuth); + } + SslContext ctx = builder.build(); + if (ctx instanceof OpenSslServerContext){ + SSLSessionContext sslSessionContext = ctx.sessionContext(); + if (sslSessionContext instanceof OpenSslServerSessionContext){ + ((OpenSslServerSessionContext)sslSessionContext).setSessionCacheEnabled(sslSessionCacheEnabled); + } + } + return ctx; + } +} diff --git a/src/main/java/io/vertx/core/spi/tls/SslContextFactory.java b/src/main/java/io/vertx/core/spi/tls/SslContextFactory.java new file mode 100644 index 00000000000..61201e9c12a --- /dev/null +++ b/src/main/java/io/vertx/core/spi/tls/SslContextFactory.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2011-2022 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.spi.tls; + +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.SslContext; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManagerFactory; +import java.util.List; +import java.util.Set; + +/** + * A factory for a Netty {@link SslContext}, the factory is configured with the fluent setters until {@link #create()} + * to obtain a properly configured {@link SslContext}. + * + * @author Julien Viet + */ +public interface SslContextFactory { + + /** + * Set whether to use ALPN. + * + * @param useAlpn {@code true} to use ALPN + * @return a reference to this, so the API can be used fluently + */ + default SslContextFactory useAlpn(boolean useAlpn) { + return this; + } + + /** + * Configures the client auth + * @param clientAuth the client auth to use + * @return a reference to this, so the API can be used fluently + */ + default SslContextFactory clientAuth(ClientAuth clientAuth) { + return this; + } + + /** + * Set whether to build a context for clients or for servers + * @param forClient {@code true} for client otherwise for servers + * @return a reference to this, so the API can be used fluently + */ + default SslContextFactory forClient(boolean forClient) { + return this; + } + + /** + * Set the key manager factory to use. + * @param kmf the key manager factory instance + * @return a reference to this, so the API can be used fluently + */ + default SslContextFactory keyMananagerFactory(KeyManagerFactory kmf) { + return this; + } + + /** + * Set the trust manager factory to use. + * @param tmf the trust manager factory instance + * @return a reference to this, so the API can be used fluently + */ + default SslContextFactory trustManagerFactory(TrustManagerFactory tmf) { + return this; + } + + /** + * Set the enabled cipher suites. + * @param enabledCipherSuites the set of cipher suites + * @return a reference to this, so the API can be used fluently + */ + default SslContextFactory enabledCipherSuites(Set enabledCipherSuites) { + return this; + } + + /** + * Set the application protocols to use when using ALPN. + * @param applicationProtocols this list of application protocols + * @return a reference to this, so the API can be used fluently + */ + default SslContextFactory applicationProtocols(List applicationProtocols) { + return this; + } + + /** + * Set the SNI server name. + * @param serverName the server name + * @return a reference to this, so the API can be used fluently + */ + default SslContextFactory serverName(String serverName) { + return this; + } + + /** + * @return a configured {@link SslContext} + */ + SslContext create() throws SSLException; + +} diff --git a/src/test/java/io/vertx/core/VertxTest.java b/src/test/java/io/vertx/core/VertxTest.java index 8b0925bfd52..f1be96a3fd1 100644 --- a/src/test/java/io/vertx/core/VertxTest.java +++ b/src/test/java/io/vertx/core/VertxTest.java @@ -20,6 +20,7 @@ import io.vertx.core.net.NetClient; import io.vertx.core.net.NetClientOptions; import io.vertx.core.net.NetSocket; +import io.vertx.core.net.impl.NetClientBuilder; import io.vertx.test.core.AsyncTestBase; import io.vertx.test.core.Repeat; import io.vertx.test.core.RepeatRule; @@ -197,7 +198,7 @@ public void testFinalizeNetClient() throws Exception { awaitLatch(latch); AtomicBoolean closed = new AtomicBoolean(); CloseFuture closeFuture = new CloseFuture(); - NetClient client = vertx.createNetClient(new NetClientOptions(), closeFuture); + NetClient client = new NetClientBuilder(vertx, new NetClientOptions()).closeFuture(closeFuture).build(); vertx.addCloseHook(closeFuture); closeFuture.future().onComplete(ar -> closed.set(true)); closeFuture = null; diff --git a/src/test/java/io/vertx/core/http/Http2ClientTest.java b/src/test/java/io/vertx/core/http/Http2ClientTest.java index 27cde9d4d66..5aee5ff4211 100644 --- a/src/test/java/io/vertx/core/http/Http2ClientTest.java +++ b/src/test/java/io/vertx/core/http/Http2ClientTest.java @@ -13,6 +13,7 @@ import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; @@ -24,8 +25,11 @@ import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.HttpServerUpgradeHandler; import io.netty.handler.codec.http2.*; +import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslHandler; import io.netty.util.AsciiString; import io.vertx.core.AsyncResult; @@ -36,12 +40,10 @@ import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.impl.HttpClientConnection; -import io.vertx.core.impl.VertxInternal; import io.vertx.core.net.NetServer; import io.vertx.core.net.NetSocket; import io.vertx.core.net.SocketAddress; import io.vertx.core.net.impl.ConnectionBase; -import io.vertx.core.net.impl.SSLHelper; import io.vertx.test.core.AsyncTestBase; import io.vertx.test.core.TestUtils; import io.vertx.test.tls.Cert; @@ -1151,8 +1153,16 @@ private ServerBootstrap createH2Server(BiFunction() { @Override protected void initChannel(Channel ch) throws Exception { - SSLHelper sslHelper = new SSLHelper(serverOptions, Cert.SERVER_JKS.get(), null); - SslHandler sslHandler = new SslHandler(sslHelper.setApplicationProtocols(Arrays.asList(HttpVersion.HTTP_2.alpnName(), HttpVersion.HTTP_1_1.alpnName())).createEngine((VertxInternal) vertx, DEFAULT_HTTPS_HOST, DEFAULT_HTTPS_PORT)); + SslContext sslContext = SslContextBuilder + .forServer(Cert.SERVER_JKS.get().getKeyManagerFactory(vertx)) + .applicationProtocolConfig(new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + HttpVersion.HTTP_2.alpnName(), HttpVersion.HTTP_1_1.alpnName() + )) + .build(); + SslHandler sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT); ch.pipeline().addLast(sslHandler); ch.pipeline().addLast(new ApplicationProtocolNegotiationHandler("whatever") { @Override diff --git a/src/test/java/io/vertx/core/http/Http2ServerTest.java b/src/test/java/io/vertx/core/http/Http2ServerTest.java index 9ae9f592db4..d74136967d8 100644 --- a/src/test/java/io/vertx/core/http/Http2ServerTest.java +++ b/src/test/java/io/vertx/core/http/Http2ServerTest.java @@ -13,6 +13,7 @@ import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; @@ -41,8 +42,11 @@ import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2Settings; import io.netty.handler.codec.http2.Http2Stream; +import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslHandler; import io.vertx.core.AsyncResult; import io.vertx.core.Context; @@ -55,8 +59,6 @@ import io.vertx.core.http.impl.Http1xOrH2CHandler; import io.vertx.core.http.impl.HttpUtils; import io.vertx.core.impl.Utils; -import io.vertx.core.impl.VertxInternal; -import io.vertx.core.net.impl.SSLHelper; import io.vertx.core.streams.ReadStream; import io.vertx.core.streams.WriteStream; import io.vertx.test.core.DetectFileDescriptorLeaks; @@ -213,8 +215,16 @@ protected ChannelInitializer channelInitializer(int port, String host, Consumer< return new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { - SSLHelper sslHelper = new SSLHelper(new HttpClientOptions().setUseAlpn(true).setSsl(true), null, Trust.SERVER_JKS.get()); - SslHandler sslHandler = new SslHandler(sslHelper.setApplicationProtocols(Arrays.asList(HttpVersion.HTTP_2.alpnName(), HttpVersion.HTTP_1_1.alpnName())).createEngine((VertxInternal) vertx, host, port)); + SslContext sslContext = SslContextBuilder + .forClient() + .applicationProtocolConfig(new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + HttpVersion.HTTP_2.alpnName(), HttpVersion.HTTP_1_1.alpnName() + )).trustManager(Trust.SERVER_JKS.get().getTrustManagerFactory(vertx)) + .build(); + SslHandler sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, host, port); ch.pipeline().addLast(sslHandler); ch.pipeline().addLast(new ApplicationProtocolNegotiationHandler("whatever") { @Override diff --git a/src/test/java/io/vertx/core/http/HttpTLSTest.java b/src/test/java/io/vertx/core/http/HttpTLSTest.java index c524477ae2f..a32bf0259f0 100755 --- a/src/test/java/io/vertx/core/http/HttpTLSTest.java +++ b/src/test/java/io/vertx/core/http/HttpTLSTest.java @@ -1487,9 +1487,9 @@ private void testStore(HttpServerOptions serverOptions, List expectedPos AtomicReference failure = new AtomicReference<>(); server.listen(onFailure(failure::set)); assertWaitUntil(() -> failure.get() != null); - Throwable cause = failure.get().getCause(); + Throwable cause = failure.get(); String exceptionMessage = cause.getMessage(); - if(expectedSuffix == null) { + if (expectedSuffix == null) { boolean ok = expectedPossiblePrefixes.isEmpty(); for (String expectedPossiblePrefix : expectedPossiblePrefixes) { ok |= expectedPossiblePrefix.equals(exceptionMessage); @@ -1510,18 +1510,17 @@ private void testStore(HttpServerOptions serverOptions, List expectedPos } @Test - public void testCrlInvalidPath() throws Exception { + public void testCrlInvalidPath() { HttpClientOptions clientOptions = new HttpClientOptions(); clientOptions.setTrustOptions(Trust.SERVER_PEM_ROOT_CA.get()); clientOptions.setSsl(true); clientOptions.addCrlPath("/invalid.pem"); - try { - vertx.createHttpClient(clientOptions); - fail("Was expecting a failure"); - } catch (VertxException e) { - assertNotNull(e.getCause()); - assertEquals(NoSuchFileException.class, e.getCause().getCause().getClass()); - } + HttpClient client = vertx.createHttpClient(clientOptions); + client.request(HttpMethod.GET, 9292, "localhost", "/", onFailure(err -> { + assertEquals(NoSuchFileException.class, TestUtils.rootCause(err).getClass()); + testComplete(); + })); + await(); } // Proxy tests diff --git a/src/test/java/io/vertx/core/net/NetTest.java b/src/test/java/io/vertx/core/net/NetTest.java index b031eda6e59..c96c8941528 100755 --- a/src/test/java/io/vertx/core/net/NetTest.java +++ b/src/test/java/io/vertx/core/net/NetTest.java @@ -21,6 +21,10 @@ import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.*; +import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.IdentityCipherSuiteFilter; +import io.netty.handler.ssl.JdkSslContext; +import io.netty.handler.ssl.SslContext; import io.netty.handler.timeout.IdleStateEvent; import io.netty.util.internal.PlatformDependent; import io.vertx.core.*; @@ -36,10 +40,10 @@ 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.NetClientImpl; 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.spi.tls.SslContextFactory; import io.vertx.core.streams.ReadStream; import io.vertx.test.core.CheckingSender; import io.vertx.test.core.TestUtils; @@ -65,7 +69,6 @@ import java.io.InputStream; import java.io.OutputStreamWriter; import java.net.InetSocketAddress; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -1036,7 +1039,7 @@ public void testServerCloseHandlersCloseFromServer() { await(); } - void serverCloseHandlers(boolean closeFromServer, Handler> listenHandler) { + void serverCloseHandlers(boolean closeFromServer, Handler listenHandler) { server.connectHandler((sock) -> { AtomicInteger counter = new AtomicInteger(0); sock.endHandler(v -> assertEquals(1, counter.incrementAndGet())); @@ -1047,7 +1050,7 @@ void serverCloseHandlers(boolean closeFromServer, Handler if (closeFromServer) { sock.close(); } - }).listen(testAddress, listenHandler); + }).listen(testAddress, onSuccess(listenHandler::handle)); } @Test @@ -1611,9 +1614,8 @@ public void testServerCertificateMultipleWrongAlias() throws Exception { .clientTrustAll(true); test.setupServer(true); server.listen(test.bindAddress, onFailure(t -> { - assertThat(t, is(instanceOf(VertxException.class))); - assertThat(t.getCause(), is(instanceOf(IllegalArgumentException.class))); - assertThat(t.getCause().getMessage(), containsString("alias does not exist in the keystore")); + assertThat(t, is(instanceOf(IllegalArgumentException.class))); + assertThat(t.getMessage(), containsString("alias does not exist in the keystore")); testComplete(); })); await(); @@ -3508,7 +3510,6 @@ public void testNetClientInternalTLS() throws Exception { @Test public void testNetClientInternalTLSWithSuppliedSSLContext() throws Exception { client.close(); - client = vertx.createNetClient(new NetClientOptions().setSsl(true)); Path tsPath = Paths.get(this.getClass().getClassLoader().getResource(Trust.SERVER_JKS.get().getPath()).toURI()); TrustManagerFactory tmFactory; @@ -3525,7 +3526,27 @@ public void testNetClientInternalTLSWithSuppliedSSLContext() throws Exception { tmFactory.getTrustManagers(), null ); - ((NetClientImpl)client).setSuppliedSSLContext(sslContext); + + client = vertx.createNetClient(new NetClientOptions().setSsl(true) + .setSslEngineOptions(new JdkSSLEngineOptions() { + @Override + public SslContextFactory sslContextFactory() { + return new SslContextFactory() { + @Override + public SslContext create() { + return new JdkSslContext( + sslContext, + true, + null, + IdentityCipherSuiteFilter.INSTANCE, + ApplicationProtocolConfig.DISABLED, + io.netty.handler.ssl.ClientAuth.NONE, + null, + false); + } + }; + } + })); testNetClientInternal_(new HttpServerOptions() .setHost("localhost") diff --git a/src/test/java/io/vertx/core/net/SSLHelperTest.java b/src/test/java/io/vertx/core/net/SSLHelperTest.java index 53f8388b1e1..4150371a2f4 100755 --- a/src/test/java/io/vertx/core/net/SSLHelperTest.java +++ b/src/test/java/io/vertx/core/net/SSLHelperTest.java @@ -17,6 +17,7 @@ import io.netty.handler.ssl.SslContext; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.VertxInternal; import io.vertx.core.json.JsonObject; import io.vertx.core.net.impl.SSLHelper; @@ -41,22 +42,30 @@ public void testUseJdkCiphersWhenNotSpecified() throws Exception { context.init(null, null, null); SSLEngine engine = context.createSSLEngine(); String[] expected = engine.getEnabledCipherSuites(); - SSLHelper helper = new SSLHelper(new HttpClientOptions(), - Cert.CLIENT_JKS.get(), - Trust.SERVER_JKS.get()); - SslContext ctx = helper.getContext((VertxInternal) vertx); - assertEquals(new HashSet<>(Arrays.asList(expected)), new HashSet<>(ctx.cipherSuites())); + SSLHelper helper = new SSLHelper(new HttpClientOptions().setKeyStoreOptions(Cert.CLIENT_JKS.get()).setTrustOptions(Trust.SERVER_JKS.get()), + null); + helper + .init((ContextInternal) vertx.getOrCreateContext()) + .onComplete(onSuccess(v -> { + SslContext ctx = helper.createContext((VertxInternal) vertx); + assertEquals(new HashSet<>(Arrays.asList(expected)), new HashSet<>(ctx.cipherSuites())); + testComplete(); + })); + await(); } @Test public void testUseOpenSSLCiphersWhenNotSpecified() throws Exception { Set expected = OpenSsl.availableOpenSslCipherSuites(); SSLHelper helper = new SSLHelper( - new HttpClientOptions().setOpenSslEngineOptions(new OpenSSLEngineOptions()), - Cert.CLIENT_PEM.get(), - Trust.SERVER_PEM.get()); - SslContext ctx = helper.getContext((VertxInternal) vertx); - assertEquals(expected, new HashSet<>(ctx.cipherSuites())); + new HttpClientOptions().setOpenSslEngineOptions(new OpenSSLEngineOptions()).setPemKeyCertOptions(Cert.CLIENT_PEM.get()).setTrustOptions(Trust.SERVER_PEM.get()), + null); + helper.init((ContextInternal) vertx.getOrCreateContext()).onComplete(onSuccess(v -> { + SslContext ctx = helper.createContext((VertxInternal) vertx); + assertEquals(expected, new HashSet<>(ctx.cipherSuites())); + testComplete(); + })); + await(); } @Test @@ -76,19 +85,25 @@ private void testOpenSslServerSessionContext(boolean testDefault){ httpServerOptions.setOpenSslEngineOptions(new OpenSSLEngineOptions().setSessionCacheEnabled(false)); } - SSLHelper defaultHelper = new SSLHelper(httpServerOptions, - Cert.SERVER_PEM.get(), - Trust.SERVER_PEM.get()); + SSLHelper defaultHelper = new SSLHelper(httpServerOptions.setPemKeyCertOptions(Cert.SERVER_PEM.get()).setTrustOptions(Trust.SERVER_PEM.get()), + null); - SslContext ctx = defaultHelper.getContext((VertxInternal) vertx); - assertTrue(ctx instanceof OpenSslServerContext); + defaultHelper + .init((ContextInternal) vertx.getOrCreateContext()) + .onComplete(onSuccess(v -> { + SslContext ctx = defaultHelper.createContext((VertxInternal) vertx); + assertTrue(ctx instanceof OpenSslServerContext); - SSLSessionContext sslSessionContext = ctx.sessionContext(); - assertTrue(sslSessionContext instanceof OpenSslServerSessionContext); + SSLSessionContext sslSessionContext = ctx.sessionContext(); + assertTrue(sslSessionContext instanceof OpenSslServerSessionContext); - if (sslSessionContext instanceof OpenSslServerSessionContext) { - assertEquals(testDefault, ((OpenSslServerSessionContext) sslSessionContext).isSessionCacheEnabled()); - } + if (sslSessionContext instanceof OpenSslServerSessionContext) { + assertEquals(testDefault, ((OpenSslServerSessionContext) sslSessionContext).isSessionCacheEnabled()); + } + testComplete(); + })); + + await(); } @Test @@ -104,8 +119,14 @@ public void testPreserveEnabledCipherSuitesOrder() throws Exception { assertEquals(new ArrayList<>(new HttpServerOptions(options).getEnabledCipherSuites()), Arrays.asList(engine.getEnabledCipherSuites())); JsonObject json = options.toJson(); assertEquals(new ArrayList<>(new HttpServerOptions(json).getEnabledCipherSuites()), Arrays.asList(engine.getEnabledCipherSuites())); - SSLHelper helper = new SSLHelper(options, Cert.SERVER_JKS.get(), null); - assertEquals(Arrays.asList(helper.createEngine((VertxInternal) vertx).getEnabledCipherSuites()), Arrays.asList(engine.getEnabledCipherSuites())); + SSLHelper helper = new SSLHelper(options.setKeyCertOptions(Cert.SERVER_JKS.get()), null); + helper + .init((ContextInternal) vertx.getOrCreateContext()) + .onComplete(onSuccess(v -> { + assertEquals(new HashSet<>(Arrays.asList(helper.createEngine((VertxInternal) vertx).getEnabledCipherSuites())), new HashSet<>(Arrays.asList(engine.getEnabledCipherSuites()))); + testComplete(); + })); + await(); } @Test diff --git a/src/test/java/io/vertx/it/SSLEngineTest.java b/src/test/java/io/vertx/it/SSLEngineTest.java index 97884cf40f2..cd6577cdc02 100644 --- a/src/test/java/io/vertx/it/SSLEngineTest.java +++ b/src/test/java/io/vertx/it/SSLEngineTest.java @@ -14,7 +14,6 @@ import io.netty.handler.ssl.JdkSslContext; import io.netty.handler.ssl.OpenSslContext; import io.netty.handler.ssl.SslContext; -import io.vertx.core.VertxException; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerOptions; @@ -106,7 +105,7 @@ private void doTest(SSLEngineOptions engine, } } SSLHelper sslHelper = ((HttpServerImpl)server).sslHelper(); - SslContext ctx = sslHelper.getContext((VertxInternal) vertx); + SslContext ctx = sslHelper.createContext((VertxInternal) vertx); switch (expectedSslContext != null ? expectedSslContext : "jdk") { case "jdk": assertTrue(ctx instanceof JdkSslContext); diff --git a/src/test/java/io/vertx/test/core/TestUtils.java b/src/test/java/io/vertx/test/core/TestUtils.java index eecb14079b7..27a2129413b 100644 --- a/src/test/java/io/vertx/test/core/TestUtils.java +++ b/src/test/java/io/vertx/test/core/TestUtils.java @@ -531,4 +531,15 @@ public static boolean isECCSupportedByVM() { return false; } } + + /** + * @return the most inner root cause of {@code throwable} + */ + public static Throwable rootCause(Throwable throwable) { + Throwable root = throwable; + while (root.getCause() != null) { + root = root.getCause(); + } + return root; + } }