diff --git a/src/main/java/io/vertx/core/http/impl/Http1xServerConnection.java b/src/main/java/io/vertx/core/http/impl/Http1xServerConnection.java index fc426622c1a..959f0e7ccc7 100644 --- a/src/main/java/io/vertx/core/http/impl/Http1xServerConnection.java +++ b/src/main/java/io/vertx/core/http/impl/Http1xServerConnection.java @@ -40,7 +40,7 @@ import io.vertx.core.net.NetSocket; import io.vertx.core.net.impl.MessageWrite; import io.vertx.core.net.impl.NetSocketImpl; -import io.vertx.core.net.impl.SSLHelper; +import io.vertx.core.internal.tls.SslContextManager; import io.vertx.core.net.impl.VertxHandler; import io.vertx.core.spi.metrics.HttpServerMetrics; import io.vertx.core.spi.tracing.VertxTracer; @@ -87,10 +87,10 @@ public class Http1xServerConnection extends Http1xConnection implements HttpServ final HttpServerMetrics metrics; final boolean handle100ContinueAutomatically; final HttpServerOptions options; - final SSLHelper sslHelper; + final SslContextManager sslContextManager; public Http1xServerConnection(Supplier streamContextSupplier, - SSLHelper sslHelper, + SslContextManager sslContextManager, HttpServerOptions options, ChannelHandlerContext chctx, ContextInternal context, @@ -100,7 +100,7 @@ public Http1xServerConnection(Supplier streamContextSupplier, this.serverOrigin = serverOrigin; this.streamContextSupplier = streamContextSupplier; this.options = options; - this.sslHelper = sslHelper; + this.sslContextManager = sslContextManager; this.metrics = metrics; this.handle100ContinueAutomatically = options.isHandle100ContinueAutomatically(); this.tracingPolicy = options.getTracingPolicy(); @@ -380,7 +380,7 @@ void netSocket(Promise promise) { } pipeline.replace("handler", "handler", VertxHandler.create(ctx -> { - NetSocketImpl socket = new NetSocketImpl(context, ctx, sslHelper, options.getSslOptions(), metrics, false) { + NetSocketImpl socket = new NetSocketImpl(context, ctx, sslContextManager, options.getSslOptions(), metrics, false) { @Override protected void handleClosed() { if (metrics != null) { diff --git a/src/main/java/io/vertx/core/http/impl/Http1xUpgradeToH2CHandler.java b/src/main/java/io/vertx/core/http/impl/Http1xUpgradeToH2CHandler.java index eeb9c97f5ac..568d7b50855 100644 --- a/src/main/java/io/vertx/core/http/impl/Http1xUpgradeToH2CHandler.java +++ b/src/main/java/io/vertx/core/http/impl/Http1xUpgradeToH2CHandler.java @@ -13,7 +13,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelPipeline; @@ -21,13 +20,10 @@ import io.netty.handler.codec.http2.*; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; -import io.netty.handler.timeout.IdleStateHandler; -import io.vertx.core.net.impl.SSLHelper; -import io.vertx.core.net.impl.SslChannelProvider; +import io.vertx.core.internal.tls.SslContextManager; +import io.vertx.core.internal.net.SslChannelProvider; import io.vertx.core.net.impl.VertxHandler; -import java.util.Map; - import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; @@ -36,15 +32,15 @@ public class Http1xUpgradeToH2CHandler extends ChannelInboundHandlerAdapter { private final HttpServerConnectionInitializer initializer; private final SslChannelProvider sslChannelProvider; - private final SSLHelper sslHelper; + private final SslContextManager sslContextManager; private VertxHttp2ConnectionHandler handler; private final boolean isCompressionSupported; private final boolean isDecompressionSupported; - Http1xUpgradeToH2CHandler(HttpServerConnectionInitializer initializer, SslChannelProvider sslChannelProvider, SSLHelper sslHelper, boolean isCompressionSupported, boolean isDecompressionSupported) { + Http1xUpgradeToH2CHandler(HttpServerConnectionInitializer initializer, SslChannelProvider sslChannelProvider, SslContextManager sslContextManager, boolean isCompressionSupported, boolean isDecompressionSupported) { this.initializer = initializer; this.sslChannelProvider = sslChannelProvider; - this.sslHelper = sslHelper; + this.sslContextManager = sslContextManager; this.isCompressionSupported = isCompressionSupported; this.isDecompressionSupported = isDecompressionSupported; } @@ -120,7 +116,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception ctx.writeAndFlush(res); } } else { - initializer.configureHttp1Handler(ctx.pipeline(), sslChannelProvider, sslHelper); + initializer.configureHttp1Handler(ctx.pipeline(), sslChannelProvider, sslContextManager); ctx.fireChannelRead(msg); ctx.pipeline().remove(this); } diff --git a/src/main/java/io/vertx/core/http/impl/HttpServerConnectionInitializer.java b/src/main/java/io/vertx/core/http/impl/HttpServerConnectionInitializer.java index 976cb28a717..55601608284 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpServerConnectionInitializer.java +++ b/src/main/java/io/vertx/core/http/impl/HttpServerConnectionInitializer.java @@ -23,6 +23,8 @@ import io.vertx.core.http.HttpServerOptions; import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.VertxInternal; +import io.vertx.core.internal.tls.SslContextManager; +import io.vertx.core.internal.net.SslChannelProvider; import io.vertx.core.net.impl.*; import io.vertx.core.spi.metrics.HttpServerMetrics; @@ -88,7 +90,7 @@ class HttpServerConnectionInitializer { this.encodingDetector = compressionOptions != null ? new EncodingDetector(compressionOptions)::determineEncoding : null; } - void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider, SSLHelper sslHelper) { + void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider, SslContextManager sslContextManager) { ChannelPipeline pipeline = ch.pipeline(); if (options.isSsl()) { SslHandler sslHandler = pipeline.get(SslHandler.class); @@ -101,23 +103,23 @@ void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider, SSLHel break; case "http/1.1": case "http/1.0": - configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslHelper); - configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslHelper); + configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslContextManager); + configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslContextManager); break; } } else { // No alpn presented or OpenSSL - configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslHelper); - configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslHelper); + configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslContextManager); + configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslContextManager); } } else { - configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslHelper); - configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslHelper); + configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslContextManager); + configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslContextManager); } } else { if (disableH2C) { - configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslHelper); - configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslHelper); + configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslContextManager); + configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslContextManager); } else { Http1xOrH2CHandler handler = new Http1xOrH2CHandler() { @Override @@ -125,8 +127,8 @@ protected void configure(ChannelHandlerContext ctx, boolean h2c) { if (h2c) { configureHttp2(ctx.pipeline()); } else { - configureHttp1Pipeline(ctx.pipeline(), sslChannelProvider, sslHelper); - configureHttp1OrH2CUpgradeHandler(ctx.pipeline(), sslChannelProvider, sslHelper); + configureHttp1Pipeline(ctx.pipeline(), sslChannelProvider, sslContextManager); + configureHttp1OrH2CUpgradeHandler(ctx.pipeline(), sslChannelProvider, sslContextManager); } } @Override @@ -205,9 +207,9 @@ private VertxHttp2ConnectionHandler buildHttp2ConnectionH return handler; } - private void configureHttp1OrH2CUpgradeHandler(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider, SSLHelper sslHelper) { + private void configureHttp1OrH2CUpgradeHandler(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider, SslContextManager sslContextManager) { // DO WE NEED TO ADD SOMEWHERE BEFORE IDLE ? - pipeline.addAfter("httpEncoder", "h2c", new Http1xUpgradeToH2CHandler(this, sslChannelProvider, sslHelper, options.isCompressionSupported(), options.isDecompressionSupported())); + pipeline.addAfter("httpEncoder", "h2c", new Http1xUpgradeToH2CHandler(this, sslChannelProvider, sslContextManager, options.isCompressionSupported(), options.isDecompressionSupported())); } /** @@ -226,7 +228,7 @@ private static String computeChannelName(ChannelPipeline pipeline) { } } - private void configureHttp1Pipeline(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider, SSLHelper sslHelper) { + private void configureHttp1Pipeline(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider, SslContextManager sslContextManager) { String name = computeChannelName(pipeline); pipeline.addBefore(name, "httpDecoder", new VertxHttpRequestDecoder(options)); pipeline.addBefore(name, "httpEncoder", new VertxHttpResponseEncoder()); @@ -238,7 +240,7 @@ private void configureHttp1Pipeline(ChannelPipeline pipeline, SslChannelProvider } } - void configureHttp1Handler(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider, SSLHelper sslHelper) { + void configureHttp1Handler(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider, SslContextManager sslContextManager) { if (!server.requestAccept()) { sendServiceUnavailable(pipeline.channel()); return; @@ -247,7 +249,7 @@ void configureHttp1Handler(ChannelPipeline pipeline, SslChannelProvider sslChann VertxHandler handler = VertxHandler.create(chctx -> { Http1xServerConnection conn = new Http1xServerConnection( streamContextSupplier, - sslHelper, + sslContextManager, options, chctx, context, diff --git a/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java b/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java new file mode 100644 index 00000000000..0885ebd2b6e --- /dev/null +++ b/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java @@ -0,0 +1,83 @@ +/* + * 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.internal.net; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelHandler; +import io.netty.handler.ssl.SniHandler; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.concurrent.ImmediateExecutor; +import io.vertx.core.internal.VertxInternal; +import io.vertx.core.internal.tls.SslContextProvider; +import io.vertx.core.net.SocketAddress; + +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +/** + * Provider for Netty {@link SslHandler} and {@link SniHandler}. + *
+ * {@link SslContext} instances are cached and reused. + */ +public class SslChannelProvider { + + private final Executor workerPool; + private final boolean sni; + private final SslContextProvider sslContextProvider; + + public SslChannelProvider(VertxInternal vertx, + SslContextProvider sslContextProvider, + boolean sni) { + this.workerPool = vertx.getInternalWorkerPool().executor(); + this.sni = sni; + this.sslContextProvider = sslContextProvider; + } + + public SslContextProvider sslContextProvider() { + return sslContextProvider; + } + + public SslHandler createClientSslHandler(SocketAddress peerAddress, String serverName, boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { + SslContext sslContext = sslContextProvider.sslClientContext(serverName, useAlpn); + SslHandler sslHandler; + Executor delegatedTaskExec = sslContextProvider.useWorkerPool() ? workerPool : ImmediateExecutor.INSTANCE; + if (peerAddress != null && peerAddress.isInetSocket()) { + sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, peerAddress.host(), peerAddress.port(), delegatedTaskExec); + } else { + sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); + } + sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); + return sslHandler; + } + + public ChannelHandler createServerHandler(boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { + if (sni) { + return createSniHandler(useAlpn, sslHandshakeTimeout, sslHandshakeTimeoutUnit); + } else { + return createServerSslHandler(useAlpn, sslHandshakeTimeout, sslHandshakeTimeoutUnit); + } + } + + private SslHandler createServerSslHandler(boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { + SslContext sslContext = sslContextProvider.sslServerContext(useAlpn); + Executor delegatedTaskExec = sslContextProvider.useWorkerPool() ? workerPool : ImmediateExecutor.INSTANCE; + SslHandler sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); + sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); + return sslHandler; + } + + private SniHandler createSniHandler(boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { + Executor delegatedTaskExec = sslContextProvider.useWorkerPool() ? workerPool : ImmediateExecutor.INSTANCE; + return new VertxSniHandler(sslContextProvider.serverNameMapping(delegatedTaskExec, useAlpn), sslHandshakeTimeoutUnit.toMillis(sslHandshakeTimeout), delegatedTaskExec); + } + +} diff --git a/src/main/java/io/vertx/core/net/impl/SslHandshakeCompletionHandler.java b/src/main/java/io/vertx/core/internal/net/SslHandshakeCompletionHandler.java similarity index 85% rename from src/main/java/io/vertx/core/net/impl/SslHandshakeCompletionHandler.java rename to src/main/java/io/vertx/core/internal/net/SslHandshakeCompletionHandler.java index 90c9a47a77b..5c07bf9ffac 100644 --- a/src/main/java/io/vertx/core/net/impl/SslHandshakeCompletionHandler.java +++ b/src/main/java/io/vertx/core/internal/net/SslHandshakeCompletionHandler.java @@ -8,9 +8,8 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.net.impl; +package io.vertx.core.internal.net; -import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.ssl.SniCompletionEvent; @@ -18,18 +17,19 @@ import io.netty.util.Attribute; import io.netty.util.AttributeKey; import io.netty.util.concurrent.Promise; -import io.vertx.core.AsyncResult; -import io.vertx.core.Future; -import io.vertx.core.Handler; /** - * An handler that waits for SSL handshake completion and dispatch it to the server handler. + * A handler that waits for SSL handshake completion and dispatch it to the server handler. * * @author Julien Viet */ public class SslHandshakeCompletionHandler extends ChannelInboundHandlerAdapter { - static AttributeKey SERVER_NAME_ATTR = AttributeKey.valueOf("sniServerName"); + /** + * The channel attribute providing the SNI server name, this name is set upon handshake completion when available. + */ + public static AttributeKey SERVER_NAME_ATTR = AttributeKey.valueOf("sniServerName"); + private final Promise promise; public SslHandshakeCompletionHandler(Promise promise) { diff --git a/src/main/java/io/vertx/core/net/impl/VertxSniHandler.java b/src/main/java/io/vertx/core/internal/net/VertxSniHandler.java similarity index 97% rename from src/main/java/io/vertx/core/net/impl/VertxSniHandler.java rename to src/main/java/io/vertx/core/internal/net/VertxSniHandler.java index 90d5a571c57..36e43815858 100644 --- a/src/main/java/io/vertx/core/net/impl/VertxSniHandler.java +++ b/src/main/java/io/vertx/core/internal/net/VertxSniHandler.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.internal.net; import io.netty.buffer.ByteBufAllocator; import io.netty.handler.ssl.SniHandler; diff --git a/src/main/java/io/vertx/core/net/impl/VertxSslContext.java b/src/main/java/io/vertx/core/internal/net/VertxSslContext.java similarity index 96% rename from src/main/java/io/vertx/core/net/impl/VertxSslContext.java rename to src/main/java/io/vertx/core/internal/net/VertxSslContext.java index ed08d5d6140..a100076fb05 100644 --- a/src/main/java/io/vertx/core/net/impl/VertxSslContext.java +++ b/src/main/java/io/vertx/core/internal/net/VertxSslContext.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.internal.net; import io.netty.handler.ssl.DelegatingSslContext; import io.netty.handler.ssl.SslContext; diff --git a/src/main/java/io/vertx/core/net/impl/SSLHelper.java b/src/main/java/io/vertx/core/internal/tls/SslContextManager.java similarity index 64% rename from src/main/java/io/vertx/core/net/impl/SSLHelper.java rename to src/main/java/io/vertx/core/internal/tls/SslContextManager.java index 86fcc5b49fe..7b72850add7 100755 --- a/src/main/java/io/vertx/core/net/impl/SSLHelper.java +++ b/src/main/java/io/vertx/core/internal/tls/SslContextManager.java @@ -9,9 +9,8 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.net.impl; +package io.vertx.core.internal.tls; -import io.netty.handler.ssl.OpenSsl; import io.vertx.core.Future; import io.vertx.core.Promise; import io.vertx.core.VertxException; @@ -34,7 +33,7 @@ * @author Tim Fox * @author Julien Viet */ -public class SSLHelper { +public class SslContextManager { private static final Config NULL_CONFIG = new Config(null, null, null, null, null); static final EnumMap CLIENT_AUTH_MAPPING = new EnumMap<>(ClientAuth.class); @@ -45,63 +44,22 @@ public class SSLHelper { 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(SSLEngineOptions engineOptions, boolean useAlpn) { - if (engineOptions == null) { - if (useAlpn) { - if (JdkSSLEngineOptions.isAlpnAvailable()) { - engineOptions = new JdkSSLEngineOptions(); - } else if (OpenSSLEngineOptions.isAlpnAvailable()) { - engineOptions = new OpenSSLEngineOptions(); - } - } - } - if (engineOptions == null) { - engineOptions = new JdkSSLEngineOptions(); - } else if (engineOptions instanceof OpenSSLEngineOptions) { - if (!OpenSsl.isAvailable()) { - VertxException ex = new VertxException("OpenSSL is not available"); - Throwable cause = OpenSsl.unavailabilityCause(); - if (cause != null) { - ex.initCause(cause); - } - throw ex; - } - } - - if (useAlpn) { - if (engineOptions instanceof JdkSSLEngineOptions) { - if (!JdkSSLEngineOptions.isAlpnAvailable()) { - throw new VertxException("ALPN not available for JDK SSL/TLS engine"); - } - } - if (engineOptions instanceof OpenSSLEngineOptions) { - if (!OpenSSLEngineOptions.isAlpnAvailable()) { - throw new VertxException("ALPN is not available for OpenSSL SSL/TLS engine"); - } - } - } - return engineOptions; - } - private final Supplier supplier; private final boolean useWorkerPool; private final Map> configMap; - private final Map> sslChannelProviderMap; + private final Map> sslContextProviderMap; - public SSLHelper(SSLEngineOptions sslEngineOptions, int cacheMaxSize) { + public SslContextManager(SSLEngineOptions sslEngineOptions, int cacheMaxSize) { this.configMap = new LruCache<>(cacheMaxSize); - this.sslChannelProviderMap = new LruCache<>(cacheMaxSize); + this.sslContextProviderMap = new LruCache<>(cacheMaxSize); this.supplier = sslEngineOptions::sslContextFactory; this.useWorkerPool = sslEngineOptions.getUseWorkerThread(); } public synchronized int sniEntrySize() { int size = 0; - for (Future fut : sslChannelProviderMap.values()) { - SslChannelProvider result = fut.result(); + for (Future fut : sslContextProviderMap.values()) { + SslContextProvider result = fut.result(); if (result != null) { size += result.sniEntrySize(); } @@ -109,30 +67,30 @@ public synchronized int sniEntrySize() { return size; } - public SSLHelper(SSLEngineOptions sslEngineOptions) { + public SslContextManager(SSLEngineOptions sslEngineOptions) { this(sslEngineOptions, 256); } - public Future resolveSslChannelProvider(SSLOptions options, String endpointIdentificationAlgorithm, boolean useSNI, ClientAuth clientAuth, List applicationProtocols, ContextInternal ctx) { - return resolveSslChannelProvider(options, endpointIdentificationAlgorithm, useSNI, clientAuth, applicationProtocols, false, ctx); + public Future resolveSslContextProvider(SSLOptions options, String endpointIdentificationAlgorithm, ClientAuth clientAuth, List applicationProtocols, ContextInternal ctx) { + return resolveSslContextProvider(options, endpointIdentificationAlgorithm, clientAuth, applicationProtocols, false, ctx); } - public Future resolveSslChannelProvider(SSLOptions options, String hostnameVerificationAlgorithm, boolean useSNI, ClientAuth clientAuth, List applicationProtocols, boolean force, ContextInternal ctx) { - Promise promise; + public Future resolveSslContextProvider(SSLOptions options, String hostnameVerificationAlgorithm, ClientAuth clientAuth, List applicationProtocols, boolean force, ContextInternal ctx) { + Promise promise; ConfigKey k = new ConfigKey(options); synchronized (this) { if (force) { - sslChannelProviderMap.remove(k); + sslContextProviderMap.remove(k); } else { - Future v = sslChannelProviderMap.get(k); + Future v = sslContextProviderMap.get(k); if (v != null) { return v; } } promise = Promise.promise(); - sslChannelProviderMap.put(k, promise.future()); + sslContextProviderMap.put(k, promise.future()); } - buildChannelProvider(options, hostnameVerificationAlgorithm, useSNI, clientAuth, applicationProtocols, force, ctx) + buildSslContextProvider(options, hostnameVerificationAlgorithm, clientAuth, applicationProtocols, force, ctx) .onComplete(promise); return promise.future(); } @@ -143,13 +101,14 @@ public Future resolveSslChannelProvider(SSLOptions options, * @param ctx the context * @return a future resolved when the helper is initialized */ - Future buildContextProvider(SSLOptions sslOptions, - String hostnameVerificationAlgorithm, - ClientAuth clientAuth, - List applicationProtocols, - boolean force, - ContextInternal ctx) { - return buildConfig(sslOptions, force, ctx).map(config -> buildSslContextProvider(sslOptions, hostnameVerificationAlgorithm, supplier, clientAuth, applicationProtocols, config)); + Future buildSslContextProvider(SSLOptions sslOptions, + String hostnameVerificationAlgorithm, + ClientAuth clientAuth, + List applicationProtocols, + boolean force, + ContextInternal ctx) { + return buildConfig(sslOptions, force, ctx) + .map(config -> buildSslContextProvider(sslOptions, hostnameVerificationAlgorithm, supplier, clientAuth, applicationProtocols, config)); } private SslContextProvider buildSslContextProvider(SSLOptions sslOptions, String hostnameVerificationAlgorithm, Supplier supplier, ClientAuth clientAuth, List applicationProtocols, Config config) { @@ -158,6 +117,7 @@ private SslContextProvider buildSslContextProvider(SSLOptions sslOptions, String " verification algorithm"); } return new SslContextProvider( + useWorkerPool, clientAuth, hostnameVerificationAlgorithm, applicationProtocols, @@ -171,31 +131,6 @@ private SslContextProvider buildSslContextProvider(SSLOptions sslOptions, String supplier); } - /** - * Initialize the helper, this loads and validates the configuration. - * - * @param ctx the context - * @return a future resolved when the helper is initialized - */ - protected Future buildChannelProvider(SSLOptions sslOptions, - String endpointIdentificationAlgorithm, - boolean useSNI, - ClientAuth clientAuth, - List applicationProtocols, - boolean force, - ContextInternal ctx) { - Future f; - boolean useWorker; - f = buildConfig(sslOptions, force, ctx).map(config -> buildSslContextProvider(sslOptions, - endpointIdentificationAlgorithm, supplier, clientAuth, applicationProtocols, config)); - useWorker = useWorkerPool; - return f.map(c -> new SslChannelProvider( - c, - useSNI, - ctx.owner().getInternalWorkerPool().executor(), - useWorker)); - } - private static TrustOptions trustOptionsOf(SSLOptions sslOptions) { if (sslOptions instanceof ClientSSLOptions) { ClientSSLOptions clientSSLOptions = (ClientSSLOptions) sslOptions; diff --git a/src/main/java/io/vertx/core/net/impl/SslContextProvider.java b/src/main/java/io/vertx/core/internal/tls/SslContextProvider.java similarity index 78% rename from src/main/java/io/vertx/core/net/impl/SslContextProvider.java rename to src/main/java/io/vertx/core/internal/tls/SslContextProvider.java index c5d57a4b55b..8edcc5fc34b 100644 --- a/src/main/java/io/vertx/core/net/impl/SslContextProvider.java +++ b/src/main/java/io/vertx/core/internal/tls/SslContextProvider.java @@ -8,11 +8,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.net.impl; +package io.vertx.core.internal.tls; +import io.netty.handler.ssl.SniHandler; import io.netty.handler.ssl.SslContext; +import io.netty.util.AsyncMapping; import io.vertx.core.VertxException; import io.vertx.core.http.ClientAuth; +import io.vertx.core.internal.net.VertxSslContext; import io.vertx.core.spi.tls.SslContextFactory; import javax.net.ssl.*; @@ -20,6 +23,8 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; import java.util.function.Function; import java.util.function.Supplier; @@ -30,6 +35,11 @@ */ public class SslContextProvider { + private static int idx(boolean useAlpn) { + return useAlpn ? 0 : 1; + } + + private final boolean useWorkerPool; private final Supplier provider; private final Set enabledProtocols; private final List crls; @@ -42,7 +52,13 @@ public class SslContextProvider { private final Function keyManagerFactoryMapper; private final Function trustManagerMapper; - public SslContextProvider(ClientAuth clientAuth, + private final SslContext[] sslContexts = new SslContext[2]; + private final Map[] sslContextMaps = new Map[]{ + new ConcurrentHashMap<>(), new ConcurrentHashMap<>() + }; + + public SslContextProvider(boolean useWorkerPool, + ClientAuth clientAuth, String endpointIdentificationAlgorithm, List applicationProtocols, Set enabledCipherSuites, @@ -53,6 +69,7 @@ public SslContextProvider(ClientAuth clientAuth, Function trustManagerMapper, List crls, Supplier provider) { + this.useWorkerPool = useWorkerPool; this.provider = provider; this.clientAuth = clientAuth; this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm; @@ -66,6 +83,14 @@ public SslContextProvider(ClientAuth clientAuth, this.crls = crls; } + public boolean useWorkerPool() { + return useWorkerPool; + } + + public int sniEntrySize() { + return sslContextMaps[0].size() + sslContextMaps[1].size(); + } + public VertxSslContext createContext(boolean server, KeyManagerFactory keyManagerFactory, TrustManager[] trustManagers, @@ -84,6 +109,59 @@ public VertxSslContext createContext(boolean server, } } + public SslContext sslClientContext(String serverName, boolean useAlpn) { + try { + return sslContext(serverName, useAlpn, false); + } catch (Exception e) { + throw new VertxException(e); + } + } + + public SslContext sslContext(String serverName, boolean useAlpn, boolean server) throws Exception { + int idx = idx(useAlpn); + if (serverName != null) { + KeyManagerFactory kmf = resolveKeyManagerFactory(serverName); + TrustManager[] trustManagers = resolveTrustManagers(serverName); + if (kmf != null || trustManagers != null || !server) { + return sslContextMaps[idx].computeIfAbsent(serverName, s -> createContext(server, kmf, trustManagers, s, useAlpn)); + } + } + if (sslContexts[idx] == null) { + SslContext context = createContext(server, null, null, serverName, useAlpn); + sslContexts[idx] = context; + } + return sslContexts[idx]; + } + + public SslContext sslServerContext(boolean useAlpn) { + try { + return sslContext(null, useAlpn, true); + } catch (Exception e) { + throw new VertxException(e); + } + } + + /** + * Server name {@link AsyncMapping} for {@link SniHandler}, mapping happens on a Vert.x worker thread. + * + * @return the {@link AsyncMapping} + */ + public AsyncMapping serverNameMapping(Executor workerPool, boolean useAlpn) { + return (AsyncMapping) (serverName, promise) -> { + workerPool.execute(() -> { + SslContext sslContext; + try { + sslContext = sslContext(serverName, useAlpn, true); + } catch (Exception e) { + promise.setFailure(e); + return; + } + promise.setSuccess(sslContext); + }); + return promise; + }; + } + public VertxSslContext createContext(boolean server, boolean useAlpn) { return createContext(server, defaultKeyManagerFactory(), defaultTrustManagers(), null, useAlpn); } @@ -128,7 +206,7 @@ public VertxSslContext createServerContext(KeyManagerFactory keyManagerFactory, .forClient(false) .enabledCipherSuites(enabledCipherSuites) .applicationProtocols(applicationProtocols); - factory.clientAuth(SSLHelper.CLIENT_AUTH_MAPPING.get(clientAuth)); + factory.clientAuth(SslContextManager.CLIENT_AUTH_MAPPING.get(clientAuth)); if (serverName != null) { factory.serverName(serverName); } diff --git a/src/main/java/io/vertx/core/net/impl/TrustAllOptions.java b/src/main/java/io/vertx/core/internal/tls/TrustAllOptions.java similarity index 96% rename from src/main/java/io/vertx/core/net/impl/TrustAllOptions.java rename to src/main/java/io/vertx/core/internal/tls/TrustAllOptions.java index 0212c0cf362..9afa34fb041 100644 --- a/src/main/java/io/vertx/core/net/impl/TrustAllOptions.java +++ b/src/main/java/io/vertx/core/internal/tls/TrustAllOptions.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.internal.tls; import io.vertx.core.Vertx; import io.vertx.core.net.TrustOptions; @@ -16,7 +16,6 @@ import javax.net.ssl.*; import java.security.KeyStore; import java.security.Provider; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.function.Function; diff --git a/src/main/java/io/vertx/core/net/impl/VertxTrustManagerFactory.java b/src/main/java/io/vertx/core/internal/tls/VertxTrustManagerFactory.java similarity index 97% rename from src/main/java/io/vertx/core/net/impl/VertxTrustManagerFactory.java rename to src/main/java/io/vertx/core/internal/tls/VertxTrustManagerFactory.java index aaa52650481..ce36d74064c 100644 --- a/src/main/java/io/vertx/core/net/impl/VertxTrustManagerFactory.java +++ b/src/main/java/io/vertx/core/internal/tls/VertxTrustManagerFactory.java @@ -9,7 +9,7 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.net.impl; +package io.vertx.core.internal.tls; import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.TrustManager; 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 fc110186b38..1709c127144 100644 --- a/src/main/java/io/vertx/core/net/impl/ChannelProvider.java +++ b/src/main/java/io/vertx/core/net/impl/ChannelProvider.java @@ -22,6 +22,8 @@ import io.vertx.core.Handler; import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.VertxInternal; +import io.vertx.core.internal.net.SslChannelProvider; +import io.vertx.core.internal.tls.SslContextProvider; import io.vertx.core.net.ClientSSLOptions; import io.vertx.core.net.ProxyOptions; import io.vertx.core.net.ProxyType; @@ -42,14 +44,14 @@ public final class ChannelProvider { private final Bootstrap bootstrap; - private final SslChannelProvider sslContextProvider; + private final SslContextProvider sslContextProvider; private final ContextInternal context; private ProxyOptions proxyOptions; private String applicationProtocol; private Handler handler; public ChannelProvider(Bootstrap bootstrap, - SslChannelProvider sslContextProvider, + SslContextProvider sslContextProvider, ContextInternal context) { this.bootstrap = bootstrap; this.context = context; @@ -107,7 +109,8 @@ private void connect(Handler handler, SocketAddress remoteAddress, Sock private void initSSL(Handler handler, SocketAddress peerAddress, String serverName, boolean ssl, ClientSSLOptions sslOptions, Channel ch, Promise channelHandler) { if (ssl) { - SslHandler sslHandler = sslContextProvider.createClientSslHandler(peerAddress, serverName, sslOptions.isUseAlpn(), sslOptions.getSslHandshakeTimeout(), sslOptions.getSslHandshakeTimeoutUnit()); + SslChannelProvider sslChannelProvider = new SslChannelProvider(context.owner(), sslContextProvider, false); + SslHandler sslHandler = sslChannelProvider.createClientSslHandler(peerAddress, serverName, sslOptions.isUseAlpn(), sslOptions.getSslHandshakeTimeout(), sslOptions.getSslHandshakeTimeoutUnit()); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("ssl", sslHandler); pipeline.addLast(new ChannelInboundHandlerAdapter() { diff --git a/src/main/java/io/vertx/core/net/impl/ConnectionBase.java b/src/main/java/io/vertx/core/net/impl/ConnectionBase.java index 7faf493edb6..4f47fba0177 100644 --- a/src/main/java/io/vertx/core/net/impl/ConnectionBase.java +++ b/src/main/java/io/vertx/core/net/impl/ConnectionBase.java @@ -25,6 +25,7 @@ import io.vertx.core.internal.logging.Logger; import io.vertx.core.internal.logging.LoggerFactory; import io.vertx.core.internal.net.NetSocketInternal; +import io.vertx.core.internal.net.SslHandshakeCompletionHandler; import io.vertx.core.net.SocketAddress; import io.vertx.core.spi.metrics.NetworkMetrics; import io.vertx.core.spi.metrics.TCPMetrics; 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 8d85ea0450c..2d0fdeaa920 100644 --- a/src/main/java/io/vertx/core/net/impl/NetClientImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetClientImpl.java @@ -29,6 +29,8 @@ import io.vertx.core.internal.PromiseInternal; import io.vertx.core.internal.logging.Logger; import io.vertx.core.internal.logging.LoggerFactory; +import io.vertx.core.internal.tls.SslContextManager; +import io.vertx.core.internal.tls.SslContextProvider; import io.vertx.core.net.*; import io.vertx.core.spi.metrics.Metrics; import io.vertx.core.spi.metrics.TCPMetrics; @@ -57,7 +59,7 @@ class NetClientImpl implements NetClientInternal { private final VertxInternal vertx; private final NetClientOptions options; - private final SSLHelper sslHelper; + private final SslContextManager sslContextManager; private volatile ClientSSLOptions sslOptions; public final ChannelGroup channelGroup; private final TCPMetrics metrics; @@ -78,7 +80,7 @@ public NetClientImpl(VertxInternal vertx, TCPMetrics metrics, NetClientOptions o this.vertx = vertx; this.channelGroup = new DefaultChannelGroup(vertx.getAcceptorEventLoopGroup().next(), true); this.options = new NetClientOptions(options); - this.sslHelper = new SSLHelper(SSLHelper.resolveEngineOptions(options.getSslEngineOptions(), options.isUseAlpn())); + this.sslContextManager = new SslContextManager(NetServerImpl.resolveEngineOptions(options.getSslEngineOptions(), options.isUseAlpn())); this.metrics = metrics; this.logEnabled = options.getLogActivity(); this.idleTimeout = options.getIdleTimeout(); @@ -246,11 +248,10 @@ private void connectInternal(ConnectOptions connectOptions, connectHandler.fail("ClientSSLOptions must be provided when connecting to a TLS server"); return; } - Future fut; - fut = sslHelper.resolveSslChannelProvider( + Future fut; + fut = sslContextManager.resolveSslContextProvider( sslOptions, sslOptions.getHostnameVerificationAlgorithm(), - false, null, sslOptions.getApplicationLayerProtocols(), context); @@ -269,7 +270,7 @@ private void connectInternal(ConnectOptions connectOptions, private void connectInternal2(ConnectOptions connectOptions, ClientSSLOptions sslOptions, - SslChannelProvider sslChannelProvider, + SslContextProvider sslContextProvider, boolean registerWriteHandlers, Promise connectHandler, ContextInternal context, @@ -310,7 +311,7 @@ private void connectInternal2(ConnectOptions connectOptions, } } - ChannelProvider channelProvider = new ChannelProvider(bootstrap, sslChannelProvider, context) + ChannelProvider channelProvider = new ChannelProvider(bootstrap, sslContextProvider, context) .proxyOptions(proxyOptions); SocketAddress captured = remoteAddress; @@ -354,7 +355,7 @@ private void connectInternal2(ConnectOptions connectOptions, } }); } else { - eventLoop.execute(() -> connectInternal2(connectOptions, sslOptions, sslChannelProvider, registerWriteHandlers, connectHandler, context, remainingAttempts)); + eventLoop.execute(() -> connectInternal2(connectOptions, sslOptions, sslContextProvider, registerWriteHandlers, connectHandler, context, remainingAttempts)); } } @@ -393,7 +394,7 @@ private void connected(ContextInternal context, context, ctx, remoteAddress, - sslHelper, + sslContextManager, sslOptions, metrics, applicationLayerProtocol, 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 94728123627..d841ba496fb 100644 --- a/src/main/java/io/vertx/core/net/impl/NetServerImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetServerImpl.java @@ -18,14 +18,12 @@ import io.netty.channel.group.DefaultChannelGroup; import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.ssl.OpenSsl; import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.traffic.GlobalTrafficShapingHandler; import io.netty.util.concurrent.GenericFutureListener; -import io.vertx.core.Closeable; -import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.Promise; +import io.vertx.core.*; import io.vertx.core.buffer.impl.PartialPooledByteBufAllocator; import io.vertx.core.http.ClientAuth; import io.vertx.core.http.HttpServerOptions; @@ -36,6 +34,10 @@ import io.vertx.core.internal.VertxInternal; import io.vertx.core.internal.logging.Logger; import io.vertx.core.internal.logging.LoggerFactory; +import io.vertx.core.internal.tls.SslContextManager; +import io.vertx.core.internal.net.SslChannelProvider; +import io.vertx.core.internal.tls.SslContextProvider; +import io.vertx.core.internal.net.SslHandshakeCompletionHandler; import io.vertx.core.net.*; import io.vertx.core.spi.metrics.MetricsProvider; import io.vertx.core.spi.metrics.TCPMetrics; @@ -76,9 +78,9 @@ public class NetServerImpl implements Closeable, MetricsProvider, NetServerInter private ChannelGroupFuture graceFuture; // Main - private SSLHelper sslHelper; - private volatile Future sslChannelProvider; - private Future updateInProgress; + private SslContextManager sslContextManager; + private volatile Future sslContextProvider; + private Future updateInProgress; private GlobalTrafficShapingHandler trafficShapingHandler; private ServerChannelLoadBalancer channelBalancer; private Future bindFuture; @@ -100,8 +102,49 @@ public NetServerImpl(VertxInternal vertx, NetServerOptions options) { this.closeSequence = closeSequence; } + /** + * Resolve the ssl engine options to use for properly running the configured options. + */ + public static SSLEngineOptions resolveEngineOptions(SSLEngineOptions engineOptions, boolean useAlpn) { + if (engineOptions == null) { + if (useAlpn) { + if (JdkSSLEngineOptions.isAlpnAvailable()) { + engineOptions = new JdkSSLEngineOptions(); + } else if (OpenSSLEngineOptions.isAlpnAvailable()) { + engineOptions = new OpenSSLEngineOptions(); + } + } + } + if (engineOptions == null) { + engineOptions = new JdkSSLEngineOptions(); + } else if (engineOptions instanceof OpenSSLEngineOptions) { + if (!OpenSsl.isAvailable()) { + VertxException ex = new VertxException("OpenSSL is not available"); + Throwable cause = OpenSsl.unavailabilityCause(); + if (cause != null) { + ex.initCause(cause); + } + throw ex; + } + } + + if (useAlpn) { + if (engineOptions instanceof JdkSSLEngineOptions) { + if (!JdkSSLEngineOptions.isAlpnAvailable()) { + throw new VertxException("ALPN not available for JDK SSL/TLS engine"); + } + } + if (engineOptions instanceof OpenSSLEngineOptions) { + if (!OpenSSLEngineOptions.isAlpnAvailable()) { + throw new VertxException("ALPN is not available for OpenSSL SSL/TLS engine"); + } + } + } + return engineOptions; + } + public SslContextProvider sslContextProvider() { - return sslChannelProvider.result().sslContextProvider(); + return sslContextProvider.result(); } @Override @@ -193,7 +236,7 @@ protected synchronized boolean accept() { return true; } - public void accept(Channel ch, SslChannelProvider sslChannelProvider, SSLHelper sslHelper, ServerSSLOptions sslOptions) { + public void accept(Channel ch, SslContextProvider sslChannelProvider, SslContextManager sslContextManager, ServerSSLOptions sslOptions) { if (!this.accept()) { ch.close(); return; @@ -213,31 +256,32 @@ public void accept(Channel ch, SslChannelProvider sslChannelProvider, SSLHelper if (idle != null) { ch.pipeline().remove(idle); } - configurePipeline(future.getNow(), sslChannelProvider, sslHelper, sslOptions); + configurePipeline(future.getNow(), sslChannelProvider, sslContextManager, sslOptions); } else { //No need to close the channel.HAProxyMessageDecoder already did handleException(future.cause()); } }); } else { - configurePipeline(ch, sslChannelProvider, sslHelper, sslOptions); + configurePipeline(ch, sslChannelProvider, sslContextManager, sslOptions); } } - private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider, SSLHelper sslHelper, SSLOptions sslOptions) { + private void configurePipeline(Channel ch, SslContextProvider sslContextProvider, SslContextManager sslContextManager, ServerSSLOptions sslOptions) { if (options.isSsl()) { + SslChannelProvider sslChannelProvider = new SslChannelProvider(vertx, sslContextProvider, sslOptions.isSni()); ch.pipeline().addLast("ssl", sslChannelProvider.createServerHandler(options.isUseAlpn(), options.getSslHandshakeTimeout(), options.getSslHandshakeTimeoutUnit())); ChannelPromise p = ch.newPromise(); ch.pipeline().addLast("handshaker", new SslHandshakeCompletionHandler(p)); p.addListener(future -> { if (future.isSuccess()) { - connected(ch, sslHelper, sslOptions); + connected(ch, sslContextManager, sslOptions); } else { handleException(future.cause()); } }); } else { - connected(ch, sslHelper, sslOptions); + connected(ch, sslContextManager, sslOptions); } if (trafficShapingHandler != null) { ch.pipeline().addFirst("globalTrafficShaping", trafficShapingHandler); @@ -250,10 +294,10 @@ private void handleException(Throwable cause) { } } - private void connected(Channel ch, SSLHelper sslHelper, SSLOptions sslOptions) { + private void connected(Channel ch, SslContextManager sslContextManager, SSLOptions sslOptions) { initChannel(ch.pipeline(), options.isSsl()); TCPMetrics metrics = getMetrics(); - VertxHandler handler = VertxHandler.create(ctx -> new NetSocketImpl(context, ctx, sslHelper, sslOptions, metrics, options.isRegisterWriteHandler())); + VertxHandler handler = VertxHandler.create(ctx -> new NetSocketImpl(context, ctx, sslContextManager, sslOptions, metrics, options.isRegisterWriteHandler())); handler.removeHandler(NetSocketImpl::unregisterEventBusHandler); handler.addHandler(conn -> { if (metrics != null) { @@ -309,7 +353,7 @@ protected void configure(SSLOptions options) { } public int sniEntrySize() { - return sslHelper.sniEntrySize(); + return sslContextManager.sniEntrySize(); } public Future updateSSLOptions(ServerSSLOptions options, boolean force) { @@ -318,10 +362,10 @@ public Future updateSSLOptions(ServerSSLOptions options, boolean force) return server.updateSSLOptions(options, force); } else { ContextInternal ctx = vertx.getOrCreateContext(); - Future fut; - SslChannelProvider current; + Future fut; + SslContextProvider current; synchronized (this) { - current = sslChannelProvider.result(); + current = sslContextProvider.result(); if (updateInProgress == null) { ServerSSLOptions sslOptions = options.copy(); configure(sslOptions); @@ -329,10 +373,9 @@ public Future updateSSLOptions(ServerSSLOptions options, boolean force) if (clientAuth == null) { clientAuth = ClientAuth.NONE; } - updateInProgress = sslHelper.resolveSslChannelProvider( + updateInProgress = sslContextManager.resolveSslContextProvider( sslOptions, null, - sslOptions.isSni(), clientAuth, sslOptions.getApplicationLayerProtocols(), force, @@ -346,7 +389,7 @@ public Future updateSSLOptions(ServerSSLOptions options, boolean force) synchronized (this) { updateInProgress = null; if (ar.succeeded()) { - sslChannelProvider = fut; + sslContextProvider = fut; } } }); @@ -419,9 +462,9 @@ private synchronized Future bind(ContextInternal context, SocketAddress PromiseInternal promise = listenContext.promise(); if (main == null) { - SSLHelper helper; + SslContextManager helper; try { - helper = new SSLHelper(SSLHelper.resolveEngineOptions(options.getSslEngineOptions(), options.isUseAlpn())); + helper = new SslContextManager(resolveEngineOptions(options.getSslEngineOptions(), options.isUseAlpn())); } catch (Exception e) { return context.failedFuture(e); } @@ -429,14 +472,14 @@ private synchronized Future bind(ContextInternal context, SocketAddress // The first server binds the socket actualServer = this; bindFuture = promise; - sslHelper = helper; + sslContextManager = helper; trafficShapingHandler = createTrafficShapingHandler(); initializer = new NetSocketInitializer(context, handler, exceptionHandler, trafficShapingHandler); worker = ch -> { // Should close if the channel group is closed actually or check that channelGroup.add(ch); - Future scp = sslChannelProvider; - initializer.accept(ch, scp != null ? scp.result() : null, sslHelper, options.getSslOptions()); + Future scp = sslContextProvider; + initializer.accept(ch, scp != null ? scp.result() : null, sslContextManager, options.getSslOptions()); }; servers = new HashSet<>(); servers.add(this); @@ -457,7 +500,7 @@ private synchronized Future bind(ContextInternal context, SocketAddress if (options.isSsl()) { ServerSSLOptions sslOptions = options.getSslOptions(); configure(sslOptions); - sslChannelProvider = sslHelper.resolveSslChannelProvider(sslOptions, null, sslOptions.isSni(), sslOptions.getClientAuth(), sslOptions.getApplicationLayerProtocols(), listenContext).onComplete(ar -> { + sslContextProvider = sslContextManager.resolveSslContextProvider(sslOptions, null, sslOptions.getClientAuth(), sslOptions.getApplicationLayerProtocols(), listenContext).onComplete(ar -> { if (ar.succeeded()) { bind(hostOrPath, context, bindAddress, localAddress, shared, promise, sharedNetServers, id); } else { @@ -486,8 +529,8 @@ private synchronized Future bind(ContextInternal context, SocketAddress initializer = new NetSocketInitializer(context, handler, exceptionHandler, trafficShapingHandler); worker = ch -> { group.add(ch); - Future scp = actualServer.sslChannelProvider; - initializer.accept(ch, scp != null ? scp.result() : null, sslHelper, options.getSslOptions()); + Future scp = actualServer.sslContextProvider; + initializer.accept(ch, scp != null ? scp.result() : null, sslContextManager, options.getSslOptions()); }; actualServer.servers.add(this); actualServer.channelBalancer.addWorker(eventLoop, worker); diff --git a/src/main/java/io/vertx/core/net/impl/NetServerInternal.java b/src/main/java/io/vertx/core/net/impl/NetServerInternal.java index 4c7586205b6..b40edec1c2e 100644 --- a/src/main/java/io/vertx/core/net/impl/NetServerInternal.java +++ b/src/main/java/io/vertx/core/net/impl/NetServerInternal.java @@ -14,6 +14,7 @@ import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.internal.ContextInternal; +import io.vertx.core.internal.tls.SslContextProvider; import io.vertx.core.net.*; /** 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 bfae59f940c..98f22cc48e1 100644 --- a/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java @@ -29,6 +29,9 @@ import io.vertx.core.http.ClientAuth; import io.vertx.core.internal.ContextInternal; import io.vertx.core.internal.PromiseInternal; +import io.vertx.core.internal.tls.SslContextManager; +import io.vertx.core.internal.net.SslChannelProvider; +import io.vertx.core.internal.net.SslHandshakeCompletionHandler; import io.vertx.core.net.*; import io.vertx.core.internal.net.NetSocketInternal; import io.vertx.core.spi.metrics.TCPMetrics; @@ -53,7 +56,7 @@ public class NetSocketImpl extends VertxConnection implements NetSocketInternal { private final String writeHandlerID; - private final SSLHelper sslHelper; + private final SslContextManager sslContextManager; private final SSLOptions sslOptions; private final SocketAddress remoteAddress; private final TCPMetrics metrics; @@ -68,23 +71,23 @@ public class NetSocketImpl extends VertxConnection implements NetSocketInternal public NetSocketImpl(ContextInternal context, ChannelHandlerContext channel, - SSLHelper sslHelper, + SslContextManager sslContextManager, SSLOptions sslOptions, TCPMetrics metrics, boolean registerWriteHandler) { - this(context, channel, null, sslHelper, sslOptions, metrics, null, registerWriteHandler); + this(context, channel, null, sslContextManager, sslOptions, metrics, null, registerWriteHandler); } public NetSocketImpl(ContextInternal context, ChannelHandlerContext channel, SocketAddress remoteAddress, - SSLHelper sslHelper, + SslContextManager sslContextManager, SSLOptions sslOptions, TCPMetrics metrics, String negotiatedApplicationLayerProtocol, boolean registerWriteHandler) { super(context, channel); - this.sslHelper = sslHelper; + this.sslContextManager = sslContextManager; this.sslOptions = sslOptions; this.writeHandlerID = registerWriteHandler ? "__vertx.net." + UUID.randomUUID() : null; this.remoteAddress = remoteAddress; @@ -300,25 +303,23 @@ private Future sslUpgrade(String serverName, SSLOptions sslOptions) { .compose(v -> { if (sslOptions instanceof ClientSSLOptions) { ClientSSLOptions clientSSLOptions = (ClientSSLOptions) sslOptions; - return sslHelper.resolveSslChannelProvider( + return sslContextManager.resolveSslContextProvider( sslOptions, clientSSLOptions.getHostnameVerificationAlgorithm(), - false, null, null, - context); + context).map(p -> new SslChannelProvider(context.owner(), p, false)); } else { ServerSSLOptions serverSSLOptions = (ServerSSLOptions) sslOptions; ClientAuth clientAuth = serverSSLOptions.getClientAuth(); if (clientAuth == null) { clientAuth = ClientAuth.NONE; } - return sslHelper.resolveSslChannelProvider( + return sslContextManager.resolveSslContextProvider( sslOptions, null, - serverSSLOptions.isSni(), clientAuth, - null, context); + null, context).map(p -> new SslChannelProvider(context.owner(), p, serverSSLOptions.isSni())); } }) .transform(ar -> { diff --git a/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java b/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java deleted file mode 100644 index 117efb636c0..00000000000 --- a/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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.netty.buffer.ByteBufAllocator; -import io.netty.channel.ChannelHandler; -import io.netty.handler.ssl.SniHandler; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslHandler; -import io.netty.util.AsyncMapping; -import io.netty.util.concurrent.ImmediateExecutor; -import io.vertx.core.VertxException; -import io.vertx.core.net.SocketAddress; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.TrustManager; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; - -/** - * Provider for {@link SslHandler} and {@link SniHandler}. - *
- * {@link SslContext} instances are cached and reused. - */ -public class SslChannelProvider { - - private final Executor workerPool; - private final boolean useWorkerPool; - private final boolean sni; - private final SslContextProvider sslContextProvider; - private final SslContext[] sslContexts = new SslContext[2]; - private final Map[] sslContextMaps = new Map[]{ - new ConcurrentHashMap<>(), new ConcurrentHashMap<>() - }; - - public SslChannelProvider(SslContextProvider sslContextProvider, - boolean sni, - Executor workerPool, - boolean useWorkerPool) { - this.workerPool = workerPool; - this.useWorkerPool = useWorkerPool; - this.sni = sni; - this.sslContextProvider = sslContextProvider; - } - - public int sniEntrySize() { - return sslContextMaps[0].size() + sslContextMaps[1].size(); - } - - public SslContextProvider sslContextProvider() { - return sslContextProvider; - } - - public SslContext sslClientContext(String serverName, boolean useAlpn) { - try { - return sslContext(serverName, useAlpn, false); - } catch (Exception e) { - throw new VertxException(e); - } - } - - public SslContext sslContext(String serverName, boolean useAlpn, boolean server) throws Exception { - int idx = idx(useAlpn); - if (serverName != null) { - KeyManagerFactory kmf = sslContextProvider.resolveKeyManagerFactory(serverName); - TrustManager[] trustManagers = sslContextProvider.resolveTrustManagers(serverName); - if (kmf != null || trustManagers != null || !server) { - return sslContextMaps[idx].computeIfAbsent(serverName, s -> sslContextProvider.createContext(server, kmf, trustManagers, s, useAlpn)); - } - } - if (sslContexts[idx] == null) { - SslContext context = sslContextProvider.createContext(server, null, null, serverName, useAlpn); - sslContexts[idx] = context; - } - return sslContexts[idx]; - } - - public SslContext sslServerContext(boolean useAlpn) { - try { - return sslContext(null, useAlpn, true); - } catch (Exception e) { - throw new VertxException(e); - } - } - - /** - * Server name {@link AsyncMapping} for {@link SniHandler}, mapping happens on a Vert.x worker thread. - * - * @return the {@link AsyncMapping} - */ - public AsyncMapping serverNameMapping(boolean useAlpn) { - return (AsyncMapping) (serverName, promise) -> { - workerPool.execute(() -> { - SslContext sslContext; - try { - sslContext = sslContext(serverName, useAlpn, true); - } catch (Exception e) { - promise.setFailure(e); - return; - } - promise.setSuccess(sslContext); - }); - return promise; - }; - } - - public SslHandler createClientSslHandler(SocketAddress peerAddress, String serverName, boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { - SslContext sslContext = sslClientContext(serverName, useAlpn); - SslHandler sslHandler; - Executor delegatedTaskExec = useWorkerPool ? workerPool : ImmediateExecutor.INSTANCE; - if (peerAddress != null && peerAddress.isInetSocket()) { - sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, peerAddress.host(), peerAddress.port(), delegatedTaskExec); - } else { - sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); - } - sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); - return sslHandler; - } - - public ChannelHandler createServerHandler(boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { - if (sni) { - return createSniHandler(useAlpn, sslHandshakeTimeout, sslHandshakeTimeoutUnit); - } else { - return createServerSslHandler(useAlpn, sslHandshakeTimeout, sslHandshakeTimeoutUnit); - } - } - - private SslHandler createServerSslHandler(boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { - SslContext sslContext = sslServerContext(useAlpn); - Executor delegatedTaskExec = useWorkerPool ? workerPool : ImmediateExecutor.INSTANCE; - SslHandler sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); - sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); - return sslHandler; - } - - private SniHandler createSniHandler(boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { - Executor delegatedTaskExec = useWorkerPool ? workerPool : ImmediateExecutor.INSTANCE; - return new VertxSniHandler(serverNameMapping(useAlpn), sslHandshakeTimeoutUnit.toMillis(sslHandshakeTimeout), delegatedTaskExec); - } - - private static int idx(boolean useAlpn) { - return useAlpn ? 0 : 1; - } -} diff --git a/src/main/java/io/vertx/core/net/impl/SslContextUpdate.java b/src/main/java/io/vertx/core/net/impl/SslContextUpdate.java index 4579f798103..b93b8951a05 100644 --- a/src/main/java/io/vertx/core/net/impl/SslContextUpdate.java +++ b/src/main/java/io/vertx/core/net/impl/SslContextUpdate.java @@ -10,6 +10,8 @@ */ package io.vertx.core.net.impl; +import io.vertx.core.internal.net.SslChannelProvider; + /** * Result of the SslContext update operation. */ diff --git a/src/test/java/io/vertx/core/net/impl/SSLHelperTest.java b/src/test/java/io/vertx/core/internal/tls/SslContextManagerTest.java similarity index 74% rename from src/test/java/io/vertx/core/net/impl/SSLHelperTest.java rename to src/test/java/io/vertx/core/internal/tls/SslContextManagerTest.java index b30330b93ac..8f3dcfbf4c4 100755 --- a/src/test/java/io/vertx/core/net/impl/SSLHelperTest.java +++ b/src/test/java/io/vertx/core/internal/tls/SslContextManagerTest.java @@ -9,7 +9,7 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.net.impl; +package io.vertx.core.internal.tls; import io.netty.buffer.ByteBufAllocator; import io.netty.handler.ssl.*; @@ -18,6 +18,7 @@ import io.vertx.core.internal.ContextInternal; import io.vertx.core.json.JsonObject; import io.vertx.core.net.*; +import io.vertx.core.net.impl.NetServerImpl; import io.vertx.test.core.VertxTestBase; import io.vertx.test.tls.Cert; import io.vertx.test.tls.Trust; @@ -32,7 +33,7 @@ /** * @author Julien Viet */ -public class SSLHelperTest extends VertxTestBase { +public class SslContextManagerTest extends VertxTestBase { @Test public void testUseJdkCiphersWhenNotSpecified() throws Exception { @@ -40,9 +41,9 @@ public void testUseJdkCiphersWhenNotSpecified() throws Exception { context.init(null, null, null); SSLEngine engine = context.createSSLEngine(); String[] expected = engine.getEnabledCipherSuites(); - SSLHelper helper = new SSLHelper(SSLHelper.resolveEngineOptions(null, false)); + SslContextManager helper = new SslContextManager(NetServerImpl.resolveEngineOptions(null, false)); helper - .buildContextProvider(new SSLOptions().setKeyCertOptions(Cert.CLIENT_JKS.get()).setTrustOptions(Trust.SERVER_JKS.get()), null, ClientAuth.NONE, null, false, (ContextInternal) vertx.getOrCreateContext()) + .buildSslContextProvider(new SSLOptions().setKeyCertOptions(Cert.CLIENT_JKS.get()).setTrustOptions(Trust.SERVER_JKS.get()), null, ClientAuth.NONE, null, false, (ContextInternal) vertx.getOrCreateContext()) .onComplete(onSuccess(provider -> { SslContext ctx = provider.createContext(false, false); assertEquals(new HashSet<>(Arrays.asList(expected)), new HashSet<>(ctx.cipherSuites())); @@ -54,8 +55,8 @@ public void testUseJdkCiphersWhenNotSpecified() throws Exception { @Test public void testUseOpenSSLCiphersWhenNotSpecified() throws Exception { Set expected = OpenSsl.availableOpenSslCipherSuites(); - SSLHelper helper = new SSLHelper(new OpenSSLEngineOptions()); - helper.buildContextProvider(new SSLOptions().setKeyCertOptions(Cert.CLIENT_PEM.get()).setTrustOptions(Trust.SERVER_PEM.get()), null, ClientAuth.NONE, null, false, (ContextInternal) vertx.getOrCreateContext()).onComplete(onSuccess(provider -> { + SslContextManager helper = new SslContextManager(new OpenSSLEngineOptions()); + helper.buildSslContextProvider(new SSLOptions().setKeyCertOptions(Cert.CLIENT_PEM.get()).setTrustOptions(Trust.SERVER_PEM.get()), null, ClientAuth.NONE, null, false, (ContextInternal) vertx.getOrCreateContext()).onComplete(onSuccess(provider -> { SslContext ctx = provider.createContext(false, false); assertEquals(expected, new HashSet<>(ctx.cipherSuites())); testComplete(); @@ -82,13 +83,13 @@ private void testOpenSslServerSessionContext(boolean testDefault){ engineOptions = new OpenSSLEngineOptions(); } - SSLHelper defaultHelper = new SSLHelper(engineOptions); + SslContextManager defaultHelper = new SslContextManager(engineOptions); SSLOptions sslOptions = new SSLOptions(); sslOptions.setKeyCertOptions(Cert.SERVER_PEM.get()).setTrustOptions(Trust.SERVER_PEM.get()); defaultHelper - .buildContextProvider(sslOptions, null, ClientAuth.NONE, null, false, (ContextInternal) vertx.getOrCreateContext()) + .buildSslContextProvider(sslOptions, null, ClientAuth.NONE, null, false, (ContextInternal) vertx.getOrCreateContext()) .onComplete(onSuccess(provider -> { SslContext ctx = provider.createContext(true, false); @@ -116,9 +117,9 @@ public void testPreserveEnabledCipherSuitesOrder() throws Exception { assertEquals(new ArrayList<>(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(SSLHelper.resolveEngineOptions(null, false)); + SslContextManager helper = new SslContextManager(NetServerImpl.resolveEngineOptions(null, false)); helper - .buildContextProvider(options, null, ClientAuth.NONE, null, false, (ContextInternal) vertx.getOrCreateContext()) + .buildSslContextProvider(options, null, ClientAuth.NONE, null, false, (ContextInternal) vertx.getOrCreateContext()) .onComplete(onSuccess(sslContextProvider -> { assertEquals(new HashSet<>(Arrays.asList(createEngine(sslContextProvider).getEnabledCipherSuites())), new HashSet<>(Arrays.asList(engine.getEnabledCipherSuites()))); testComplete(); @@ -145,16 +146,16 @@ public void testPreserveEnabledSecureTransportProtocolOrder() throws Exception { @Test public void testCache() throws Exception { ContextInternal ctx = (ContextInternal) vertx.getOrCreateContext(); - SSLHelper helper = new SSLHelper(new JdkSSLEngineOptions(), 4); + SslContextManager helper = new SslContextManager(new JdkSSLEngineOptions(), 4); SSLOptions options = new SSLOptions().setKeyCertOptions(Cert.SERVER_JKS.get()); - SslChannelProvider f1 = awaitFuture(helper.resolveSslChannelProvider(options, "", false, ClientAuth.NONE, null, ctx)); - SslChannelProvider f2 = awaitFuture(helper.resolveSslChannelProvider(options, "", false, ClientAuth.NONE, null, ctx)); + SslContextProvider f1 = awaitFuture(helper.resolveSslContextProvider(options, "", ClientAuth.NONE, null, ctx)); + SslContextProvider f2 = awaitFuture(helper.resolveSslContextProvider(options, "", ClientAuth.NONE, null, ctx)); assertSame(f1, f2); - awaitFuture(helper.resolveSslChannelProvider(new SSLOptions().setKeyCertOptions(Cert.SERVER_PKCS12.get()), "", false, ClientAuth.NONE, null, ctx)); - awaitFuture(helper.resolveSslChannelProvider(new SSLOptions().setKeyCertOptions(Cert.SERVER_PEM.get()), "", false, ClientAuth.NONE, null, ctx)); - awaitFuture(helper.resolveSslChannelProvider(new SSLOptions().setKeyCertOptions(Cert.CLIENT_PEM.get()), "", false, ClientAuth.NONE, null, ctx)); - awaitFuture(helper.resolveSslChannelProvider(new SSLOptions().setKeyCertOptions(Cert.SNI_PEM.get()), "", false, ClientAuth.NONE, null, ctx)); - f2 = awaitFuture(helper.resolveSslChannelProvider(options, "", false, ClientAuth.NONE, null, ctx)); + awaitFuture(helper.resolveSslContextProvider(new SSLOptions().setKeyCertOptions(Cert.SERVER_PKCS12.get()), "", ClientAuth.NONE, null, ctx)); + awaitFuture(helper.resolveSslContextProvider(new SSLOptions().setKeyCertOptions(Cert.SERVER_PEM.get()), "", ClientAuth.NONE, null, ctx)); + awaitFuture(helper.resolveSslContextProvider(new SSLOptions().setKeyCertOptions(Cert.CLIENT_PEM.get()), "", ClientAuth.NONE, null, ctx)); + awaitFuture(helper.resolveSslContextProvider(new SSLOptions().setKeyCertOptions(Cert.SNI_PEM.get()), "", ClientAuth.NONE, null, ctx)); + f2 = awaitFuture(helper.resolveSslContextProvider(options, "", ClientAuth.NONE, null, ctx)); assertNotSame(f1, f2); } @@ -188,9 +189,9 @@ public void testSetVersions() { } private void testTLSVersions(SSLOptions options, Consumer check) { - SSLHelper helper = new SSLHelper(SSLHelper.resolveEngineOptions(null, false)); + SslContextManager helper = new SslContextManager(NetServerImpl.resolveEngineOptions(null, false)); helper - .buildContextProvider(options, null, ClientAuth.NONE, null, false, (ContextInternal) vertx.getOrCreateContext()) + .buildSslContextProvider(options, null, ClientAuth.NONE, null, false, (ContextInternal) vertx.getOrCreateContext()) .onComplete(onSuccess(sslContextProvider -> { SSLEngine engine = createEngine(sslContextProvider); check.accept(engine); diff --git a/src/test/java/io/vertx/it/SSLEngineTest.java b/src/test/java/io/vertx/it/SSLEngineTest.java index b4db8e60561..493a28a9410 100644 --- a/src/test/java/io/vertx/it/SSLEngineTest.java +++ b/src/test/java/io/vertx/it/SSLEngineTest.java @@ -19,7 +19,7 @@ import io.vertx.core.net.OpenSSLEngineOptions; import io.vertx.core.net.SSLEngineOptions; import io.vertx.core.net.impl.NetServerInternal; -import io.vertx.core.net.impl.SslContextProvider; +import io.vertx.core.internal.tls.SslContextProvider; import io.vertx.test.tls.Cert; import org.junit.Test;