diff --git a/0001-Remove-optional-dep-Blockhound.patch b/0001-Remove-optional-dep-Blockhound.patch new file mode 100644 index 0000000000000000000000000000000000000000..f98082fc9d657f9436eac463b2359b92095fd482 --- /dev/null +++ b/0001-Remove-optional-dep-Blockhound.patch @@ -0,0 +1,1118 @@ +From fde40bfedf245b8cd6f48d8681a29df1aa5d4036 Mon Sep 17 00:00:00 2001 +From: Mat Booth +Date: Mon, 7 Sep 2020 12:17:31 +0100 +Subject: [PATCH 1/7] Remove optional dep Blockhound + +--- + common/pom.xml | 5 - + .../java/io/netty/util/internal/Hidden.java | 190 ------ + ...ockhound.integration.BlockHoundIntegration | 14 - + pom.xml | 8 - + transport-blockhound-tests/pom.xml | 163 ----- + .../NettyBlockHoundIntegrationTest.java | 575 ------------------ + .../netty/util/internal/localhost_server.key | 28 - + .../netty/util/internal/localhost_server.pem | 17 - + .../io/netty/util/internal/mutual_auth_ca.pem | 19 - + 9 files changed, 1019 deletions(-) + delete mode 100644 common/src/main/java/io/netty/util/internal/Hidden.java + delete mode 100644 common/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration + delete mode 100644 transport-blockhound-tests/pom.xml + delete mode 100644 transport-blockhound-tests/src/test/java/io/netty/util/internal/NettyBlockHoundIntegrationTest.java + delete mode 100644 transport-blockhound-tests/src/test/resources/io/netty/util/internal/localhost_server.key + delete mode 100644 transport-blockhound-tests/src/test/resources/io/netty/util/internal/localhost_server.pem + delete mode 100644 transport-blockhound-tests/src/test/resources/io/netty/util/internal/mutual_auth_ca.pem + +diff --git a/common/pom.xml b/common/pom.xml +index d59644adcf..77a3e430da 100644 +--- a/common/pom.xml ++++ b/common/pom.xml +@@ -82,11 +82,6 @@ + log4j-core + test + +- +- io.projectreactor.tools +- blockhound +- true +- + + org.mockito + mockito-core +diff --git a/common/src/main/java/io/netty/util/internal/Hidden.java b/common/src/main/java/io/netty/util/internal/Hidden.java +deleted file mode 100644 +index cf32e154ff..0000000000 +--- a/common/src/main/java/io/netty/util/internal/Hidden.java ++++ /dev/null +@@ -1,190 +0,0 @@ +-/* +- * Copyright 2019 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +- +-package io.netty.util.internal; +- +-import io.netty.util.concurrent.FastThreadLocalThread; +-import reactor.blockhound.BlockHound; +-import reactor.blockhound.integration.BlockHoundIntegration; +- +-import java.util.function.Function; +-import java.util.function.Predicate; +- +-/** +- * Contains classes that must have public visibility but are not public API. +- */ +-class Hidden { +- +- /** +- * This class integrates Netty with BlockHound. +- *

+- * It is public but only because of the ServiceLoader's limitations +- * and SHOULD NOT be considered a public API. +- */ +- @UnstableApi +- @SuppressJava6Requirement(reason = "BlockHound is Java 8+, but this class is only loaded by it's SPI") +- public static final class NettyBlockHoundIntegration implements BlockHoundIntegration { +- +- @Override +- public void applyTo(BlockHound.Builder builder) { +- builder.allowBlockingCallsInside( +- "io.netty.channel.nio.NioEventLoop", +- "handleLoopException" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.channel.kqueue.KQueueEventLoop", +- "handleLoopException" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.channel.epoll.EpollEventLoop", +- "handleLoopException" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.util.HashedWheelTimer", +- "start" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.util.HashedWheelTimer", +- "stop" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.util.HashedWheelTimer$Worker", +- "waitForNextTick" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.util.concurrent.SingleThreadEventExecutor", +- "confirmShutdown" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.buffer.PoolArena", +- "lock" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.buffer.PoolSubpage", +- "lock" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.buffer.PoolChunk", +- "allocateRun" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.buffer.PoolChunk", +- "free" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.handler.ssl.SslHandler", +- "handshake" +- ); +- +- builder.allowBlockingCallsInside( +- "io.netty.handler.ssl.SslHandler", +- "runAllDelegatedTasks" +- ); +- builder.allowBlockingCallsInside( +- "io.netty.handler.ssl.SslHandler", +- "runDelegatedTasks" +- ); +- builder.allowBlockingCallsInside( +- "io.netty.util.concurrent.GlobalEventExecutor", +- "takeTask"); +- +- builder.allowBlockingCallsInside( +- "io.netty.util.concurrent.GlobalEventExecutor", +- "addTask"); +- +- builder.allowBlockingCallsInside( +- "io.netty.util.concurrent.SingleThreadEventExecutor", +- "takeTask"); +- +- builder.allowBlockingCallsInside( +- "io.netty.util.concurrent.SingleThreadEventExecutor", +- "addTask"); +- +- builder.allowBlockingCallsInside( +- "io.netty.handler.ssl.ReferenceCountedOpenSslClientContext$ExtendedTrustManagerVerifyCallback", +- "verify"); +- +- builder.allowBlockingCallsInside( +- "io.netty.handler.ssl.JdkSslContext$Defaults", +- "init"); +- +- // Let's whitelist SSLEngineImpl.unwrap(...) for now as it may fail otherwise for TLS 1.3. +- // See https://mail.openjdk.java.net/pipermail/security-dev/2020-August/022271.html +- builder.allowBlockingCallsInside( +- "sun.security.ssl.SSLEngineImpl", +- "unwrap"); +- +- builder.allowBlockingCallsInside( +- "sun.security.ssl.SSLEngineImpl", +- "wrap"); +- +- builder.allowBlockingCallsInside( +- "io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider", +- "parse"); +- +- builder.allowBlockingCallsInside( +- "io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider", +- "parseEtcResolverSearchDomains"); +- +- builder.allowBlockingCallsInside( +- "io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider", +- "parseEtcResolverOptions"); +- +- builder.allowBlockingCallsInside( +- "io.netty.resolver.HostsFileEntriesProvider$ParserImpl", +- "parse"); +- +- builder.allowBlockingCallsInside( +- "io.netty.util.NetUtil$SoMaxConnAction", +- "run"); +- +- builder.allowBlockingCallsInside("io.netty.util.internal.ReferenceCountUpdater", +- "retryRelease0"); +- +- builder.allowBlockingCallsInside("io.netty.util.internal.PlatformDependent", "createTempFile"); +- builder.nonBlockingThreadPredicate(new Function, Predicate>() { +- @Override +- public Predicate apply(final Predicate p) { +- return new Predicate() { +- @Override +- @SuppressJava6Requirement(reason = "Predicate#test") +- public boolean test(Thread thread) { +- return p.test(thread) || +- thread instanceof FastThreadLocalThread && +- !((FastThreadLocalThread) thread).permitBlockingCalls(); +- } +- }; +- } +- }); +- } +- +- @Override +- public int compareTo(BlockHoundIntegration o) { +- return 0; +- } +- } +-} +diff --git a/common/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration b/common/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration +deleted file mode 100644 +index e33bea796c..0000000000 +--- a/common/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration ++++ /dev/null +@@ -1,14 +0,0 @@ +-# Copyright 2019 The Netty Project +-# +-# The Netty Project licenses this file to you under the Apache License, +-# version 2.0 (the "License"); you may not use this file except in compliance +-# with the License. You may obtain a copy of the License at: +-# +-# https://www.apache.org/licenses/LICENSE-2.0 +-# +-# Unless required by applicable law or agreed to in writing, software +-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +-# License for the specific language governing permissions and limitations +-# under the License. +-io.netty.util.internal.Hidden$NettyBlockHoundIntegration +\ No newline at end of file +diff --git a/pom.xml b/pom.xml +index 0cea28ab0a..bf36e0b533 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -779,7 +779,6 @@ + testsuite-native-image + testsuite-native-image-client + testsuite-native-image-client-runtime-init +- transport-blockhound-tests + microbench + bom + +@@ -1198,13 +1197,6 @@ + ${log4j2.version} + test + +- +- +- +- io.projectreactor.tools +- blockhound +- 1.0.6.RELEASE +- + + + +diff --git a/transport-blockhound-tests/pom.xml b/transport-blockhound-tests/pom.xml +deleted file mode 100644 +index 4b273251fe..0000000000 +--- a/transport-blockhound-tests/pom.xml ++++ /dev/null +@@ -1,163 +0,0 @@ +- +- +- +- +- 4.0.0 +- +- io.netty +- netty-parent +- 4.1.114.Final +- +- +- netty-transport-blockhound-tests +- jar +- +- Tests for the BlockHound integration. +- +- +- Netty/Transport/BlockHound/Tests +- +- +- +- java13 +- +- 13 +- +- +- -XX:+AllowRedefinitionToAddDeleteMethods +- +- +- +- java14 +- +- 14 +- +- +- -XX:+AllowRedefinitionToAddDeleteMethods +- +- +- +- java15 +- +- 15 +- +- +- -XX:+AllowRedefinitionToAddDeleteMethods +- +- +- +- java16 +- +- 16 +- +- +- -XX:+AllowRedefinitionToAddDeleteMethods +- +- +- +- java17 +- +- 17 +- +- +- -XX:+AllowRedefinitionToAddDeleteMethods +- +- +- +- java18 +- +- 18 +- +- +- -XX:+AllowRedefinitionToAddDeleteMethods +- +- +- +- java19 +- +- 19 +- +- +- -XX:+AllowRedefinitionToAddDeleteMethods +- +- +- +- java20 +- +- 20 +- +- +- -XX:+AllowRedefinitionToAddDeleteMethods +- +- +- +- java21 +- +- 21 +- +- +- -XX:+AllowRedefinitionToAddDeleteMethods +- +- +- +- +- +- 1.8 +- 1.8 +- +- --add-exports java.base/sun.security.x509=ALL-UNNAMED +- true +- +- true +- io.netty.transport_blockhound_tests +- +- +- +- +- ${project.groupId} +- netty-transport +- ${project.version} +- +- +- ${project.groupId} +- netty-handler +- ${project.version} +- +- +- ${project.groupId} +- netty-resolver-dns +- ${project.version} +- +- +- ${project.groupId} +- ${tcnative.artifactId} +- ${tcnative.classifier} +- true +- +- +- +- org.bouncycastle +- bcpkix-jdk15on +- true +- +- +- io.projectreactor.tools +- blockhound +- test +- +- +- +diff --git a/transport-blockhound-tests/src/test/java/io/netty/util/internal/NettyBlockHoundIntegrationTest.java b/transport-blockhound-tests/src/test/java/io/netty/util/internal/NettyBlockHoundIntegrationTest.java +deleted file mode 100644 +index 945f2a2891..0000000000 +--- a/transport-blockhound-tests/src/test/java/io/netty/util/internal/NettyBlockHoundIntegrationTest.java ++++ /dev/null +@@ -1,575 +0,0 @@ +-/* +- * Copyright 2019 The Netty Project +- +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- +- * https://www.apache.org/licenses/LICENSE-2.0 +- +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.util.internal; +- +-import io.netty.bootstrap.Bootstrap; +-import io.netty.bootstrap.ServerBootstrap; +-import io.netty.buffer.ByteBuf; +-import io.netty.buffer.PooledByteBufAllocator; +-import io.netty.buffer.UnpooledByteBufAllocator; +-import io.netty.channel.Channel; +-import io.netty.channel.ChannelFuture; +-import io.netty.channel.ChannelFutureListener; +-import io.netty.channel.ChannelHandlerContext; +-import io.netty.channel.ChannelInboundHandlerAdapter; +-import io.netty.channel.ChannelInitializer; +-import io.netty.channel.EventLoopGroup; +-import io.netty.channel.nio.NioEventLoopGroup; +-import io.netty.channel.socket.nio.NioDatagramChannel; +-import io.netty.channel.socket.nio.NioServerSocketChannel; +-import io.netty.channel.socket.nio.NioSocketChannel; +-import io.netty.handler.ssl.SslContext; +-import io.netty.handler.ssl.SslContextBuilder; +-import io.netty.handler.ssl.SslHandler; +-import io.netty.handler.ssl.SslHandshakeCompletionEvent; +-import io.netty.handler.ssl.SslProvider; +-import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +-import io.netty.handler.ssl.util.SelfSignedCertificate; +-import io.netty.resolver.dns.DnsNameResolverBuilder; +-import io.netty.resolver.dns.DnsServerAddressStreamProviders; +-import io.netty.util.HashedWheelTimer; +-import io.netty.util.ReferenceCountUtil; +-import io.netty.util.concurrent.DefaultThreadFactory; +-import io.netty.util.concurrent.EventExecutor; +-import io.netty.util.concurrent.FastThreadLocalThread; +-import io.netty.util.concurrent.GlobalEventExecutor; +-import io.netty.util.concurrent.ImmediateEventExecutor; +-import io.netty.util.concurrent.ImmediateExecutor; +-import io.netty.util.concurrent.ScheduledFuture; +-import io.netty.util.concurrent.SingleThreadEventExecutor; +-import io.netty.util.internal.Hidden.NettyBlockHoundIntegration; +-import org.hamcrest.Matchers; +-import org.junit.jupiter.api.BeforeAll; +-import org.junit.jupiter.api.Test; +-import org.junit.jupiter.api.Timeout; +-import org.junit.jupiter.api.condition.DisabledIf; +-import reactor.blockhound.BlockHound; +-import reactor.blockhound.BlockingOperationError; +-import reactor.blockhound.integration.BlockHoundIntegration; +- +-import java.net.InetSocketAddress; +-import java.util.ArrayList; +-import java.util.List; +-import java.util.Queue; +-import java.util.ServiceLoader; +-import java.util.concurrent.Callable; +-import java.util.concurrent.CountDownLatch; +-import java.util.concurrent.ExecutionException; +-import java.util.concurrent.Executor; +-import java.util.concurrent.ExecutorService; +-import java.util.concurrent.Executors; +-import java.util.concurrent.Future; +-import java.util.concurrent.FutureTask; +-import java.util.concurrent.LinkedBlockingQueue; +-import java.util.concurrent.TimeUnit; +-import java.util.concurrent.atomic.AtomicLong; +-import java.util.concurrent.atomic.AtomicReference; +-import java.util.concurrent.locks.ReentrantLock; +- +-import static io.netty.buffer.Unpooled.wrappedBuffer; +-import static org.hamcrest.MatcherAssert.assertThat; +-import static org.junit.jupiter.api.Assertions.assertEquals; +-import static org.junit.jupiter.api.Assertions.assertNull; +-import static org.junit.jupiter.api.Assertions.assertTrue; +-import static org.junit.jupiter.api.Assertions.fail; +-import static org.junit.jupiter.api.Assumptions.assumeTrue; +- +-@DisabledIf("isDisabledIfJavaVersion18OrAbove") +-public class NettyBlockHoundIntegrationTest { +- +- private static boolean isDisabledIfJavaVersion18OrAbove() { +- return PlatformDependent.javaVersion() >= 18; +- } +- +- @BeforeAll +- public static void setUpClass() { +- BlockHound.install(); +- } +- +- @Test +- public void testServiceLoader() { +- for (BlockHoundIntegration integration : ServiceLoader.load(BlockHoundIntegration.class)) { +- if (integration instanceof NettyBlockHoundIntegration) { +- return; +- } +- } +- +- fail("NettyBlockHoundIntegration cannot be loaded with ServiceLoader"); +- } +- +- @Test +- public void testBlockingCallsInNettyThreads() throws Exception { +- final FutureTask future = new FutureTask<>(() -> { +- Thread.sleep(0); +- return null; +- }); +- GlobalEventExecutor.INSTANCE.execute(future); +- +- try { +- future.get(5, TimeUnit.SECONDS); +- fail("Expected an exception due to a blocking call but none was thrown"); +- } catch (ExecutionException e) { +- assertThat(e.getCause(), Matchers.instanceOf(BlockingOperationError.class)); +- } +- } +- +- @Test +- @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) +- public void testGlobalEventExecutorTakeTask() throws InterruptedException { +- testEventExecutorTakeTask(GlobalEventExecutor.INSTANCE); +- } +- +- @Test +- @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) +- public void testSingleThreadEventExecutorTakeTask() throws InterruptedException { +- SingleThreadEventExecutor executor = +- new SingleThreadEventExecutor(null, new DefaultThreadFactory("test"), true) { +- @Override +- protected void run() { +- while (!confirmShutdown()) { +- Runnable task = takeTask(); +- if (task != null) { +- task.run(); +- } +- } +- } +- }; +- testEventExecutorTakeTask(executor); +- } +- +- private static void testEventExecutorTakeTask(EventExecutor eventExecutor) throws InterruptedException { +- CountDownLatch latch = new CountDownLatch(1); +- ScheduledFuture f = eventExecutor.schedule(latch::countDown, 10, TimeUnit.MILLISECONDS); +- f.sync(); +- latch.await(); +- } +- +- @Test +- @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) +- public void testSingleThreadEventExecutorAddTask() throws Exception { +- TestLinkedBlockingQueue taskQueue = new TestLinkedBlockingQueue<>(); +- SingleThreadEventExecutor executor = +- new SingleThreadEventExecutor(null, new DefaultThreadFactory("test"), true) { +- @Override +- protected Queue newTaskQueue(int maxPendingTasks) { +- return taskQueue; +- } +- +- @Override +- protected void run() { +- while (!confirmShutdown()) { +- Runnable task = takeTask(); +- if (task != null) { +- task.run(); +- } +- } +- } +- }; +- taskQueue.emulateContention(); +- CountDownLatch latch = new CountDownLatch(1); +- executor.submit(() -> { +- executor.execute(() -> { }); // calls addTask +- latch.countDown(); +- }); +- taskQueue.waitUntilContented(); +- taskQueue.removeContention(); +- latch.await(); +- } +- +- @Test +- void permittingBlockingCallsInFastThreadLocalThreadSubclass() throws Exception { +- final FutureTask future = new FutureTask<>(() -> { +- Thread.sleep(0); +- return null; +- }); +- FastThreadLocalThread thread = new FastThreadLocalThread(future) { +- @Override +- public boolean permitBlockingCalls() { +- return true; // The Thread.sleep(0) call should not be flagged because we allow blocking calls. +- } +- }; +- thread.start(); +- future.get(5, TimeUnit.SECONDS); +- thread.join(); +- } +- +- @Test +- @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) +- public void testHashedWheelTimerStartStop() throws Exception { +- HashedWheelTimer timer = new HashedWheelTimer(); +- Future futureStart = GlobalEventExecutor.INSTANCE.submit(timer::start); +- futureStart.get(5, TimeUnit.SECONDS); +- Future futureStop = GlobalEventExecutor.INSTANCE.submit(timer::stop); +- futureStop.get(5, TimeUnit.SECONDS); +- } +- +- // Tests copied from io.netty.handler.ssl.SslHandlerTest +- @Test +- public void testHandshakeWithExecutorThatExecuteDirectory() throws Exception { +- testHandshakeWithExecutor(Runnable::run, "TLSv1.2"); +- } +- +- @Test +- public void testHandshakeWithExecutorThatExecuteDirectoryTLSv13() throws Exception { +- assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK)); +- testHandshakeWithExecutor(Runnable::run, "TLSv1.3"); +- } +- +- @Test +- public void testHandshakeWithImmediateExecutor() throws Exception { +- testHandshakeWithExecutor(ImmediateExecutor.INSTANCE, "TLSv1.2"); +- } +- +- @Test +- public void testHandshakeWithImmediateExecutorTLSv13() throws Exception { +- assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK)); +- testHandshakeWithExecutor(ImmediateExecutor.INSTANCE, "TLSv1.3"); +- } +- +- @Test +- public void testHandshakeWithImmediateEventExecutor() throws Exception { +- testHandshakeWithExecutor(ImmediateEventExecutor.INSTANCE, "TLSv1.2"); +- } +- +- @Test +- public void testHandshakeWithImmediateEventExecutorTLSv13() throws Exception { +- assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK)); +- testHandshakeWithExecutor(ImmediateEventExecutor.INSTANCE, "TLSv1.3"); +- } +- +- @Test +- public void testHandshakeWithExecutor() throws Exception { +- ExecutorService executorService = Executors.newCachedThreadPool(); +- try { +- testHandshakeWithExecutor(executorService, "TLSv1.2"); +- } finally { +- executorService.shutdown(); +- } +- } +- +- @Test +- public void testHandshakeWithExecutorTLSv13() throws Exception { +- assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK)); +- ExecutorService executorService = Executors.newCachedThreadPool(); +- try { +- testHandshakeWithExecutor(executorService, "TLSv1.3"); +- } finally { +- executorService.shutdown(); +- } +- } +- +- @Test +- public void testTrustManagerVerifyJDK() throws Exception { +- testTrustManagerVerify(SslProvider.JDK, "TLSv1.2"); +- } +- +- @Test +- public void testTrustManagerVerifyTLSv13JDK() throws Exception { +- assumeTrue(SslProvider.isTlsv13Supported(SslProvider.JDK)); +- testTrustManagerVerify(SslProvider.JDK, "TLSv1.3"); +- } +- +- @Test +- public void testTrustManagerVerifyOpenSSL() throws Exception { +- testTrustManagerVerify(SslProvider.OPENSSL, "TLSv1.2"); +- } +- +- @Test +- public void testTrustManagerVerifyTLSv13OpenSSL() throws Exception { +- assumeTrue(SslProvider.isTlsv13Supported(SslProvider.OPENSSL)); +- testTrustManagerVerify(SslProvider.OPENSSL, "TLSv1.3"); +- } +- +- @Test +- public void testSslHandlerWrapAllowsBlockingCalls() throws Exception { +- final SslContext sslClientCtx = +- SslContextBuilder.forClient() +- .trustManager(InsecureTrustManagerFactory.INSTANCE) +- .sslProvider(SslProvider.JDK) +- .build(); +- final SslHandler sslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT); +- final EventLoopGroup group = new NioEventLoopGroup(); +- final CountDownLatch activeLatch = new CountDownLatch(1); +- final AtomicReference error = new AtomicReference<>(); +- +- Channel sc = null; +- Channel cc = null; +- try { +- sc = new ServerBootstrap() +- .group(group) +- .channel(NioServerSocketChannel.class) +- .childHandler(new ChannelInboundHandlerAdapter()) +- .bind(new InetSocketAddress(0)) +- .syncUninterruptibly() +- .channel(); +- +- cc = new Bootstrap() +- .group(group) +- .channel(NioSocketChannel.class) +- .handler(new ChannelInitializer() { +- +- @Override +- protected void initChannel(Channel ch) { +- ch.pipeline().addLast(sslHandler); +- ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { +- +- @Override +- public void channelActive(ChannelHandlerContext ctx) { +- activeLatch.countDown(); +- } +- +- @Override +- public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { +- if (evt instanceof SslHandshakeCompletionEvent && +- ((SslHandshakeCompletionEvent) evt).cause() != null) { +- Throwable cause = ((SslHandshakeCompletionEvent) evt).cause(); +- cause.printStackTrace(); +- error.set(cause); +- } +- ctx.fireUserEventTriggered(evt); +- } +- }); +- } +- }) +- .connect(sc.localAddress()) +- .addListener((ChannelFutureListener) future -> +- future.channel().writeAndFlush(wrappedBuffer(new byte [] { 1, 2, 3, 4 }))) +- .syncUninterruptibly() +- .channel(); +- +- assertTrue(activeLatch.await(5, TimeUnit.SECONDS)); +- assertNull(error.get()); +- } finally { +- if (cc != null) { +- cc.close().syncUninterruptibly(); +- } +- if (sc != null) { +- sc.close().syncUninterruptibly(); +- } +- group.shutdownGracefully(); +- ReferenceCountUtil.release(sslClientCtx); +- } +- } +- +- @Test +- @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) +- public void pooledBufferAllocation() throws Exception { +- AtomicLong iterationCounter = new AtomicLong(); +- PooledByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT; +- FutureTask task = new FutureTask<>(() -> { +- List buffers = new ArrayList<>(); +- long count; +- do { +- count = iterationCounter.get(); +- } while (count == 0); +- for (int i = 0; i < 13; i++) { +- int size = 8 << i; +- buffers.add(allocator.ioBuffer(size, size)); +- } +- for (ByteBuf buffer : buffers) { +- buffer.release(); +- } +- return null; +- }); +- FastThreadLocalThread thread = new FastThreadLocalThread(task); +- thread.start(); +- do { +- allocator.dumpStats(); // This will take internal pool locks and we'll race with the thread. +- iterationCounter.set(1); +- } while (thread.isAlive()); +- thread.join(); +- task.get(); +- } +- +- @Test +- @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) +- public void testUnixResolverDnsServerAddressStreamProvider_Parse() throws InterruptedException { +- doTestParseResolverFilesAllowsBlockingCalls(DnsServerAddressStreamProviders::unixDefault); +- } +- +- @Test +- @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) +- public void testHostsFileParser_Parse() throws InterruptedException { +- doTestParseResolverFilesAllowsBlockingCalls(DnsNameResolverBuilder::new); +- } +- +- @Test +- @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) +- public void testUnixResolverDnsServerAddressStreamProvider_ParseEtcResolverSearchDomainsAndOptions() +- throws InterruptedException { +- NioEventLoopGroup group = new NioEventLoopGroup(); +- try { +- DnsNameResolverBuilder builder = new DnsNameResolverBuilder(group.next()) +- .datagramChannelFactory(NioDatagramChannel::new); +- doTestParseResolverFilesAllowsBlockingCalls(builder::build); +- } finally { +- group.shutdownGracefully(); +- } +- } +- +- private static void doTestParseResolverFilesAllowsBlockingCalls(Callable callable) +- throws InterruptedException { +- SingleThreadEventExecutor executor = +- new SingleThreadEventExecutor(null, new DefaultThreadFactory("test"), true) { +- @Override +- protected void run() { +- while (!confirmShutdown()) { +- Runnable task = takeTask(); +- if (task != null) { +- task.run(); +- } +- } +- } +- }; +- try { +- CountDownLatch latch = new CountDownLatch(1); +- List result = new ArrayList<>(); +- List error = new ArrayList<>(); +- executor.execute(() -> { +- try { +- result.add(callable.call()); +- } catch (Throwable t) { +- error.add(t); +- } +- latch.countDown(); +- }); +- latch.await(); +- assertEquals(0, error.size()); +- assertEquals(1, result.size()); +- } finally { +- executor.shutdownGracefully(); +- } +- } +- +- private static void testTrustManagerVerify(SslProvider provider, String tlsVersion) throws Exception { +- final SslContext sslClientCtx = +- SslContextBuilder.forClient() +- .sslProvider(provider) +- .protocols(tlsVersion) +- .trustManager(ResourcesUtil.getFile( +- NettyBlockHoundIntegrationTest.class, "mutual_auth_ca.pem")) +- .build(); +- +- final SslContext sslServerCtx = +- SslContextBuilder.forServer(ResourcesUtil.getFile( +- NettyBlockHoundIntegrationTest.class, "localhost_server.pem"), +- ResourcesUtil.getFile( +- NettyBlockHoundIntegrationTest.class, "localhost_server.key"), +- null) +- .sslProvider(provider) +- .protocols(tlsVersion) +- .build(); +- +- final SslHandler clientSslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT); +- final SslHandler serverSslHandler = sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT); +- +- testHandshake(sslClientCtx, clientSslHandler, serverSslHandler); +- } +- +- private static void testHandshakeWithExecutor(Executor executor, String tlsVersion) throws Exception { +- final SslContext sslClientCtx = SslContextBuilder.forClient() +- .trustManager(InsecureTrustManagerFactory.INSTANCE) +- .sslProvider(SslProvider.JDK).protocols(tlsVersion).build(); +- +- final SelfSignedCertificate cert = new SelfSignedCertificate(); +- final SslContext sslServerCtx = SslContextBuilder.forServer(cert.key(), cert.cert()) +- .sslProvider(SslProvider.JDK).protocols(tlsVersion).build(); +- +- final SslHandler clientSslHandler = sslClientCtx.newHandler(UnpooledByteBufAllocator.DEFAULT, executor); +- final SslHandler serverSslHandler = sslServerCtx.newHandler(UnpooledByteBufAllocator.DEFAULT, executor); +- +- testHandshake(sslClientCtx, clientSslHandler, serverSslHandler); +- } +- +- private static void testHandshake(SslContext sslClientCtx, SslHandler clientSslHandler, +- SslHandler serverSslHandler) throws Exception { +- EventLoopGroup group = new NioEventLoopGroup(); +- Channel sc = null; +- Channel cc = null; +- try { +- sc = new ServerBootstrap() +- .group(group) +- .channel(NioServerSocketChannel.class) +- .childHandler(serverSslHandler) +- .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); +- +- ChannelFuture future = new Bootstrap() +- .group(group) +- .channel(NioSocketChannel.class) +- .handler(new ChannelInitializer() { +- @Override +- protected void initChannel(Channel ch) { +- ch.pipeline() +- .addLast(clientSslHandler) +- .addLast(new ChannelInboundHandlerAdapter() { +- +- @Override +- public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { +- if (evt instanceof SslHandshakeCompletionEvent && +- ((SslHandshakeCompletionEvent) evt).cause() != null) { +- ((SslHandshakeCompletionEvent) evt).cause().printStackTrace(); +- } +- ctx.fireUserEventTriggered(evt); +- } +- }); +- } +- }).connect(sc.localAddress()); +- cc = future.syncUninterruptibly().channel(); +- +- clientSslHandler.handshakeFuture().await().sync(); +- serverSslHandler.handshakeFuture().await().sync(); +- } finally { +- if (cc != null) { +- cc.close().syncUninterruptibly(); +- } +- if (sc != null) { +- sc.close().syncUninterruptibly(); +- } +- group.shutdownGracefully(); +- ReferenceCountUtil.release(sslClientCtx); +- } +- } +- +- private static class TestLinkedBlockingQueue extends LinkedBlockingQueue { +- +- private final ReentrantLock lock = new ReentrantLock(); +- +- @Override +- public boolean offer(T t) { +- lock.lock(); +- try { +- return super.offer(t); +- } finally { +- lock.unlock(); +- } +- } +- +- void emulateContention() { +- lock.lock(); +- } +- +- void waitUntilContented() throws InterruptedException { +- // wait until the lock gets contended +- while (lock.getQueueLength() == 0) { +- Thread.sleep(10L); +- } +- } +- +- void removeContention() { +- lock.unlock(); +- } +- } +-} +diff --git a/transport-blockhound-tests/src/test/resources/io/netty/util/internal/localhost_server.key b/transport-blockhound-tests/src/test/resources/io/netty/util/internal/localhost_server.key +deleted file mode 100644 +index 9aa6611400..0000000000 +--- a/transport-blockhound-tests/src/test/resources/io/netty/util/internal/localhost_server.key ++++ /dev/null +@@ -1,28 +0,0 @@ +------BEGIN PRIVATE KEY----- +-MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDYrLtMlZzoe2BP +-iCURF3So5XNLfsOLcAVERXXjnxqX6Mex55WdJiy6uWTFKbRHWJdbWELdZxVl5+GX +-pMv3OdkKZt+19ZdSfByv6bB5RNdZOEGnKOHSY2XdnzYnF5JBaWEx0fvtvIPZOUlW +-DWgsQzJk1UQhu+XnBc7P1hHYNvwsVNOR+HD9LGebDy+UcfiL34XwAyBdHUsbcIr8 +-hltABcj6vNbqOLndpU86DxU9z9b1PDmkFVfisElhpDEhpxmTCwI22Us1GC8D81LM +-ZzMlbWSzTfNPEuqNzJYGiFt/XPwPkPPyVvti0XWPBQpwzJFFUX5xKsOGERolELRT +-0yNQYznFAgMBAAECggEAOFR/xSNITbB1k3ejm1PrwlUUqlXkZIXU+LDOO0UL1t5v +-vDKm1Not2sWECzYSZlID132UtJauG3YzUgdH95gUcv3XvyiAFLOriZhJht181vcn +-KlwYiWfJ/dn8bCFWpqbM2/TpeB8AcCLSjAqkQI2ftlMziUmeNXdvEt1mej2hRay1 +-ULfoxlC0mftNRQptD5gBFzrc47O4mVpVEQt4yS3Qyzp2/9ds9UkhaCIFpXPVCalZ +-ds7R+bDDP+wiYTkUcd8fvelaMkD3Wcy8DedGRShhILZvBYTDdWcpJ7+e5EkNlEq4 +-+Ys4Y/u6aFDJD53g3zCaJhatmdAZcct2MMmWH1vewQKBgQD3Y2S245cad1D9AqYD +-ChZGp95EfRo3EzXk4VkE50bjZXjHq9fD8T0CWEZGWQZrXJCR+vBpEURy0mrPD8se +-QQ0Q5+I27RadtfPnMd6ry9nDGMPxyd/10vzU6LazzLNE+uf9ljF1RHZu1iDAvInR +-r1cQGbn/wKBF6BurPPIXABZEuQKBgQDgN6JHbIfDzHKhwEoUTvRrYJsTXqplD+h0 +-Whg+kSQyhtKdlpINFOoEj8FUNJvTjG8les1aoajyWIqikVdvHto/mrxrSIeRkEmt +-X+KG+5ld2n466tzv1DmVcIGXSrBrH3lA0i6R8Ly26FLSqw0Z12fx5GUUa1qaVRqo +-rwcrIZovbQKBgHa2mojs9AC+Sv3uvG1u9LuZKJ7jDaZqMI2R2d7xgOH0Op5Ohy6+ +-39D1PVvasqroc3Op4J36rEcRVDHi2Uy+WJ/JNpO2+AhcXRuPodP88ZWel8C6aB+V +-zL/6oFntnAU5BgR5g2hLny2W0YbLsrMNmhDe15O0AvUo6cYla+K/pu/5AoGACr/g +-EdiMMcDthf+4DX0zjqpVBPq25J18oYdoPierOpjoJBIB8oqcJZfWxvi2t8+1zHA0 +-xDGX7fZ8vwqEzJkIEaCTg/k4NqxaO+uq6pnJYoyFHMIB0aW1FQsNy3kTOC+MGqV5 +-Ahoukf5VajA1MpX3L8upZO84qsmFu6yYhWLZB4kCgYBlgSD5G4q6rX4ELa3XG61h +-fDtu75IYEsjWm4vgJzHjeYT2xPIm9OFFYXjPghto0f1oH37ODD3DoXmsnmddgpmn +-tH7aRWWHsSpB5zVgftV4urNCIsm87LWw8mvUGgCwYV1CtCX8warKokfeoA2ltz4u +-oeuUzo98hN+aKRU5RO6Bmg== +------END PRIVATE KEY----- +diff --git a/transport-blockhound-tests/src/test/resources/io/netty/util/internal/localhost_server.pem b/transport-blockhound-tests/src/test/resources/io/netty/util/internal/localhost_server.pem +deleted file mode 100644 +index 70759b29e5..0000000000 +--- a/transport-blockhound-tests/src/test/resources/io/netty/util/internal/localhost_server.pem ++++ /dev/null +@@ -1,17 +0,0 @@ +------BEGIN CERTIFICATE----- +-MIICozCCAYsCAnS/MA0GCSqGSIb3DQEBDQUAMBgxFjAUBgNVBAMTDU5ldHR5VGVz +-dFJvb3QwIBcNMTcwMjE3MDMzMzQ0WhgPMjExNzAxMjQwMzMzNDRaMBQxEjAQBgNV +-BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANis +-u0yVnOh7YE+IJREXdKjlc0t+w4twBURFdeOfGpfox7HnlZ0mLLq5ZMUptEdYl1tY +-Qt1nFWXn4Zeky/c52Qpm37X1l1J8HK/psHlE11k4Qaco4dJjZd2fNicXkkFpYTHR +-++28g9k5SVYNaCxDMmTVRCG75ecFzs/WEdg2/CxU05H4cP0sZ5sPL5Rx+IvfhfAD +-IF0dSxtwivyGW0AFyPq81uo4ud2lTzoPFT3P1vU8OaQVV+KwSWGkMSGnGZMLAjbZ +-SzUYLwPzUsxnMyVtZLNN808S6o3MlgaIW39c/A+Q8/JW+2LRdY8FCnDMkUVRfnEq +-w4YRGiUQtFPTI1BjOcUCAwEAATANBgkqhkiG9w0BAQ0FAAOCAQEAQNXnwE2MJFy5 +-ti07xyi8h/mY0Kl1dwZUqx4F9D9eoxLCq2/p3h/Z18AlOmjdW06pvC2sGtQtyEqL +-YjuQFbMjXRo9c+6+d+xwdDKTu7+XOTHvznJ8xJpKnFOlohGq/n3efBIJSsaeasTU +-slFzmdKYABDZzbsQ4X6YCIOF4XVdEQqmXpS+uEbn5C2sVtG+LXI8srmkVGpCcRew +-SuTGanwxLparhBBeN1ARjKzNxXUWuK2UKZ9p8c7n7TXGhd12ZNTcLhk4rCnOFq1J +-ySFvP5YL2q29fpEt+Tq0zm3V7An2qtaNDp26cEdevtKPjRyOLkCJx8OlZxc9DZvJ +-HjalFDoRUw== +------END CERTIFICATE----- +diff --git a/transport-blockhound-tests/src/test/resources/io/netty/util/internal/mutual_auth_ca.pem b/transport-blockhound-tests/src/test/resources/io/netty/util/internal/mutual_auth_ca.pem +deleted file mode 100644 +index 9c9241bc65..0000000000 +--- a/transport-blockhound-tests/src/test/resources/io/netty/util/internal/mutual_auth_ca.pem ++++ /dev/null +@@ -1,19 +0,0 @@ +------BEGIN CERTIFICATE----- +-MIIDLDCCAhSgAwIBAgIJAO1m5pioZhLLMA0GCSqGSIb3DQEBDQUAMBgxFjAUBgNV +-BAMTDU5ldHR5VGVzdFJvb3QwHhcNMTcwMjE3MDMzMzQ0WhcNMTcwMzE5MDMzMzQ0 +-WjAYMRYwFAYDVQQDEw1OZXR0eVRlc3RSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC +-AQ8AMIIBCgKCAQEAnC7Y/p/TSWI1KxBKETfFKaRWCPEkoYn5G973WbCF0VDT90PX +-xK6yHvhqNdDQZPmddgfDAQfjekHeeIFkjCKlvQu0js0G4Bubz4NffNumd/Mgsix8 +-SWJ13lPk+Ly4PDv0bK1zB6BxP1qQm1qxVwsPy9zNP8ylJrM0Div4TXHmnWOfc0JD +-4/XPpfeUHH1tt/GMtsS2Gx6EpTVPD2w7LDKUza1/rQ7d9sqmFpgsNcI9Db/sAtFP +-lK2iJku5WIXQkmHimn4bqZ9wkiXJ85pm5ggGQqGMPSbe+2Lh24AvZMIBiwPbkjEU +-EDFXEJfKOC3Dl71JgWOthtHZ9vcCRDQ3Sky6AQIDAQABo3kwdzAdBgNVHQ4EFgQU +-qT+cH8qrebiVPpKCBQDB6At2iOAwSAYDVR0jBEEwP4AUqT+cH8qrebiVPpKCBQDB +-6At2iOChHKQaMBgxFjAUBgNVBAMTDU5ldHR5VGVzdFJvb3SCCQDtZuaYqGYSyzAM +-BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQCEemXTIew4pR2cHEFpVsW2 +-bLHXLAnC23wBMT46D3tqyxscukMYjFuWosCdEsgRW8d50BXy9o4dHWeg94+aDo3A +-DX4OTRN/veQGIG7dgM6poDzFuVJlSN0ubKKg6gpDD60IhopZpMviFAOsmzr7OXwS +-9hjbTqUWujMIEHQ95sPlQFdSaavYSFfqhSltWmVCPSbArxrw0lZ2QcnUqGN47EFp +-whc5wFB+rSw/ojU1jBLMvgvgzf/8V8zr1IBTDSiHNlknGqGpOOaookzUh95YRiAT +-hH82y9bBeflqroOeztqMpONpWoZjlz0sWbJNvXztXINL7LaNmVYOcoUrCcxPS54T +------END CERTIFICATE----- +-- +2.46.1 + diff --git a/0003-Remove-conscrypt-ALPN.patch b/0002-Remove-optional-dep-conscrypt.patch similarity index 42% rename from 0003-Remove-conscrypt-ALPN.patch rename to 0002-Remove-optional-dep-conscrypt.patch index e71ba5a6c9068b7b8b68c5bdd53629834931b64e..749ca99be8ebc2d198e2c4e34eb6cfe81641edf1 100644 --- a/0003-Remove-conscrypt-ALPN.patch +++ b/0002-Remove-optional-dep-conscrypt.patch @@ -1,45 +1,123 @@ -From 039534e20546221c3466d1ceb663625c59edb0e7 Mon Sep 17 00:00:00 2001 -From: Michael Simacek -Date: Tue, 11 Jul 2017 13:37:22 +0200 -Subject: [PATCH 3/3] Remove conscrypt ALPN +From d0325cfed137fe5ea400d7344c38f0deac43959a Mon Sep 17 00:00:00 2001 +From: Mat Booth +Date: Mon, 7 Sep 2020 13:24:30 +0100 +Subject: [PATCH 2/7] Remove optional dep conscrypt --- - handler/pom.xml | 6 - - .../netty/handler/ssl/ConscryptAlpnSslEngine.java | 176 --------------------- - .../ssl/JdkAlpnApplicationProtocolNegotiator.java | 6 +- - .../main/java/io/netty/handler/ssl/SslHandler.java | 35 ---- - .../ssl/ConscryptJdkSslEngineInteropTest.java | 76 --------- - .../io/netty/handler/ssl/Java8SslTestUtils.java | 7 - - .../ssl/JdkConscryptSslEngineInteropTest.java | 86 ---------- - .../io/netty/handler/ssl/JdkSslEngineTest.java | 2 +- - 8 files changed, 2 insertions(+), 392 deletions(-) + handler/pom.xml | 6 - + .../java/io/netty/handler/ssl/Conscrypt.java | 75 ------- + .../handler/ssl/ConscryptAlpnSslEngine.java | 212 ------------------ + .../JdkAlpnApplicationProtocolNegotiator.java | 11 +- + .../java/io/netty/handler/ssl/SslHandler.java | 52 +---- + pom.xml | 10 - + 6 files changed, 2 insertions(+), 364 deletions(-) + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/Conscrypt.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/ConscryptAlpnSslEngine.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/ConscryptJdkSslEngineInteropTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/JdkConscryptSslEngineInteropTest.java diff --git a/handler/pom.xml b/handler/pom.xml -index 52e63ca..69af32a 100644 +index 8e21f87533..46c20028eb 100644 --- a/handler/pom.xml +++ b/handler/pom.xml -@@ -60,12 +60,6 @@ +@@ -96,12 +96,6 @@ + alpn-api true - +- - ${conscrypt.groupId} - ${conscrypt.artifactId} - ${conscrypt.classifier} - true - -- + org.mockito mockito-core - +diff --git a/handler/src/main/java/io/netty/handler/ssl/Conscrypt.java b/handler/src/main/java/io/netty/handler/ssl/Conscrypt.java +deleted file mode 100644 +index c5af3fd39a..0000000000 +--- a/handler/src/main/java/io/netty/handler/ssl/Conscrypt.java ++++ /dev/null +@@ -1,75 +0,0 @@ +-/* +- * Copyright 2017 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.util.internal.PlatformDependent; +- +-import javax.net.ssl.SSLEngine; +-import java.lang.reflect.InvocationTargetException; +-import java.lang.reflect.Method; +- +-/** +- * Contains methods that can be used to detect if conscrypt is usable. +- */ +-final class Conscrypt { +- // This class exists to avoid loading other conscrypt related classes using features only available in JDK8+, +- // because we need to maintain JDK6+ runtime compatibility. +- private static final Method IS_CONSCRYPT_SSLENGINE; +- +- static { +- Method isConscryptSSLEngine = null; +- +- if ((PlatformDependent.javaVersion() >= 8 && +- // Only works on Java14 and earlier for now +- // See https://github.com/google/conscrypt/issues/838 +- PlatformDependent.javaVersion() < 15) || PlatformDependent.isAndroid()) { +- try { +- Class providerClass = Class.forName("org.conscrypt.OpenSSLProvider", true, +- PlatformDependent.getClassLoader(ConscryptAlpnSslEngine.class)); +- providerClass.newInstance(); +- +- Class conscryptClass = Class.forName("org.conscrypt.Conscrypt", true, +- PlatformDependent.getClassLoader(ConscryptAlpnSslEngine.class)); +- isConscryptSSLEngine = conscryptClass.getMethod("isConscrypt", SSLEngine.class); +- } catch (Throwable ignore) { +- // ignore +- } +- } +- IS_CONSCRYPT_SSLENGINE = isConscryptSSLEngine; +- } +- +- /** +- * Indicates whether or not conscrypt is available on the current system. +- */ +- static boolean isAvailable() { +- return IS_CONSCRYPT_SSLENGINE != null; +- } +- +- /** +- * Returns {@code true} if the passed in {@link SSLEngine} is handled by Conscrypt, {@code false} otherwise. +- */ +- static boolean isEngineSupported(SSLEngine engine) { +- try { +- return IS_CONSCRYPT_SSLENGINE != null && (Boolean) IS_CONSCRYPT_SSLENGINE.invoke(null, engine); +- } catch (IllegalAccessException ignore) { +- return false; +- } catch (InvocationTargetException ex) { +- throw new RuntimeException(ex); +- } +- } +- +- private Conscrypt() { } +-} diff --git a/handler/src/main/java/io/netty/handler/ssl/ConscryptAlpnSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/ConscryptAlpnSslEngine.java deleted file mode 100644 -index 8e7a544..0000000 +index 917ebaea79..0000000000 --- a/handler/src/main/java/io/netty/handler/ssl/ConscryptAlpnSslEngine.java +++ /dev/null -@@ -1,176 +0,0 @@ +@@ -1,212 +0,0 @@ -/* - * Copyright 2017 The Netty Project - * @@ -47,7 +125,7 @@ index 8e7a544..0000000 - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -61,9 +139,10 @@ index 8e7a544..0000000 -import static io.netty.util.internal.ObjectUtil.checkNotNull; -import static java.lang.Math.min; - +-import io.netty.buffer.ByteBuf; +-import io.netty.buffer.ByteBufAllocator; -import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelectionListener; -import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelector; --import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.LinkedHashSet; @@ -72,7 +151,10 @@ index 8e7a544..0000000 -import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLException; - --import io.netty.util.internal.PlatformDependent; +-import io.netty.util.internal.EmptyArrays; +-import io.netty.util.internal.SystemPropertyUtil; +-import org.conscrypt.AllocatedBuffer; +-import org.conscrypt.BufferAllocator; -import org.conscrypt.Conscrypt; -import org.conscrypt.HandshakeListener; - @@ -80,34 +162,37 @@ index 8e7a544..0000000 - * A {@link JdkSslEngine} that uses the Conscrypt provider or SSL with ALPN. - */ -abstract class ConscryptAlpnSslEngine extends JdkSslEngine { -- private static final Class ENGINES_CLASS = getEnginesClass(); -- -- /** -- * Indicates whether or not conscrypt is available on the current system. -- */ -- static boolean isAvailable() { -- return ENGINES_CLASS != null && PlatformDependent.javaVersion() >= 8; -- } -- -- static boolean isEngineSupported(SSLEngine engine) { -- return isAvailable() && isConscryptEngine(engine, ENGINES_CLASS); -- } +- private static final boolean USE_BUFFER_ALLOCATOR = SystemPropertyUtil.getBoolean( +- "io.netty.handler.ssl.conscrypt.useBufferAllocator", true); - -- static ConscryptAlpnSslEngine newClientEngine(SSLEngine engine, +- static ConscryptAlpnSslEngine newClientEngine(SSLEngine engine, ByteBufAllocator alloc, - JdkApplicationProtocolNegotiator applicationNegotiator) { -- return new ClientEngine(engine, applicationNegotiator); +- return new ClientEngine(engine, alloc, applicationNegotiator); - } - -- static ConscryptAlpnSslEngine newServerEngine(SSLEngine engine, +- static ConscryptAlpnSslEngine newServerEngine(SSLEngine engine, ByteBufAllocator alloc, - JdkApplicationProtocolNegotiator applicationNegotiator) { -- return new ServerEngine(engine, applicationNegotiator); +- return new ServerEngine(engine, alloc, applicationNegotiator); - } - -- private ConscryptAlpnSslEngine(SSLEngine engine, List protocols) { +- private ConscryptAlpnSslEngine(SSLEngine engine, ByteBufAllocator alloc, List protocols) { - super(engine); - +- // Configure the Conscrypt engine to use Netty's buffer allocator. This is a trade-off of memory vs +- // performance. +- // +- // If no allocator is provided, the engine will internally allocate a direct buffer of max packet size in +- // order to optimize JNI calls (this happens the first time it is provided a non-direct buffer from the +- // application). +- // +- // Alternatively, if an allocator is provided, no internal buffer will be created and direct buffers will be +- // retrieved from the allocator on-demand. +- if (USE_BUFFER_ALLOCATOR) { +- Conscrypt.setBufferAllocator(engine, new BufferAllocatorAdapter(alloc)); +- } +- - // Set the list of supported ALPN protocols on the engine. -- Conscrypt.Engines.setAlpnProtocols(engine, protocols.toArray(new String[protocols.size()])); +- Conscrypt.setApplicationProtocols(engine, protocols.toArray(EmptyArrays.EMPTY_STRINGS)); - } - - /** @@ -120,23 +205,38 @@ index 8e7a544..0000000 - */ - final int calculateOutNetBufSize(int plaintextBytes, int numBuffers) { - // Assuming a max of one frame per component in a composite buffer. -- long maxOverhead = (long) Conscrypt.Engines.maxSealOverhead(getWrappedEngine()) * numBuffers; -- // TODO(nmittler): update this to use MAX_ENCRYPTED_PACKET_LENGTH instead of Integer.MAX_VALUE -- return (int) min(Integer.MAX_VALUE, plaintextBytes + maxOverhead); +- return calculateSpace(plaintextBytes, numBuffers, Integer.MAX_VALUE); +- } +- +- /** +- * Calculate the space necessary in an out buffer to hold the max size that the given +- * plaintextBytes and numBuffers can produce when encrypted. Assumes as a worst case +- * that there is one TLS record per buffer. +- * @param plaintextBytes the number of plaintext bytes to be wrapped. +- * @param numBuffers the number of buffers that the plaintext bytes are spread across. +- * @return the maximum size of the encrypted output buffer required for the wrap operation. +- */ +- final int calculateRequiredOutBufSpace(int plaintextBytes, int numBuffers) { +- return calculateSpace(plaintextBytes, numBuffers, Conscrypt.maxEncryptedPacketLength()); +- } +- +- private int calculateSpace(int plaintextBytes, int numBuffers, long maxPacketLength) { +- long maxOverhead = (long) Conscrypt.maxSealOverhead(getWrappedEngine()) * numBuffers; +- return (int) min(maxPacketLength, plaintextBytes + maxOverhead); - } - - final SSLEngineResult unwrap(ByteBuffer[] srcs, ByteBuffer[] dests) throws SSLException { -- return Conscrypt.Engines.unwrap(getWrappedEngine(), srcs, dests); +- return Conscrypt.unwrap(getWrappedEngine(), srcs, dests); - } - - private static final class ClientEngine extends ConscryptAlpnSslEngine { - private final ProtocolSelectionListener protocolListener; - -- ClientEngine(SSLEngine engine, +- ClientEngine(SSLEngine engine, ByteBufAllocator alloc, - JdkApplicationProtocolNegotiator applicationNegotiator) { -- super(engine, applicationNegotiator.protocols()); +- super(engine, alloc, applicationNegotiator.protocols()); - // Register for completion of the handshake. -- Conscrypt.Engines.setHandshakeListener(engine, new HandshakeListener() { +- Conscrypt.setHandshakeListener(engine, new HandshakeListener() { - @Override - public void onHandshakeFinished() throws SSLException { - selectProtocol(); @@ -149,7 +249,7 @@ index 8e7a544..0000000 - } - - private void selectProtocol() throws SSLException { -- String protocol = Conscrypt.Engines.getAlpnSelectedProtocol(getWrappedEngine()); +- String protocol = Conscrypt.getApplicationProtocol(getWrappedEngine()); - try { - protocolListener.selected(protocol); - } catch (Throwable e) { @@ -161,11 +261,12 @@ index 8e7a544..0000000 - private static final class ServerEngine extends ConscryptAlpnSslEngine { - private final ProtocolSelector protocolSelector; - -- ServerEngine(SSLEngine engine, JdkApplicationProtocolNegotiator applicationNegotiator) { -- super(engine, applicationNegotiator.protocols()); +- ServerEngine(SSLEngine engine, ByteBufAllocator alloc, +- JdkApplicationProtocolNegotiator applicationNegotiator) { +- super(engine, alloc, applicationNegotiator.protocols()); - - // Register for completion of the handshake. -- Conscrypt.Engines.setHandshakeListener(engine, new HandshakeListener() { +- Conscrypt.setHandshakeListener(engine, new HandshakeListener() { - @Override - public void onHandshakeFinished() throws SSLException { - selectProtocol(); @@ -180,7 +281,7 @@ index 8e7a544..0000000 - - private void selectProtocol() throws SSLException { - try { -- String protocol = Conscrypt.Engines.getAlpnSelectedProtocol(getWrappedEngine()); +- String protocol = Conscrypt.getApplicationProtocol(getWrappedEngine()); - protocolSelector.select(protocol != null ? Collections.singletonList(protocol) - : Collections.emptyList()); - } catch (Throwable e) { @@ -189,69 +290,93 @@ index 8e7a544..0000000 - } - } - -- private static Class getEnginesClass() { -- try { -- // Always use bootstrap class loader. -- Class engineClass = Class.forName("org.conscrypt.Conscrypt$Engines", true, -- ConscryptAlpnSslEngine.class.getClassLoader()); -- // Ensure that it also has the isConscrypt method. -- getIsConscryptMethod(engineClass); -- return engineClass; -- } catch (Throwable ignore) { -- // Conscrypt was not loaded. -- return null; +- private static final class BufferAllocatorAdapter extends BufferAllocator { +- private final ByteBufAllocator alloc; +- +- BufferAllocatorAdapter(ByteBufAllocator alloc) { +- this.alloc = alloc; - } -- } - -- private static boolean isConscryptEngine(SSLEngine engine, Class enginesClass) { -- try { -- Method method = getIsConscryptMethod(enginesClass); -- return (Boolean) method.invoke(null, engine); -- } catch (Throwable ignore) { -- return false; +- @Override +- public AllocatedBuffer allocateDirectBuffer(int capacity) { +- return new BufferAdapter(alloc.directBuffer(capacity)); - } - } - -- private static Method getIsConscryptMethod(Class enginesClass) throws NoSuchMethodException { -- return enginesClass.getMethod("isConscrypt", SSLEngine.class); +- private static final class BufferAdapter extends AllocatedBuffer { +- private final ByteBuf nettyBuffer; +- private final ByteBuffer buffer; +- +- BufferAdapter(ByteBuf nettyBuffer) { +- this.nettyBuffer = nettyBuffer; +- buffer = nettyBuffer.nioBuffer(0, nettyBuffer.capacity()); +- } +- +- @Override +- public ByteBuffer nioBuffer() { +- return buffer; +- } +- +- @Override +- public AllocatedBuffer retain() { +- nettyBuffer.retain(); +- return this; +- } +- +- @Override +- public AllocatedBuffer release() { +- nettyBuffer.release(); +- return this; +- } - } -} diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java b/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java -index f82c7da..9c4ab9e 100644 +index 9eb8f15d14..b5715e87ff 100644 --- a/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java +++ b/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java -@@ -21,7 +21,7 @@ import javax.net.ssl.SSLEngine; - * The {@link JdkApplicationProtocolNegotiator} to use if you need ALPN and are using {@link SslProvider#JDK}. +@@ -26,8 +26,7 @@ import javax.net.ssl.SSLEngine; */ + @Deprecated public final class JdkAlpnApplicationProtocolNegotiator extends JdkBaseApplicationProtocolNegotiator { -- private static final boolean AVAILABLE = ConscryptAlpnSslEngine.isAvailable() || JettyAlpnSslEngine.isAvailable(); -+ private static final boolean AVAILABLE = JettyAlpnSslEngine.isAvailable(); - private static final SslEngineWrapperFactory ALPN_WRAPPER = AVAILABLE ? new AlpnWrapper() : new FailureWrapper(); +- private static final boolean AVAILABLE = Conscrypt.isAvailable() || +- JdkAlpnSslUtils.supportsAlpn() || ++ private static final boolean AVAILABLE = JdkAlpnSslUtils.supportsAlpn() || + JettyAlpnSslEngine.isAvailable() || + BouncyCastle.isAvailable(); - /** -@@ -121,10 +121,6 @@ public final class JdkAlpnApplicationProtocolNegotiator extends JdkBaseApplicati +@@ -120,7 +119,6 @@ public final class JdkAlpnApplicationProtocolNegotiator extends JdkBaseApplicati + public SSLEngine wrapSslEngine(SSLEngine engine, ByteBufAllocator alloc, + JdkApplicationProtocolNegotiator applicationNegotiator, boolean isServer) { + throw new RuntimeException("ALPN unsupported. Is your classpath configured correctly?" +- + " For Conscrypt, add the appropriate Conscrypt JAR to classpath and set the security provider." + + " For Jetty-ALPN, see " + + "https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-starting"); + } +@@ -130,13 +128,6 @@ public final class JdkAlpnApplicationProtocolNegotiator extends JdkBaseApplicati @Override - public SSLEngine wrapSslEngine(SSLEngine engine, JdkApplicationProtocolNegotiator applicationNegotiator, - boolean isServer) { -- if (ConscryptAlpnSslEngine.isEngineSupported(engine)) { -- return isServer ? ConscryptAlpnSslEngine.newServerEngine(engine, applicationNegotiator) -- : ConscryptAlpnSslEngine.newClientEngine(engine, applicationNegotiator); + public SSLEngine wrapSslEngine(SSLEngine engine, ByteBufAllocator alloc, + JdkApplicationProtocolNegotiator applicationNegotiator, boolean isServer) { +- if (Conscrypt.isEngineSupported(engine)) { +- return isServer ? ConscryptAlpnSslEngine.newServerEngine(engine, alloc, applicationNegotiator) +- : ConscryptAlpnSslEngine.newClientEngine(engine, alloc, applicationNegotiator); +- } +- if (BouncyCastle.isInUse(engine)) { +- return new BouncyCastleAlpnSslEngine(engine, applicationNegotiator, isServer); - } - if (JettyAlpnSslEngine.isAvailable()) { - return isServer ? JettyAlpnSslEngine.newServerEngine(engine, applicationNegotiator) - : JettyAlpnSslEngine.newClientEngine(engine, applicationNegotiator); + // ALPN support was recently backported to Java8 as + // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8230977. + // Because of this lets not do a Java version runtime check but just depend on if the required methods are diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java -index 05c451a..8693011 100644 +index 019bb38f90..40a46d21b7 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java -@@ -187,38 +187,6 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH - new ClosedChannelException(), SslHandler.class, "channelInactive(...)"); - - private enum SslEngineType { +@@ -251,55 +251,6 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH + return ((ReferenceCountedOpenSslEngine) engine).jdkCompatibilityMode; + } + }, - CONSCRYPT(true, COMPOSITE_CUMULATOR) { - @Override -- SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int readerIndex, int len, ByteBuf out) -- throws SSLException { +- SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int len, ByteBuf out) throws SSLException { - int nioBufferCount = in.nioBufferCount(); - int writerIndex = out.writerIndex(); - final SSLEngineResult result; @@ -262,13 +387,13 @@ index 05c451a..8693011 100644 - try { - handler.singleBuffer[0] = toByteBuffer(out, writerIndex, out.writableBytes()); - result = ((ConscryptAlpnSslEngine) handler.engine).unwrap( -- in.nioBuffers(readerIndex, len), +- in.nioBuffers(in.readerIndex(), len), - handler.singleBuffer); - } finally { - handler.singleBuffer[0] = null; - } - } else { -- result = handler.engine.unwrap(toByteBuffer(in, readerIndex, len), +- result = handler.engine.unwrap(toByteBuffer(in, in.readerIndex(), len), - toByteBuffer(out, writerIndex, out.writableBytes())); - } - out.writerIndex(writerIndex + result.bytesProduced()); @@ -276,236 +401,62 @@ index 05c451a..8693011 100644 - } - - @Override -- int calculateWrapBufferCapacity(SslHandler handler, int pendingBytes, int numComponents) { -- return ((ConscryptAlpnSslEngine) handler.engine).calculateOutNetBufSize(pendingBytes, numComponents); +- ByteBuf allocateWrapBuffer(SslHandler handler, ByteBufAllocator allocator, +- int pendingBytes, int numComponents) { +- return allocator.directBuffer( +- ((ConscryptAlpnSslEngine) handler.engine).calculateOutNetBufSize(pendingBytes, numComponents)); +- } +- +- @Override +- int calculateRequiredOutBufSpace(SslHandler handler, int pendingBytes, int numComponents) { +- return ((ConscryptAlpnSslEngine) handler.engine) +- .calculateRequiredOutBufSpace(pendingBytes, numComponents); +- } +- +- @Override +- int calculatePendingData(SslHandler handler, int guess) { +- return guess; +- } +- +- @Override +- boolean jdkCompatibilityMode(SSLEngine engine) { +- return true; - } - }, JDK(false, MERGE_CUMULATOR) { @Override - SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int readerIndex, int len, ByteBuf out) -@@ -237,9 +205,6 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH + SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int len, ByteBuf out) throws SSLException { +@@ -358,8 +309,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH }; static SslEngineType forEngine(SSLEngine engine) { -- if (engine instanceof ConscryptAlpnSslEngine) { -- return CONSCRYPT; -- } - return JDK; +- return engine instanceof ReferenceCountedOpenSslEngine ? TCNATIVE : +- engine instanceof ConscryptAlpnSslEngine ? CONSCRYPT : JDK; ++ return engine instanceof ReferenceCountedOpenSslEngine ? TCNATIVE : JDK; } -diff --git a/handler/src/test/java/io/netty/handler/ssl/ConscryptJdkSslEngineInteropTest.java b/handler/src/test/java/io/netty/handler/ssl/ConscryptJdkSslEngineInteropTest.java -deleted file mode 100644 -index e217136..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/ConscryptJdkSslEngineInteropTest.java -+++ /dev/null -@@ -1,76 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; -- --import java.security.Provider; --import org.junit.BeforeClass; --import org.junit.Ignore; -- --import org.junit.runner.RunWith; --import org.junit.runners.Parameterized; -- --import java.util.ArrayList; --import java.util.Collection; --import java.util.List; -- --import static org.junit.Assume.assumeTrue; -- --@RunWith(Parameterized.class) --public class ConscryptJdkSslEngineInteropTest extends SSLEngineTest { -- -- @Parameterized.Parameters(name = "{index}: bufferType = {0}") -- public static Collection data() { -- List params = new ArrayList(); -- for (BufferType type: BufferType.values()) { -- params.add(type); -- } -- return params; -- } -- -- public ConscryptJdkSslEngineInteropTest(BufferType type) { -- super(type); -- } -- -- @BeforeClass -- public static void checkConscrypt() { -- assumeTrue(ConscryptAlpnSslEngine.isAvailable()); -- } -- -- @Override -- protected SslProvider sslClientProvider() { -- return SslProvider.JDK; -- } -- -- @Override -- protected SslProvider sslServerProvider() { -- return SslProvider.JDK; -- } -- -- @Override -- protected Provider clientSslContextProvider() { -- return Java8SslTestUtils.conscryptProvider(); -- } -- -- @Ignore /* Does the JDK support a "max certificate chain length"? */ -- @Override -- public void testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth() throws Exception { -- } -- -- @Ignore /* Does the JDK support a "max certificate chain length"? */ -- @Override -- public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth() throws Exception { -- } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/Java8SslTestUtils.java b/handler/src/test/java/io/netty/handler/ssl/Java8SslTestUtils.java -index cc2e6c6..f9cf771 100644 ---- a/handler/src/test/java/io/netty/handler/ssl/Java8SslTestUtils.java -+++ b/handler/src/test/java/io/netty/handler/ssl/Java8SslTestUtils.java -@@ -16,12 +16,9 @@ - - package io.netty.handler.ssl; - --import org.conscrypt.OpenSSLProvider; -- - import javax.net.ssl.SNIMatcher; - import javax.net.ssl.SNIServerName; - import javax.net.ssl.SSLParameters; --import java.security.Provider; - import java.util.Collections; - - final class Java8SslTestUtils { -@@ -37,8 +34,4 @@ final class Java8SslTestUtils { - }; - parameters.setSNIMatchers(Collections.singleton(matcher)); - } -- -- static Provider conscryptProvider() { -- return new OpenSSLProvider(); -- } - } -diff --git a/handler/src/test/java/io/netty/handler/ssl/JdkConscryptSslEngineInteropTest.java b/handler/src/test/java/io/netty/handler/ssl/JdkConscryptSslEngineInteropTest.java -deleted file mode 100644 -index 0625f7a..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/JdkConscryptSslEngineInteropTest.java -+++ /dev/null -@@ -1,86 +0,0 @@ --/* -- * Copyright 2017 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; -- --import java.security.Provider; --import org.junit.BeforeClass; --import org.junit.Ignore; --import org.junit.Test; --import org.junit.runner.RunWith; --import org.junit.runners.Parameterized; -- --import java.util.ArrayList; --import java.util.Collection; --import java.util.List; -- --import static org.junit.Assume.assumeTrue; -- --@RunWith(Parameterized.class) --public class JdkConscryptSslEngineInteropTest extends SSLEngineTest { -- -- @Parameterized.Parameters(name = "{index}: bufferType = {0}") -- public static Collection data() { -- List params = new ArrayList(); -- for (BufferType type: BufferType.values()) { -- params.add(type); -- } -- return params; -- } -- -- public JdkConscryptSslEngineInteropTest(BufferType type) { -- super(type); -- } -- -- @BeforeClass -- public static void checkConscrypt() { -- assumeTrue(ConscryptAlpnSslEngine.isAvailable()); -- } -- -- @Override -- protected SslProvider sslClientProvider() { -- return SslProvider.JDK; -- } -- -- @Override -- protected SslProvider sslServerProvider() { -- return SslProvider.JDK; -- } -- -- @Override -- protected Provider serverSslContextProvider() { -- return Java8SslTestUtils.conscryptProvider(); -- } -- -- @Override -- @Test -- @Ignore("TODO: Make this work with Conscrypt") -- public void testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth() throws Exception { -- super.testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth(); -- } -- -- @Override -- @Test -- @Ignore("TODO: Make this work with Conscrypt") -- public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth() throws Exception { -- super.testMutualAuthValidClientCertChainTooLongFailRequireClientAuth(); -- } -- -- @Override -- protected boolean mySetupMutualAuthServerIsValidClientException(Throwable cause) { -- // TODO(scott): work around for a JDK issue. The exception should be SSLHandshakeException. -- return super.mySetupMutualAuthServerIsValidClientException(cause) || causedBySSLException(cause); -- } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java -index 4489b16..e32fa0d 100644 ---- a/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java -+++ b/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java -@@ -81,7 +81,7 @@ public class JdkSslEngineTest extends SSLEngineTest { - - @Override - boolean isAvailable() { -- return ConscryptAlpnSslEngine.isAvailable(); -+ return false; - } + SslEngineType(boolean wantsDirectBuffer, Cumulator cumulator) { +diff --git a/pom.xml b/pom.xml +index bf36e0b533..ae68f4ebe8 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -858,16 +858,6 @@ + true + - @Override +- +- +- ${conscrypt.groupId} +- ${conscrypt.artifactId} +- ${conscrypt.classifier} +- ${conscrypt.version} +- compile +- true +- +- + +- +- org.eclipse.jetty.npn +- npn-api +- 1.1.1.v20141010 +- provided +- +- +- org.eclipse.jetty.alpn +- alpn-api +- 1.1.2.v20150522 +- provided +- +- + + + com.google.protobuf -- -2.9.4 +2.46.1 diff --git a/0001-Remove-OpenSSL-parts-depending-on-tcnative.patch b/0004-Remove-optional-dep-tcnative.patch similarity index 45% rename from 0001-Remove-OpenSSL-parts-depending-on-tcnative.patch rename to 0004-Remove-optional-dep-tcnative.patch index aee06500bbd216a7f966d649806bbcbf6243adf5..eb6642d5f882bf9b526a67cdcf30fd495f386aae 100644 --- a/0001-Remove-OpenSSL-parts-depending-on-tcnative.patch +++ b/0004-Remove-optional-dep-tcnative.patch @@ -1,108 +1,103 @@ -From 39b320920d3473d8cbc94d4a35dad37fa236e278 Mon Sep 17 00:00:00 2001 -From: Severin Gehwolf -Date: Thu, 20 Oct 2016 15:54:52 +0200 -Subject: [PATCH 1/3] Remove OpenSSL parts depending on tcnative. +From 0d48b0e20264f3391a11c590dc74ac1ee1040156 Mon Sep 17 00:00:00 2001 +From: starlet-dx <15929766099@163.com> +Date: Thu, 14 Nov 2024 14:57:08 +0800 +Subject: [PATCH 1/1] case1 --- - handler/pom.xml | 6 - - .../main/java/io/netty/handler/ssl/OpenSsl.java | 503 ----- - .../handler/ssl/OpenSslCertificateException.java | 79 - - .../io/netty/handler/ssl/OpenSslClientContext.java | 211 -- - .../java/io/netty/handler/ssl/OpenSslContext.java | 58 - - .../java/io/netty/handler/ssl/OpenSslEngine.java | 40 - - .../io/netty/handler/ssl/OpenSslEngineMap.java | 35 - - .../ssl/OpenSslExtendedKeyMaterialManager.java | 40 - - .../handler/ssl/OpenSslKeyMaterialManager.java | 179 -- - .../io/netty/handler/ssl/OpenSslServerContext.java | 373 ---- - .../handler/ssl/OpenSslServerSessionContext.java | 124 -- - .../netty/handler/ssl/OpenSslSessionContext.java | 137 -- - .../io/netty/handler/ssl/OpenSslSessionStats.java | 253 --- - .../netty/handler/ssl/OpenSslSessionTicketKey.java | 78 - - .../ssl/ReferenceCountedOpenSslClientContext.java | 298 --- - .../ssl/ReferenceCountedOpenSslContext.java | 867 --------- - .../handler/ssl/ReferenceCountedOpenSslEngine.java | 2037 -------------------- - .../ssl/ReferenceCountedOpenSslServerContext.java | 239 --- - .../main/java/io/netty/handler/ssl/SslContext.java | 30 +- - .../main/java/io/netty/handler/ssl/SslHandler.java | 47 +- - .../netty/handler/ssl/ocsp/OcspClientHandler.java | 65 - - .../io/netty/handler/ssl/ocsp/package-info.java | 23 - - .../handler/ssl/JdkOpenSslEngineInteroptTest.java | 108 -- - .../ssl/OpenSslCertificateExceptionTest.java | 49 - - .../handler/ssl/OpenSslClientContextTest.java | 38 - - .../io/netty/handler/ssl/OpenSslEngineTest.java | 661 ------- - .../ssl/OpenSslJdkSslEngineInteroptTest.java | 114 -- - .../ssl/OpenSslRenegotiateSmallBIOTest.java | 23 - - .../netty/handler/ssl/OpenSslRenegotiateTest.java | 36 - - .../handler/ssl/OpenSslServerContextTest.java | 39 - - .../io/netty/handler/ssl/OpenSslTestUtils.java | 27 - - .../java/io/netty/handler/ssl/PemEncodedTest.java | 95 - - .../ssl/ReferenceCountedOpenSslEngineTest.java | 57 - - .../java/io/netty/handler/ssl/SniClientTest.java | 161 -- - .../java/io/netty/handler/ssl/SniHandlerTest.java | 496 ----- - .../netty/handler/ssl/SslContextBuilderTest.java | 132 -- - .../java/io/netty/handler/ssl/SslErrorTest.java | 255 --- - .../java/io/netty/handler/ssl/SslHandlerTest.java | 58 +- - .../java/io/netty/handler/ssl/ocsp/OcspTest.java | 501 ----- - 39 files changed, 10 insertions(+), 8562 deletions(-) + handler/pom.xml | 6 - + .../handler/ssl/CipherSuiteConverter.java | 516 --- + .../ssl/DefaultOpenSslKeyMaterial.java | 126 - + .../handler/ssl/ExtendedOpenSslSession.java | 268 -- + .../java/io/netty/handler/ssl/OpenSsl.java | 747 ----- + .../ssl/OpenSslAsyncPrivateKeyMethod.java | 58 - + .../OpenSslCachingKeyMaterialProvider.java | 79 - + .../OpenSslCachingX509KeyManagerFactory.java | 80 - + .../ssl/OpenSslCertificateException.java | 81 - + .../handler/ssl/OpenSslClientContext.java | 212 -- + .../ssl/OpenSslClientSessionCache.java | 188 -- + .../io/netty/handler/ssl/OpenSslContext.java | 64 - + .../handler/ssl/OpenSslContextOption.java | 77 - + .../io/netty/handler/ssl/OpenSslEngine.java | 41 - + .../netty/handler/ssl/OpenSslEngineMap.java | 35 - + .../ssl/OpenSslKeyMaterialManager.java | 138 - + .../ssl/OpenSslKeyMaterialProvider.java | 154 - + .../netty/handler/ssl/OpenSslPrivateKey.java | 191 -- + .../handler/ssl/OpenSslPrivateKeyMethod.java | 62 - + .../handler/ssl/OpenSslServerContext.java | 374 --- + .../ssl/OpenSslServerSessionContext.java | 50 - + .../io/netty/handler/ssl/OpenSslSession.java | 95 - + .../handler/ssl/OpenSslSessionCache.java | 521 ---- + .../handler/ssl/OpenSslSessionContext.java | 229 -- + .../handler/ssl/OpenSslSessionStats.java | 253 -- + .../handler/ssl/OpenSslSessionTicketKey.java | 78 - + .../ssl/OpenSslX509KeyManagerFactory.java | 416 --- + .../ReferenceCountedOpenSslClientContext.java | 324 -- + .../ssl/ReferenceCountedOpenSslContext.java | 1173 ------- + .../ssl/ReferenceCountedOpenSslEngine.java | 2760 ----------------- + .../ReferenceCountedOpenSslServerContext.java | 302 -- + .../java/io/netty/handler/ssl/SslContext.java | 32 +- + .../java/io/netty/handler/ssl/SslHandler.java | 61 +- + .../handler/ssl/SslMasterKeyHandler.java | 3 - + .../io/netty/handler/ssl/SslProvider.java | 23 +- + .../handler/ssl/ocsp/OcspClientHandler.java | 61 - + .../netty/handler/ssl/ocsp/package-info.java | 23 - + 37 files changed, 3 insertions(+), 9898 deletions(-) + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/CipherSuiteConverter.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/DefaultOpenSslKeyMaterial.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/ExtendedOpenSslSession.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSsl.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslAsyncPrivateKeyMethod.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslCachingKeyMaterialProvider.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslCachingX509KeyManagerFactory.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslCertificateException.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslClientSessionCache.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslContextOption.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java - delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialProvider.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslPrivateKey.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslPrivateKeyMethod.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslSession.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslSessionCache.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslSessionTicketKey.java + delete mode 100644 handler/src/main/java/io/netty/handler/ssl/OpenSslX509KeyManagerFactory.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/ocsp/OcspClientHandler.java delete mode 100644 handler/src/main/java/io/netty/handler/ssl/ocsp/package-info.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/JdkOpenSslEngineInteroptTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/OpenSslCertificateExceptionTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/OpenSslClientContextTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/OpenSslJdkSslEngineInteroptTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/OpenSslRenegotiateSmallBIOTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/OpenSslRenegotiateTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/OpenSslServerContextTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/OpenSslTestUtils.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/PemEncodedTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngineTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/SniClientTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/SslContextBuilderTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/SslErrorTest.java - delete mode 100644 handler/src/test/java/io/netty/handler/ssl/ocsp/OcspTest.java diff --git a/handler/pom.xml b/handler/pom.xml -index 7535c45..d0ed1bc 100644 +index 980c79d..08a693c 100644 --- a/handler/pom.xml +++ b/handler/pom.xml -@@ -50,12 +50,6 @@ - ${project.version} +@@ -70,12 +70,6 @@ + netty-tcnative-classes + true - +- - ${project.groupId} - ${tcnative.artifactId} - ${tcnative.classifier} - true - -- + org.bouncycastle bcpkix-jdk15on - true -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java b/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/CipherSuiteConverter.java b/handler/src/main/java/io/netty/handler/ssl/CipherSuiteConverter.java deleted file mode 100644 -index d2f091a..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java +index 910b5e3..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/CipherSuiteConverter.java +++ /dev/null -@@ -1,503 +0,0 @@ +@@ -1,516 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * @@ -110,7 +105,7 @@ index d2f091a..0000000 - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -121,505 +116,518 @@ index d2f091a..0000000 - -package io.netty.handler.ssl; - --import io.netty.buffer.ByteBuf; --import io.netty.handler.ssl.util.SelfSignedCertificate; --import io.netty.util.ReferenceCountUtil; --import io.netty.util.ReferenceCounted; --import io.netty.util.internal.NativeLibraryLoader; --import io.netty.util.internal.SystemPropertyUtil; +-import io.netty.util.internal.PlatformDependent; +-import io.netty.util.internal.UnstableApi; -import io.netty.util.internal.logging.InternalLogger; -import io.netty.util.internal.logging.InternalLoggerFactory; --import io.netty.internal.tcnative.Buffer; --import io.netty.internal.tcnative.Library; --import io.netty.internal.tcnative.SSL; --import io.netty.internal.tcnative.SSLContext; - --import java.security.AccessController; --import java.security.PrivilegedAction; -import java.util.Collections; --import java.util.LinkedHashSet; --import java.util.Locale; --import java.util.Set; +-import java.util.HashMap; +-import java.util.Map; +-import java.util.concurrent.ConcurrentMap; +-import java.util.regex.Matcher; +-import java.util.regex.Pattern; +- +-import static java.util.Collections.singletonMap; - -/** -- * Tells if {@code netty-tcnative} and its OpenSSL support -- * are available. +- * Converts a Java cipher suite string to an OpenSSL cipher suite string and vice versa. +- * +- * @see Wikipedia page about cipher suite - */ --public final class OpenSsl { -- -- private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class); -- private static final String LINUX = "linux"; -- private static final String UNKNOWN = "unknown"; -- private static final Throwable UNAVAILABILITY_CAUSE; -- -- static final Set AVAILABLE_CIPHER_SUITES; -- private static final Set AVAILABLE_OPENSSL_CIPHER_SUITES; -- private static final Set AVAILABLE_JAVA_CIPHER_SUITES; -- private static final boolean SUPPORTS_KEYMANAGER_FACTORY; -- private static final boolean SUPPORTS_HOSTNAME_VALIDATION; -- private static final boolean USE_KEYMANAGER_FACTORY; -- private static final boolean SUPPORTS_OCSP; -- -- // Protocols -- static final String PROTOCOL_SSL_V2_HELLO = "SSLv2Hello"; -- static final String PROTOCOL_SSL_V2 = "SSLv2"; -- static final String PROTOCOL_SSL_V3 = "SSLv3"; -- static final String PROTOCOL_TLS_V1 = "TLSv1"; -- static final String PROTOCOL_TLS_V1_1 = "TLSv1.1"; -- static final String PROTOCOL_TLS_V1_2 = "TLSv1.2"; -- -- static final Set SUPPORTED_PROTOCOLS_SET; -- -- static { -- Throwable cause = null; -- -- // Test if netty-tcnative is in the classpath first. -- try { -- Class.forName("io.netty.internal.tcnative.SSL", false, OpenSsl.class.getClassLoader()); -- } catch (ClassNotFoundException t) { -- cause = t; -- logger.debug( -- "netty-tcnative not in the classpath; " + -- OpenSslEngine.class.getSimpleName() + " will be unavailable."); -- } -- -- // If in the classpath, try to load the native library and initialize netty-tcnative. -- if (cause == null) { -- try { -- // The JNI library was not already loaded. Load it now. -- loadTcNative(); -- } catch (Throwable t) { -- cause = t; -- logger.debug( -- "Failed to load netty-tcnative; " + -- OpenSslEngine.class.getSimpleName() + " will be unavailable, unless the " + -- "application has already loaded the symbols by some other means. " + -- "See http://netty.io/wiki/forked-tomcat-native.html for more information.", t); -- } -- -- try { -- initializeTcNative(); -- -- // The library was initialized successfully. If loading the library failed above, -- // reset the cause now since it appears that the library was loaded by some other -- // means. -- cause = null; -- } catch (Throwable t) { -- if (cause == null) { -- cause = t; -- } -- logger.debug( -- "Failed to initialize netty-tcnative; " + -- OpenSslEngine.class.getSimpleName() + " will be unavailable. " + -- "See http://netty.io/wiki/forked-tomcat-native.html for more information.", t); -- } -- } -- -- UNAVAILABILITY_CAUSE = cause; -- -- if (cause == null) { -- logger.debug("netty-tcnative using native library: {}", SSL.versionString()); +-@UnstableApi +-public final class CipherSuiteConverter { - -- final Set availableOpenSslCipherSuites = new LinkedHashSet(128); -- boolean supportsKeyManagerFactory = false; -- boolean useKeyManagerFactory = false; -- boolean supportsHostNameValidation = false; -- try { -- final long sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); -- long certBio = 0; -- SelfSignedCertificate cert = null; -- try { -- SSLContext.setCipherSuite(sslCtx, "ALL"); -- final long ssl = SSL.newSSL(sslCtx, true); -- try { -- for (String c: SSL.getCiphers(ssl)) { -- // Filter out bad input. -- if (c == null || c.isEmpty() || availableOpenSslCipherSuites.contains(c)) { -- continue; -- } -- availableOpenSslCipherSuites.add(c); -- } -- try { -- SSL.setHostNameValidation(ssl, 0, "netty.io"); -- supportsHostNameValidation = true; -- } catch (Throwable ignore) { -- logger.debug("Hostname Verification not supported."); -- } -- try { -- cert = new SelfSignedCertificate(); -- certBio = ReferenceCountedOpenSslContext.toBIO(cert.cert()); -- SSL.setCertificateChainBio(ssl, certBio, false); -- supportsKeyManagerFactory = true; -- try { -- useKeyManagerFactory = AccessController.doPrivileged(new PrivilegedAction() { -- @Override -- public Boolean run() { -- return SystemPropertyUtil.getBoolean( -- "io.netty.handler.ssl.openssl.useKeyManagerFactory", true); -- } -- }); -- } catch (Throwable ignore) { -- logger.debug("Failed to get useKeyManagerFactory system property."); -- } -- } catch (Throwable ignore) { -- logger.debug("KeyManagerFactory not supported."); -- } -- } finally { -- SSL.freeSSL(ssl); -- if (certBio != 0) { -- SSL.freeBIO(certBio); -- } -- if (cert != null) { -- cert.delete(); -- } -- } -- } finally { -- SSLContext.free(sslCtx); -- } -- } catch (Exception e) { -- logger.warn("Failed to get the list of available OpenSSL cipher suites.", e); -- } -- AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.unmodifiableSet(availableOpenSslCipherSuites); +- private static final InternalLogger logger = InternalLoggerFactory.getInstance(CipherSuiteConverter.class); - -- final Set availableJavaCipherSuites = new LinkedHashSet( -- AVAILABLE_OPENSSL_CIPHER_SUITES.size() * 2); -- for (String cipher: AVAILABLE_OPENSSL_CIPHER_SUITES) { -- // Included converted but also openssl cipher name -- availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "TLS")); -- availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "SSL")); -- } -- AVAILABLE_JAVA_CIPHER_SUITES = Collections.unmodifiableSet(availableJavaCipherSuites); +- /** +- * A_B_WITH_C_D, where: +- * +- * A - TLS or SSL (protocol) +- * B - handshake algorithm (key exchange and authentication algorithms to be precise) +- * C - bulk cipher +- * D - HMAC algorithm +- * +- * This regular expression assumes that: +- * +- * 1) A is always TLS or SSL, and +- * 2) D is always a single word. +- */ +- private static final Pattern JAVA_CIPHERSUITE_PATTERN = +- Pattern.compile("^(?:TLS|SSL)_((?:(?!_WITH_).)+)_WITH_(.*)_(.*)$"); - -- final Set availableCipherSuites = new LinkedHashSet( -- AVAILABLE_OPENSSL_CIPHER_SUITES.size() + AVAILABLE_JAVA_CIPHER_SUITES.size()); -- availableCipherSuites.addAll(AVAILABLE_OPENSSL_CIPHER_SUITES); -- availableCipherSuites.addAll(AVAILABLE_JAVA_CIPHER_SUITES); +- /** +- * A-B-C, where: +- * +- * A - handshake algorithm (key exchange and authentication algorithms to be precise) +- * B - bulk cipher +- * C - HMAC algorithm +- * +- * This regular expression assumes that: +- * +- * 1) A has some deterministic pattern as shown below, and +- * 2) C is always a single word +- */ +- private static final Pattern OPENSSL_CIPHERSUITE_PATTERN = +- // Be very careful not to break the indentation while editing. +- Pattern.compile( +- "^(?:(" + // BEGIN handshake algorithm +- "(?:(?:EXP-)?" + +- "(?:" + +- "(?:DHE|EDH|ECDH|ECDHE|SRP|RSA)-(?:DSS|RSA|ECDSA|PSK)|" + +- "(?:ADH|AECDH|KRB5|PSK|SRP)" + +- ')' + +- ")|" + +- "EXP" + +- ")-)?" + // END handshake algorithm +- "(.*)-(.*)$"); +- +- private static final Pattern JAVA_AES_CBC_PATTERN = Pattern.compile("^(AES)_([0-9]+)_CBC$"); +- private static final Pattern JAVA_AES_PATTERN = Pattern.compile("^(AES)_([0-9]+)_(.*)$"); +- private static final Pattern OPENSSL_AES_CBC_PATTERN = Pattern.compile("^(AES)([0-9]+)$"); +- private static final Pattern OPENSSL_AES_PATTERN = Pattern.compile("^(AES)([0-9]+)-(.*)$"); - -- AVAILABLE_CIPHER_SUITES = availableCipherSuites; -- SUPPORTS_KEYMANAGER_FACTORY = supportsKeyManagerFactory; -- SUPPORTS_HOSTNAME_VALIDATION = supportsHostNameValidation; -- USE_KEYMANAGER_FACTORY = useKeyManagerFactory; +- /** +- * Used to store nullable values in a CHM +- */ +- private static final class CachedValue { - -- Set protocols = new LinkedHashSet(6); -- // Seems like there is no way to explicitly disable SSLv2Hello in openssl so it is always enabled -- protocols.add(PROTOCOL_SSL_V2_HELLO); -- if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV2)) { -- protocols.add(PROTOCOL_SSL_V2); -- } -- if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV3)) { -- protocols.add(PROTOCOL_SSL_V3); -- } -- if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1)) { -- protocols.add(PROTOCOL_TLS_V1); -- } -- if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_1)) { -- protocols.add(PROTOCOL_TLS_V1_1); -- } -- if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_2)) { -- protocols.add(PROTOCOL_TLS_V1_2); -- } +- private static final CachedValue NULL = new CachedValue(null); - -- SUPPORTED_PROTOCOLS_SET = Collections.unmodifiableSet(protocols); -- SUPPORTS_OCSP = doesSupportOcsp(); -- } else { -- AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.emptySet(); -- AVAILABLE_JAVA_CIPHER_SUITES = Collections.emptySet(); -- AVAILABLE_CIPHER_SUITES = Collections.emptySet(); -- SUPPORTS_KEYMANAGER_FACTORY = false; -- SUPPORTS_HOSTNAME_VALIDATION = false; -- USE_KEYMANAGER_FACTORY = false; -- SUPPORTED_PROTOCOLS_SET = Collections.emptySet(); -- SUPPORTS_OCSP = false; +- static CachedValue of(String value) { +- return value != null ? new CachedValue(value) : NULL; - } -- } - -- private static boolean doesSupportOcsp() { -- boolean supportsOcsp = false; -- if (version() >= 0x10002000L) { -- long sslCtx = -1; -- try { -- sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_MODE_SERVER); -- SSLContext.enableOcsp(sslCtx, false); -- supportsOcsp = true; -- } catch (Exception ignore) { -- // ignore -- } finally { -- if (sslCtx != -1) { -- SSLContext.free(sslCtx); -- } -- } -- } -- return supportsOcsp; -- } -- private static boolean doesSupportProtocol(int protocol) { -- long sslCtx = -1; -- try { -- sslCtx = SSLContext.make(protocol, SSL.SSL_MODE_COMBINED); -- return true; -- } catch (Exception ignore) { -- return false; -- } finally { -- if (sslCtx != -1) { -- SSLContext.free(sslCtx); -- } +- final String value; +- private CachedValue(String value) { +- this.value = value; - } - } - - /** -- * Returns {@code true} if and only if -- * {@code netty-tcnative} and its OpenSSL support -- * are available. +- * Java-to-OpenSSL cipher suite conversion map +- * Note that the Java cipher suite has the protocol prefix (TLS_, SSL_) - */ -- public static boolean isAvailable() { -- return UNAVAILABILITY_CAUSE == null; -- } +- private static final ConcurrentMap j2o = PlatformDependent.newConcurrentHashMap(); - - /** -- * Returns {@code true} if the used version of openssl supports -- * ALPN. +- * OpenSSL-to-Java cipher suite conversion map. +- * Note that one OpenSSL cipher suite can be converted to more than one Java cipher suites because +- * a Java cipher suite has the protocol name prefix (TLS_, SSL_) - */ -- public static boolean isAlpnSupported() { -- return version() >= 0x10002000L; -- } +- private static final ConcurrentMap> o2j = PlatformDependent.newConcurrentHashMap(); - -- /** -- * Returns {@code true} if the used version of OpenSSL supports OCSP stapling. -- */ -- public static boolean isOcspSupported() { -- return SUPPORTS_OCSP; +- private static final Map j2oTls13; +- private static final Map> o2jTls13; +- +- static { +- Map j2oTls13Map = new HashMap(); +- j2oTls13Map.put("TLS_AES_128_GCM_SHA256", "AEAD-AES128-GCM-SHA256"); +- j2oTls13Map.put("TLS_AES_256_GCM_SHA384", "AEAD-AES256-GCM-SHA384"); +- j2oTls13Map.put("TLS_CHACHA20_POLY1305_SHA256", "AEAD-CHACHA20-POLY1305-SHA256"); +- j2oTls13 = Collections.unmodifiableMap(j2oTls13Map); +- +- Map> o2jTls13Map = new HashMap>(); +- o2jTls13Map.put("TLS_AES_128_GCM_SHA256", singletonMap("TLS", "TLS_AES_128_GCM_SHA256")); +- o2jTls13Map.put("TLS_AES_256_GCM_SHA384", singletonMap("TLS", "TLS_AES_256_GCM_SHA384")); +- o2jTls13Map.put("TLS_CHACHA20_POLY1305_SHA256", singletonMap("TLS", "TLS_CHACHA20_POLY1305_SHA256")); +- o2jTls13Map.put("AEAD-AES128-GCM-SHA256", singletonMap("TLS", "TLS_AES_128_GCM_SHA256")); +- o2jTls13Map.put("AEAD-AES256-GCM-SHA384", singletonMap("TLS", "TLS_AES_256_GCM_SHA384")); +- o2jTls13Map.put("AEAD-CHACHA20-POLY1305-SHA256", singletonMap("TLS", "TLS_CHACHA20_POLY1305_SHA256")); +- o2jTls13 = Collections.unmodifiableMap(o2jTls13Map); - } - - /** -- * Returns the version of the used available OpenSSL library or {@code -1} if {@link #isAvailable()} -- * returns {@code false}. +- * Clears the cache for testing purpose. - */ -- public static int version() { -- return isAvailable() ? SSL.version() : -1; +- static void clearCache() { +- j2o.clear(); +- o2j.clear(); - } - - /** -- * Returns the version string of the used available OpenSSL library or {@code null} if {@link #isAvailable()} -- * returns {@code false}. +- * Tests if the specified key-value pair has been cached in Java-to-OpenSSL cache. - */ -- public static String versionString() { -- return isAvailable() ? SSL.versionString() : null; +- static boolean isJ2OCached(String key, String value) { +- CachedValue cached = j2o.get(key); +- return cached != null && value.equals(cached.value); - } - - /** -- * Ensure that {@code netty-tcnative} and -- * its OpenSSL support are available. -- * -- * @throws UnsatisfiedLinkError if unavailable +- * Tests if the specified key-value pair has been cached in OpenSSL-to-Java cache. - */ -- public static void ensureAvailability() { -- if (UNAVAILABILITY_CAUSE != null) { -- throw (Error) new UnsatisfiedLinkError( -- "failed to load the required native library").initCause(UNAVAILABILITY_CAUSE); +- static boolean isO2JCached(String key, String protocol, String value) { +- Map p2j = o2j.get(key); +- if (p2j == null) { +- return false; +- } else { +- return value.equals(p2j.get(protocol)); - } - } - - /** -- * Returns the cause of unavailability of -- * {@code netty-tcnative} and its OpenSSL support. +- * Converts the specified Java cipher suite to its corresponding OpenSSL cipher suite name. - * -- * @return the cause if unavailable. {@code null} if available. +- * @return {@code null} if the conversion has failed - */ -- public static Throwable unavailabilityCause() { -- return UNAVAILABILITY_CAUSE; +- public static String toOpenSsl(String javaCipherSuite, boolean boringSSL) { +- CachedValue converted = j2o.get(javaCipherSuite); +- if (converted != null) { +- return converted.value; +- } +- return cacheFromJava(javaCipherSuite, boringSSL); - } - -- /** -- * @deprecated use {@link #availableOpenSslCipherSuites()} -- */ -- @Deprecated -- public static Set availableCipherSuites() { -- return availableOpenSslCipherSuites(); -- } +- private static String cacheFromJava(String javaCipherSuite, boolean boringSSL) { +- String converted = j2oTls13.get(javaCipherSuite); +- if (converted != null) { +- return boringSSL ? converted : javaCipherSuite; +- } - -- /** -- * Returns all the available OpenSSL cipher suites. -- * Please note that the returned array may include the cipher suites that are insecure or non-functional. -- */ -- public static Set availableOpenSslCipherSuites() { -- return AVAILABLE_OPENSSL_CIPHER_SUITES; -- } +- String openSslCipherSuite = toOpenSslUncached(javaCipherSuite, boringSSL); - -- /** -- * Returns all the available cipher suites (Java-style). -- * Please note that the returned array may include the cipher suites that are insecure or non-functional. -- */ -- public static Set availableJavaCipherSuites() { -- return AVAILABLE_JAVA_CIPHER_SUITES; +- // Cache the mapping. +- j2o.putIfAbsent(javaCipherSuite, CachedValue.of(openSslCipherSuite)); +- +- if (openSslCipherSuite == null) { +- return null; +- } +- +- // Cache the reverse mapping after stripping the protocol prefix (TLS_ or SSL_) +- final String javaCipherSuiteSuffix = javaCipherSuite.substring(4); +- Map p2j = new HashMap(4); +- p2j.put("", javaCipherSuiteSuffix); +- p2j.put("SSL", "SSL_" + javaCipherSuiteSuffix); +- p2j.put("TLS", "TLS_" + javaCipherSuiteSuffix); +- o2j.put(openSslCipherSuite, p2j); +- +- logger.debug("Cipher suite mapping: {} => {}", javaCipherSuite, openSslCipherSuite); +- +- return openSslCipherSuite; - } - -- /** -- * Returns {@code true} if and only if the specified cipher suite is available in OpenSSL. -- * Both Java-style cipher suite and OpenSSL-style cipher suite are accepted. -- */ -- public static boolean isCipherSuiteAvailable(String cipherSuite) { -- String converted = CipherSuiteConverter.toOpenSsl(cipherSuite); +- static String toOpenSslUncached(String javaCipherSuite, boolean boringSSL) { +- String converted = j2oTls13.get(javaCipherSuite); - if (converted != null) { -- cipherSuite = converted; +- return boringSSL ? converted : javaCipherSuite; - } -- return AVAILABLE_OPENSSL_CIPHER_SUITES.contains(cipherSuite); -- } - -- /** -- * Returns {@code true} if {@link javax.net.ssl.KeyManagerFactory} is supported when using OpenSSL. -- */ -- public static boolean supportsKeyManagerFactory() { -- return SUPPORTS_KEYMANAGER_FACTORY; -- } -- -- /** -- * Returns {@code true} if Hostname Validation -- * is supported when using OpenSSL. -- */ -- public static boolean supportsHostnameValidation() { -- return SUPPORTS_HOSTNAME_VALIDATION; -- } -- -- static boolean useKeyManagerFactory() { -- return USE_KEYMANAGER_FACTORY; -- } +- Matcher m = JAVA_CIPHERSUITE_PATTERN.matcher(javaCipherSuite); +- if (!m.matches()) { +- return null; +- } - -- static long memoryAddress(ByteBuf buf) { -- assert buf.isDirect(); -- return buf.hasMemoryAddress() ? buf.memoryAddress() : Buffer.address(buf.nioBuffer()); +- String handshakeAlgo = toOpenSslHandshakeAlgo(m.group(1)); +- String bulkCipher = toOpenSslBulkCipher(m.group(2)); +- String hmacAlgo = toOpenSslHmacAlgo(m.group(3)); +- if (handshakeAlgo.isEmpty()) { +- return bulkCipher + '-' + hmacAlgo; +- } else if (bulkCipher.contains("CHACHA20")) { +- return handshakeAlgo + '-' + bulkCipher; +- } else { +- return handshakeAlgo + '-' + bulkCipher + '-' + hmacAlgo; +- } - } - -- private OpenSsl() { } +- private static String toOpenSslHandshakeAlgo(String handshakeAlgo) { +- final boolean export = handshakeAlgo.endsWith("_EXPORT"); +- if (export) { +- handshakeAlgo = handshakeAlgo.substring(0, handshakeAlgo.length() - 7); +- } - -- private static void loadTcNative() throws Exception { -- String os = normalizeOs(SystemPropertyUtil.get("os.name", "")); -- String arch = normalizeArch(SystemPropertyUtil.get("os.arch", "")); +- if ("RSA".equals(handshakeAlgo)) { +- handshakeAlgo = ""; +- } else if (handshakeAlgo.endsWith("_anon")) { +- handshakeAlgo = 'A' + handshakeAlgo.substring(0, handshakeAlgo.length() - 5); +- } - -- Set libNames = new LinkedHashSet(4); -- // First, try loading the platform-specific library. Platform-specific -- // libraries will be available if using a tcnative uber jar. -- libNames.add("netty-tcnative-" + os + '-' + arch); -- if (LINUX.equalsIgnoreCase(os)) { -- // Fedora SSL lib so naming (libssl.so.10 vs libssl.so.1.0.0).. -- libNames.add("netty-tcnative-" + os + '-' + arch + "-fedora"); +- if (export) { +- if (handshakeAlgo.isEmpty()) { +- handshakeAlgo = "EXP"; +- } else { +- handshakeAlgo = "EXP-" + handshakeAlgo; +- } - } -- // finally the default library. -- libNames.add("netty-tcnative"); -- // in Java 8, statically compiled JNI code is namespaced -- libNames.add("netty_tcnative"); - -- NativeLibraryLoader.loadFirstAvailable(SSL.class.getClassLoader(), -- libNames.toArray(new String[libNames.size()])); +- return handshakeAlgo.replace('_', '-'); - } - -- private static boolean initializeTcNative() throws Exception { -- return Library.initialize(); -- } +- private static String toOpenSslBulkCipher(String bulkCipher) { +- if (bulkCipher.startsWith("AES_")) { +- Matcher m = JAVA_AES_CBC_PATTERN.matcher(bulkCipher); +- if (m.matches()) { +- return m.replaceFirst("$1$2"); +- } - -- private static String normalizeOs(String value) { -- value = normalize(value); -- if (value.startsWith("aix")) { -- return "aix"; -- } -- if (value.startsWith("hpux")) { -- return "hpux"; -- } -- if (value.startsWith("os400")) { -- // Avoid the names such as os4000 -- if (value.length() <= 5 || !Character.isDigit(value.charAt(5))) { -- return "os400"; +- m = JAVA_AES_PATTERN.matcher(bulkCipher); +- if (m.matches()) { +- return m.replaceFirst("$1$2-$3"); - } - } -- if (value.startsWith(LINUX)) { -- return LINUX; -- } -- if (value.startsWith("macosx") || value.startsWith("osx")) { -- return "osx"; +- +- if ("3DES_EDE_CBC".equals(bulkCipher)) { +- return "DES-CBC3"; - } -- if (value.startsWith("freebsd")) { -- return "freebsd"; +- +- if ("RC4_128".equals(bulkCipher) || "RC4_40".equals(bulkCipher)) { +- return "RC4"; - } -- if (value.startsWith("openbsd")) { -- return "openbsd"; +- +- if ("DES40_CBC".equals(bulkCipher) || "DES_CBC_40".equals(bulkCipher)) { +- return "DES-CBC"; - } -- if (value.startsWith("netbsd")) { -- return "netbsd"; +- +- if ("RC2_CBC_40".equals(bulkCipher)) { +- return "RC2-CBC"; - } -- if (value.startsWith("solaris") || value.startsWith("sunos")) { -- return "sunos"; +- +- return bulkCipher.replace('_', '-'); +- } +- +- private static String toOpenSslHmacAlgo(String hmacAlgo) { +- // Java and OpenSSL use the same algorithm names for: +- // +- // * SHA +- // * SHA256 +- // * MD5 +- // +- return hmacAlgo; +- } +- +- /** +- * Convert from OpenSSL cipher suite name convention to java cipher suite name convention. +- * @param openSslCipherSuite An OpenSSL cipher suite name. +- * @param protocol The cryptographic protocol (i.e. SSL, TLS, ...). +- * @return The translated cipher suite name according to java conventions. This will not be {@code null}. +- */ +- public static String toJava(String openSslCipherSuite, String protocol) { +- Map p2j = o2j.get(openSslCipherSuite); +- if (p2j == null) { +- p2j = cacheFromOpenSsl(openSslCipherSuite); +- // This may happen if this method is queried when OpenSSL doesn't yet have a cipher setup. It will return +- // "(NONE)" in this case. +- if (p2j == null) { +- return null; +- } - } -- if (value.startsWith("windows")) { -- return "windows"; +- +- String javaCipherSuite = p2j.get(protocol); +- if (javaCipherSuite == null) { +- String cipher = p2j.get(""); +- if (cipher == null) { +- return null; +- } +- javaCipherSuite = protocol + '_' + cipher; - } - -- return UNKNOWN; +- return javaCipherSuite; - } - -- private static String normalizeArch(String value) { -- value = normalize(value); -- if (value.matches("^(x8664|amd64|ia32e|em64t|x64)$")) { -- return "x86_64"; +- private static Map cacheFromOpenSsl(String openSslCipherSuite) { +- Map converted = o2jTls13.get(openSslCipherSuite); +- if (converted != null) { +- return converted; - } -- if (value.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) { -- return "x86_32"; +- +- String javaCipherSuiteSuffix = toJavaUncached0(openSslCipherSuite, false); +- if (javaCipherSuiteSuffix == null) { +- return null; - } -- if (value.matches("^(ia64|itanium64)$")) { -- return "itanium_64"; +- +- final String javaCipherSuiteSsl = "SSL_" + javaCipherSuiteSuffix; +- final String javaCipherSuiteTls = "TLS_" + javaCipherSuiteSuffix; +- +- // Cache the mapping. +- final Map p2j = new HashMap(4); +- p2j.put("", javaCipherSuiteSuffix); +- p2j.put("SSL", javaCipherSuiteSsl); +- p2j.put("TLS", javaCipherSuiteTls); +- o2j.putIfAbsent(openSslCipherSuite, p2j); +- +- // Cache the reverse mapping after adding the protocol prefix (TLS_ or SSL_) +- CachedValue cachedValue = CachedValue.of(openSslCipherSuite); +- j2o.putIfAbsent(javaCipherSuiteTls, cachedValue); +- j2o.putIfAbsent(javaCipherSuiteSsl, cachedValue); +- +- logger.debug("Cipher suite mapping: {} => {}", javaCipherSuiteTls, openSslCipherSuite); +- logger.debug("Cipher suite mapping: {} => {}", javaCipherSuiteSsl, openSslCipherSuite); +- +- return p2j; +- } +- +- static String toJavaUncached(String openSslCipherSuite) { +- return toJavaUncached0(openSslCipherSuite, true); +- } +- +- private static String toJavaUncached0(String openSslCipherSuite, boolean checkTls13) { +- if (checkTls13) { +- Map converted = o2jTls13.get(openSslCipherSuite); +- if (converted != null) { +- return converted.get("TLS"); +- } - } -- if (value.matches("^(sparc|sparc32)$")) { -- return "sparc_32"; +- +- Matcher m = OPENSSL_CIPHERSUITE_PATTERN.matcher(openSslCipherSuite); +- if (!m.matches()) { +- return null; - } -- if (value.matches("^(sparcv9|sparc64)$")) { -- return "sparc_64"; +- +- String handshakeAlgo = m.group(1); +- final boolean export; +- if (handshakeAlgo == null) { +- handshakeAlgo = ""; +- export = false; +- } else if (handshakeAlgo.startsWith("EXP-")) { +- handshakeAlgo = handshakeAlgo.substring(4); +- export = true; +- } else if ("EXP".equals(handshakeAlgo)) { +- handshakeAlgo = ""; +- export = true; +- } else { +- export = false; - } -- if (value.matches("^(arm|arm32)$")) { -- return "arm_32"; +- +- handshakeAlgo = toJavaHandshakeAlgo(handshakeAlgo, export); +- String bulkCipher = toJavaBulkCipher(m.group(2), export); +- String hmacAlgo = toJavaHmacAlgo(m.group(3)); +- +- String javaCipherSuite = handshakeAlgo + "_WITH_" + bulkCipher + '_' + hmacAlgo; +- // For historical reasons the CHACHA20 ciphers do not follow OpenSSL's custom naming convention and omits the +- // HMAC algorithm portion of the name. There is currently no way to derive this information because it is +- // omitted from the OpenSSL cipher name, but they currently all use SHA256 for HMAC [1]. +- // [1] https://www.openssl.org/docs/man1.1.0/apps/ciphers.html +- return bulkCipher.contains("CHACHA20") ? javaCipherSuite + "_SHA256" : javaCipherSuite; +- } +- +- private static String toJavaHandshakeAlgo(String handshakeAlgo, boolean export) { +- if (handshakeAlgo.isEmpty()) { +- handshakeAlgo = "RSA"; +- } else if ("ADH".equals(handshakeAlgo)) { +- handshakeAlgo = "DH_anon"; +- } else if ("AECDH".equals(handshakeAlgo)) { +- handshakeAlgo = "ECDH_anon"; - } -- if ("aarch64".equals(value)) { -- return "aarch_64"; +- +- handshakeAlgo = handshakeAlgo.replace('-', '_'); +- if (export) { +- return handshakeAlgo + "_EXPORT"; +- } else { +- return handshakeAlgo; - } -- if (value.matches("^(ppc|ppc32)$")) { -- return "ppc_32"; +- } +- +- private static String toJavaBulkCipher(String bulkCipher, boolean export) { +- if (bulkCipher.startsWith("AES")) { +- Matcher m = OPENSSL_AES_CBC_PATTERN.matcher(bulkCipher); +- if (m.matches()) { +- return m.replaceFirst("$1_$2_CBC"); +- } +- +- m = OPENSSL_AES_PATTERN.matcher(bulkCipher); +- if (m.matches()) { +- return m.replaceFirst("$1_$2_$3"); +- } - } -- if ("ppc64".equals(value)) { -- return "ppc_64"; +- +- if ("DES-CBC3".equals(bulkCipher)) { +- return "3DES_EDE_CBC"; - } -- if ("ppc64le".equals(value)) { -- return "ppcle_64"; +- +- if ("RC4".equals(bulkCipher)) { +- if (export) { +- return "RC4_40"; +- } else { +- return "RC4_128"; +- } - } -- if ("s390".equals(value)) { -- return "s390_32"; +- +- if ("DES-CBC".equals(bulkCipher)) { +- if (export) { +- return "DES_CBC_40"; +- } else { +- return "DES_CBC"; +- } - } -- if ("s390x".equals(value)) { -- return "s390_64"; +- +- if ("RC2-CBC".equals(bulkCipher)) { +- if (export) { +- return "RC2_CBC_40"; +- } else { +- return "RC2_CBC"; +- } - } - -- return UNKNOWN; +- return bulkCipher.replace('-', '_'); - } - -- private static String normalize(String value) { -- return value.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", ""); +- private static String toJavaHmacAlgo(String hmacAlgo) { +- // Java and OpenSSL use the same algorithm names for: +- // +- // * SHA +- // * SHA256 +- // * MD5 +- // +- return hmacAlgo; - } - -- static void releaseIfNeeded(ReferenceCounted counted) { -- if (counted.refCnt() > 0) { -- ReferenceCountUtil.safeRelease(counted); +- /** +- * Convert the given ciphers if needed to OpenSSL format and append them to the correct {@link StringBuilder} +- * depending on if its a TLSv1.3 cipher or not. If this methods returns without throwing an exception its +- * guaranteed that at least one of the {@link StringBuilder}s contain some ciphers that can be used to configure +- * OpenSSL. +- */ +- static void convertToCipherStrings(Iterable cipherSuites, StringBuilder cipherBuilder, +- StringBuilder cipherTLSv13Builder, boolean boringSSL) { +- for (String c: cipherSuites) { +- if (c == null) { +- break; +- } +- +- String converted = toOpenSsl(c, boringSSL); +- if (converted == null) { +- converted = c; +- } +- +- if (!OpenSsl.isCipherSuiteAvailable(converted)) { +- throw new IllegalArgumentException("unsupported cipher suite: " + c + '(' + converted + ')'); +- } +- +- if (SslUtils.isTLSv13Cipher(converted) || SslUtils.isTLSv13Cipher(c)) { +- cipherTLSv13Builder.append(converted); +- cipherTLSv13Builder.append(':'); +- } else { +- cipherBuilder.append(converted); +- cipherBuilder.append(':'); +- } +- } +- +- if (cipherBuilder.length() == 0 && cipherTLSv13Builder.length() == 0) { +- throw new IllegalArgumentException("empty cipher suites"); +- } +- if (cipherBuilder.length() > 0) { +- cipherBuilder.setLength(cipherBuilder.length() - 1); +- } +- if (cipherTLSv13Builder.length() > 0) { +- cipherTLSv13Builder.setLength(cipherTLSv13Builder.length() - 1); - } - } +- +- private CipherSuiteConverter() { } -} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslCertificateException.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslCertificateException.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/DefaultOpenSslKeyMaterial.java b/handler/src/main/java/io/netty/handler/ssl/DefaultOpenSslKeyMaterial.java deleted file mode 100644 -index 4672d00..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslCertificateException.java +index 6673044..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/DefaultOpenSslKeyMaterial.java +++ /dev/null -@@ -1,79 +0,0 @@ +@@ -1,126 +0,0 @@ -/* -- * Copyright 2016 The Netty Project +- * Copyright 2018 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -629,82 +637,129 @@ index 4672d00..0000000 - */ -package io.netty.handler.ssl; - --import io.netty.internal.tcnative.CertificateVerifier; +-import io.netty.internal.tcnative.SSL; +-import io.netty.util.AbstractReferenceCounted; +-import io.netty.util.IllegalReferenceCountException; +-import io.netty.util.ResourceLeakDetector; +-import io.netty.util.ResourceLeakDetectorFactory; +-import io.netty.util.ResourceLeakTracker; - --import java.security.cert.CertificateException; +-import java.security.cert.X509Certificate; - --/** -- * A special {@link CertificateException} which allows to specify which error code is included in the -- * SSL Record. This only work when {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} is used. -- */ --public final class OpenSslCertificateException extends CertificateException { -- private static final long serialVersionUID = 5542675253797129798L; +-final class DefaultOpenSslKeyMaterial extends AbstractReferenceCounted implements OpenSslKeyMaterial { - -- private final int errorCode; +- private static final ResourceLeakDetector leakDetector = +- ResourceLeakDetectorFactory.instance().newResourceLeakDetector(DefaultOpenSslKeyMaterial.class); +- private final ResourceLeakTracker leak; +- private final X509Certificate[] x509CertificateChain; +- private long chain; +- private long privateKey; - -- /** -- * Construct a new exception with the -- * error code. -- */ -- public OpenSslCertificateException(int errorCode) { -- this((String) null, errorCode); +- DefaultOpenSslKeyMaterial(long chain, long privateKey, X509Certificate[] x509CertificateChain) { +- this.chain = chain; +- this.privateKey = privateKey; +- this.x509CertificateChain = x509CertificateChain; +- leak = leakDetector.track(this); - } - -- /** -- * Construct a new exception with the msg and -- * error code . -- */ -- public OpenSslCertificateException(String msg, int errorCode) { -- super(msg); -- this.errorCode = checkErrorCode(errorCode); +- @Override +- public X509Certificate[] certificateChain() { +- return x509CertificateChain.clone(); - } - -- /** -- * Construct a new exception with the msg, cause and -- * error code . -- */ -- public OpenSslCertificateException(String message, Throwable cause, int errorCode) { -- super(message, cause); -- this.errorCode = checkErrorCode(errorCode); +- @Override +- public long certificateChainAddress() { +- if (refCnt() <= 0) { +- throw new IllegalReferenceCountException(); +- } +- return chain; - } - -- /** -- * Construct a new exception with the cause and -- * error code . -- */ -- public OpenSslCertificateException(Throwable cause, int errorCode) { -- this(null, cause, errorCode); +- @Override +- public long privateKeyAddress() { +- if (refCnt() <= 0) { +- throw new IllegalReferenceCountException(); +- } +- return privateKey; - } - -- /** -- * Return the error code to use. -- */ -- public int errorCode() { -- return errorCode; +- @Override +- protected void deallocate() { +- SSL.freeX509Chain(chain); +- chain = 0; +- SSL.freePrivateKey(privateKey); +- privateKey = 0; +- if (leak != null) { +- boolean closed = leak.close(this); +- assert closed; +- } - } - -- private static int checkErrorCode(int errorCode) { -- if (!CertificateVerifier.isValid(errorCode)) { -- throw new IllegalArgumentException("errorCode '" + errorCode + -- "' invalid, see https://www.openssl.org/docs/man1.0.2/apps/verify.html."); +- @Override +- public DefaultOpenSslKeyMaterial retain() { +- if (leak != null) { +- leak.record(); - } -- return errorCode; +- super.retain(); +- return this; +- } +- +- @Override +- public DefaultOpenSslKeyMaterial retain(int increment) { +- if (leak != null) { +- leak.record(); +- } +- super.retain(increment); +- return this; +- } +- +- @Override +- public DefaultOpenSslKeyMaterial touch() { +- if (leak != null) { +- leak.record(); +- } +- super.touch(); +- return this; +- } +- +- @Override +- public DefaultOpenSslKeyMaterial touch(Object hint) { +- if (leak != null) { +- leak.record(hint); +- } +- return this; +- } +- +- @Override +- public boolean release() { +- if (leak != null) { +- leak.record(); +- } +- return super.release(); +- } +- +- @Override +- public boolean release(int decrement) { +- if (leak != null) { +- leak.record(); +- } +- return super.release(decrement); - } -} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/ExtendedOpenSslSession.java b/handler/src/main/java/io/netty/handler/ssl/ExtendedOpenSslSession.java deleted file mode 100644 -index 46412e9..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java +index a016426..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/ExtendedOpenSslSession.java +++ /dev/null -@@ -1,211 +0,0 @@ +@@ -1,268 +0,0 @@ -/* -- * Copyright 2014 The Netty Project +- * Copyright 2018 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -714,270 +769,263 @@ index 46412e9..0000000 - */ -package io.netty.handler.ssl; - --import io.netty.internal.tcnative.SSL; -- --import java.io.File; --import java.security.PrivateKey; --import java.security.cert.X509Certificate; +-import io.netty.util.internal.EmptyArrays; +-import io.netty.util.internal.SuppressJava6Requirement; - --import javax.net.ssl.KeyManagerFactory; +-import javax.net.ssl.ExtendedSSLSession; -import javax.net.ssl.SSLException; --import javax.net.ssl.TrustManager; --import javax.net.ssl.TrustManagerFactory; -- --import static io.netty.handler.ssl.ReferenceCountedOpenSslClientContext.newSessionContext; +-import javax.net.ssl.SSLPeerUnverifiedException; +-import javax.net.ssl.SSLSessionBindingEvent; +-import javax.net.ssl.SSLSessionBindingListener; +-import javax.security.cert.X509Certificate; +-import java.security.Principal; +-import java.security.cert.Certificate; +-import java.util.Collections; +-import java.util.List; +-import java.util.Map; - -/** -- * A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. -- *

This class will use a finalizer to ensure native resources are automatically cleaned up. To avoid finalizers -- * and manually release the native memory see {@link ReferenceCountedOpenSslClientContext}. +- * Delegates all operations to a wrapped {@link OpenSslSession} except the methods defined by {@link ExtendedSSLSession} +- * itself. - */ --public final class OpenSslClientContext extends OpenSslContext { -- private final OpenSslSessionContext sessionContext; +-@SuppressJava6Requirement(reason = "Usage guarded by java version check") +-abstract class ExtendedOpenSslSession extends ExtendedSSLSession implements OpenSslSession { +- +- // TODO: use OpenSSL API to actually fetch the real data but for now just do what Conscrypt does: +- // https://github.com/google/conscrypt/blob/1.2.0/common/ +- // src/main/java/org/conscrypt/Java7ExtendedSSLSession.java#L32 +- private static final String[] LOCAL_SUPPORTED_SIGNATURE_ALGORITHMS = { +- "SHA512withRSA", "SHA512withECDSA", "SHA384withRSA", "SHA384withECDSA", "SHA256withRSA", +- "SHA256withECDSA", "SHA224withRSA", "SHA224withECDSA", "SHA1withRSA", "SHA1withECDSA", +- "RSASSA-PSS", +- }; - -- /** -- * Creates a new instance. -- * @deprecated use {@link SslContextBuilder} -- */ -- @Deprecated -- public OpenSslClientContext() throws SSLException { -- this((File) null, null, null, null, null, null, null, IdentityCipherSuiteFilter.INSTANCE, null, 0, 0); +- private final OpenSslSession wrapped; +- +- ExtendedOpenSslSession(OpenSslSession wrapped) { +- this.wrapped = wrapped; - } - -- /** -- * Creates a new instance. -- * -- * @param certChainFile an X.509 certificate chain file in PEM format. -- * {@code null} to use the system default -- * @deprecated use {@link SslContextBuilder} -- */ -- @Deprecated -- public OpenSslClientContext(File certChainFile) throws SSLException { -- this(certChainFile, null); +- // Use rawtypes an unchecked override to be able to also work on java7. +- @Override +- @SuppressWarnings({ "unchecked", "rawtypes" }) +- public abstract List getRequestedServerNames(); +- +- // Do not mark as override so we can compile on java8. +- public List getStatusResponses() { +- // Just return an empty list for now until we support it as otherwise we will fail in java9 +- // because of their sun.security.ssl.X509TrustManagerImpl class. +- return Collections.emptyList(); - } - -- /** -- * Creates a new instance. -- * -- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s -- * that verifies the certificates sent from servers. -- * {@code null} to use the default. -- * @deprecated use {@link SslContextBuilder} -- */ -- @Deprecated -- public OpenSslClientContext(TrustManagerFactory trustManagerFactory) throws SSLException { -- this(null, trustManagerFactory); +- @Override +- public void prepareHandshake() { +- wrapped.prepareHandshake(); - } - -- /** -- * Creates a new instance. -- * -- * @param certChainFile an X.509 certificate chain file in PEM format. -- * {@code null} to use the system default -- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s -- * that verifies the certificates sent from servers. -- * {@code null} to use the default. -- * @deprecated use {@link SslContextBuilder} -- */ -- @Deprecated -- public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException { -- this(certChainFile, trustManagerFactory, null, null, null, null, null, -- IdentityCipherSuiteFilter.INSTANCE, null, 0, 0); +- @Override +- public Map keyValueStorage() { +- return wrapped.keyValueStorage(); - } - -- /** -- * Creates a new instance. -- * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s -- * that verifies the certificates sent from servers. -- * {@code null} to use the default.. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param apn Provides a means to configure parameters related to application protocol negotiation. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder} -- */ -- @Deprecated -- public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory, Iterable ciphers, -- ApplicationProtocolConfig apn, long sessionCacheSize, long sessionTimeout) -- throws SSLException { -- this(certChainFile, trustManagerFactory, null, null, null, null, ciphers, IdentityCipherSuiteFilter.INSTANCE, -- apn, sessionCacheSize, sessionTimeout); +- @Override +- public OpenSslSessionId sessionId() { +- return wrapped.sessionId(); - } - -- /** -- * Creates a new instance. -- * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s -- * that verifies the certificates sent from servers. -- * {@code null} to use the default.. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param cipherFilter a filter to apply over the supplied list of ciphers -- * @param apn Provides a means to configure parameters related to application protocol negotiation. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder} -- */ -- @Deprecated -- public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory, Iterable ciphers, -- CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, -- long sessionCacheSize, long sessionTimeout) throws SSLException { -- this(certChainFile, trustManagerFactory, null, null, null, null, -- ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout); +- @Override +- public void setSessionDetails(long creationTime, long lastAccessedTime, OpenSslSessionId id, +- Map keyValueStorage) { +- wrapped.setSessionDetails(creationTime, lastAccessedTime, id, keyValueStorage); - } - -- /** -- * Creates a new instance. -- * @param trustCertCollectionFile an X.509 certificate collection file in PEM format. -- * {@code null} to use the system default -- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s -- * that verifies the certificates sent from servers. -- * {@code null} to use the default or the results of parsing -- * {@code trustCertCollectionFile} -- * @param keyCertChainFile an X.509 certificate chain file in PEM format. -- * This provides the public key for mutual authentication. -- * {@code null} to use the system default -- * @param keyFile a PKCS#8 private key file in PEM format. -- * This provides the private key for mutual authentication. -- * {@code null} for no mutual authentication. -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * Ignored if {@code keyFile} is {@code null}. -- * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link javax.net.ssl.KeyManager}s -- * that is used to encrypt data being sent to servers. -- * {@code null} to use the default or the results of parsing -- * {@code keyCertChainFile} and {@code keyFile}. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param cipherFilter a filter to apply over the supplied list of ciphers -- * @param apn Application Protocol Negotiator object. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder} -- */ -- @Deprecated -- public OpenSslClientContext(File trustCertCollectionFile, TrustManagerFactory trustManagerFactory, -- File keyCertChainFile, File keyFile, String keyPassword, -- KeyManagerFactory keyManagerFactory, Iterable ciphers, -- CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, -- long sessionCacheSize, long sessionTimeout) -- throws SSLException { -- this(toX509CertificatesInternal(trustCertCollectionFile), trustManagerFactory, -- toX509CertificatesInternal(keyCertChainFile), toPrivateKeyInternal(keyFile, keyPassword), -- keyPassword, keyManagerFactory, ciphers, cipherFilter, apn, null, sessionCacheSize, -- sessionTimeout, false); +- @Override +- public final void setLocalCertificate(Certificate[] localCertificate) { +- wrapped.setLocalCertificate(localCertificate); - } - -- OpenSslClientContext(X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, -- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, -- KeyManagerFactory keyManagerFactory, Iterable ciphers, -- CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, String[] protocols, -- long sessionCacheSize, long sessionTimeout, boolean enableOcsp) -- throws SSLException { -- super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain, -- ClientAuth.NONE, protocols, false, enableOcsp); -- boolean success = false; -- try { -- sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, -- keyCertChain, key, keyPassword, keyManagerFactory); -- success = true; -- } finally { -- if (!success) { -- release(); -- } +- @Override +- public String[] getPeerSupportedSignatureAlgorithms() { +- return EmptyArrays.EMPTY_STRINGS; +- } +- +- @Override +- public final void tryExpandApplicationBufferSize(int packetLengthDataOnly) { +- wrapped.tryExpandApplicationBufferSize(packetLengthDataOnly); +- } +- +- @Override +- public final String[] getLocalSupportedSignatureAlgorithms() { +- return LOCAL_SUPPORTED_SIGNATURE_ALGORITHMS.clone(); +- } +- +- @Override +- public final byte[] getId() { +- return wrapped.getId(); +- } +- +- @Override +- public final OpenSslSessionContext getSessionContext() { +- return wrapped.getSessionContext(); +- } +- +- @Override +- public final long getCreationTime() { +- return wrapped.getCreationTime(); +- } +- +- @Override +- public final long getLastAccessedTime() { +- return wrapped.getLastAccessedTime(); +- } +- +- @Override +- public void setLastAccessedTime(long time) { +- wrapped.setLastAccessedTime(time); +- } +- +- @Override +- public final void invalidate() { +- wrapped.invalidate(); +- } +- +- @Override +- public final boolean isValid() { +- return wrapped.isValid(); +- } +- +- @Override +- public final void putValue(String name, Object value) { +- if (value instanceof SSLSessionBindingListener) { +- // Decorate the value if needed so we submit the correct SSLSession instance +- value = new SSLSessionBindingListenerDecorator((SSLSessionBindingListener) value); - } +- wrapped.putValue(name, value); - } - - @Override -- public OpenSslSessionContext sessionContext() { -- return sessionContext; +- public final Object getValue(String s) { +- Object value = wrapped.getValue(s); +- if (value instanceof SSLSessionBindingListenerDecorator) { +- // Unwrap as needed so we return the original value +- return ((SSLSessionBindingListenerDecorator) value).delegate; +- } +- return value; - } - - @Override -- OpenSslKeyMaterialManager keyMaterialManager() { -- return null; +- public final void removeValue(String s) { +- wrapped.removeValue(s); - } --} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java -deleted file mode 100644 -index c4ca6b5..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java -+++ /dev/null -@@ -1,58 +0,0 @@ --/* -- * Copyright 2014 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; - --import io.netty.buffer.ByteBufAllocator; +- @Override +- public final String[] getValueNames() { +- return wrapped.getValueNames(); +- } - --import java.security.cert.Certificate; +- @Override +- public final Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { +- return wrapped.getPeerCertificates(); +- } - --import javax.net.ssl.SSLEngine; --import javax.net.ssl.SSLException; +- @Override +- public final Certificate[] getLocalCertificates() { +- return wrapped.getLocalCertificates(); +- } - --/** -- * This class will use a finalizer to ensure native resources are automatically cleaned up. To avoid finalizers -- * and manually release the native memory see {@link ReferenceCountedOpenSslContext}. -- */ --public abstract class OpenSslContext extends ReferenceCountedOpenSslContext { -- OpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apnCfg, -- long sessionCacheSize, long sessionTimeout, int mode, Certificate[] keyCertChain, -- ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp) -- throws SSLException { -- super(ciphers, cipherFilter, apnCfg, sessionCacheSize, sessionTimeout, mode, keyCertChain, -- clientAuth, protocols, startTls, enableOcsp, false); +- @Override +- public final X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { +- return wrapped.getPeerCertificateChain(); - } - -- OpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, -- OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize, -- long sessionTimeout, int mode, Certificate[] keyCertChain, -- ClientAuth clientAuth, String[] protocols, boolean startTls, -- boolean enableOcsp) throws SSLException { -- super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, mode, keyCertChain, clientAuth, protocols, -- startTls, enableOcsp, false); +- @Override +- public final Principal getPeerPrincipal() throws SSLPeerUnverifiedException { +- return wrapped.getPeerPrincipal(); - } - - @Override -- final SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort) { -- return new OpenSslEngine(this, alloc, peerHost, peerPort); +- public final Principal getLocalPrincipal() { +- return wrapped.getLocalPrincipal(); - } - - @Override -- @SuppressWarnings("FinalizeDeclaration") -- protected final void finalize() throws Throwable { -- super.finalize(); -- OpenSsl.releaseIfNeeded(this); +- public final String getCipherSuite() { +- return wrapped.getCipherSuite(); +- } +- +- @Override +- public String getProtocol() { +- return wrapped.getProtocol(); +- } +- +- @Override +- public final String getPeerHost() { +- return wrapped.getPeerHost(); +- } +- +- @Override +- public final int getPeerPort() { +- return wrapped.getPeerPort(); +- } +- +- @Override +- public final int getPacketBufferSize() { +- return wrapped.getPacketBufferSize(); +- } +- +- @Override +- public final int getApplicationBufferSize() { +- return wrapped.getApplicationBufferSize(); +- } +- +- private final class SSLSessionBindingListenerDecorator implements SSLSessionBindingListener { +- +- final SSLSessionBindingListener delegate; +- +- SSLSessionBindingListenerDecorator(SSLSessionBindingListener delegate) { +- this.delegate = delegate; +- } +- +- @Override +- public void valueBound(SSLSessionBindingEvent event) { +- delegate.valueBound(new SSLSessionBindingEvent(ExtendedOpenSslSession.this, event.getName())); +- } +- +- @Override +- public void valueUnbound(SSLSessionBindingEvent event) { +- delegate.valueUnbound(new SSLSessionBindingEvent(ExtendedOpenSslSession.this, event.getName())); +- } +- } +- +- @Override +- public void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate, +- byte[][] peerCertificateChain, long creationTime, long timeout) throws SSLException { +- wrapped.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime, timeout); +- } +- +- @Override +- public boolean equals(Object o) { +- return wrapped.equals(o); +- } +- +- @Override +- public int hashCode() { +- return wrapped.hashCode(); +- } +- +- @Override +- public String toString() { +- return "ExtendedOpenSslSession{" + +- "wrapped=" + wrapped + +- '}'; - } -} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java b/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java deleted file mode 100644 -index cbc7ee4..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +index efd58d2..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSsl.java +++ /dev/null -@@ -1,40 +0,0 @@ +@@ -1,747 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * @@ -985,7 +1033,7 @@ index cbc7ee4..0000000 - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -993,696 +1041,1871 @@ index cbc7ee4..0000000 - * License for the specific language governing permissions and limitations - * under the License. - */ +- -package io.netty.handler.ssl; - +-import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -- --import javax.net.ssl.SSLEngine; -- --/** -- * Implements a {@link SSLEngine} using -- * OpenSSL BIO abstractions. -- *

-- * This class will use a finalizer to ensure native resources are automatically cleaned up. To avoid finalizers -- * and manually release the native memory see {@link ReferenceCountedOpenSslEngine}. -- */ --public final class OpenSslEngine extends ReferenceCountedOpenSslEngine { -- OpenSslEngine(OpenSslContext context, ByteBufAllocator alloc, String peerHost, int peerPort) { -- super(context, alloc, peerHost, peerPort, false); -- } -- -- @Override -- @SuppressWarnings("FinalizeDeclaration") -- protected void finalize() throws Throwable { -- super.finalize(); -- OpenSsl.releaseIfNeeded(this); -- } --} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java -deleted file mode 100644 -index 02131b4..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java -+++ /dev/null -@@ -1,35 +0,0 @@ --/* -- * Copyright 2014 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; -- --interface OpenSslEngineMap { -- -- /** -- * Remove the {@link OpenSslEngine} with the given {@code ssl} address and -- * return it. -- */ -- ReferenceCountedOpenSslEngine remove(long ssl); -- -- /** -- * Add a {@link OpenSslEngine} to this {@link OpenSslEngineMap}. -- */ -- void add(ReferenceCountedOpenSslEngine engine); -- -- /** -- * Get the {@link OpenSslEngine} for the given {@code ssl} address. -- */ -- ReferenceCountedOpenSslEngine get(long ssl); --} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java -deleted file mode 100644 -index 38f6a7f..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslExtendedKeyMaterialManager.java -+++ /dev/null -@@ -1,40 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; -- --import javax.net.ssl.X509ExtendedKeyManager; --import javax.security.auth.x500.X500Principal; -- --final class OpenSslExtendedKeyMaterialManager extends OpenSslKeyMaterialManager { -- -- private final X509ExtendedKeyManager keyManager; -- -- OpenSslExtendedKeyMaterialManager(X509ExtendedKeyManager keyManager, String password) { -- super(keyManager, password); -- this.keyManager = keyManager; -- } -- -- @Override -- protected String chooseClientAlias(ReferenceCountedOpenSslEngine engine, String[] keyTypes, -- X500Principal[] issuer) { -- return keyManager.chooseEngineClientAlias(keyTypes, issuer, engine); -- } -- -- @Override -- protected String chooseServerAlias(ReferenceCountedOpenSslEngine engine, String type) { -- return keyManager.chooseEngineServerAlias(type, null, engine); -- } --} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java -deleted file mode 100644 -index 2e48e8b..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java -+++ /dev/null -@@ -1,179 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; -- --import io.netty.buffer.ByteBufAllocator; --import io.netty.internal.tcnative.CertificateRequestedCallback; +-import io.netty.buffer.UnpooledByteBufAllocator; +-import io.netty.internal.tcnative.Buffer; +-import io.netty.internal.tcnative.Library; -import io.netty.internal.tcnative.SSL; +-import io.netty.internal.tcnative.SSLContext; +-import io.netty.util.CharsetUtil; +-import io.netty.util.ReferenceCountUtil; +-import io.netty.util.ReferenceCounted; +-import io.netty.util.internal.EmptyArrays; +-import io.netty.util.internal.NativeLibraryLoader; +-import io.netty.util.internal.PlatformDependent; +-import io.netty.util.internal.StringUtil; +-import io.netty.util.internal.SystemPropertyUtil; +-import io.netty.util.internal.logging.InternalLogger; +-import io.netty.util.internal.logging.InternalLoggerFactory; - --import javax.net.ssl.SSLException; --import javax.net.ssl.X509KeyManager; --import javax.security.auth.x500.X500Principal; --import java.security.PrivateKey; +-import java.io.ByteArrayInputStream; +-import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; --import java.util.HashMap; +-import java.util.ArrayList; +-import java.util.Arrays; +-import java.util.Collection; +-import java.util.Collections; -import java.util.HashSet; --import java.util.Map; +-import java.util.LinkedHashSet; +-import java.util.List; -import java.util.Set; - --import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.freeBio; --import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.toBIO; +-import static io.netty.handler.ssl.SslUtils.*; - -/** -- * Manages key material for {@link OpenSslEngine}s and so set the right {@link PrivateKey}s and -- * {@link X509Certificate}s. +- * Tells if {@code netty-tcnative} and its OpenSSL support +- * are available. - */ --class OpenSslKeyMaterialManager { +-public final class OpenSsl { - -- // Code in this class is inspired by code of conscrypts: -- // - https://android.googlesource.com/platform/external/ -- // conscrypt/+/master/src/main/java/org/conscrypt/OpenSSLEngineImpl.java -- // - https://android.googlesource.com/platform/external/ -- // conscrypt/+/master/src/main/java/org/conscrypt/SSLParametersImpl.java -- // -- static final String KEY_TYPE_RSA = "RSA"; -- static final String KEY_TYPE_DH_RSA = "DH_RSA"; -- static final String KEY_TYPE_EC = "EC"; -- static final String KEY_TYPE_EC_EC = "EC_EC"; -- static final String KEY_TYPE_EC_RSA = "EC_RSA"; +- private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class); +- private static final Throwable UNAVAILABILITY_CAUSE; +- static final List DEFAULT_CIPHERS; +- static final Set AVAILABLE_CIPHER_SUITES; +- private static final Set AVAILABLE_OPENSSL_CIPHER_SUITES; +- private static final Set AVAILABLE_JAVA_CIPHER_SUITES; +- private static final boolean SUPPORTS_KEYMANAGER_FACTORY; +- private static final boolean USE_KEYMANAGER_FACTORY; +- private static final boolean SUPPORTS_OCSP; +- private static final boolean TLSV13_SUPPORTED; +- private static final boolean IS_BORINGSSL; +- private static final Set CLIENT_DEFAULT_PROTOCOLS; +- private static final Set SERVER_DEFAULT_PROTOCOLS; +- static final Set SUPPORTED_PROTOCOLS_SET; +- static final String[] EXTRA_SUPPORTED_TLS_1_3_CIPHERS; +- static final String EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING; +- static final String[] NAMED_GROUPS; +- +- static final boolean JAVAX_CERTIFICATE_CREATION_SUPPORTED; +- +- // Use default that is supported in java 11 and earlier and also in OpenSSL / BoringSSL. +- // See https://github.com/netty/netty-tcnative/issues/567 +- // See https://www.java.com/en/configure_crypto.html for ordering +- private static final String[] DEFAULT_NAMED_GROUPS = { "x25519", "secp256r1", "secp384r1", "secp521r1" }; - -- // key type mappings for types. -- private static final Map KEY_TYPES = new HashMap(); - static { -- KEY_TYPES.put("RSA", KEY_TYPE_RSA); -- KEY_TYPES.put("DHE_RSA", KEY_TYPE_RSA); -- KEY_TYPES.put("ECDHE_RSA", KEY_TYPE_RSA); -- KEY_TYPES.put("ECDHE_ECDSA", KEY_TYPE_EC); -- KEY_TYPES.put("ECDH_RSA", KEY_TYPE_EC_RSA); -- KEY_TYPES.put("ECDH_ECDSA", KEY_TYPE_EC_EC); -- KEY_TYPES.put("DH_RSA", KEY_TYPE_DH_RSA); -- } +- Throwable cause = null; - -- private final X509KeyManager keyManager; -- private final String password; +- if (SystemPropertyUtil.getBoolean("io.netty.handler.ssl.noOpenSsl", false)) { +- cause = new UnsupportedOperationException( +- "OpenSSL was explicit disabled with -Dio.netty.handler.ssl.noOpenSsl=true"); - -- OpenSslKeyMaterialManager(X509KeyManager keyManager, String password) { -- this.keyManager = keyManager; -- this.password = password; -- } +- logger.debug( +- "netty-tcnative explicit disabled; " + +- OpenSslEngine.class.getSimpleName() + " will be unavailable.", cause); +- } else { +- // Test if netty-tcnative is in the classpath first. +- try { +- Class.forName("io.netty.internal.tcnative.SSLContext", false, +- PlatformDependent.getClassLoader(OpenSsl.class)); +- } catch (ClassNotFoundException t) { +- cause = t; +- logger.debug( +- "netty-tcnative not in the classpath; " + +- OpenSslEngine.class.getSimpleName() + " will be unavailable."); +- } - -- void setKeyMaterial(ReferenceCountedOpenSslEngine engine) throws SSLException { -- long ssl = engine.sslPointer(); -- String[] authMethods = SSL.authenticationMethods(ssl); -- Set aliases = new HashSet(authMethods.length); -- for (String authMethod : authMethods) { -- String type = KEY_TYPES.get(authMethod); -- if (type != null) { -- String alias = chooseServerAlias(engine, type); -- if (alias != null && aliases.add(alias)) { -- setKeyMaterial(ssl, alias); +- // If in the classpath, try to load the native library and initialize netty-tcnative. +- if (cause == null) { +- try { +- // The JNI library was not already loaded. Load it now. +- loadTcNative(); +- } catch (Throwable t) { +- cause = t; +- logger.debug( +- "Failed to load netty-tcnative; " + +- OpenSslEngine.class.getSimpleName() + " will be unavailable, unless the " + +- "application has already loaded the symbols by some other means. " + +- "See https://netty.io/wiki/forked-tomcat-native.html for more information.", t); +- } +- +- try { +- String engine = SystemPropertyUtil.get("io.netty.handler.ssl.openssl.engine", null); +- if (engine == null) { +- logger.debug("Initialize netty-tcnative using engine: 'default'"); +- } else { +- logger.debug("Initialize netty-tcnative using engine: '{}'", engine); +- } +- initializeTcNative(engine); +- +- // The library was initialized successfully. If loading the library failed above, +- // reset the cause now since it appears that the library was loaded by some other +- // means. +- cause = null; +- } catch (Throwable t) { +- if (cause == null) { +- cause = t; +- } +- logger.debug( +- "Failed to initialize netty-tcnative; " + +- OpenSslEngine.class.getSimpleName() + " will be unavailable. " + +- "See https://netty.io/wiki/forked-tomcat-native.html for more information.", t); - } - } - } -- } - -- CertificateRequestedCallback.KeyMaterial keyMaterial(ReferenceCountedOpenSslEngine engine, String[] keyTypes, -- X500Principal[] issuer) throws SSLException { -- String alias = chooseClientAlias(engine, keyTypes, issuer); -- long keyBio = 0; -- long keyCertChainBio = 0; -- long pkey = 0; -- long certChain = 0; +- UNAVAILABILITY_CAUSE = cause; +- CLIENT_DEFAULT_PROTOCOLS = defaultProtocols("jdk.tls.client.protocols"); +- SERVER_DEFAULT_PROTOCOLS = defaultProtocols("jdk.tls.server.protocols"); - -- try { -- // TODO: Should we cache these and so not need to do a memory copy all the time ? -- X509Certificate[] certificates = keyManager.getCertificateChain(alias); -- if (certificates == null || certificates.length == 0) { -- return null; -- } +- if (cause == null) { +- logger.debug("netty-tcnative using native library: {}", SSL.versionString()); - -- PrivateKey key = keyManager.getPrivateKey(alias); -- keyCertChainBio = toBIO(certificates); -- certChain = SSL.parseX509Chain(keyCertChainBio); -- if (key != null) { -- keyBio = toBIO(key); -- pkey = SSL.parsePrivateKey(keyBio, password); +- final List defaultCiphers = new ArrayList(); +- final Set availableOpenSslCipherSuites = new LinkedHashSet(128); +- boolean supportsKeyManagerFactory = false; +- boolean useKeyManagerFactory = false; +- boolean tlsv13Supported = false; +- String[] namedGroups = DEFAULT_NAMED_GROUPS; +- String[] defaultConvertedNamedGroups = new String[namedGroups.length]; +- for (int i = 0; i < namedGroups.length; i++) { +- defaultConvertedNamedGroups[i] = GroupsConverter.toOpenSsl(namedGroups[i]); +- } +- +- IS_BORINGSSL = "BoringSSL".equals(versionString()); +- if (IS_BORINGSSL) { +- EXTRA_SUPPORTED_TLS_1_3_CIPHERS = new String [] { "TLS_AES_128_GCM_SHA256", +- "TLS_AES_256_GCM_SHA384" , +- "TLS_CHACHA20_POLY1305_SHA256" }; +- +- StringBuilder ciphersBuilder = new StringBuilder(128); +- for (String cipher: EXTRA_SUPPORTED_TLS_1_3_CIPHERS) { +- ciphersBuilder.append(cipher).append(":"); +- } +- ciphersBuilder.setLength(ciphersBuilder.length() - 1); +- EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING = ciphersBuilder.toString(); +- } else { +- EXTRA_SUPPORTED_TLS_1_3_CIPHERS = EmptyArrays.EMPTY_STRINGS; +- EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING = StringUtil.EMPTY_STRING; - } -- CertificateRequestedCallback.KeyMaterial material = new CertificateRequestedCallback.KeyMaterial( -- certChain, pkey); - -- // Reset to 0 so we do not free these. This is needed as the client certificate callback takes ownership -- // of both the key and the certificate if they are returned from this method, and thus must not -- // be freed here. -- certChain = pkey = 0; -- return material; -- } catch (SSLException e) { -- throw e; -- } catch (Exception e) { -- throw new SSLException(e); -- } finally { -- freeBio(keyBio); -- freeBio(keyCertChainBio); -- SSL.freePrivateKey(pkey); -- SSL.freeX509Chain(certChain); -- } -- } +- try { +- final long sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); +- long certBio = 0; +- long keyBio = 0; +- long cert = 0; +- long key = 0; +- try { +- // As we delegate to the KeyManager / TrustManager of the JDK we need to ensure it can actually +- // handle TLSv13 as otherwise we may see runtime exceptions +- if (SslProvider.isTlsv13Supported(SslProvider.JDK)) { +- try { +- StringBuilder tlsv13Ciphers = new StringBuilder(); - -- private void setKeyMaterial(long ssl, String alias) throws SSLException { -- long keyBio = 0; -- long keyCertChainBio = 0; -- long keyCertChainBio2 = 0; +- for (String cipher : TLSV13_CIPHERS) { +- String converted = CipherSuiteConverter.toOpenSsl(cipher, IS_BORINGSSL); +- if (converted != null) { +- tlsv13Ciphers.append(converted).append(':'); +- } +- } +- if (tlsv13Ciphers.length() == 0) { +- tlsv13Supported = false; +- } else { +- tlsv13Ciphers.setLength(tlsv13Ciphers.length() - 1); +- SSLContext.setCipherSuite(sslCtx, tlsv13Ciphers.toString(), true); +- tlsv13Supported = true; +- } - -- try { -- // TODO: Should we cache these and so not need to do a memory copy all the time ? -- X509Certificate[] certificates = keyManager.getCertificateChain(alias); -- if (certificates == null || certificates.length == 0) { -- return; -- } +- } catch (Exception ignore) { +- tlsv13Supported = false; +- } +- } - -- PrivateKey key = keyManager.getPrivateKey(alias); +- SSLContext.setCipherSuite(sslCtx, "ALL", false); - -- // Only encode one time -- PemEncoded encoded = PemX509Certificate.toPEM(ByteBufAllocator.DEFAULT, true, certificates); -- try { -- keyCertChainBio = toBIO(ByteBufAllocator.DEFAULT, encoded.retain()); -- keyCertChainBio2 = toBIO(ByteBufAllocator.DEFAULT, encoded.retain()); +- final long ssl = SSL.newSSL(sslCtx, true); +- try { +- for (String c: SSL.getCiphers(ssl)) { +- // Filter out bad input. +- if (c == null || c.isEmpty() || availableOpenSslCipherSuites.contains(c) || +- // Filter out TLSv1.3 ciphers if not supported. +- !tlsv13Supported && isTLSv13Cipher(c)) { +- continue; +- } +- availableOpenSslCipherSuites.add(c); +- } +- if (IS_BORINGSSL) { +- // Currently BoringSSL does not include these when calling SSL.getCiphers() even when these +- // are supported. +- Collections.addAll(availableOpenSslCipherSuites, EXTRA_SUPPORTED_TLS_1_3_CIPHERS); +- Collections.addAll(availableOpenSslCipherSuites, +- "AEAD-AES128-GCM-SHA256", +- "AEAD-AES256-GCM-SHA384", +- "AEAD-CHACHA20-POLY1305-SHA256"); +- } - -- if (key != null) { -- keyBio = toBIO(key); -- } -- SSL.setCertificateBio(ssl, keyCertChainBio, keyBio, password); +- PemEncoded privateKey = PemPrivateKey.valueOf(PROBING_KEY.getBytes(CharsetUtil.US_ASCII)); +- try { +- // Let's check if we can set a callback, which may not work if the used OpenSSL version +- // is to old. +- SSLContext.setCertificateCallback(sslCtx, null); - -- // We may have more then one cert in the chain so add all of them now. -- SSL.setCertificateChainBio(ssl, keyCertChainBio2, true); -- } finally { -- encoded.release(); -- } -- } catch (SSLException e) { -- throw e; -- } catch (Exception e) { -- throw new SSLException(e); -- } finally { -- freeBio(keyBio); -- freeBio(keyCertChainBio); -- freeBio(keyCertChainBio2); -- } -- } +- X509Certificate certificate = selfSignedCertificate(); +- certBio = ReferenceCountedOpenSslContext.toBIO(ByteBufAllocator.DEFAULT, certificate); +- cert = SSL.parseX509Chain(certBio); - -- protected String chooseClientAlias(@SuppressWarnings("unused") ReferenceCountedOpenSslEngine engine, -- String[] keyTypes, X500Principal[] issuer) { -- return keyManager.chooseClientAlias(keyTypes, issuer, null); -- } +- keyBio = ReferenceCountedOpenSslContext.toBIO( +- UnpooledByteBufAllocator.DEFAULT, privateKey.retain()); +- key = SSL.parsePrivateKey(keyBio, null); - -- protected String chooseServerAlias(@SuppressWarnings("unused") ReferenceCountedOpenSslEngine engine, String type) { -- return keyManager.chooseServerAlias(type, null, null); -- } --} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java -deleted file mode 100644 -index f57434b..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java -+++ /dev/null -@@ -1,373 +0,0 @@ --/* -- * Copyright 2014 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; +- SSL.setKeyMaterial(ssl, cert, key); +- supportsKeyManagerFactory = true; +- try { +- boolean propertySet = SystemPropertyUtil.contains( +- "io.netty.handler.ssl.openssl.useKeyManagerFactory"); +- if (!IS_BORINGSSL) { +- useKeyManagerFactory = SystemPropertyUtil.getBoolean( +- "io.netty.handler.ssl.openssl.useKeyManagerFactory", true); +- +- if (propertySet) { +- logger.info("System property " + +- "'io.netty.handler.ssl.openssl.useKeyManagerFactory'" + +- " is deprecated and so will be ignored in the future"); +- } +- } else { +- useKeyManagerFactory = true; +- if (propertySet) { +- logger.info("System property " + +- "'io.netty.handler.ssl.openssl.useKeyManagerFactory'" + +- " is deprecated and will be ignored when using BoringSSL"); +- } +- } +- } catch (Throwable ignore) { +- logger.debug("Failed to get useKeyManagerFactory system property."); +- } +- } catch (Error ignore) { +- logger.debug("KeyManagerFactory not supported."); +- } finally { +- privateKey.release(); +- } +- } finally { +- SSL.freeSSL(ssl); +- if (certBio != 0) { +- SSL.freeBIO(certBio); +- } +- if (keyBio != 0) { +- SSL.freeBIO(keyBio); +- } +- if (cert != 0) { +- SSL.freeX509Chain(cert); +- } +- if (key != 0) { +- SSL.freePrivateKey(key); +- } +- } - --import io.netty.handler.ssl.ReferenceCountedOpenSslServerContext.ServerContext; --import io.netty.internal.tcnative.SSL; +- String groups = SystemPropertyUtil.get("jdk.tls.namedGroups", null); +- if (groups != null) { +- String[] nGroups = groups.split(","); +- Set supportedNamedGroups = new LinkedHashSet(nGroups.length); +- Set supportedConvertedNamedGroups = new LinkedHashSet(nGroups.length); +- +- Set unsupportedNamedGroups = new LinkedHashSet(); +- for (String namedGroup : nGroups) { +- String converted = GroupsConverter.toOpenSsl(namedGroup); +- if (SSLContext.setCurvesList(sslCtx, converted)) { +- supportedConvertedNamedGroups.add(converted); +- supportedNamedGroups.add(namedGroup); +- } else { +- unsupportedNamedGroups.add(namedGroup); +- } +- } - --import java.io.File; --import java.security.PrivateKey; --import java.security.cert.X509Certificate; +- if (supportedNamedGroups.isEmpty()) { +- namedGroups = defaultConvertedNamedGroups; +- logger.info("All configured namedGroups are not supported: {}. Use default: {}.", +- Arrays.toString(unsupportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS)), +- Arrays.toString(DEFAULT_NAMED_GROUPS)); +- } else { +- String[] groupArray = supportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS); +- if (unsupportedNamedGroups.isEmpty()) { +- logger.info("Using configured namedGroups -D 'jdk.tls.namedGroup': {} ", +- Arrays.toString(groupArray)); +- } else { +- logger.info("Using supported configured namedGroups: {}. Unsupported namedGroups: {}. ", +- Arrays.toString(groupArray), +- Arrays.toString(unsupportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS))); +- } +- namedGroups = supportedConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS); +- } +- } else { +- namedGroups = defaultConvertedNamedGroups; +- } +- } finally { +- SSLContext.free(sslCtx); +- } +- } catch (Exception e) { +- logger.warn("Failed to get the list of available OpenSSL cipher suites.", e); +- } +- NAMED_GROUPS = namedGroups; +- AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.unmodifiableSet(availableOpenSslCipherSuites); +- final Set availableJavaCipherSuites = new LinkedHashSet( +- AVAILABLE_OPENSSL_CIPHER_SUITES.size() * 2); +- for (String cipher: AVAILABLE_OPENSSL_CIPHER_SUITES) { +- // Included converted but also openssl cipher name +- if (!isTLSv13Cipher(cipher)) { +- availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "TLS")); +- availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "SSL")); +- } else { +- // TLSv1.3 ciphers have the correct format. +- availableJavaCipherSuites.add(cipher); +- } +- } - --import javax.net.ssl.KeyManager; --import javax.net.ssl.KeyManagerFactory; --import javax.net.ssl.SSLException; --import javax.net.ssl.TrustManager; --import javax.net.ssl.TrustManagerFactory; +- addIfSupported(availableJavaCipherSuites, defaultCiphers, DEFAULT_CIPHER_SUITES); +- addIfSupported(availableJavaCipherSuites, defaultCiphers, TLSV13_CIPHER_SUITES); +- // Also handle the extra supported ciphers as these will contain some more stuff on BoringSSL. +- addIfSupported(availableJavaCipherSuites, defaultCiphers, EXTRA_SUPPORTED_TLS_1_3_CIPHERS); - --import static io.netty.handler.ssl.ReferenceCountedOpenSslServerContext.newSessionContext; +- useFallbackCiphersIfDefaultIsEmpty(defaultCiphers, availableJavaCipherSuites); +- DEFAULT_CIPHERS = Collections.unmodifiableList(defaultCiphers); - --/** -- * A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. -- *

This class will use a finalizer to ensure native resources are automatically cleaned up. To avoid finalizers -- * and manually release the native memory see {@link ReferenceCountedOpenSslServerContext}. -- */ --public final class OpenSslServerContext extends OpenSslContext { -- private final OpenSslServerSessionContext sessionContext; -- private final OpenSslKeyMaterialManager keyMaterialManager; +- AVAILABLE_JAVA_CIPHER_SUITES = Collections.unmodifiableSet(availableJavaCipherSuites); +- +- final Set availableCipherSuites = new LinkedHashSet( +- AVAILABLE_OPENSSL_CIPHER_SUITES.size() + AVAILABLE_JAVA_CIPHER_SUITES.size()); +- availableCipherSuites.addAll(AVAILABLE_OPENSSL_CIPHER_SUITES); +- availableCipherSuites.addAll(AVAILABLE_JAVA_CIPHER_SUITES); +- +- AVAILABLE_CIPHER_SUITES = availableCipherSuites; +- SUPPORTS_KEYMANAGER_FACTORY = supportsKeyManagerFactory; +- USE_KEYMANAGER_FACTORY = useKeyManagerFactory; +- +- Set protocols = new LinkedHashSet(6); +- // Seems like there is no way to explicitly disable SSLv2Hello in openssl so it is always enabled +- protocols.add(SslProtocols.SSL_v2_HELLO); +- if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV2, SSL.SSL_OP_NO_SSLv2)) { +- protocols.add(SslProtocols.SSL_v2); +- } +- if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV3, SSL.SSL_OP_NO_SSLv3)) { +- protocols.add(SslProtocols.SSL_v3); +- } +- if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1, SSL.SSL_OP_NO_TLSv1)) { +- protocols.add(SslProtocols.TLS_v1); +- } +- if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_1, SSL.SSL_OP_NO_TLSv1_1)) { +- protocols.add(SslProtocols.TLS_v1_1); +- } +- if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_OP_NO_TLSv1_2)) { +- protocols.add(SslProtocols.TLS_v1_2); +- } +- +- // This is only supported by java8u272 and later. +- if (tlsv13Supported && doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_3, SSL.SSL_OP_NO_TLSv1_3)) { +- protocols.add(SslProtocols.TLS_v1_3); +- TLSV13_SUPPORTED = true; +- } else { +- TLSV13_SUPPORTED = false; +- } +- +- SUPPORTED_PROTOCOLS_SET = Collections.unmodifiableSet(protocols); +- SUPPORTS_OCSP = doesSupportOcsp(); +- +- if (logger.isDebugEnabled()) { +- logger.debug("Supported protocols (OpenSSL): {} ", SUPPORTED_PROTOCOLS_SET); +- logger.debug("Default cipher suites (OpenSSL): {}", DEFAULT_CIPHERS); +- } +- +- // Check if we can create a javax.security.cert.X509Certificate from our cert. This might fail on +- // JDK17 and above. In this case we will later throw an UnsupportedOperationException if someone +- // tries to access these via SSLSession. See https://github.com/netty/netty/issues/13560. +- boolean javaxCertificateCreationSupported; +- try { +- javax.security.cert.X509Certificate.getInstance(PROBING_CERT.getBytes(CharsetUtil.US_ASCII)); +- javaxCertificateCreationSupported = true; +- } catch (javax.security.cert.CertificateException ex) { +- javaxCertificateCreationSupported = false; +- } +- JAVAX_CERTIFICATE_CREATION_SUPPORTED = javaxCertificateCreationSupported; +- } else { +- DEFAULT_CIPHERS = Collections.emptyList(); +- AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.emptySet(); +- AVAILABLE_JAVA_CIPHER_SUITES = Collections.emptySet(); +- AVAILABLE_CIPHER_SUITES = Collections.emptySet(); +- SUPPORTS_KEYMANAGER_FACTORY = false; +- USE_KEYMANAGER_FACTORY = false; +- SUPPORTED_PROTOCOLS_SET = Collections.emptySet(); +- SUPPORTS_OCSP = false; +- TLSV13_SUPPORTED = false; +- IS_BORINGSSL = false; +- EXTRA_SUPPORTED_TLS_1_3_CIPHERS = EmptyArrays.EMPTY_STRINGS; +- EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING = StringUtil.EMPTY_STRING; +- NAMED_GROUPS = DEFAULT_NAMED_GROUPS; +- JAVAX_CERTIFICATE_CREATION_SUPPORTED = false; +- } +- } +- +- static String checkTls13Ciphers(InternalLogger logger, String ciphers) { +- if (IS_BORINGSSL && !ciphers.isEmpty()) { +- assert EXTRA_SUPPORTED_TLS_1_3_CIPHERS.length > 0; +- Set boringsslTlsv13Ciphers = new HashSet(EXTRA_SUPPORTED_TLS_1_3_CIPHERS.length); +- Collections.addAll(boringsslTlsv13Ciphers, EXTRA_SUPPORTED_TLS_1_3_CIPHERS); +- boolean ciphersNotMatch = false; +- for (String cipher: ciphers.split(":")) { +- if (boringsslTlsv13Ciphers.isEmpty()) { +- ciphersNotMatch = true; +- break; +- } +- if (!boringsslTlsv13Ciphers.remove(cipher) && +- !boringsslTlsv13Ciphers.remove(CipherSuiteConverter.toJava(cipher, "TLS"))) { +- ciphersNotMatch = true; +- break; +- } +- } +- +- // Also check if there are ciphers left. +- ciphersNotMatch |= !boringsslTlsv13Ciphers.isEmpty(); +- +- if (ciphersNotMatch) { +- if (logger.isInfoEnabled()) { +- StringBuilder javaCiphers = new StringBuilder(128); +- for (String cipher : ciphers.split(":")) { +- javaCiphers.append(CipherSuiteConverter.toJava(cipher, "TLS")).append(":"); +- } +- javaCiphers.setLength(javaCiphers.length() - 1); +- logger.info( +- "BoringSSL doesn't allow to enable or disable TLSv1.3 ciphers explicitly." + +- " Provided TLSv1.3 ciphers: '{}', default TLSv1.3 ciphers that will be used: '{}'.", +- javaCiphers, EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING); +- } +- return EXTRA_SUPPORTED_TLS_1_3_CIPHERS_STRING; +- } +- } +- return ciphers; +- } +- +- static boolean isSessionCacheSupported() { +- return version() >= 0x10100000L; +- } - - /** -- * Creates a new instance. -- * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @deprecated use {@link SslContextBuilder} +- * Returns a self-signed {@link X509Certificate} for {@code netty.io}. - */ -- @Deprecated -- public OpenSslServerContext(File certChainFile, File keyFile) throws SSLException { -- this(certChainFile, keyFile, null); +- static X509Certificate selfSignedCertificate() throws CertificateException { +- return (X509Certificate) SslContext.X509_CERT_FACTORY.generateCertificate( +- new ByteArrayInputStream(PROBING_CERT.getBytes(CharsetUtil.US_ASCII)) +- ); +- } +- +- private static boolean doesSupportOcsp() { +- boolean supportsOcsp = false; +- if (version() >= 0x10002000L) { +- long sslCtx = -1; +- try { +- sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_MODE_SERVER); +- SSLContext.enableOcsp(sslCtx, false); +- supportsOcsp = true; +- } catch (Exception ignore) { +- // ignore +- } finally { +- if (sslCtx != -1) { +- SSLContext.free(sslCtx); +- } +- } +- } +- return supportsOcsp; +- } +- private static boolean doesSupportProtocol(int protocol, int opt) { +- if (opt == 0) { +- // If the opt is 0 the protocol is not supported. This is for example the case with BoringSSL and SSLv2. +- return false; +- } +- long sslCtx = -1; +- try { +- sslCtx = SSLContext.make(protocol, SSL.SSL_MODE_COMBINED); +- return true; +- } catch (Exception ignore) { +- return false; +- } finally { +- if (sslCtx != -1) { +- SSLContext.free(sslCtx); +- } +- } - } - - /** -- * Creates a new instance. -- * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * @deprecated use {@link SslContextBuilder} +- * Returns {@code true} if and only if +- * {@code netty-tcnative} and its OpenSSL support +- * are available. - */ -- @Deprecated -- public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException { -- this(certChainFile, keyFile, keyPassword, null, IdentityCipherSuiteFilter.INSTANCE, -- ApplicationProtocolConfig.DISABLED, 0, 0); +- public static boolean isAvailable() { +- return UNAVAILABILITY_CAUSE == null; - } - - /** -- * Creates a new instance. +- * Returns {@code true} if the used version of openssl supports +- * ALPN. - * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param apn Provides a means to configure parameters related to application protocol negotiation. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder} +- * @deprecated use {@link SslProvider#isAlpnSupported(SslProvider)} with {@link SslProvider#OPENSSL}. - */ - @Deprecated -- public OpenSslServerContext( -- File certChainFile, File keyFile, String keyPassword, -- Iterable ciphers, ApplicationProtocolConfig apn, -- long sessionCacheSize, long sessionTimeout) throws SSLException { -- this(certChainFile, keyFile, keyPassword, ciphers, IdentityCipherSuiteFilter.INSTANCE, -- apn, sessionCacheSize, sessionTimeout); +- public static boolean isAlpnSupported() { +- return version() >= 0x10002000L; - } - - /** -- * Creates a new instance. -- * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param nextProtocols the application layer protocols to accept, in the order of preference. -- * {@code null} to disable TLS NPN/ALPN extension. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder} +- * Returns {@code true} if the used version of OpenSSL supports OCSP stapling. - */ -- @Deprecated -- public OpenSslServerContext( -- File certChainFile, File keyFile, String keyPassword, -- Iterable ciphers, Iterable nextProtocols, -- long sessionCacheSize, long sessionTimeout) throws SSLException { -- this(certChainFile, keyFile, keyPassword, ciphers, -- toApplicationProtocolConfig(nextProtocols), sessionCacheSize, sessionTimeout); +- public static boolean isOcspSupported() { +- return SUPPORTS_OCSP; - } - - /** -- * Creates a new instance. -- * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param config Application protocol config. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder} +- * Returns the version of the used available OpenSSL library or {@code -1} if {@link #isAvailable()} +- * returns {@code false}. - */ -- @Deprecated -- public OpenSslServerContext( -- File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory, -- Iterable ciphers, ApplicationProtocolConfig config, -- long sessionCacheSize, long sessionTimeout) throws SSLException { -- this(certChainFile, keyFile, keyPassword, trustManagerFactory, ciphers, -- toNegotiator(config), sessionCacheSize, sessionTimeout); +- public static int version() { +- return isAvailable() ? SSL.version() : -1; - } - - /** -- * Creates a new instance. -- * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param apn Application protocol negotiator. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder} +- * Returns the version string of the used available OpenSSL library or {@code null} if {@link #isAvailable()} +- * returns {@code false}. - */ -- @Deprecated -- public OpenSslServerContext( -- File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory, -- Iterable ciphers, OpenSslApplicationProtocolNegotiator apn, -- long sessionCacheSize, long sessionTimeout) throws SSLException { -- this(null, trustManagerFactory, certChainFile, keyFile, keyPassword, null, -- ciphers, null, apn, sessionCacheSize, sessionTimeout); +- public static String versionString() { +- return isAvailable() ? SSL.versionString() : null; - } - - /** -- * Creates a new instance. +- * Ensure that {@code netty-tcnative} and +- * its OpenSSL support are available. - * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param cipherFilter a filter to apply over the supplied list of ciphers -- * @param apn Provides a means to configure parameters related to application protocol negotiation. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder} +- * @throws UnsatisfiedLinkError if unavailable - */ -- @Deprecated -- public OpenSslServerContext( -- File certChainFile, File keyFile, String keyPassword, -- Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, -- long sessionCacheSize, long sessionTimeout) throws SSLException { -- this(null, null, certChainFile, keyFile, keyPassword, null, -- ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout); +- public static void ensureAvailability() { +- if (UNAVAILABILITY_CAUSE != null) { +- throw (Error) new UnsatisfiedLinkError( +- "failed to load the required native library").initCause(UNAVAILABILITY_CAUSE); +- } - } - - /** -- * Creates a new instance. +- * Returns the cause of unavailability of +- * {@code netty-tcnative} and its OpenSSL support. - * -- * @param trustCertCollectionFile an X.509 certificate collection file in PEM format. -- * This provides the certificate collection used for mutual authentication. -- * {@code null} to use the system default -- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s -- * that verifies the certificates sent from clients. -- * {@code null} to use the default or the results of parsing -- * {@code trustCertCollectionFile}. -- * @param keyCertChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s -- * that is used to encrypt data being sent to clients. -- * {@code null} to use the default or the results of parsing -- * {@code keyCertChainFile} and {@code keyFile}. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param cipherFilter a filter to apply over the supplied list of ciphers -- * Only required if {@code provider} is {@link SslProvider#JDK} -- * @param config Provides a means to configure parameters related to application protocol negotiation. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder} +- * @return the cause if unavailable. {@code null} if available. - */ -- @Deprecated -- public OpenSslServerContext( -- File trustCertCollectionFile, TrustManagerFactory trustManagerFactory, -- File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory, -- Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig config, -- long sessionCacheSize, long sessionTimeout) throws SSLException { -- this(trustCertCollectionFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword, keyManagerFactory, -- ciphers, cipherFilter, toNegotiator(config), sessionCacheSize, sessionTimeout); +- public static Throwable unavailabilityCause() { +- return UNAVAILABILITY_CAUSE; - } - - /** -- * Creates a new instance. -- * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param cipherFilter a filter to apply over the supplied list of ciphers -- * @param config Application protocol config. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder} +- * @deprecated use {@link #availableOpenSslCipherSuites()} - */ - @Deprecated -- public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword, -- TrustManagerFactory trustManagerFactory, Iterable ciphers, -- CipherSuiteFilter cipherFilter, ApplicationProtocolConfig config, -- long sessionCacheSize, long sessionTimeout) throws SSLException { -- this(null, trustManagerFactory, certChainFile, keyFile, keyPassword, null, ciphers, cipherFilter, -- toNegotiator(config), sessionCacheSize, sessionTimeout); +- public static Set availableCipherSuites() { +- return availableOpenSslCipherSuites(); - } - - /** -- * Creates a new instance. +- * Returns all the available OpenSSL cipher suites. +- * Please note that the returned array may include the cipher suites that are insecure or non-functional. +- */ +- public static Set availableOpenSslCipherSuites() { +- return AVAILABLE_OPENSSL_CIPHER_SUITES; +- } +- +- /** +- * Returns all the available cipher suites (Java-style). +- * Please note that the returned array may include the cipher suites that are insecure or non-functional. +- */ +- public static Set availableJavaCipherSuites() { +- return AVAILABLE_JAVA_CIPHER_SUITES; +- } +- +- /** +- * Returns {@code true} if and only if the specified cipher suite is available in OpenSSL. +- * Both Java-style cipher suite and OpenSSL-style cipher suite are accepted. +- */ +- public static boolean isCipherSuiteAvailable(String cipherSuite) { +- String converted = CipherSuiteConverter.toOpenSsl(cipherSuite, IS_BORINGSSL); +- if (converted != null) { +- cipherSuite = converted; +- } +- return AVAILABLE_OPENSSL_CIPHER_SUITES.contains(cipherSuite); +- } +- +- /** +- * Returns {@code true} if {@link javax.net.ssl.KeyManagerFactory} is supported when using OpenSSL. +- */ +- public static boolean supportsKeyManagerFactory() { +- return SUPPORTS_KEYMANAGER_FACTORY; +- } +- +- /** +- * Always returns {@code true} if {@link #isAvailable()} returns {@code true}. - * -- * @param certChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param cipherFilter a filter to apply over the supplied list of ciphers -- * @param apn Application protocol negotiator. -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. -- * @deprecated use {@link SslContextBuilder}} +- * @deprecated Will be removed because hostname validation is always done by a +- * {@link javax.net.ssl.TrustManager} implementation. - */ - @Deprecated -- public OpenSslServerContext( -- File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory, -- Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, -- long sessionCacheSize, long sessionTimeout) throws SSLException { -- this(null, trustManagerFactory, certChainFile, keyFile, keyPassword, null, ciphers, cipherFilter, -- apn, sessionCacheSize, sessionTimeout); +- public static boolean supportsHostnameValidation() { +- return isAvailable(); +- } +- +- static boolean useKeyManagerFactory() { +- return USE_KEYMANAGER_FACTORY; +- } +- +- static long memoryAddress(ByteBuf buf) { +- assert buf.isDirect(); +- return buf.hasMemoryAddress() ? buf.memoryAddress() : +- // Use internalNioBuffer to reduce object creation. +- Buffer.address(buf.internalNioBuffer(0, buf.readableBytes())); +- } +- +- private OpenSsl() { } +- +- private static void loadTcNative() throws Exception { +- String os = PlatformDependent.normalizedOs(); +- String arch = PlatformDependent.normalizedArch(); +- +- Set libNames = new LinkedHashSet(5); +- String staticLibName = "netty_tcnative"; +- +- // First, try loading the platform-specific library. Platform-specific +- // libraries will be available if using a tcnative uber jar. +- if ("linux".equals(os)) { +- Set classifiers = PlatformDependent.normalizedLinuxClassifiers(); +- for (String classifier : classifiers) { +- libNames.add(staticLibName + "_" + os + '_' + arch + "_" + classifier); +- } +- // generic arch-dependent library +- libNames.add(staticLibName + "_" + os + '_' + arch); +- +- // Fedora SSL lib so naming (libssl.so.10 vs libssl.so.1.0.0). +- // note: should already be included from the classifiers but if not, we use this as an +- // additional fallback option here +- libNames.add(staticLibName + "_" + os + '_' + arch + "_fedora"); +- } else { +- libNames.add(staticLibName + "_" + os + '_' + arch); +- } +- libNames.add(staticLibName + "_" + arch); +- libNames.add(staticLibName); +- +- NativeLibraryLoader.loadFirstAvailable(PlatformDependent.getClassLoader(SSLContext.class), +- libNames.toArray(EmptyArrays.EMPTY_STRINGS)); +- } +- +- private static boolean initializeTcNative(String engine) throws Exception { +- return Library.initialize("provided", engine); +- } +- +- static void releaseIfNeeded(ReferenceCounted counted) { +- if (counted.refCnt() > 0) { +- ReferenceCountUtil.safeRelease(counted); +- } +- } +- +- static boolean isTlsv13Supported() { +- return TLSV13_SUPPORTED; +- } +- +- static boolean isOptionSupported(SslContextOption option) { +- if (isAvailable()) { +- if (option == OpenSslContextOption.USE_TASKS) { +- return true; +- } +- // Check for options that are only supported by BoringSSL atm. +- if (isBoringSSL()) { +- return option == OpenSslContextOption.ASYNC_PRIVATE_KEY_METHOD || +- option == OpenSslContextOption.PRIVATE_KEY_METHOD || +- option == OpenSslContextOption.CERTIFICATE_COMPRESSION_ALGORITHMS || +- option == OpenSslContextOption.TLS_FALSE_START || +- option == OpenSslContextOption.MAX_CERTIFICATE_LIST_BYTES; +- } +- } +- return false; +- } +- +- private static Set defaultProtocols(String property) { +- String protocolsString = SystemPropertyUtil.get(property, null); +- Set protocols = new HashSet(); +- if (protocolsString != null) { +- for (String proto : protocolsString.split(",")) { +- String p = proto.trim(); +- protocols.add(p); +- } +- } else { +- protocols.add(SslProtocols.TLS_v1_2); +- protocols.add(SslProtocols.TLS_v1_3); +- } +- return protocols; +- } +- +- static String[] defaultProtocols(boolean isClient) { +- final Collection defaultProtocols = isClient ? CLIENT_DEFAULT_PROTOCOLS : SERVER_DEFAULT_PROTOCOLS; +- assert defaultProtocols != null; +- List protocols = new ArrayList(defaultProtocols.size()); +- for (String proto : defaultProtocols) { +- if (SUPPORTED_PROTOCOLS_SET.contains(proto)) { +- protocols.add(proto); +- } +- } +- return protocols.toArray(EmptyArrays.EMPTY_STRINGS); +- } +- +- static boolean isBoringSSL() { +- return IS_BORINGSSL; - } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslAsyncPrivateKeyMethod.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslAsyncPrivateKeyMethod.java +deleted file mode 100644 +index 27edaa6..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslAsyncPrivateKeyMethod.java ++++ /dev/null +@@ -1,58 +0,0 @@ +-/* +- * Copyright 2021 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.internal.tcnative.SSLPrivateKeyMethod; +-import io.netty.util.concurrent.Future; +- +-import javax.net.ssl.SSLEngine; +- +-public interface OpenSslAsyncPrivateKeyMethod { +- int SSL_SIGN_RSA_PKCS1_SHA1 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA1; +- int SSL_SIGN_RSA_PKCS1_SHA256 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA256; +- int SSL_SIGN_RSA_PKCS1_SHA384 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA384; +- int SSL_SIGN_RSA_PKCS1_SHA512 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA512; +- int SSL_SIGN_ECDSA_SHA1 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SHA1; +- int SSL_SIGN_ECDSA_SECP256R1_SHA256 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256; +- int SSL_SIGN_ECDSA_SECP384R1_SHA384 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP384R1_SHA384; +- int SSL_SIGN_ECDSA_SECP521R1_SHA512 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP521R1_SHA512; +- int SSL_SIGN_RSA_PSS_RSAE_SHA256 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA256; +- int SSL_SIGN_RSA_PSS_RSAE_SHA384 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA384; +- int SSL_SIGN_RSA_PSS_RSAE_SHA512 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA512; +- int SSL_SIGN_ED25519 = SSLPrivateKeyMethod.SSL_SIGN_ED25519; +- int SSL_SIGN_RSA_PKCS1_MD5_SHA1 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_MD5_SHA1; - - /** -- * Creates a new instance. +- * Signs the input with the given key and notifies the returned {@link Future} with the signed bytes. - * +- * @param engine the {@link SSLEngine} +- * @param signatureAlgorithm the algorithm to use for signing +- * @param input the digest itself +- * @return the {@link Future} that will be notified with the signed data +- * (must not be {@code null}) when the operation completes. +- */ +- Future sign(SSLEngine engine, int signatureAlgorithm, byte[] input); +- +- /** +- * Decrypts the input with the given key and notifies the returned {@link Future} with the decrypted bytes. - * -- * @param trustCertCollectionFile an X.509 certificate collection file in PEM format. -- * This provides the certificate collection used for mutual authentication. -- * {@code null} to use the system default -- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s -- * that verifies the certificates sent from clients. -- * {@code null} to use the default or the results of parsing -- * {@code trustCertCollectionFile}. -- * @param keyCertChainFile an X.509 certificate chain file in PEM format -- * @param keyFile a PKCS#8 private key file in PEM format -- * @param keyPassword the password of the {@code keyFile}. -- * {@code null} if it's not password-protected. -- * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s -- * that is used to encrypt data being sent to clients. -- * {@code null} to use the default or the results of parsing -- * {@code keyCertChainFile} and {@code keyFile}. -- * @param ciphers the cipher suites to enable, in the order of preference. -- * {@code null} to use the default cipher suites. -- * @param cipherFilter a filter to apply over the supplied list of ciphers -- * Only required if {@code provider} is {@link SslProvider#JDK} -- * @param apn Application Protocol Negotiator object -- * @param sessionCacheSize the size of the cache used for storing SSL session objects. -- * {@code 0} to use the default value. -- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. -- * {@code 0} to use the default value. +- * @param engine the {@link SSLEngine} +- * @param input the input which should be decrypted +- * @return the {@link Future} that will be notified with the decrypted data +- * (must not be {@code null}) when the operation completes. +- */ +- Future decrypt(SSLEngine engine, byte[] input); +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingKeyMaterialProvider.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingKeyMaterialProvider.java +deleted file mode 100644 +index a55007d..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingKeyMaterialProvider.java ++++ /dev/null +@@ -1,79 +0,0 @@ +-/* +- * Copyright 2018 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.buffer.ByteBufAllocator; +- +-import javax.net.ssl.X509KeyManager; +-import java.util.Iterator; +-import java.util.concurrent.ConcurrentHashMap; +-import java.util.concurrent.ConcurrentMap; +- +-/** +- * {@link OpenSslKeyMaterialProvider} that will cache the {@link OpenSslKeyMaterial} to reduce the overhead +- * of parsing the chain and the key for generation of the material. +- */ +-final class OpenSslCachingKeyMaterialProvider extends OpenSslKeyMaterialProvider { +- +- private final int maxCachedEntries; +- private volatile boolean full; +- private final ConcurrentMap cache = new ConcurrentHashMap(); +- +- OpenSslCachingKeyMaterialProvider(X509KeyManager keyManager, String password, int maxCachedEntries) { +- super(keyManager, password); +- this.maxCachedEntries = maxCachedEntries; +- } +- +- @Override +- OpenSslKeyMaterial chooseKeyMaterial(ByteBufAllocator allocator, String alias) throws Exception { +- OpenSslKeyMaterial material = cache.get(alias); +- if (material == null) { +- material = super.chooseKeyMaterial(allocator, alias); +- if (material == null) { +- // No keymaterial should be used. +- return null; +- } +- +- if (full) { +- return material; +- } +- if (cache.size() > maxCachedEntries) { +- full = true; +- // Do not cache... +- return material; +- } +- OpenSslKeyMaterial old = cache.putIfAbsent(alias, material); +- if (old != null) { +- material.release(); +- material = old; +- } +- } +- // We need to call retain() as we want to always have at least a refCnt() of 1 before destroy() was called. +- return material.retain(); +- } +- +- @Override +- void destroy() { +- // Remove and release all entries. +- do { +- Iterator iterator = cache.values().iterator(); +- while (iterator.hasNext()) { +- iterator.next().release(); +- iterator.remove(); +- } +- } while (!cache.isEmpty()); +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingX509KeyManagerFactory.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingX509KeyManagerFactory.java +deleted file mode 100644 +index 7f644e2..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslCachingX509KeyManagerFactory.java ++++ /dev/null +@@ -1,80 +0,0 @@ +-/* +- * Copyright 2018 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.util.internal.ObjectUtil; +- +-import javax.net.ssl.KeyManager; +-import javax.net.ssl.KeyManagerFactory; +-import javax.net.ssl.KeyManagerFactorySpi; +-import javax.net.ssl.ManagerFactoryParameters; +-import javax.net.ssl.X509KeyManager; +-import java.security.InvalidAlgorithmParameterException; +-import java.security.KeyStore; +-import java.security.KeyStoreException; +-import java.security.NoSuchAlgorithmException; +-import java.security.PrivateKey; +-import java.security.UnrecoverableKeyException; +-import java.security.cert.X509Certificate; +- +-/** +- * Wraps another {@link KeyManagerFactory} and caches its chains / certs for an alias for better performance when using +- * {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT}. +- * +- * Because of the caching its important that the wrapped {@link KeyManagerFactory}s {@link X509KeyManager}s always +- * return the same {@link X509Certificate} chain and {@link PrivateKey} for the same alias. +- */ +-public final class OpenSslCachingX509KeyManagerFactory extends KeyManagerFactory { +- +- private final int maxCachedEntries; +- +- public OpenSslCachingX509KeyManagerFactory(final KeyManagerFactory factory) { +- this(factory, 1024); +- } +- +- public OpenSslCachingX509KeyManagerFactory(final KeyManagerFactory factory, int maxCachedEntries) { +- super(new KeyManagerFactorySpi() { +- @Override +- protected void engineInit(KeyStore keyStore, char[] chars) +- throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { +- factory.init(keyStore, chars); +- } +- +- @Override +- protected void engineInit(ManagerFactoryParameters managerFactoryParameters) +- throws InvalidAlgorithmParameterException { +- factory.init(managerFactoryParameters); +- } +- +- @Override +- protected KeyManager[] engineGetKeyManagers() { +- return factory.getKeyManagers(); +- } +- }, factory.getProvider(), factory.getAlgorithm()); +- this.maxCachedEntries = ObjectUtil.checkPositive(maxCachedEntries, "maxCachedEntries"); +- } +- +- OpenSslKeyMaterialProvider newProvider(String password) { +- X509KeyManager keyManager = ReferenceCountedOpenSslContext.chooseX509KeyManager(getKeyManagers()); +- if ("sun.security.ssl.X509KeyManagerImpl".equals(keyManager.getClass().getName())) { +- // Don't do caching if X509KeyManagerImpl is used as the returned aliases are not stable and will change +- // between invocations. +- return new OpenSslKeyMaterialProvider(keyManager, password); +- } +- return new OpenSslCachingKeyMaterialProvider( +- ReferenceCountedOpenSslContext.chooseX509KeyManager(getKeyManagers()), password, maxCachedEntries); +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslCertificateException.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslCertificateException.java +deleted file mode 100644 +index 39fddf2..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslCertificateException.java ++++ /dev/null +@@ -1,81 +0,0 @@ +-/* +- * Copyright 2016 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.internal.tcnative.CertificateVerifier; +- +-import java.security.cert.CertificateException; +- +-/** +- * A special {@link CertificateException} which allows to specify which error code is included in the +- * SSL Record. This only work when {@link SslProvider#OPENSSL} or {@link SslProvider#OPENSSL_REFCNT} is used. +- */ +-public final class OpenSslCertificateException extends CertificateException { +- private static final long serialVersionUID = 5542675253797129798L; +- +- private final int errorCode; +- +- /** +- * Construct a new exception with the +- * error code. +- */ +- public OpenSslCertificateException(int errorCode) { +- this((String) null, errorCode); +- } +- +- /** +- * Construct a new exception with the msg and +- * error code . +- */ +- public OpenSslCertificateException(String msg, int errorCode) { +- super(msg); +- this.errorCode = checkErrorCode(errorCode); +- } +- +- /** +- * Construct a new exception with the msg, cause and +- * error code . +- */ +- public OpenSslCertificateException(String message, Throwable cause, int errorCode) { +- super(message, cause); +- this.errorCode = checkErrorCode(errorCode); +- } +- +- /** +- * Construct a new exception with the cause and +- * error code . +- */ +- public OpenSslCertificateException(Throwable cause, int errorCode) { +- this(null, cause, errorCode); +- } +- +- /** +- * Return the error code to use. +- */ +- public int errorCode() { +- return errorCode; +- } +- +- private static int checkErrorCode(int errorCode) { +- // Call OpenSsl.isAvailable() to ensure we try to load the native lib as CertificateVerifier.isValid(...) +- // will depend on it. If loading fails we will just skip the validation. +- if (OpenSsl.isAvailable() && !CertificateVerifier.isValid(errorCode)) { +- throw new IllegalArgumentException("errorCode '" + errorCode + +- "' invalid, see https://www.openssl.org/docs/man1.0.2/apps/verify.html."); +- } +- return errorCode; +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java +deleted file mode 100644 +index 57152de..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java ++++ /dev/null +@@ -1,212 +0,0 @@ +-/* +- * Copyright 2014 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.internal.tcnative.SSL; +- +-import java.io.File; +-import java.security.KeyStore; +-import java.security.PrivateKey; +-import java.security.cert.X509Certificate; +-import java.util.Map; +- +-import javax.net.ssl.KeyManagerFactory; +-import javax.net.ssl.SSLException; +-import javax.net.ssl.TrustManager; +-import javax.net.ssl.TrustManagerFactory; +- +-import static io.netty.handler.ssl.ReferenceCountedOpenSslClientContext.newSessionContext; +- +-/** +- * A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. +- *

This class will use a finalizer to ensure native resources are automatically cleaned up. To avoid finalizers +- * and manually release the native memory see {@link ReferenceCountedOpenSslClientContext}. +- */ +-public final class OpenSslClientContext extends OpenSslContext { +- private final OpenSslSessionContext sessionContext; +- +- /** +- * Creates a new instance. - * @deprecated use {@link SslContextBuilder} - */ - @Deprecated -- public OpenSslServerContext( -- File trustCertCollectionFile, TrustManagerFactory trustManagerFactory, -- File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory, -- Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, -- long sessionCacheSize, long sessionTimeout) throws SSLException { -- this(toX509CertificatesInternal(trustCertCollectionFile), trustManagerFactory, -- toX509CertificatesInternal(keyCertChainFile), toPrivateKeyInternal(keyFile, keyPassword), -- keyPassword, keyManagerFactory, ciphers, cipherFilter, -- apn, sessionCacheSize, sessionTimeout, ClientAuth.NONE, null, false, false); +- public OpenSslClientContext() throws SSLException { +- this(null, null, null, null, null, null, null, IdentityCipherSuiteFilter.INSTANCE, null, 0, 0); +- } +- +- /** +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format. +- * {@code null} to use the system default +- * @deprecated use {@link SslContextBuilder} +- */ +- @Deprecated +- public OpenSslClientContext(File certChainFile) throws SSLException { +- this(certChainFile, null); +- } +- +- /** +- * Creates a new instance. +- * +- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s +- * that verifies the certificates sent from servers. +- * {@code null} to use the default. +- * @deprecated use {@link SslContextBuilder} +- */ +- @Deprecated +- public OpenSslClientContext(TrustManagerFactory trustManagerFactory) throws SSLException { +- this(null, trustManagerFactory); +- } +- +- /** +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format. +- * {@code null} to use the system default +- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s +- * that verifies the certificates sent from servers. +- * {@code null} to use the default. +- * @deprecated use {@link SslContextBuilder} +- */ +- @Deprecated +- public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException { +- this(certChainFile, trustManagerFactory, null, null, null, null, null, +- IdentityCipherSuiteFilter.INSTANCE, null, 0, 0); +- } +- +- /** +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s +- * that verifies the certificates sent from servers. +- * {@code null} to use the default.. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param apn Provides a means to configure parameters related to application protocol negotiation. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} +- */ +- @Deprecated +- public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory, Iterable ciphers, +- ApplicationProtocolConfig apn, long sessionCacheSize, long sessionTimeout) +- throws SSLException { +- this(certChainFile, trustManagerFactory, null, null, null, null, ciphers, IdentityCipherSuiteFilter.INSTANCE, +- apn, sessionCacheSize, sessionTimeout); +- } +- +- /** +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s +- * that verifies the certificates sent from servers. +- * {@code null} to use the default.. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param cipherFilter a filter to apply over the supplied list of ciphers +- * @param apn Provides a means to configure parameters related to application protocol negotiation. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} +- */ +- @Deprecated +- public OpenSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory, Iterable ciphers, +- CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, +- long sessionCacheSize, long sessionTimeout) throws SSLException { +- this(certChainFile, trustManagerFactory, null, null, null, null, +- ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout); +- } +- +- /** +- * Creates a new instance. +- * @param trustCertCollectionFile an X.509 certificate collection file in PEM format. +- * {@code null} to use the system default +- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s +- * that verifies the certificates sent from servers. +- * {@code null} to use the default or the results of parsing +- * {@code trustCertCollectionFile} +- * @param keyCertChainFile an X.509 certificate chain file in PEM format. +- * This provides the public key for mutual authentication. +- * {@code null} to use the system default +- * @param keyFile a PKCS#8 private key file in PEM format. +- * This provides the private key for mutual authentication. +- * {@code null} for no mutual authentication. +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * Ignored if {@code keyFile} is {@code null}. +- * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link javax.net.ssl.KeyManager}s +- * that is used to encrypt data being sent to servers. +- * {@code null} to use the default or the results of parsing +- * {@code keyCertChainFile} and {@code keyFile}. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param cipherFilter a filter to apply over the supplied list of ciphers +- * @param apn Application Protocol Negotiator object. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} +- */ +- @Deprecated +- public OpenSslClientContext(File trustCertCollectionFile, TrustManagerFactory trustManagerFactory, +- File keyCertChainFile, File keyFile, String keyPassword, +- KeyManagerFactory keyManagerFactory, Iterable ciphers, +- CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, +- long sessionCacheSize, long sessionTimeout) +- throws SSLException { +- this(toX509CertificatesInternal(trustCertCollectionFile), trustManagerFactory, +- toX509CertificatesInternal(keyCertChainFile), toPrivateKeyInternal(keyFile, keyPassword), +- keyPassword, keyManagerFactory, ciphers, cipherFilter, apn, null, sessionCacheSize, +- sessionTimeout, false, KeyStore.getDefaultType(), null, null); +- } +- +- OpenSslClientContext(X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, +- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, +- KeyManagerFactory keyManagerFactory, Iterable ciphers, +- CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, String[] protocols, +- long sessionCacheSize, long sessionTimeout, boolean enableOcsp, String keyStore, +- String endpointIdentificationAlgorithm, ResumptionController resumptionController, +- Map.Entry, Object>... options) +- throws SSLException { +- super(ciphers, cipherFilter, apn, SSL.SSL_MODE_CLIENT, keyCertChain, ClientAuth.NONE, protocols, false, +- endpointIdentificationAlgorithm, enableOcsp, resumptionController, options); +- boolean success = false; +- try { +- OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword); +- sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, +- keyCertChain, key, keyPassword, keyManagerFactory, keyStore, +- sessionCacheSize, sessionTimeout, resumptionController); +- success = true; +- } finally { +- if (!success) { +- release(); +- } +- } +- } +- +- @Override +- public OpenSslSessionContext sessionContext() { +- return sessionContext; +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientSessionCache.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslClientSessionCache.java +deleted file mode 100644 +index 66238c4..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslClientSessionCache.java ++++ /dev/null +@@ -1,188 +0,0 @@ +-/* +- * Copyright 2021 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.internal.tcnative.SSL; +-import io.netty.util.AsciiString; +- +-import java.util.ArrayList; +-import java.util.HashMap; +-import java.util.HashSet; +-import java.util.List; +-import java.util.Map; +-import java.util.Set; +- +-/** +- * {@link OpenSslSessionCache} that is used by the client-side. +- */ +-final class OpenSslClientSessionCache extends OpenSslSessionCache { +- private final Map> sessions = new HashMap>(); +- +- OpenSslClientSessionCache(OpenSslEngineMap engineMap) { +- super(engineMap); +- } +- +- @Override +- protected boolean sessionCreated(NativeSslSession session) { +- assert Thread.holdsLock(this); +- HostPort hostPort = keyFor(session.getPeerHost(), session.getPeerPort()); +- if (hostPort == null) { +- return false; +- } +- Set sessionsForHost = sessions.get(hostPort); +- if (sessionsForHost == null) { +- // Let's start with something small as usually the server does not provide too many of these per hostPort +- // mapping. +- sessionsForHost = new HashSet(4); +- sessions.put(hostPort, sessionsForHost); +- } +- sessionsForHost.add(session); +- return true; +- } +- +- @Override +- protected void sessionRemoved(NativeSslSession session) { +- assert Thread.holdsLock(this); +- HostPort hostPort = keyFor(session.getPeerHost(), session.getPeerPort()); +- if (hostPort == null) { +- return; +- } +- Set sessionsForHost = sessions.get(hostPort); +- if (sessionsForHost != null) { +- sessionsForHost.remove(session); +- if (sessionsForHost.isEmpty()) { +- sessions.remove(hostPort); +- } +- } +- } +- +- @Override +- boolean setSession(long ssl, OpenSslSession session, String host, int port) { +- HostPort hostPort = keyFor(host, port); +- if (hostPort == null) { +- return false; +- } +- NativeSslSession nativeSslSession = null; +- final boolean reused; +- boolean singleUsed = false; +- synchronized (this) { +- Set sessionsForHost = sessions.get(hostPort); +- if (sessionsForHost == null) { +- return false; +- } +- if (sessionsForHost.isEmpty()) { +- sessions.remove(hostPort); +- // There is no session that we can use. +- return false; +- } +- +- List toBeRemoved = null; +- // Loop through all the sessions that might be usable and check if we can use one of these. +- for (NativeSslSession sslSession : sessionsForHost) { +- if (sslSession.isValid()) { +- nativeSslSession = sslSession; +- break; +- } else { +- if (toBeRemoved == null) { +- toBeRemoved = new ArrayList(2); +- } +- toBeRemoved.add(sslSession); +- } +- } +- +- // Remove everything that is not valid anymore +- if (toBeRemoved != null) { +- for (NativeSslSession sslSession : toBeRemoved) { +- removeSessionWithId(sslSession.sessionId()); +- } +- } +- if (nativeSslSession == null) { +- // Couldn't find a valid session that could be used. +- return false; +- } +- +- // Try to set the session, if true is returned OpenSSL incremented the reference count +- // of the underlying SSL_SESSION*. +- reused = SSL.setSession(ssl, nativeSslSession.session()); +- if (reused) { +- singleUsed = nativeSslSession.shouldBeSingleUse(); +- } +- } +- +- if (reused) { +- if (singleUsed) { +- // Should only be used once +- nativeSslSession.invalidate(); +- session.invalidate(); +- } +- nativeSslSession.setLastAccessedTime(System.currentTimeMillis()); +- session.setSessionDetails(nativeSslSession.getCreationTime(), nativeSslSession.getLastAccessedTime(), +- nativeSslSession.sessionId(), nativeSslSession.keyValueStorage); +- } +- return reused; +- } +- +- private static HostPort keyFor(String host, int port) { +- if (host == null && port < 1) { +- return null; +- } +- return new HostPort(host, port); +- } +- +- @Override +- synchronized void clear() { +- super.clear(); +- sessions.clear(); +- } +- +- /** +- * Host / Port tuple used to find a {@link OpenSslSession} in the cache. +- */ +- private static final class HostPort { +- private final int hash; +- private final String host; +- private final int port; +- +- HostPort(String host, int port) { +- this.host = host; +- this.port = port; +- // Calculate a hashCode that does ignore case. +- this.hash = 31 * AsciiString.hashCode(host) + port; +- } +- +- @Override +- public int hashCode() { +- return hash; +- } +- +- @Override +- public boolean equals(Object obj) { +- if (!(obj instanceof HostPort)) { +- return false; +- } +- HostPort other = (HostPort) obj; +- return port == other.port && host.equalsIgnoreCase(other.host); +- } +- +- @Override +- public String toString() { +- return "HostPort{" + +- "host='" + host + '\'' + +- ", port=" + port + +- '}'; +- } +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java +deleted file mode 100644 +index 62a76fa..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslContext.java ++++ /dev/null +@@ -1,64 +0,0 @@ +-/* +- * Copyright 2014 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.buffer.ByteBufAllocator; +- +-import java.security.cert.Certificate; +-import java.util.Map; +- +-import javax.net.ssl.SSLEngine; +-import javax.net.ssl.SSLException; +- +-/** +- * This class will use a finalizer to ensure native resources are automatically cleaned up. To avoid finalizers +- * and manually release the native memory see {@link ReferenceCountedOpenSslContext}. +- */ +-public abstract class OpenSslContext extends ReferenceCountedOpenSslContext { +- OpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apnCfg, +- int mode, Certificate[] keyCertChain, +- ClientAuth clientAuth, String[] protocols, boolean startTls, String endpointIdentificationAlgorithm, +- boolean enableOcsp, ResumptionController resumptionController, +- Map.Entry, Object>... options) +- throws SSLException { +- super(ciphers, cipherFilter, toNegotiator(apnCfg), mode, keyCertChain, +- clientAuth, protocols, startTls, endpointIdentificationAlgorithm, enableOcsp, false, +- resumptionController, options); +- } +- +- OpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, +- int mode, Certificate[] keyCertChain, +- ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp, +- ResumptionController resumptionController, +- Map.Entry, Object>... options) +- throws SSLException { +- super(ciphers, cipherFilter, apn, mode, keyCertChain, +- clientAuth, protocols, startTls, null, enableOcsp, false, resumptionController, options); +- } +- +- @Override +- final SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort, boolean jdkCompatibilityMode) { +- return new OpenSslEngine(this, alloc, peerHost, peerPort, jdkCompatibilityMode, +- endpointIdentificationAlgorithm); +- } +- +- @Override +- @SuppressWarnings("FinalizeDeclaration") +- protected final void finalize() throws Throwable { +- super.finalize(); +- OpenSsl.releaseIfNeeded(this); +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslContextOption.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslContextOption.java +deleted file mode 100644 +index 7e538dd..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslContextOption.java ++++ /dev/null +@@ -1,77 +0,0 @@ +-/* +- * Copyright 2021 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-/** +- * {@link SslContextOption}s that are specific to the {@link SslProvider#OPENSSL} / {@link SslProvider#OPENSSL_REFCNT}. +- * +- * @param the type of the value. +- */ +-public final class OpenSslContextOption extends SslContextOption { +- +- private OpenSslContextOption(String name) { +- super(name); +- } +- +- /** +- * If enabled heavy-operations may be offloaded from the {@link io.netty.channel.EventLoop} if possible. +- */ +- public static final OpenSslContextOption USE_TASKS = +- new OpenSslContextOption("USE_TASKS"); +- /** +- * If enabled TLS false start will be enabled if supported. +- * When TLS false start is enabled the flow of {@link SslHandshakeCompletionEvent}s may be different compared when, +- * not enabled. +- * +- * This is currently only supported when {@code BoringSSL} and ALPN is used. +- */ +- public static final OpenSslContextOption TLS_FALSE_START = +- new OpenSslContextOption("TLS_FALSE_START"); +- +- /** +- * Set the {@link OpenSslPrivateKeyMethod} to use. This allows to offload private-key operations +- * if needed. +- * +- * This is currently only supported when {@code BoringSSL} is used. +- */ +- public static final OpenSslContextOption PRIVATE_KEY_METHOD = +- new OpenSslContextOption("PRIVATE_KEY_METHOD"); +- +- /** +- * Set the {@link OpenSslAsyncPrivateKeyMethod} to use. This allows to offload private-key operations +- * if needed. +- * +- * This is currently only supported when {@code BoringSSL} is used. +- */ +- public static final OpenSslContextOption ASYNC_PRIVATE_KEY_METHOD = +- new OpenSslContextOption("ASYNC_PRIVATE_KEY_METHOD"); +- +- /** +- * Set the {@link OpenSslCertificateCompressionConfig} to use. This allows for the configuration of certificate +- * compression algorithms which should be used, the priority of those algorithms and the directions in which +- * they should be used. +- * +- * This is currently only supported when {@code BoringSSL} is used. +- */ +- public static final OpenSslContextOption CERTIFICATE_COMPRESSION_ALGORITHMS = +- new OpenSslContextOption("CERTIFICATE_COMPRESSION_ALGORITHMS"); +- +- /** +- * Set the maximum number of bytes that is allowed during the handshake for certificate chain. +- */ +- public static final OpenSslContextOption MAX_CERTIFICATE_LIST_BYTES = +- new OpenSslContextOption("MAX_CERTIFICATE_LIST_BYTES"); +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java +deleted file mode 100644 +index e43aade..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java ++++ /dev/null +@@ -1,41 +0,0 @@ +-/* +- * Copyright 2014 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.buffer.ByteBufAllocator; +- +-import javax.net.ssl.SSLEngine; +- +-/** +- * Implements a {@link SSLEngine} using +- * OpenSSL BIO abstractions. +- *

+- * This class will use a finalizer to ensure native resources are automatically cleaned up. To avoid finalizers +- * and manually release the native memory see {@link ReferenceCountedOpenSslEngine}. +- */ +-public final class OpenSslEngine extends ReferenceCountedOpenSslEngine { +- OpenSslEngine(OpenSslContext context, ByteBufAllocator alloc, String peerHost, int peerPort, +- boolean jdkCompatibilityMode, String endpointIdentificationAlgorithm) { +- super(context, alloc, peerHost, peerPort, jdkCompatibilityMode, false, endpointIdentificationAlgorithm); +- } +- +- @Override +- @SuppressWarnings("FinalizeDeclaration") +- protected void finalize() throws Throwable { +- super.finalize(); +- OpenSsl.releaseIfNeeded(this); +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java +deleted file mode 100644 +index 68e2df5..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslEngineMap.java ++++ /dev/null +@@ -1,35 +0,0 @@ +-/* +- * Copyright 2014 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-interface OpenSslEngineMap { +- +- /** +- * Remove the {@link OpenSslEngine} with the given {@code ssl} address and +- * return it. +- */ +- ReferenceCountedOpenSslEngine remove(long ssl); +- +- /** +- * Add a {@link OpenSslEngine} to this {@link OpenSslEngineMap}. +- */ +- void add(ReferenceCountedOpenSslEngine engine); +- +- /** +- * Get the {@link OpenSslEngine} for the given {@code ssl} address. +- */ +- ReferenceCountedOpenSslEngine get(long ssl); +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java +deleted file mode 100644 +index e2e2069..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialManager.java ++++ /dev/null +@@ -1,138 +0,0 @@ +-/* +- * Copyright 2016 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import javax.net.ssl.SSLException; +-import javax.net.ssl.SSLHandshakeException; +-import javax.net.ssl.X509ExtendedKeyManager; +-import javax.net.ssl.X509KeyManager; +-import javax.security.auth.x500.X500Principal; +-import java.security.PrivateKey; +-import java.security.cert.X509Certificate; +-import java.util.Arrays; +-import java.util.HashMap; +-import java.util.HashSet; +-import java.util.Map; +-import java.util.Set; +- +- +-/** +- * Manages key material for {@link OpenSslEngine}s and so set the right {@link PrivateKey}s and +- * {@link X509Certificate}s. +- */ +-final class OpenSslKeyMaterialManager { +- +- // Code in this class is inspired by code of conscrypts: +- // - https://android.googlesource.com/platform/external/ +- // conscrypt/+/master/src/main/java/org/conscrypt/OpenSSLEngineImpl.java +- // - https://android.googlesource.com/platform/external/ +- // conscrypt/+/master/src/main/java/org/conscrypt/SSLParametersImpl.java +- // +- static final String KEY_TYPE_RSA = "RSA"; +- static final String KEY_TYPE_DH_RSA = "DH_RSA"; +- static final String KEY_TYPE_EC = "EC"; +- static final String KEY_TYPE_EC_EC = "EC_EC"; +- static final String KEY_TYPE_EC_RSA = "EC_RSA"; +- +- // key type mappings for types. +- private static final Map KEY_TYPES = new HashMap(); +- static { +- KEY_TYPES.put("RSA", KEY_TYPE_RSA); +- KEY_TYPES.put("DHE_RSA", KEY_TYPE_RSA); +- KEY_TYPES.put("ECDHE_RSA", KEY_TYPE_RSA); +- KEY_TYPES.put("ECDHE_ECDSA", KEY_TYPE_EC); +- KEY_TYPES.put("ECDH_RSA", KEY_TYPE_EC_RSA); +- KEY_TYPES.put("ECDH_ECDSA", KEY_TYPE_EC_EC); +- KEY_TYPES.put("DH_RSA", KEY_TYPE_DH_RSA); - } - -- OpenSslServerContext( -- X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, -- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory, -- Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, -- long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls, -- boolean enableOcsp) throws SSLException { -- this(trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory, ciphers, -- cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls, -- enableOcsp); +- private final OpenSslKeyMaterialProvider provider; +- +- OpenSslKeyMaterialManager(OpenSslKeyMaterialProvider provider) { +- this.provider = provider; - } - -- @SuppressWarnings("deprecation") -- private OpenSslServerContext( -- X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, -- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory, -- Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, -- long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls, -- boolean enableOcsp) throws SSLException { -- super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain, -- clientAuth, protocols, startTls, enableOcsp); -- // Create a new SSL_CTX and configure it. -- boolean success = false; +- void setKeyMaterialServerSide(ReferenceCountedOpenSslEngine engine) throws SSLException { +- String[] authMethods = engine.authMethods(); +- if (authMethods.length == 0) { +- throw new SSLHandshakeException("Unable to find key material"); +- } +- +- // authMethods may contain duplicates or may result in the same type +- // but call chooseServerAlias(...) may be expensive. So let's ensure +- // we filter out duplicates. +- Set typeSet = new HashSet(KEY_TYPES.size()); +- for (String authMethod : authMethods) { +- String type = KEY_TYPES.get(authMethod); +- if (type != null && typeSet.add(type)) { +- String alias = chooseServerAlias(engine, type); +- if (alias != null) { +- // We found a match... let's set the key material and return. +- setKeyMaterial(engine, alias); +- return; +- } +- } +- } +- throw new SSLHandshakeException("Unable to find key material for auth method(s): " +- + Arrays.toString(authMethods)); +- } +- +- void setKeyMaterialClientSide(ReferenceCountedOpenSslEngine engine, String[] keyTypes, +- X500Principal[] issuer) throws SSLException { +- String alias = chooseClientAlias(engine, keyTypes, issuer); +- // Only try to set the keymaterial if we have a match. This is also consistent with what OpenJDK does: +- // https://hg.openjdk.java.net/jdk/jdk11/file/76072a077ee1/ +- // src/java.base/share/classes/sun/security/ssl/CertificateRequest.java#l362 +- if (alias != null) { +- setKeyMaterial(engine, alias); +- } +- } +- +- private void setKeyMaterial(ReferenceCountedOpenSslEngine engine, String alias) throws SSLException { +- OpenSslKeyMaterial keyMaterial = null; - try { -- ServerContext context = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, -- keyCertChain, key, keyPassword, keyManagerFactory); -- sessionContext = context.sessionContext; -- keyMaterialManager = context.keyMaterialManager; -- success = true; +- keyMaterial = provider.chooseKeyMaterial(engine.alloc, alias); +- if (keyMaterial == null) { +- return; +- } +- engine.setKeyMaterial(keyMaterial); +- } catch (SSLException e) { +- throw e; +- } catch (Exception e) { +- throw new SSLException(e); - } finally { -- if (!success) { -- release(); +- if (keyMaterial != null) { +- keyMaterial.release(); - } - } - } -- -- @Override -- public OpenSslServerSessionContext sessionContext() { -- return sessionContext; +- private String chooseClientAlias(ReferenceCountedOpenSslEngine engine, +- String[] keyTypes, X500Principal[] issuer) { +- X509KeyManager manager = provider.keyManager(); +- if (manager instanceof X509ExtendedKeyManager) { +- return ((X509ExtendedKeyManager) manager).chooseEngineClientAlias(keyTypes, issuer, engine); +- } +- return manager.chooseClientAlias(keyTypes, issuer, null); - } - -- @Override -- OpenSslKeyMaterialManager keyMaterialManager() { -- return keyMaterialManager; +- private String chooseServerAlias(ReferenceCountedOpenSslEngine engine, String type) { +- X509KeyManager manager = provider.keyManager(); +- if (manager instanceof X509ExtendedKeyManager) { +- return ((X509ExtendedKeyManager) manager).chooseEngineServerAlias(type, null, engine); +- } +- return manager.chooseServerAlias(type, null, null); - } -} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialProvider.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialProvider.java deleted file mode 100644 -index 8c92deb..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java +index adf545f..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslKeyMaterialProvider.java +++ /dev/null -@@ -1,124 +0,0 @@ +@@ -1,154 +0,0 @@ -/* -- * Copyright 2014 The Netty Project +- * Copyright 2018 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -1692,127 +2915,354 @@ index 8c92deb..0000000 - */ -package io.netty.handler.ssl; - +-import io.netty.buffer.ByteBufAllocator; +-import io.netty.buffer.UnpooledByteBufAllocator; -import io.netty.internal.tcnative.SSL; --import io.netty.internal.tcnative.SSLContext; - --import java.util.concurrent.locks.Lock; +-import javax.net.ssl.SSLException; +-import javax.net.ssl.X509KeyManager; +-import java.security.PrivateKey; +-import java.security.cert.X509Certificate; - +-import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.toBIO; - -/** -- * {@link OpenSslSessionContext} implementation which offers extra methods which are only useful for the server-side. +- * Provides {@link OpenSslKeyMaterial} for a given alias. - */ --public final class OpenSslServerSessionContext extends OpenSslSessionContext { -- OpenSslServerSessionContext(ReferenceCountedOpenSslContext context) { -- super(context); +-class OpenSslKeyMaterialProvider { +- +- private final X509KeyManager keyManager; +- private final String password; +- +- OpenSslKeyMaterialProvider(X509KeyManager keyManager, String password) { +- this.keyManager = keyManager; +- this.password = password; - } - -- @Override -- public void setSessionTimeout(int seconds) { -- if (seconds < 0) { -- throw new IllegalArgumentException(); +- static void validateKeyMaterialSupported(X509Certificate[] keyCertChain, PrivateKey key, String keyPassword) +- throws SSLException { +- validateSupported(keyCertChain); +- validateSupported(key, keyPassword); +- } +- +- private static void validateSupported(PrivateKey key, String password) throws SSLException { +- if (key == null) { +- return; - } -- Lock writerLock = context.ctxLock.writeLock(); -- writerLock.lock(); +- +- long pkeyBio = 0; +- long pkey = 0; +- - try { -- SSLContext.setSessionCacheTimeout(context.ctx, seconds); +- pkeyBio = toBIO(UnpooledByteBufAllocator.DEFAULT, key); +- pkey = SSL.parsePrivateKey(pkeyBio, password); +- } catch (Exception e) { +- throw new SSLException("PrivateKey type not supported " + key.getFormat(), e); - } finally { -- writerLock.unlock(); +- SSL.freeBIO(pkeyBio); +- if (pkey != 0) { +- SSL.freePrivateKey(pkey); +- } - } - } - -- @Override -- public int getSessionTimeout() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); +- private static void validateSupported(X509Certificate[] certificates) throws SSLException { +- if (certificates == null || certificates.length == 0) { +- return; +- } +- +- long chainBio = 0; +- long chain = 0; +- PemEncoded encoded = null; - try { -- return (int) SSLContext.getSessionCacheTimeout(context.ctx); +- encoded = PemX509Certificate.toPEM(UnpooledByteBufAllocator.DEFAULT, true, certificates); +- chainBio = toBIO(UnpooledByteBufAllocator.DEFAULT, encoded.retain()); +- chain = SSL.parseX509Chain(chainBio); +- } catch (Exception e) { +- throw new SSLException("Certificate type not supported", e); - } finally { -- readerLock.unlock(); +- SSL.freeBIO(chainBio); +- if (chain != 0) { +- SSL.freeX509Chain(chain); +- } +- if (encoded != null) { +- encoded.release(); +- } - } - } - -- @Override -- public void setSessionCacheSize(int size) { -- if (size < 0) { -- throw new IllegalArgumentException(); +- /** +- * Returns the underlying {@link X509KeyManager} that is used. +- */ +- X509KeyManager keyManager() { +- return keyManager; +- } +- +- /** +- * Returns the {@link OpenSslKeyMaterial} or {@code null} (if none) that should be used during the handshake by +- * OpenSSL. +- */ +- OpenSslKeyMaterial chooseKeyMaterial(ByteBufAllocator allocator, String alias) throws Exception { +- X509Certificate[] certificates = keyManager.getCertificateChain(alias); +- if (certificates == null || certificates.length == 0) { +- return null; - } -- Lock writerLock = context.ctxLock.writeLock(); -- writerLock.lock(); +- +- PrivateKey key = keyManager.getPrivateKey(alias); +- PemEncoded encoded = PemX509Certificate.toPEM(allocator, true, certificates); +- long chainBio = 0; +- long pkeyBio = 0; +- long chain = 0; +- long pkey = 0; - try { -- SSLContext.setSessionCacheSize(context.ctx, size); +- chainBio = toBIO(allocator, encoded.retain()); +- chain = SSL.parseX509Chain(chainBio); +- +- OpenSslKeyMaterial keyMaterial; +- if (key instanceof OpenSslPrivateKey) { +- keyMaterial = ((OpenSslPrivateKey) key).newKeyMaterial(chain, certificates); +- } else { +- pkeyBio = toBIO(allocator, key); +- pkey = key == null ? 0 : SSL.parsePrivateKey(pkeyBio, password); +- keyMaterial = new DefaultOpenSslKeyMaterial(chain, pkey, certificates); +- } +- +- // See the chain and pkey to 0 so we will not release it as the ownership was +- // transferred to OpenSslKeyMaterial. +- chain = 0; +- pkey = 0; +- return keyMaterial; - } finally { -- writerLock.unlock(); +- SSL.freeBIO(chainBio); +- SSL.freeBIO(pkeyBio); +- if (chain != 0) { +- SSL.freeX509Chain(chain); +- } +- if (pkey != 0) { +- SSL.freePrivateKey(pkey); +- } +- encoded.release(); - } - } - +- /** +- * Will be invoked once the provider should be destroyed. +- */ +- void destroy() { +- // NOOP. +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslPrivateKey.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslPrivateKey.java +deleted file mode 100644 +index fb6caed..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslPrivateKey.java ++++ /dev/null +@@ -1,191 +0,0 @@ +-/* +- * Copyright 2018 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.internal.tcnative.SSL; +-import io.netty.util.AbstractReferenceCounted; +-import io.netty.util.IllegalReferenceCountException; +-import io.netty.util.internal.EmptyArrays; +- +-import javax.security.auth.Destroyable; +-import java.security.PrivateKey; +-import java.security.cert.X509Certificate; +- +-final class OpenSslPrivateKey extends AbstractReferenceCounted implements PrivateKey { +- +- private long privateKeyAddress; +- +- OpenSslPrivateKey(long privateKeyAddress) { +- this.privateKeyAddress = privateKeyAddress; +- } +- - @Override -- public int getSessionCacheSize() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return (int) SSLContext.getSessionCacheSize(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- public String getAlgorithm() { +- return "unknown"; - } - - @Override -- public void setSessionCacheEnabled(boolean enabled) { -- long mode = enabled ? SSL.SSL_SESS_CACHE_SERVER : SSL.SSL_SESS_CACHE_OFF; +- public String getFormat() { +- // As we do not support encoding we should return null as stated in the javadocs of PrivateKey. +- return null; +- } - -- Lock writerLock = context.ctxLock.writeLock(); -- writerLock.lock(); -- try { -- SSLContext.setSessionCacheMode(context.ctx, mode); -- } finally { -- writerLock.unlock(); +- @Override +- public byte[] getEncoded() { +- return null; +- } +- +- private long privateKeyAddress() { +- if (refCnt() <= 0) { +- throw new IllegalReferenceCountException(); - } +- return privateKeyAddress; +- } +- +- @Override +- protected void deallocate() { +- SSL.freePrivateKey(privateKeyAddress); +- privateKeyAddress = 0; +- } +- +- @Override +- public OpenSslPrivateKey retain() { +- super.retain(); +- return this; +- } +- +- @Override +- public OpenSslPrivateKey retain(int increment) { +- super.retain(increment); +- return this; +- } +- +- @Override +- public OpenSslPrivateKey touch() { +- super.touch(); +- return this; +- } +- +- @Override +- public OpenSslPrivateKey touch(Object hint) { +- return this; +- } +- +- /** +- * NOTE: This is a JDK8 interface/method. Due to backwards compatibility +- * reasons it's not possible to slap the {@code @Override} annotation onto +- * this method. +- * +- * @see Destroyable#destroy() +- */ +- @Override +- public void destroy() { +- release(refCnt()); +- } +- +- /** +- * NOTE: This is a JDK8 interface/method. Due to backwards compatibility +- * reasons it's not possible to slap the {@code @Override} annotation onto +- * this method. +- * +- * @see Destroyable#isDestroyed() +- */ +- @Override +- public boolean isDestroyed() { +- return refCnt() == 0; - } - -- @Override -- public boolean isSessionCacheEnabled() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.getSessionCacheMode(context.ctx) == SSL.SSL_SESS_CACHE_SERVER; -- } finally { -- readerLock.unlock(); +- /** +- * Create a new {@link OpenSslKeyMaterial} which uses the private key that is held by {@link OpenSslPrivateKey}. +- * +- * When the material is created we increment the reference count of the enclosing {@link OpenSslPrivateKey} and +- * decrement it again when the reference count of the {@link OpenSslKeyMaterial} reaches {@code 0}. +- */ +- OpenSslKeyMaterial newKeyMaterial(long certificateChain, X509Certificate[] chain) { +- return new OpenSslPrivateKeyMaterial(certificateChain, chain); +- } +- +- // Package-private for unit-test only +- final class OpenSslPrivateKeyMaterial extends AbstractReferenceCounted implements OpenSslKeyMaterial { +- +- // Package-private for unit-test only +- long certificateChain; +- private final X509Certificate[] x509CertificateChain; +- +- OpenSslPrivateKeyMaterial(long certificateChain, X509Certificate[] x509CertificateChain) { +- this.certificateChain = certificateChain; +- this.x509CertificateChain = x509CertificateChain == null ? +- EmptyArrays.EMPTY_X509_CERTIFICATES : x509CertificateChain; +- OpenSslPrivateKey.this.retain(); +- } +- +- @Override +- public X509Certificate[] certificateChain() { +- return x509CertificateChain.clone(); +- } +- +- @Override +- public long certificateChainAddress() { +- if (refCnt() <= 0) { +- throw new IllegalReferenceCountException(); +- } +- return certificateChain; +- } +- +- @Override +- public long privateKeyAddress() { +- if (refCnt() <= 0) { +- throw new IllegalReferenceCountException(); +- } +- return OpenSslPrivateKey.this.privateKeyAddress(); +- } +- +- @Override +- public OpenSslKeyMaterial touch(Object hint) { +- OpenSslPrivateKey.this.touch(hint); +- return this; +- } +- +- @Override +- public OpenSslKeyMaterial retain() { +- super.retain(); +- return this; +- } +- +- @Override +- public OpenSslKeyMaterial retain(int increment) { +- super.retain(increment); +- return this; +- } +- +- @Override +- public OpenSslKeyMaterial touch() { +- OpenSslPrivateKey.this.touch(); +- return this; +- } +- +- @Override +- protected void deallocate() { +- releaseChain(); +- OpenSslPrivateKey.this.release(); - } -- } - -- /** -- * Set the context within which session be reused (server side only) -- * See -- * man SSL_CTX_set_session_id_context -- * -- * @param sidCtx can be any kind of binary data, it is therefore possible to use e.g. the name -- * of the application and/or the hostname and/or service name -- * @return {@code true} if success, {@code false} otherwise. -- */ -- public boolean setSessionIdContext(byte[] sidCtx) { -- Lock writerLock = context.ctxLock.writeLock(); -- writerLock.lock(); -- try { -- return SSLContext.setSessionIdContext(context.ctx, sidCtx); -- } finally { -- writerLock.unlock(); +- private void releaseChain() { +- SSL.freeX509Chain(certificateChain); +- certificateChain = 0; - } - } -} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslPrivateKeyMethod.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslPrivateKeyMethod.java deleted file mode 100644 -index 846a968..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java +index 84c8229..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslPrivateKeyMethod.java +++ /dev/null -@@ -1,137 +0,0 @@ +@@ -1,62 +0,0 @@ -/* -- * Copyright 2014 The Netty Project +- * Copyright 2019 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -1822,132 +3272,57 @@ index 846a968..0000000 - */ -package io.netty.handler.ssl; - --import io.netty.util.internal.ObjectUtil; --import io.netty.internal.tcnative.SSL; --import io.netty.internal.tcnative.SSLContext; --import io.netty.internal.tcnative.SessionTicketKey; +-import io.netty.internal.tcnative.SSLPrivateKeyMethod; +-import io.netty.util.internal.UnstableApi; - --import javax.net.ssl.SSLSession; --import javax.net.ssl.SSLSessionContext; --import java.util.Arrays; --import java.util.Enumeration; --import java.util.NoSuchElementException; --import java.util.concurrent.locks.Lock; +-import javax.net.ssl.SSLEngine; - -/** -- * OpenSSL specific {@link SSLSessionContext} implementation. +- * Allow to customize private key signing / decrypting (when using RSA). Only supported when using BoringSSL atm. - */ --public abstract class OpenSslSessionContext implements SSLSessionContext { -- private static final Enumeration EMPTY = new EmptyEnumeration(); -- -- private final OpenSslSessionStats stats; -- final ReferenceCountedOpenSslContext context; -- -- // IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent -- // the GC to collect OpenSslContext as this would also free the pointer and so could result in a -- // segfault when the user calls any of the methods here that try to pass the pointer down to the native -- // level. -- OpenSslSessionContext(ReferenceCountedOpenSslContext context) { -- this.context = context; -- stats = new OpenSslSessionStats(context); -- } -- -- @Override -- public SSLSession getSession(byte[] bytes) { -- if (bytes == null) { -- throw new NullPointerException("bytes"); -- } -- return null; -- } -- -- @Override -- public Enumeration getIds() { -- return EMPTY; -- } -- -- /** -- * Sets the SSL session ticket keys of this context. -- * @deprecated use {@link #setTicketKeys(OpenSslSessionTicketKey...)}. -- */ -- @Deprecated -- public void setTicketKeys(byte[] keys) { -- if (keys.length % SessionTicketKey.TICKET_KEY_SIZE != 0) { -- throw new IllegalArgumentException("keys.length % " + SessionTicketKey.TICKET_KEY_SIZE + " != 0"); -- } -- SessionTicketKey[] tickets = new SessionTicketKey[keys.length / SessionTicketKey.TICKET_KEY_SIZE]; -- for (int i = 0, a = 0; i < tickets.length; i++) { -- byte[] name = Arrays.copyOfRange(keys, a, SessionTicketKey.NAME_SIZE); -- a += SessionTicketKey.NAME_SIZE; -- byte[] hmacKey = Arrays.copyOfRange(keys, a, SessionTicketKey.HMAC_KEY_SIZE); -- i += SessionTicketKey.HMAC_KEY_SIZE; -- byte[] aesKey = Arrays.copyOfRange(keys, a, SessionTicketKey.AES_KEY_SIZE); -- a += SessionTicketKey.AES_KEY_SIZE; -- tickets[i] = new SessionTicketKey(name, hmacKey, aesKey); -- } -- Lock writerLock = context.ctxLock.writeLock(); -- writerLock.lock(); -- try { -- SSLContext.clearOptions(context.ctx, SSL.SSL_OP_NO_TICKET); -- SSLContext.setSessionTicketKeys(context.ctx, tickets); -- } finally { -- writerLock.unlock(); -- } -- } -- -- /** -- * Sets the SSL session ticket keys of this context. -- */ -- public void setTicketKeys(OpenSslSessionTicketKey... keys) { -- ObjectUtil.checkNotNull(keys, "keys"); -- SessionTicketKey[] ticketKeys = new SessionTicketKey[keys.length]; -- for (int i = 0; i < ticketKeys.length; i++) { -- ticketKeys[i] = keys[i].key; -- } -- Lock writerLock = context.ctxLock.writeLock(); -- writerLock.lock(); -- try { -- SSLContext.clearOptions(context.ctx, SSL.SSL_OP_NO_TICKET); -- SSLContext.setSessionTicketKeys(context.ctx, ticketKeys); -- } finally { -- writerLock.unlock(); -- } -- } -- -- /** -- * Enable or disable caching of SSL sessions. -- */ -- public abstract void setSessionCacheEnabled(boolean enabled); +-@UnstableApi +-public interface OpenSslPrivateKeyMethod { +- int SSL_SIGN_RSA_PKCS1_SHA1 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA1; +- int SSL_SIGN_RSA_PKCS1_SHA256 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA256; +- int SSL_SIGN_RSA_PKCS1_SHA384 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA384; +- int SSL_SIGN_RSA_PKCS1_SHA512 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA512; +- int SSL_SIGN_ECDSA_SHA1 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SHA1; +- int SSL_SIGN_ECDSA_SECP256R1_SHA256 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256; +- int SSL_SIGN_ECDSA_SECP384R1_SHA384 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP384R1_SHA384; +- int SSL_SIGN_ECDSA_SECP521R1_SHA512 = SSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP521R1_SHA512; +- int SSL_SIGN_RSA_PSS_RSAE_SHA256 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA256; +- int SSL_SIGN_RSA_PSS_RSAE_SHA384 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA384; +- int SSL_SIGN_RSA_PSS_RSAE_SHA512 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA512; +- int SSL_SIGN_ED25519 = SSLPrivateKeyMethod.SSL_SIGN_ED25519; +- int SSL_SIGN_RSA_PKCS1_MD5_SHA1 = SSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_MD5_SHA1; - - /** -- * Return {@code true} if caching of SSL sessions is enabled, {@code false} otherwise. +- * Signs the input with the given key and returns the signed bytes. +- * +- * @param engine the {@link SSLEngine} +- * @param signatureAlgorithm the algorithm to use for signing +- * @param input the digest itself +- * @return the signed data (must not be {@code null}) +- * @throws Exception thrown if an error is encountered during the signing - */ -- public abstract boolean isSessionCacheEnabled(); +- byte[] sign(SSLEngine engine, int signatureAlgorithm, byte[] input) throws Exception; - - /** -- * Returns the stats of this context. +- * Decrypts the input with the given key and returns the decrypted bytes. +- * +- * @param engine the {@link SSLEngine} +- * @param input the input which should be decrypted +- * @return the decrypted data (must not be {@code null}) +- * @throws Exception thrown if an error is encountered during the decrypting - */ -- public OpenSslSessionStats stats() { -- return stats; -- } -- -- private static final class EmptyEnumeration implements Enumeration { -- @Override -- public boolean hasMoreElements() { -- return false; -- } -- -- @Override -- public byte[] nextElement() { -- throw new NoSuchElementException(); -- } -- } +- byte[] decrypt(SSLEngine engine, byte[] input) throws Exception; -} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java deleted file mode 100644 -index f49b95f..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java +index feca8d1..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerContext.java +++ /dev/null -@@ -1,253 +0,0 @@ +@@ -1,374 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * @@ -1955,7 +3330,7 @@ index f49b95f..0000000 - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -1963,258 +3338,435 @@ index f49b95f..0000000 - * License for the specific language governing permissions and limitations - * under the License. - */ -- -package io.netty.handler.ssl; - --import io.netty.internal.tcnative.SSLContext; +-import io.netty.internal.tcnative.SSL; - --import java.util.concurrent.locks.Lock; +-import java.io.File; +-import java.security.KeyStore; +-import java.security.PrivateKey; +-import java.security.cert.X509Certificate; +-import java.util.Map; - --/** -- * Stats exposed by an OpenSSL session context. -- * -- * @see SSL_CTX_sess_number -- */ --public final class OpenSslSessionStats { +-import javax.net.ssl.KeyManager; +-import javax.net.ssl.KeyManagerFactory; +-import javax.net.ssl.SSLException; +-import javax.net.ssl.TrustManager; +-import javax.net.ssl.TrustManagerFactory; - -- private final ReferenceCountedOpenSslContext context; +-import static io.netty.handler.ssl.ReferenceCountedOpenSslServerContext.newSessionContext; - -- // IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent -- // the GC to collect OpenSslContext as this would also free the pointer and so could result in a -- // segfault when the user calls any of the methods here that try to pass the pointer down to the native -- // level. -- OpenSslSessionStats(ReferenceCountedOpenSslContext context) { -- this.context = context; -- } +-/** +- * A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. +- *

This class will use a finalizer to ensure native resources are automatically cleaned up. To avoid finalizers +- * and manually release the native memory see {@link ReferenceCountedOpenSslServerContext}. +- */ +-public final class OpenSslServerContext extends OpenSslContext { +- private final OpenSslServerSessionContext sessionContext; - - /** -- * Returns the current number of sessions in the internal session cache. +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @deprecated use {@link SslContextBuilder} - */ -- public long number() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionNumber(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Deprecated +- public OpenSslServerContext(File certChainFile, File keyFile) throws SSLException { +- this(certChainFile, keyFile, null); - } - - /** -- * Returns the number of started SSL/TLS handshakes in client mode. +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * @deprecated use {@link SslContextBuilder} - */ -- public long connect() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionConnect(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Deprecated +- public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException { +- this(certChainFile, keyFile, keyPassword, null, IdentityCipherSuiteFilter.INSTANCE, +- ApplicationProtocolConfig.DISABLED, 0, 0); - } - - /** -- * Returns the number of successfully established SSL/TLS sessions in client mode. +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param apn Provides a means to configure parameters related to application protocol negotiation. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} - */ -- public long connectGood() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionConnectGood(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Deprecated +- public OpenSslServerContext( +- File certChainFile, File keyFile, String keyPassword, +- Iterable ciphers, ApplicationProtocolConfig apn, +- long sessionCacheSize, long sessionTimeout) throws SSLException { +- this(certChainFile, keyFile, keyPassword, ciphers, IdentityCipherSuiteFilter.INSTANCE, +- apn, sessionCacheSize, sessionTimeout); - } - - /** -- * Returns the number of start renegotiations in client mode. -- */ -- public long connectRenegotiate() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionConnectRenegotiate(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param nextProtocols the application layer protocols to accept, in the order of preference. +- * {@code null} to disable TLS NPN/ALPN extension. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} +- */ +- @Deprecated +- public OpenSslServerContext( +- File certChainFile, File keyFile, String keyPassword, +- Iterable ciphers, Iterable nextProtocols, +- long sessionCacheSize, long sessionTimeout) throws SSLException { +- this(certChainFile, keyFile, keyPassword, ciphers, +- toApplicationProtocolConfig(nextProtocols), sessionCacheSize, sessionTimeout); - } - - /** -- * Returns the number of started SSL/TLS handshakes in server mode. +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param config Application protocol config. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} - */ -- public long accept() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionAccept(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Deprecated +- public OpenSslServerContext( +- File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory, +- Iterable ciphers, ApplicationProtocolConfig config, +- long sessionCacheSize, long sessionTimeout) throws SSLException { +- this(certChainFile, keyFile, keyPassword, trustManagerFactory, ciphers, +- toNegotiator(config), sessionCacheSize, sessionTimeout); - } - - /** -- * Returns the number of successfully established SSL/TLS sessions in server mode. +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param apn Application protocol negotiator. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} - */ -- public long acceptGood() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionAcceptGood(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Deprecated +- public OpenSslServerContext( +- File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory, +- Iterable ciphers, OpenSslApplicationProtocolNegotiator apn, +- long sessionCacheSize, long sessionTimeout) throws SSLException { +- this(null, trustManagerFactory, certChainFile, keyFile, keyPassword, null, +- ciphers, null, apn, sessionCacheSize, sessionTimeout); - } - - /** -- * Returns the number of start renegotiations in server mode. +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param cipherFilter a filter to apply over the supplied list of ciphers +- * @param apn Provides a means to configure parameters related to application protocol negotiation. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} - */ -- public long acceptRenegotiate() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionAcceptRenegotiate(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Deprecated +- public OpenSslServerContext( +- File certChainFile, File keyFile, String keyPassword, +- Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, +- long sessionCacheSize, long sessionTimeout) throws SSLException { +- this(null, null, certChainFile, keyFile, keyPassword, null, +- ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout); - } - - /** -- * Returns the number of successfully reused sessions. In client mode, a session set with {@code SSL_set_session} -- * successfully reused is counted as a hit. In server mode, a session successfully retrieved from internal or -- * external cache is counted as a hit. +- * Creates a new instance. +- * +- * @param trustCertCollectionFile an X.509 certificate collection file in PEM format. +- * This provides the certificate collection used for mutual authentication. +- * {@code null} to use the system default +- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s +- * that verifies the certificates sent from clients. +- * {@code null} to use the default or the results of parsing +- * {@code trustCertCollectionFile}. +- * @param keyCertChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s +- * that is used to encrypt data being sent to clients. +- * {@code null} to use the default or the results of parsing +- * {@code keyCertChainFile} and {@code keyFile}. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param cipherFilter a filter to apply over the supplied list of ciphers +- * Only required if {@code provider} is {@link SslProvider#JDK} +- * @param config Provides a means to configure parameters related to application protocol negotiation. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} - */ -- public long hits() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionHits(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Deprecated +- public OpenSslServerContext( +- File trustCertCollectionFile, TrustManagerFactory trustManagerFactory, +- File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory, +- Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig config, +- long sessionCacheSize, long sessionTimeout) throws SSLException { +- this(trustCertCollectionFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword, keyManagerFactory, +- ciphers, cipherFilter, toNegotiator(config), sessionCacheSize, sessionTimeout); - } - - /** -- * Returns the number of successfully retrieved sessions from the external session cache in server mode. +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param cipherFilter a filter to apply over the supplied list of ciphers +- * @param config Application protocol config. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} - */ -- public long cbHits() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionCbHits(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Deprecated +- public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword, +- TrustManagerFactory trustManagerFactory, Iterable ciphers, +- CipherSuiteFilter cipherFilter, ApplicationProtocolConfig config, +- long sessionCacheSize, long sessionTimeout) throws SSLException { +- this(null, trustManagerFactory, certChainFile, keyFile, keyPassword, null, ciphers, cipherFilter, +- toNegotiator(config), sessionCacheSize, sessionTimeout); - } - - /** -- * Returns the number of sessions proposed by clients that were not found in the internal session cache -- * in server mode. +- * Creates a new instance. +- * +- * @param certChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param cipherFilter a filter to apply over the supplied list of ciphers +- * @param apn Application protocol negotiator. +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder}} - */ -- public long misses() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionMisses(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Deprecated +- public OpenSslServerContext( +- File certChainFile, File keyFile, String keyPassword, TrustManagerFactory trustManagerFactory, +- Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, +- long sessionCacheSize, long sessionTimeout) throws SSLException { +- this(null, trustManagerFactory, certChainFile, keyFile, keyPassword, null, ciphers, cipherFilter, +- apn, sessionCacheSize, sessionTimeout); - } - - /** -- * Returns the number of sessions proposed by clients and either found in the internal or external session cache -- * in server mode, but that were invalid due to timeout. These sessions are not included in the {@link #hits()} -- * count. +- * Creates a new instance. +- * +- * +- * @param trustCertCollectionFile an X.509 certificate collection file in PEM format. +- * This provides the certificate collection used for mutual authentication. +- * {@code null} to use the system default +- * @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s +- * that verifies the certificates sent from clients. +- * {@code null} to use the default or the results of parsing +- * {@code trustCertCollectionFile}. +- * @param keyCertChainFile an X.509 certificate chain file in PEM format +- * @param keyFile a PKCS#8 private key file in PEM format +- * @param keyPassword the password of the {@code keyFile}. +- * {@code null} if it's not password-protected. +- * @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s +- * that is used to encrypt data being sent to clients. +- * {@code null} to use the default or the results of parsing +- * {@code keyCertChainFile} and {@code keyFile}. +- * @param ciphers the cipher suites to enable, in the order of preference. +- * {@code null} to use the default cipher suites. +- * @param cipherFilter a filter to apply over the supplied list of ciphers +- * Only required if {@code provider} is {@link SslProvider#JDK} +- * @param apn Application Protocol Negotiator object +- * @param sessionCacheSize the size of the cache used for storing SSL session objects. +- * {@code 0} to use the default value. +- * @param sessionTimeout the timeout for the cached SSL session objects, in seconds. +- * {@code 0} to use the default value. +- * @deprecated use {@link SslContextBuilder} - */ -- public long timeouts() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionTimeouts(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Deprecated +- public OpenSslServerContext( +- File trustCertCollectionFile, TrustManagerFactory trustManagerFactory, +- File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory, +- Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, +- long sessionCacheSize, long sessionTimeout) throws SSLException { +- this(toX509CertificatesInternal(trustCertCollectionFile), trustManagerFactory, +- toX509CertificatesInternal(keyCertChainFile), toPrivateKeyInternal(keyFile, keyPassword), +- keyPassword, keyManagerFactory, ciphers, cipherFilter, +- apn, sessionCacheSize, sessionTimeout, ClientAuth.NONE, null, false, false, KeyStore.getDefaultType(), +- null); - } - -- /** -- * Returns the number of sessions that were removed because the maximum session cache size was exceeded. -- */ -- public long cacheFull() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionCacheFull(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- OpenSslServerContext( +- X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, +- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory, +- Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, +- long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls, +- boolean enableOcsp, String keyStore, ResumptionController resumptionController, +- Map.Entry, Object>... options) +- throws SSLException { +- this(trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory, ciphers, +- cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls, +- enableOcsp, keyStore, resumptionController, options); - } - -- /** -- * Returns the number of times a client presented a ticket that did not match any key in the list. -- */ -- public long ticketKeyFail() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); +- @SuppressWarnings("deprecation") +- private OpenSslServerContext( +- X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, +- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory, +- Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, +- long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls, +- boolean enableOcsp, String keyStore, ResumptionController resumptionController, +- Map.Entry, Object>... options) +- throws SSLException { +- super(ciphers, cipherFilter, apn, SSL.SSL_MODE_SERVER, keyCertChain, +- clientAuth, protocols, startTls, enableOcsp, resumptionController, options); +- +- // Create a new SSL_CTX and configure it. +- boolean success = false; - try { -- return SSLContext.sessionTicketKeyFail(context.ctx); +- OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword); +- sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, +- keyCertChain, key, keyPassword, keyManagerFactory, keyStore, +- sessionCacheSize, sessionTimeout, resumptionController); +- success = true; - } finally { -- readerLock.unlock(); +- if (!success) { +- release(); +- } - } - } - -- /** -- * Returns the number of times a client did not present a ticket and we issued a new one -- */ -- public long ticketKeyNew() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionTicketKeyNew(context.ctx); -- } finally { -- readerLock.unlock(); -- } +- @Override +- public OpenSslServerSessionContext sessionContext() { +- return sessionContext; - } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java +deleted file mode 100644 +index eba161f..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslServerSessionContext.java ++++ /dev/null +@@ -1,50 +0,0 @@ +-/* +- * Copyright 2014 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; - -- /** -- * Returns the number of times a client presented a ticket derived from an older key, -- * and we upgraded to the primary key. -- */ -- public long ticketKeyRenew() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- return SSLContext.sessionTicketKeyRenew(context.ctx); -- } finally { -- readerLock.unlock(); -- } +-import io.netty.internal.tcnative.SSL; +-import io.netty.internal.tcnative.SSLContext; +- +-import java.util.concurrent.locks.Lock; +- +- +-/** +- * {@link OpenSslSessionContext} implementation which offers extra methods which are only useful for the server-side. +- */ +-public final class OpenSslServerSessionContext extends OpenSslSessionContext { +- OpenSslServerSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) { +- super(context, provider, SSL.SSL_SESS_CACHE_SERVER, new OpenSslSessionCache(context.engineMap)); - } - - /** -- * Returns the number of times a client presented a ticket derived from the primary key. +- * Set the context within which session be reused (server side only) +- * See +- * man SSL_CTX_set_session_id_context +- * +- * @param sidCtx can be any kind of binary data, it is therefore possible to use e.g. the name +- * of the application and/or the hostname and/or service name +- * @return {@code true} if success, {@code false} otherwise. - */ -- public long ticketKeyResume() { -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); +- public boolean setSessionIdContext(byte[] sidCtx) { +- Lock writerLock = context.ctxLock.writeLock(); +- writerLock.lock(); - try { -- return SSLContext.sessionTicketKeyResume(context.ctx); +- return SSLContext.setSessionIdContext(context.ctx, sidCtx); - } finally { -- readerLock.unlock(); +- writerLock.unlock(); - } - } -} -diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionTicketKey.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionTicketKey.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSession.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSession.java deleted file mode 100644 -index 79f71a6..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionTicketKey.java +index d7ed0fa..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSession.java +++ /dev/null -@@ -1,78 +0,0 @@ +@@ -1,95 +0,0 @@ -/* -- * Copyright 2015 The Netty Project +- * Copyright 2018 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -2224,1258 +3776,1119 @@ index 79f71a6..0000000 - */ -package io.netty.handler.ssl; - --import io.netty.internal.tcnative.SessionTicketKey; +-import javax.net.ssl.SSLException; +-import javax.net.ssl.SSLSession; +-import java.security.cert.Certificate; +-import java.util.Map; - -/** -- * Session Ticket Key +- * {@link SSLSession} that is specific to our native implementation. - */ --public final class OpenSslSessionTicketKey { +-interface OpenSslSession extends SSLSession { - - /** -- * Size of session ticket key name +- * Called on a handshake session before being exposed to a {@link javax.net.ssl.TrustManager}. +- * Session data must be cleared by this call. - */ -- public static final int NAME_SIZE = SessionTicketKey.NAME_SIZE; +- void prepareHandshake(); +- - /** -- * Size of session ticket key HMAC key +- * Return the {@link OpenSslSessionId} that can be used to identify this session. - */ -- public static final int HMAC_KEY_SIZE = SessionTicketKey.HMAC_KEY_SIZE; +- OpenSslSessionId sessionId(); +- - /** -- * Size of session ticket key AES key +- * Set the local certificate chain that is used. It is not expected that this array will be changed at all +- * and so its ok to not copy the array. - */ -- public static final int AES_KEY_SIZE = SessionTicketKey.AES_KEY_SIZE; +- void setLocalCertificate(Certificate[] localCertificate); +- - /** -- * Size of session ticker key +- * Set the details for the session which might come from a cache. +- * +- * @param creationTime the time at which the session was created. +- * @param lastAccessedTime the time at which the session was last accessed via the session infrastructure (cache). +- * @param id the {@link OpenSslSessionId} +- * @param keyValueStorage the key value store. See {@link #keyValueStorage()}. - */ -- public static final int TICKET_KEY_SIZE = SessionTicketKey.TICKET_KEY_SIZE; -- -- final SessionTicketKey key; +- void setSessionDetails(long creationTime, long lastAccessedTime, OpenSslSessionId id, +- Map keyValueStorage); - - /** -- * Construct a OpenSslSessionTicketKey. +- * Return the underlying {@link Map} that is used by the following methods: - * -- * @param name the name of the session ticket key -- * @param hmacKey the HMAC key of the session ticket key -- * @param aesKey the AES key of the session ticket key +- *

    +- *
  • {@link #putValue(String, Object)}
  • +- *
  • {@link #removeValue(String)}
  • +- *
  • {@link #getValue(String)}
  • +- *
  • {@link #getValueNames()}
  • +- *
+- * +- * The {@link Map} must be thread-safe! +- * +- * @return storage - */ -- public OpenSslSessionTicketKey(byte[] name, byte[] hmacKey, byte[] aesKey) { -- key = new SessionTicketKey(name.clone(), hmacKey.clone(), aesKey.clone()); -- } +- Map keyValueStorage(); - - /** -- * Get name. -- * @return the name of the session ticket key +- * Set the last access time which will be returned by {@link #getLastAccessedTime()}. +- * +- * @param time the time - */ -- public byte[] name() { -- return key.getName().clone(); -- } +- void setLastAccessedTime(long time); +- +- @Override +- OpenSslSessionContext getSessionContext(); - - /** -- * Get HMAC key. -- * @return the HMAC key of the session ticket key +- * Expand (or increase) the value returned by {@link #getApplicationBufferSize()} if necessary. +- *

+- * This is only called in a synchronized block, so no need to use atomic operations. +- * @param packetLengthDataOnly The packet size which exceeds the current {@link #getApplicationBufferSize()}. - */ -- public byte[] hmacKey() { -- return key.getHmacKey().clone(); -- } +- void tryExpandApplicationBufferSize(int packetLengthDataOnly); - - /** -- * Get AES Key. -- * @return the AES key of the session ticket key +- * Called once the handshake has completed. - */ -- public byte[] aesKey() { -- return key.getAesKey().clone(); -- } +- void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate, +- byte[][] peerCertificateChain, long creationTime, long timeout) throws SSLException; -} -diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionCache.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionCache.java deleted file mode 100644 -index b213573..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java +index 7a04419..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionCache.java +++ /dev/null -@@ -1,298 +0,0 @@ +@@ -1,521 +0,0 @@ -/* -- * Copyright 2016 The Netty Project +- * Copyright 2021 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; -- --import io.netty.util.internal.logging.InternalLogger; --import io.netty.util.internal.logging.InternalLoggerFactory; --import io.netty.internal.tcnative.CertificateRequestedCallback; --import io.netty.internal.tcnative.SSL; --import io.netty.internal.tcnative.SSLContext; -- --import java.security.KeyStore; --import java.security.PrivateKey; --import java.security.cert.X509Certificate; --import java.util.HashSet; --import java.util.Set; -- --import javax.net.ssl.KeyManagerFactory; --import javax.net.ssl.SSLException; --import javax.net.ssl.SSLHandshakeException; --import javax.net.ssl.TrustManagerFactory; --import javax.net.ssl.X509ExtendedKeyManager; --import javax.net.ssl.X509ExtendedTrustManager; --import javax.net.ssl.X509KeyManager; --import javax.net.ssl.X509TrustManager; --import javax.security.auth.x500.X500Principal; -- --/** -- * A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. -- *

Instances of this class must be {@link #release() released} or else native memory will leak! -- * -- *

Instances of this class must not be released before any {@link ReferenceCountedOpenSslEngine} -- * which depends upon the instance of this class is released. Otherwise if any method of -- * {@link ReferenceCountedOpenSslEngine} is called which uses this class's JNI resources the JVM may crash. -- */ --public final class ReferenceCountedOpenSslClientContext extends ReferenceCountedOpenSslContext { -- private static final InternalLogger logger = -- InternalLoggerFactory.getInstance(ReferenceCountedOpenSslClientContext.class); -- private final OpenSslSessionContext sessionContext; -- -- ReferenceCountedOpenSslClientContext(X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, -- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, -- KeyManagerFactory keyManagerFactory, Iterable ciphers, -- CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, -- String[] protocols, long sessionCacheSize, long sessionTimeout, -- boolean enableOcsp) throws SSLException { -- super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain, -- ClientAuth.NONE, protocols, false, enableOcsp, true); -- boolean success = false; -- try { -- sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, -- keyCertChain, key, keyPassword, keyManagerFactory); -- success = true; -- } finally { -- if (!success) { -- release(); -- } -- } -- } -- -- @Override -- OpenSslKeyMaterialManager keyMaterialManager() { -- return null; -- } -- -- @Override -- public OpenSslSessionContext sessionContext() { -- return sessionContext; -- } -- -- static OpenSslSessionContext newSessionContext(ReferenceCountedOpenSslContext thiz, long ctx, -- OpenSslEngineMap engineMap, -- X509Certificate[] trustCertCollection, -- TrustManagerFactory trustManagerFactory, -- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, -- KeyManagerFactory keyManagerFactory) throws SSLException { -- if (key == null && keyCertChain != null || key != null && keyCertChain == null) { -- throw new IllegalArgumentException( -- "Either both keyCertChain and key needs to be null or none of them"); -- } -- try { -- if (!OpenSsl.useKeyManagerFactory()) { -- if (keyManagerFactory != null) { -- throw new IllegalArgumentException( -- "KeyManagerFactory not supported"); -- } -- if (keyCertChain != null/* && key != null*/) { -- setKeyMaterial(ctx, keyCertChain, key, keyPassword); -- } -- } else { -- // javadocs state that keyManagerFactory has precedent over keyCertChain -- if (keyManagerFactory == null && keyCertChain != null) { -- keyManagerFactory = buildKeyManagerFactory( -- keyCertChain, key, keyPassword, keyManagerFactory); -- } +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; - -- if (keyManagerFactory != null) { -- X509KeyManager keyManager = chooseX509KeyManager(keyManagerFactory.getKeyManagers()); -- OpenSslKeyMaterialManager materialManager = useExtendedKeyManager(keyManager) ? -- new OpenSslExtendedKeyMaterialManager( -- (X509ExtendedKeyManager) keyManager, keyPassword) : -- new OpenSslKeyMaterialManager(keyManager, keyPassword); -- SSLContext.setCertRequestedCallback(ctx, new OpenSslCertificateRequestedCallback( -- engineMap, materialManager)); -- } -- } -- } catch (Exception e) { -- throw new SSLException("failed to set certificate and key", e); -- } +-import io.netty.internal.tcnative.SSLSession; +-import io.netty.internal.tcnative.SSLSessionCache; +-import io.netty.util.ResourceLeakDetector; +-import io.netty.util.ResourceLeakDetectorFactory; +-import io.netty.util.ResourceLeakTracker; +-import io.netty.util.internal.EmptyArrays; +-import io.netty.util.internal.SystemPropertyUtil; - -- SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH); +-import javax.security.cert.X509Certificate; +-import java.security.Principal; +-import java.security.cert.Certificate; +-import java.util.ArrayList; +-import java.util.Iterator; +-import java.util.LinkedHashMap; +-import java.util.List; +-import java.util.Map; +-import java.util.concurrent.atomic.AtomicInteger; - -- try { -- if (trustCertCollection != null) { -- trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory); -- } else if (trustManagerFactory == null) { -- trustManagerFactory = TrustManagerFactory.getInstance( -- TrustManagerFactory.getDefaultAlgorithm()); -- trustManagerFactory.init((KeyStore) null); -- } -- final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers()); -- -- // IMPORTANT: The callbacks set for verification must be static to prevent memory leak as -- // otherwise the context can never be collected. This is because the JNI code holds -- // a global reference to the callbacks. -- // -- // See https://github.com/netty/netty/issues/5372 +-/** +- * {@link SSLSessionCache} implementation for our native SSL implementation. +- */ +-class OpenSslSessionCache implements SSLSessionCache { +- private static final OpenSslSession[] EMPTY_SESSIONS = new OpenSslSession[0]; - -- // Use this to prevent an error when running on java < 7 -- if (useExtendedTrustManager(manager)) { -- SSLContext.setCertVerifyCallback(ctx, -- new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager)); -- } else { -- SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager)); -- } -- } catch (Exception e) { -- throw new SSLException("unable to setup trustmanager", e); +- private static final int DEFAULT_CACHE_SIZE; +- static { +- // Respect the same system property as the JDK implementation to make it easy to switch between implementations. +- int cacheSize = SystemPropertyUtil.getInt("javax.net.ssl.sessionCacheSize", 20480); +- if (cacheSize >= 0) { +- DEFAULT_CACHE_SIZE = cacheSize; +- } else { +- DEFAULT_CACHE_SIZE = 20480; - } -- return new OpenSslClientSessionContext(thiz); - } +- private final OpenSslEngineMap engineMap; - -- // No cache is currently supported for client side mode. -- static final class OpenSslClientSessionContext extends OpenSslSessionContext { -- OpenSslClientSessionContext(ReferenceCountedOpenSslContext context) { -- super(context); -- } +- private final Map sessions = +- new LinkedHashMap() { - -- @Override -- public void setSessionTimeout(int seconds) { -- if (seconds < 0) { -- throw new IllegalArgumentException(); -- } -- } +- private static final long serialVersionUID = -7773696788135734448L; - -- @Override -- public int getSessionTimeout() { -- return 0; -- } +- @Override +- protected boolean removeEldestEntry(Map.Entry eldest) { +- int maxSize = maximumCacheSize.get(); +- if (maxSize >= 0 && size() > maxSize) { +- removeSessionWithId(eldest.getKey()); +- } +- // We always need to return false as we modify the map directly. +- return false; +- } +- }; - -- @Override -- public void setSessionCacheSize(int size) { -- if (size < 0) { -- throw new IllegalArgumentException(); -- } -- } +- private final AtomicInteger maximumCacheSize = new AtomicInteger(DEFAULT_CACHE_SIZE); - -- @Override -- public int getSessionCacheSize() { -- return 0; -- } +- // Let's use the same default value as OpenSSL does. +- // See https://www.openssl.org/docs/man1.1.1/man3/SSL_get_default_timeout.html +- private final AtomicInteger sessionTimeout = new AtomicInteger(300); +- private int sessionCounter; - -- @Override -- public void setSessionCacheEnabled(boolean enabled) { -- // ignored -- } +- OpenSslSessionCache(OpenSslEngineMap engineMap) { +- this.engineMap = engineMap; +- } - -- @Override -- public boolean isSessionCacheEnabled() { -- return false; +- final void setSessionTimeout(int seconds) { +- int oldTimeout = sessionTimeout.getAndSet(seconds); +- if (oldTimeout > seconds) { +- // Drain the whole cache as this way we can use the ordering of the LinkedHashMap to detect early +- // if there are any other sessions left that are invalid. +- clear(); - } - } - -- private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier { -- private final X509TrustManager manager; +- final int getSessionTimeout() { +- return sessionTimeout.get(); +- } - -- TrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509TrustManager manager) { -- super(engineMap); -- this.manager = manager; -- } +- /** +- * Called once a new {@link OpenSslSession} was created. +- * +- * @param session the new session. +- * @return {@code true} if the session should be cached, {@code false} otherwise. +- */ +- protected boolean sessionCreated(NativeSslSession session) { +- return true; +- } - -- @Override -- void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth) -- throws Exception { -- manager.checkServerTrusted(peerCerts, auth); +- /** +- * Called once an {@link OpenSslSession} was removed from the cache. +- * +- * @param session the session to remove. +- */ +- protected void sessionRemoved(NativeSslSession session) { } +- +- final void setSessionCacheSize(int size) { +- long oldSize = maximumCacheSize.getAndSet(size); +- if (oldSize > size || size == 0) { +- // Just keep it simple for now and drain the whole cache. +- clear(); - } - } - -- private static final class ExtendedTrustManagerVerifyCallback extends AbstractCertificateVerifier { -- private final X509ExtendedTrustManager manager; +- final int getSessionCacheSize() { +- return maximumCacheSize.get(); +- } - -- ExtendedTrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509ExtendedTrustManager manager) { -- super(engineMap); -- this.manager = manager; +- private void expungeInvalidSessions() { +- if (sessions.isEmpty()) { +- return; - } +- long now = System.currentTimeMillis(); +- Iterator> iterator = sessions.entrySet().iterator(); +- while (iterator.hasNext()) { +- NativeSslSession session = iterator.next().getValue(); +- // As we use a LinkedHashMap we can break the while loop as soon as we find a valid session. +- // This is true as we always drain the cache as soon as we change the timeout to a smaller value as +- // it was set before. This way its true that the insertion order matches the timeout order. +- if (session.isValid(now)) { +- break; +- } +- iterator.remove(); - -- @Override -- void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth) -- throws Exception { -- manager.checkServerTrusted(peerCerts, auth, engine); +- notifyRemovalAndFree(session); - } - } - -- private static final class OpenSslCertificateRequestedCallback implements CertificateRequestedCallback { -- private final OpenSslEngineMap engineMap; -- private final OpenSslKeyMaterialManager keyManagerHolder; -- -- OpenSslCertificateRequestedCallback(OpenSslEngineMap engineMap, OpenSslKeyMaterialManager keyManagerHolder) { -- this.engineMap = engineMap; -- this.keyManagerHolder = keyManagerHolder; +- @Override +- public boolean sessionCreated(long ssl, long sslSession) { +- ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); +- if (engine == null) { +- // We couldn't find the engine itself. +- return false; - } +- OpenSslSession openSslSession = (OpenSslSession) engine.getSession(); +- // Create the native session that we will put into our cache. We will share the key-value storage +- // with the already existing session instance. +- NativeSslSession session = new NativeSslSession(sslSession, engine.getPeerHost(), engine.getPeerPort(), +- getSessionTimeout() * 1000L, openSslSession.keyValueStorage()); - -- @Override -- public KeyMaterial requested(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals) { -- final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); -- try { -- final Set keyTypesSet = supportedClientKeyTypes(keyTypeBytes); -- final String[] keyTypes = keyTypesSet.toArray(new String[keyTypesSet.size()]); -- final X500Principal[] issuers; -- if (asn1DerEncodedPrincipals == null) { -- issuers = null; -- } else { -- issuers = new X500Principal[asn1DerEncodedPrincipals.length]; -- for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) { -- issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]); -- } -- } -- return keyManagerHolder.keyMaterial(engine, keyTypes, issuers); -- } catch (Throwable cause) { -- logger.debug("request of key failed", cause); -- SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem"); -- e.initCause(cause); -- engine.handshakeException = e; -- return null; +- openSslSession.setSessionDetails( +- session.creationTime, session.lastAccessedTime, session.sessionId(), session.keyValueStorage); +- synchronized (this) { +- // Mimic what OpenSSL is doing and expunge every 255 new sessions +- // See https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_flush_sessions.html +- if (++sessionCounter == 255) { +- sessionCounter = 0; +- expungeInvalidSessions(); - } -- } - -- /** -- * Gets the supported key types for client certificates. -- * -- * @param clientCertificateTypes {@code ClientCertificateType} values provided by the server. -- * See https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml. -- * @return supported key types that can be used in {@code X509KeyManager.chooseClientAlias} and -- * {@code X509ExtendedKeyManager.chooseEngineClientAlias}. -- */ -- private static Set supportedClientKeyTypes(byte[] clientCertificateTypes) { -- Set result = new HashSet(clientCertificateTypes.length); -- for (byte keyTypeCode : clientCertificateTypes) { -- String keyType = clientKeyType(keyTypeCode); -- if (keyType == null) { -- // Unsupported client key type -- ignore -- continue; -- } -- result.add(keyType); +- if (!sessionCreated(session)) { +- // Should not be cached, return false. In this case we also need to call close() to ensure we +- // close the ResourceLeakTracker. +- session.close(); +- return false; +- } +- final NativeSslSession old = sessions.put(session.sessionId(), session); +- if (old != null) { +- notifyRemovalAndFree(old); - } -- return result; - } +- return true; +- } - -- private static String clientKeyType(byte clientCertificateType) { -- // See also http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml -- switch (clientCertificateType) { -- case CertificateRequestedCallback.TLS_CT_RSA_SIGN: -- return OpenSslKeyMaterialManager.KEY_TYPE_RSA; // RFC rsa_sign -- case CertificateRequestedCallback.TLS_CT_RSA_FIXED_DH: -- return OpenSslKeyMaterialManager.KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh -- case CertificateRequestedCallback.TLS_CT_ECDSA_SIGN: -- return OpenSslKeyMaterialManager.KEY_TYPE_EC; // RFC ecdsa_sign -- case CertificateRequestedCallback.TLS_CT_RSA_FIXED_ECDH: -- return OpenSslKeyMaterialManager.KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh -- case CertificateRequestedCallback.TLS_CT_ECDSA_FIXED_ECDH: -- return OpenSslKeyMaterialManager.KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh -- default: -- return null; +- @Override +- public final long getSession(long ssl, byte[] sessionId) { +- OpenSslSessionId id = new OpenSslSessionId(sessionId); +- final NativeSslSession session; +- synchronized (this) { +- session = sessions.get(id); +- if (session == null) { +- return -1; +- } +- +- // If the session is not valid anymore we should remove it from the cache and just signal back +- // that we couldn't find a session that is re-usable. +- if (!session.isValid() || +- // This needs to happen in the synchronized block so we ensure we never destroy it before we +- // incremented the reference count. If we cant increment the reference count there is something +- // wrong. In this case just remove the session from the cache and signal back that we couldn't +- // find a session for re-use. +- !session.upRef()) { +- // Remove the session from the cache. This will also take care of calling SSL_SESSION_free(...) +- removeSessionWithId(session.sessionId()); +- return -1; +- } +- +- // At this point we already incremented the reference count via SSL_SESSION_up_ref(...). +- if (session.shouldBeSingleUse()) { +- // Should only be used once. In this case invalidate the session which will also ensure we remove it +- // from the cache and call SSL_SESSION_free(...). +- removeSessionWithId(session.sessionId()); - } - } +- session.setLastAccessedTime(System.currentTimeMillis()); +- ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); +- if (engine != null) { +- OpenSslSession sslSession = (OpenSslSession) engine.getSession(); +- sslSession.setSessionDetails(session.getCreationTime(), +- session.getLastAccessedTime(), session.sessionId(), session.keyValueStorage); +- } +- +- return session.session(); - } --} -diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java -deleted file mode 100644 -index ee049ab..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java -+++ /dev/null -@@ -1,867 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; - --import io.netty.buffer.ByteBuf; --import io.netty.buffer.ByteBufAllocator; --import io.netty.internal.tcnative.CertificateVerifier; --import io.netty.internal.tcnative.SSL; --import io.netty.internal.tcnative.SSLContext; --import io.netty.util.AbstractReferenceCounted; --import io.netty.util.ReferenceCounted; --import io.netty.util.ResourceLeakDetector; --import io.netty.util.ResourceLeakDetectorFactory; --import io.netty.util.ResourceLeakTracker; --import io.netty.util.internal.PlatformDependent; --import io.netty.util.internal.StringUtil; --import io.netty.util.internal.SystemPropertyUtil; --import io.netty.util.internal.logging.InternalLogger; --import io.netty.util.internal.logging.InternalLoggerFactory; +- boolean setSession(long ssl, OpenSslSession session, String host, int port) { +- // Do nothing by default as this needs special handling for the client side. +- return false; +- } - --import java.security.AccessController; --import java.security.PrivateKey; --import java.security.PrivilegedAction; --import java.security.cert.CertPathValidatorException; --import java.security.cert.Certificate; --import java.security.cert.CertificateExpiredException; --import java.security.cert.CertificateNotYetValidException; --import java.security.cert.CertificateRevokedException; --import java.security.cert.X509Certificate; --import java.util.ArrayList; --import java.util.Arrays; --import java.util.Collections; --import java.util.List; --import java.util.Map; +- /** +- * Remove the session with the given id from the cache +- */ +- final synchronized void removeSessionWithId(OpenSslSessionId id) { +- NativeSslSession sslSession = sessions.remove(id); +- if (sslSession != null) { +- notifyRemovalAndFree(sslSession); +- } +- } - --import java.util.concurrent.locks.Lock; --import java.util.concurrent.locks.ReadWriteLock; --import java.util.concurrent.locks.ReentrantReadWriteLock; --import javax.net.ssl.KeyManager; --import javax.net.ssl.SSLEngine; --import javax.net.ssl.SSLException; --import javax.net.ssl.SSLHandshakeException; --import javax.net.ssl.TrustManager; --import javax.net.ssl.X509ExtendedKeyManager; --import javax.net.ssl.X509ExtendedTrustManager; --import javax.net.ssl.X509KeyManager; --import javax.net.ssl.X509TrustManager; +- /** +- * Returns {@code true} if there is a session for the given id in the cache. +- */ +- final synchronized boolean containsSessionWithId(OpenSslSessionId id) { +- return sessions.containsKey(id); +- } - --import static io.netty.util.internal.ObjectUtil.checkNotNull; --import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; +- private void notifyRemovalAndFree(NativeSslSession session) { +- sessionRemoved(session); +- session.free(); +- } - --/** -- * An implementation of {@link SslContext} which works with libraries that support the -- * OpenSsl C library API. -- *

Instances of this class must be {@link #release() released} or else native memory will leak! -- * -- *

Instances of this class must not be released before any {@link ReferenceCountedOpenSslEngine} -- * which depends upon the instance of this class is released. Otherwise if any method of -- * {@link ReferenceCountedOpenSslEngine} is called which uses this class's JNI resources the JVM may crash. -- */ --public abstract class ReferenceCountedOpenSslContext extends SslContext implements ReferenceCounted { -- private static final InternalLogger logger = -- InternalLoggerFactory.getInstance(ReferenceCountedOpenSslContext.class); - /** -- * To make it easier for users to replace JDK implementation with OpenSsl version we also use -- * {@code jdk.tls.rejectClientInitiatedRenegotiation} to allow disabling client initiated renegotiation. -- * Java8+ uses this system property as well. -- *

-- * See also -- * Significant SSL/TLS improvements in Java 8 +- * Return the {@link OpenSslSession} which is cached for the given id. - */ -- private static final boolean JDK_REJECT_CLIENT_INITIATED_RENEGOTIATION = -- AccessController.doPrivileged(new PrivilegedAction() { -- @Override -- public Boolean run() { -- return SystemPropertyUtil.getBoolean("jdk.tls.rejectClientInitiatedRenegotiation", false); -- } -- }); +- final synchronized OpenSslSession getSession(OpenSslSessionId id) { +- NativeSslSession session = sessions.get(id); +- if (session != null && !session.isValid()) { +- // The session is not valid anymore, let's remove it and just signal back that there is no session +- // with the given ID in the cache anymore. This also takes care of calling SSL_SESSION_free(...) +- removeSessionWithId(session.sessionId()); +- return null; +- } +- return session; +- } - -- private static final int DEFAULT_BIO_NON_APPLICATION_BUFFER_SIZE = -- AccessController.doPrivileged(new PrivilegedAction() { -- @Override -- public Integer run() { -- return Math.max(1, -- SystemPropertyUtil.getInt("io.netty.handler.ssl.openssl.bioNonApplicationBufferSize", -- 2048)); -- } -- }); +- /** +- * Returns a snapshot of the session ids of the current valid sessions. +- */ +- final List getIds() { +- final OpenSslSession[] sessionsArray; +- synchronized (this) { +- sessionsArray = sessions.values().toArray(EMPTY_SESSIONS); +- } +- List ids = new ArrayList(sessionsArray.length); +- for (OpenSslSession session: sessionsArray) { +- if (session.isValid()) { +- ids.add(session.sessionId()); +- } +- } +- return ids; +- } - -- private static final List DEFAULT_CIPHERS; -- private static final Integer DH_KEY_LENGTH; -- private static final ResourceLeakDetector leakDetector = -- ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslContext.class); +- /** +- * Clear the cache and free all cached SSL_SESSION*. +- */ +- synchronized void clear() { +- Iterator> iterator = sessions.entrySet().iterator(); +- while (iterator.hasNext()) { +- NativeSslSession session = iterator.next().getValue(); +- iterator.remove(); - -- // TODO: Maybe make configurable ? -- protected static final int VERIFY_DEPTH = 10; +- // Notify about removal. This also takes care of calling SSL_SESSION_free(...). +- notifyRemovalAndFree(session); +- } +- } - - /** -- * The OpenSSL SSL_CTX object. -- * -- * {@link #ctxLock} must be hold while using ctx! +- * {@link OpenSslSession} implementation which wraps the native SSL_SESSION* while in cache. - */ -- protected long ctx; -- private final List unmodifiableCiphers; -- private final long sessionCacheSize; -- private final long sessionTimeout; -- private final OpenSslApplicationProtocolNegotiator apn; -- private final int mode; +- static final class NativeSslSession implements OpenSslSession { +- static final ResourceLeakDetector LEAK_DETECTOR = ResourceLeakDetectorFactory.instance() +- .newResourceLeakDetector(NativeSslSession.class); +- private final ResourceLeakTracker leakTracker; +- +- final Map keyValueStorage; +- +- private final long session; +- private final String peerHost; +- private final int peerPort; +- private final OpenSslSessionId id; +- private final long timeout; +- private final long creationTime = System.currentTimeMillis(); +- private volatile long lastAccessedTime = creationTime; +- private volatile boolean valid = true; +- private boolean freed; +- +- NativeSslSession(long session, String peerHost, int peerPort, long timeout, +- Map keyValueStorage) { +- this.session = session; +- this.peerHost = peerHost; +- this.peerPort = peerPort; +- this.timeout = timeout; +- this.id = new OpenSslSessionId(io.netty.internal.tcnative.SSLSession.getSessionId(session)); +- this.keyValueStorage = keyValueStorage; +- leakTracker = LEAK_DETECTOR.track(this); +- } - -- // Reference Counting -- private final ResourceLeakTracker leak; -- private final AbstractReferenceCounted refCnt = new AbstractReferenceCounted() { - @Override -- public ReferenceCounted touch(Object hint) { -- if (leak != null) { -- leak.record(hint); -- } +- public Map keyValueStorage() { +- return keyValueStorage; +- } - -- return ReferenceCountedOpenSslContext.this; +- @Override +- public void prepareHandshake() { +- throw new UnsupportedOperationException(); - } - - @Override -- protected void deallocate() { -- destroy(); -- if (leak != null) { -- boolean closed = leak.close(ReferenceCountedOpenSslContext.this); -- assert closed; +- public void setSessionDetails(long creationTime, long lastAccessedTime, +- OpenSslSessionId id, Map keyValueStorage) { +- throw new UnsupportedOperationException(); +- } +- +- boolean shouldBeSingleUse() { +- assert !freed; +- return SSLSession.shouldBeSingleUse(session); +- } +- +- long session() { +- assert !freed; +- return session; +- } +- +- boolean upRef() { +- assert !freed; +- return SSLSession.upRef(session); +- } +- +- synchronized void free() { +- close(); +- SSLSession.free(session); +- } +- +- void close() { +- assert !freed; +- freed = true; +- invalidate(); +- if (leakTracker != null) { +- leakTracker.close(this); - } - } -- }; - -- final Certificate[] keyCertChain; -- final ClientAuth clientAuth; -- final String[] protocols; -- final boolean enableOcsp; -- final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap(); -- final ReadWriteLock ctxLock = new ReentrantReadWriteLock(); +- @Override +- public OpenSslSessionId sessionId() { +- return id; +- } - -- private volatile boolean rejectRemoteInitiatedRenegotiation; -- private volatile int bioNonApplicationBufferSize = DEFAULT_BIO_NON_APPLICATION_BUFFER_SIZE; +- boolean isValid(long now) { +- return creationTime + timeout >= now && valid; +- } - -- static final OpenSslApplicationProtocolNegotiator NONE_PROTOCOL_NEGOTIATOR = -- new OpenSslApplicationProtocolNegotiator() { -- @Override -- public ApplicationProtocolConfig.Protocol protocol() { -- return ApplicationProtocolConfig.Protocol.NONE; -- } +- @Override +- public void setLocalCertificate(Certificate[] localCertificate) { +- throw new UnsupportedOperationException(); +- } - -- @Override -- public List protocols() { -- return Collections.emptyList(); -- } +- @Override +- public OpenSslSessionContext getSessionContext() { +- return null; +- } - -- @Override -- public ApplicationProtocolConfig.SelectorFailureBehavior selectorFailureBehavior() { -- return ApplicationProtocolConfig.SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL; -- } +- @Override +- public void tryExpandApplicationBufferSize(int packetLengthDataOnly) { +- throw new UnsupportedOperationException(); +- } - -- @Override -- public ApplicationProtocolConfig.SelectedListenerFailureBehavior selectedListenerFailureBehavior() { -- return ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT; -- } -- }; +- @Override +- public void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate, +- byte[][] peerCertificateChain, long creationTime, long timeout) { +- throw new UnsupportedOperationException(); +- } - -- static { -- List ciphers = new ArrayList(); -- // XXX: Make sure to sync this list with JdkSslEngineFactory. -- Collections.addAll( -- ciphers, -- "ECDHE-ECDSA-AES256-GCM-SHA384", -- "ECDHE-ECDSA-AES128-GCM-SHA256", -- "ECDHE-RSA-AES128-GCM-SHA256", -- "ECDHE-RSA-AES128-SHA", -- "ECDHE-RSA-AES256-SHA", -- "AES128-GCM-SHA256", -- "AES128-SHA", -- "AES256-SHA"); -- DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers); +- @Override +- public byte[] getId() { +- return id.cloneBytes(); +- } - -- if (logger.isDebugEnabled()) { -- logger.debug("Default cipher suite (OpenSSL): " + ciphers); +- @Override +- public long getCreationTime() { +- return creationTime; - } - -- Integer dhLen = null; +- @Override +- public void setLastAccessedTime(long time) { +- lastAccessedTime = time; +- } - -- try { -- String dhKeySize = AccessController.doPrivileged(new PrivilegedAction() { -- @Override -- public String run() { -- return SystemPropertyUtil.get("jdk.tls.ephemeralDHKeySize"); -- } -- }); -- if (dhKeySize != null) { -- try { -- dhLen = Integer.valueOf(dhKeySize); -- } catch (NumberFormatException e) { -- logger.debug("ReferenceCountedOpenSslContext supports -Djdk.tls.ephemeralDHKeySize={int}, but got: " -- + dhKeySize); -- } -- } -- } catch (Throwable ignore) { -- // ignore +- @Override +- public long getLastAccessedTime() { +- return lastAccessedTime; - } -- DH_KEY_LENGTH = dhLen; -- } - -- ReferenceCountedOpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, -- ApplicationProtocolConfig apnCfg, long sessionCacheSize, long sessionTimeout, -- int mode, Certificate[] keyCertChain, ClientAuth clientAuth, String[] protocols, -- boolean startTls, boolean enableOcsp, boolean leakDetection) throws SSLException { -- this(ciphers, cipherFilter, toNegotiator(apnCfg), sessionCacheSize, sessionTimeout, mode, keyCertChain, -- clientAuth, protocols, startTls, enableOcsp, leakDetection); -- } +- @Override +- public void invalidate() { +- valid = false; +- } - -- ReferenceCountedOpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, -- OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize, -- long sessionTimeout, int mode, Certificate[] keyCertChain, -- ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp, -- boolean leakDetection) throws SSLException { -- super(startTls); +- @Override +- public boolean isValid() { +- return isValid(System.currentTimeMillis()); +- } - -- OpenSsl.ensureAvailability(); +- @Override +- public void putValue(String name, Object value) { +- throw new UnsupportedOperationException(); +- } - -- if (enableOcsp && !OpenSsl.isOcspSupported()) { -- throw new IllegalStateException("OCSP is not supported."); +- @Override +- public Object getValue(String name) { +- return null; - } - -- if (mode != SSL.SSL_MODE_SERVER && mode != SSL.SSL_MODE_CLIENT) { -- throw new IllegalArgumentException("mode most be either SSL.SSL_MODE_SERVER or SSL.SSL_MODE_CLIENT"); +- @Override +- public void removeValue(String name) { +- // NOOP - } -- leak = leakDetection ? leakDetector.track(this) : null; -- this.mode = mode; -- this.clientAuth = isServer() ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE; -- this.protocols = protocols; -- this.enableOcsp = enableOcsp; - -- if (mode == SSL.SSL_MODE_SERVER) { -- rejectRemoteInitiatedRenegotiation = -- JDK_REJECT_CLIENT_INITIATED_RENEGOTIATION; +- @Override +- public String[] getValueNames() { +- return EmptyArrays.EMPTY_STRINGS; - } -- this.keyCertChain = keyCertChain == null ? null : keyCertChain.clone(); -- final List convertedCiphers; -- if (ciphers == null) { -- convertedCiphers = null; -- } else { -- convertedCiphers = new ArrayList(); -- for (String c : ciphers) { -- if (c == null) { -- break; -- } - -- String converted = CipherSuiteConverter.toOpenSsl(c); -- if (converted != null) { -- c = converted; -- } -- convertedCiphers.add(c); -- } +- @Override +- public Certificate[] getPeerCertificates() { +- throw new UnsupportedOperationException(); - } - -- unmodifiableCiphers = Arrays.asList(checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites( -- convertedCiphers, DEFAULT_CIPHERS, OpenSsl.availableOpenSslCipherSuites())); +- @Override +- public Certificate[] getLocalCertificates() { +- throw new UnsupportedOperationException(); +- } - -- this.apn = checkNotNull(apn, "apn"); +- @Override +- public X509Certificate[] getPeerCertificateChain() { +- throw new UnsupportedOperationException(); +- } - -- // Create a new SSL_CTX and configure it. -- boolean success = false; -- try { -- try { -- ctx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, mode); -- } catch (Exception e) { -- throw new SSLException("failed to create an SSL_CTX", e); -- } +- @Override +- public Principal getPeerPrincipal() { +- throw new UnsupportedOperationException(); +- } - -- SSLContext.setOptions(ctx, SSLContext.getOptions(ctx) | -- SSL.SSL_OP_NO_SSLv2 | -- SSL.SSL_OP_NO_SSLv3 | -- SSL.SSL_OP_CIPHER_SERVER_PREFERENCE | +- @Override +- public Principal getLocalPrincipal() { +- throw new UnsupportedOperationException(); +- } - -- // We do not support compression at the moment so we should explicitly disable it. -- SSL.SSL_OP_NO_COMPRESSION | +- @Override +- public String getCipherSuite() { +- return null; +- } - -- // Disable ticket support by default to be more inline with SSLEngineImpl of the JDK. -- // This also let SSLSession.getId() work the same way for the JDK implementation and the -- // OpenSSLEngine. If tickets are supported SSLSession.getId() will only return an ID on the -- // server-side if it could make use of tickets. -- SSL.SSL_OP_NO_TICKET); +- @Override +- public String getProtocol() { +- return null; +- } - -- // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address may change between -- // calling OpenSSLEngine.wrap(...). -- // See https://github.com/netty/netty-tcnative/issues/100 -- SSLContext.setMode(ctx, SSLContext.getMode(ctx) | SSL.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); +- @Override +- public String getPeerHost() { +- return peerHost; +- } - -- if (DH_KEY_LENGTH != null) { -- SSLContext.setTmpDHLength(ctx, DH_KEY_LENGTH); -- } +- @Override +- public int getPeerPort() { +- return peerPort; +- } - -- /* List the ciphers that are permitted to negotiate. */ -- try { -- SSLContext.setCipherSuite(ctx, CipherSuiteConverter.toOpenSsl(unmodifiableCiphers)); -- } catch (SSLException e) { -- throw e; -- } catch (Exception e) { -- throw new SSLException("failed to set cipher suite: " + unmodifiableCiphers, e); -- } +- @Override +- public int getPacketBufferSize() { +- return ReferenceCountedOpenSslEngine.MAX_RECORD_SIZE; +- } - -- List nextProtoList = apn.protocols(); -- /* Set next protocols for next protocol negotiation extension, if specified */ -- if (!nextProtoList.isEmpty()) { -- String[] appProtocols = nextProtoList.toArray(new String[nextProtoList.size()]); -- int selectorBehavior = opensslSelectorFailureBehavior(apn.selectorFailureBehavior()); +- @Override +- public int getApplicationBufferSize() { +- return ReferenceCountedOpenSslEngine.MAX_PLAINTEXT_LENGTH; +- } - -- switch (apn.protocol()) { -- case NPN: -- SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior); -- break; -- case ALPN: -- SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior); -- break; -- case NPN_AND_ALPN: -- SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior); -- SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior); -- break; -- default: -- throw new Error(); -- } +- @Override +- public int hashCode() { +- return id.hashCode(); +- } +- +- @Override +- public boolean equals(Object o) { +- if (this == o) { +- return true; +- } +- if (!(o instanceof OpenSslSession)) { +- return false; - } +- OpenSslSession session1 = (OpenSslSession) o; +- return id.equals(session1.sessionId()); +- } +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java +deleted file mode 100644 +index baa83d3..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionContext.java ++++ /dev/null +@@ -1,229 +0,0 @@ +-/* +- * Copyright 2014 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; +- +-import io.netty.internal.tcnative.SSL; +-import io.netty.internal.tcnative.SSLContext; +-import io.netty.internal.tcnative.SessionTicketKey; +-import io.netty.util.internal.ObjectUtil; - -- /* Set session cache size, if specified */ -- if (sessionCacheSize > 0) { -- this.sessionCacheSize = sessionCacheSize; -- SSLContext.setSessionCacheSize(ctx, sessionCacheSize); -- } else { -- // Get the default session cache size using SSLContext.setSessionCacheSize() -- this.sessionCacheSize = sessionCacheSize = SSLContext.setSessionCacheSize(ctx, 20480); -- // Revert the session cache size to the default value. -- SSLContext.setSessionCacheSize(ctx, sessionCacheSize); -- } +-import javax.net.ssl.SSLSession; +-import javax.net.ssl.SSLSessionContext; +-import java.util.Arrays; +-import java.util.Enumeration; +-import java.util.Iterator; +-import java.util.concurrent.locks.Lock; - -- /* Set session timeout, if specified */ -- if (sessionTimeout > 0) { -- this.sessionTimeout = sessionTimeout; -- SSLContext.setSessionCacheTimeout(ctx, sessionTimeout); -- } else { -- // Get the default session timeout using SSLContext.setSessionCacheTimeout() -- this.sessionTimeout = sessionTimeout = SSLContext.setSessionCacheTimeout(ctx, 300); -- // Revert the session timeout to the default value. -- SSLContext.setSessionCacheTimeout(ctx, sessionTimeout); -- } +-/** +- * OpenSSL specific {@link SSLSessionContext} implementation. +- */ +-public abstract class OpenSslSessionContext implements SSLSessionContext { - -- if (enableOcsp) { -- SSLContext.enableOcsp(ctx, isClient()); -- } -- success = true; -- } finally { -- if (!success) { -- release(); -- } -- } -- } +- private final OpenSslSessionStats stats; - -- private static int opensslSelectorFailureBehavior(ApplicationProtocolConfig.SelectorFailureBehavior behavior) { -- switch (behavior) { -- case NO_ADVERTISE: -- return SSL.SSL_SELECTOR_FAILURE_NO_ADVERTISE; -- case CHOOSE_MY_LAST_PROTOCOL: -- return SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL; -- default: -- throw new Error(); -- } -- } +- // The OpenSslKeyMaterialProvider is not really used by the OpenSslSessionContext but only be stored here +- // to make it easier to destroy it later because the ReferenceCountedOpenSslContext will hold a reference +- // to OpenSslSessionContext. +- private final OpenSslKeyMaterialProvider provider; - -- @Override -- public final List cipherSuites() { -- return unmodifiableCiphers; -- } +- final ReferenceCountedOpenSslContext context; - -- @Override -- public final long sessionCacheSize() { -- return sessionCacheSize; -- } +- private final OpenSslSessionCache sessionCache; +- private final long mask; - -- @Override -- public final long sessionTimeout() { -- return sessionTimeout; +- // IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent +- // the GC to collect OpenSslContext as this would also free the pointer and so could result in a +- // segfault when the user calls any of the methods here that try to pass the pointer down to the native +- // level. +- OpenSslSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider, long mask, +- OpenSslSessionCache cache) { +- this.context = context; +- this.provider = provider; +- this.mask = mask; +- stats = new OpenSslSessionStats(context); +- sessionCache = cache; +- SSLContext.setSSLSessionCache(context.ctx, cache); - } - -- @Override -- public ApplicationProtocolNegotiator applicationProtocolNegotiator() { -- return apn; +- final boolean useKeyManager() { +- return provider != null; - } - - @Override -- public final boolean isClient() { -- return mode == SSL.SSL_MODE_CLIENT; +- public void setSessionCacheSize(int size) { +- ObjectUtil.checkPositiveOrZero(size, "size"); +- sessionCache.setSessionCacheSize(size); - } - - @Override -- public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { -- return newEngine0(alloc, peerHost, peerPort); -- } -- -- SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort) { -- return new ReferenceCountedOpenSslEngine(this, alloc, peerHost, peerPort, true); +- public int getSessionCacheSize() { +- return sessionCache.getSessionCacheSize(); - } - -- abstract OpenSslKeyMaterialManager keyMaterialManager(); -- -- /** -- * Returns a new server-side {@link SSLEngine} with the current configuration. -- */ - @Override -- public final SSLEngine newEngine(ByteBufAllocator alloc) { -- return newEngine(alloc, null, -1); -- } +- public void setSessionTimeout(int seconds) { +- ObjectUtil.checkPositiveOrZero(seconds, "seconds"); - -- /** -- * Returns the pointer to the {@code SSL_CTX} object for this {@link ReferenceCountedOpenSslContext}. -- * Be aware that it is freed as soon as the {@link #finalize()} method is called. -- * At this point {@code 0} will be returned. -- * -- * @deprecated this method is considered unsafe as the returned pointer may be released later. Dont use it! -- */ -- @Deprecated -- public final long context() { -- Lock readerLock = ctxLock.readLock(); -- readerLock.lock(); +- Lock writerLock = context.ctxLock.writeLock(); +- writerLock.lock(); - try { -- return ctx; +- SSLContext.setSessionCacheTimeout(context.ctx, seconds); +- sessionCache.setSessionTimeout(seconds); - } finally { -- readerLock.unlock(); +- writerLock.unlock(); - } - } - -- /** -- * Returns the stats of this context. -- * -- * @deprecated use {@link #sessionContext#stats()} -- */ -- @Deprecated -- public final OpenSslSessionStats stats() { -- return sessionContext().stats(); -- } -- -- /** -- * Specify if remote initiated renegotiation is supported or not. If not supported and the remote side tries -- * to initiate a renegotiation a {@link SSLHandshakeException} will be thrown during decoding. -- */ -- public void setRejectRemoteInitiatedRenegotiation(boolean rejectRemoteInitiatedRenegotiation) { -- this.rejectRemoteInitiatedRenegotiation = rejectRemoteInitiatedRenegotiation; +- @Override +- public int getSessionTimeout() { +- return sessionCache.getSessionTimeout(); - } - -- /** -- * Returns if remote initiated renegotiation is supported or not. -- */ -- public boolean getRejectRemoteInitiatedRenegotiation() { -- return rejectRemoteInitiatedRenegotiation; +- @Override +- public SSLSession getSession(byte[] bytes) { +- return sessionCache.getSession(new OpenSslSessionId(bytes)); - } - -- /** -- * Set the size of the buffer used by the BIO for non-application based writes -- * (e.g. handshake, renegotiation, etc...). -- */ -- public void setBioNonApplicationBufferSize(int bioNonApplicationBufferSize) { -- this.bioNonApplicationBufferSize = -- checkPositiveOrZero(bioNonApplicationBufferSize, "bioNonApplicationBufferSize"); -- } +- @Override +- public Enumeration getIds() { +- return new Enumeration() { +- private final Iterator ids = sessionCache.getIds().iterator(); +- @Override +- public boolean hasMoreElements() { +- return ids.hasNext(); +- } - -- /** -- * Returns the size of the buffer used by the BIO for non-application based writes -- */ -- public int getBioNonApplicationBufferSize() { -- return bioNonApplicationBufferSize; +- @Override +- public byte[] nextElement() { +- return ids.next().cloneBytes(); +- } +- }; - } - - /** - * Sets the SSL session ticket keys of this context. -- * -- * @deprecated use {@link OpenSslSessionContext#setTicketKeys(byte[])} -- */ -- @Deprecated -- public final void setTicketKeys(byte[] keys) { -- sessionContext().setTicketKeys(keys); -- } -- -- @Override -- public abstract OpenSslSessionContext sessionContext(); -- -- /** -- * Returns the pointer to the {@code SSL_CTX} object for this {@link ReferenceCountedOpenSslContext}. -- * Be aware that it is freed as soon as the {@link #release()} method is called. -- * At this point {@code 0} will be returned. -- * -- * @deprecated this method is considered unsafe as the returned pointer may be released later. Dont use it! +- * @deprecated use {@link #setTicketKeys(OpenSslSessionTicketKey...)}. - */ - @Deprecated -- public final long sslCtxPointer() { -- Lock readerLock = ctxLock.readLock(); -- readerLock.lock(); -- try { -- return ctx; -- } finally { -- readerLock.unlock(); +- public void setTicketKeys(byte[] keys) { +- if (keys.length % SessionTicketKey.TICKET_KEY_SIZE != 0) { +- throw new IllegalArgumentException("keys.length % " + SessionTicketKey.TICKET_KEY_SIZE + " != 0"); - } -- } -- -- // IMPORTANT: This method must only be called from either the constructor or the finalizer as a user MUST never -- // get access to an OpenSslSessionContext after this method was called to prevent the user from -- // producing a segfault. -- private void destroy() { -- Lock writerLock = ctxLock.writeLock(); +- SessionTicketKey[] tickets = new SessionTicketKey[keys.length / SessionTicketKey.TICKET_KEY_SIZE]; +- for (int i = 0, a = 0; i < tickets.length; i++) { +- byte[] name = Arrays.copyOfRange(keys, a, SessionTicketKey.NAME_SIZE); +- a += SessionTicketKey.NAME_SIZE; +- byte[] hmacKey = Arrays.copyOfRange(keys, a, SessionTicketKey.HMAC_KEY_SIZE); +- i += SessionTicketKey.HMAC_KEY_SIZE; +- byte[] aesKey = Arrays.copyOfRange(keys, a, SessionTicketKey.AES_KEY_SIZE); +- a += SessionTicketKey.AES_KEY_SIZE; +- tickets[i] = new SessionTicketKey(name, hmacKey, aesKey); +- } +- Lock writerLock = context.ctxLock.writeLock(); - writerLock.lock(); - try { -- if (ctx != 0) { -- if (enableOcsp) { -- SSLContext.disableOcsp(ctx); -- } -- -- SSLContext.free(ctx); -- ctx = 0; -- } +- SSLContext.clearOptions(context.ctx, SSL.SSL_OP_NO_TICKET); +- SSLContext.setSessionTicketKeys(context.ctx, tickets); - } finally { - writerLock.unlock(); - } - } - -- protected static X509Certificate[] certificates(byte[][] chain) { -- X509Certificate[] peerCerts = new X509Certificate[chain.length]; -- for (int i = 0; i < peerCerts.length; i++) { -- peerCerts[i] = new OpenSslX509Certificate(chain[i]); -- } -- return peerCerts; -- } -- -- protected static X509TrustManager chooseTrustManager(TrustManager[] managers) { -- for (TrustManager m : managers) { -- if (m instanceof X509TrustManager) { -- return (X509TrustManager) m; -- } -- } -- throw new IllegalStateException("no X509TrustManager found"); -- } -- -- protected static X509KeyManager chooseX509KeyManager(KeyManager[] kms) { -- for (KeyManager km : kms) { -- if (km instanceof X509KeyManager) { -- return (X509KeyManager) km; -- } -- } -- throw new IllegalStateException("no X509KeyManager found"); -- } -- -- /** -- * Translate a {@link ApplicationProtocolConfig} object to a -- * {@link OpenSslApplicationProtocolNegotiator} object. -- * -- * @param config The configuration which defines the translation -- * @return The results of the translation -- */ -- static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config) { -- if (config == null) { -- return NONE_PROTOCOL_NEGOTIATOR; -- } -- -- switch (config.protocol()) { -- case NONE: -- return NONE_PROTOCOL_NEGOTIATOR; -- case ALPN: -- case NPN: -- case NPN_AND_ALPN: -- switch (config.selectedListenerFailureBehavior()) { -- case CHOOSE_MY_LAST_PROTOCOL: -- case ACCEPT: -- switch (config.selectorFailureBehavior()) { -- case CHOOSE_MY_LAST_PROTOCOL: -- case NO_ADVERTISE: -- return new OpenSslDefaultApplicationProtocolNegotiator( -- config); -- default: -- throw new UnsupportedOperationException( -- new StringBuilder("OpenSSL provider does not support ") -- .append(config.selectorFailureBehavior()) -- .append(" behavior").toString()); -- } -- default: -- throw new UnsupportedOperationException( -- new StringBuilder("OpenSSL provider does not support ") -- .append(config.selectedListenerFailureBehavior()) -- .append(" behavior").toString()); -- } -- default: -- throw new Error(); -- } -- } -- -- static boolean useExtendedTrustManager(X509TrustManager trustManager) { -- return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager; -- } -- -- static boolean useExtendedKeyManager(X509KeyManager keyManager) { -- return PlatformDependent.javaVersion() >= 7 && keyManager instanceof X509ExtendedKeyManager; +- /** +- * Sets the SSL session ticket keys of this context. Depending on the underlying native library you may omit the +- * argument or pass an empty array and so let the native library handle the key generation and rotating for you. +- * If this is supported by the underlying native library should be checked in this case. For example +- * +- * BoringSSL is known to support this. +- */ +- public void setTicketKeys(OpenSslSessionTicketKey... keys) { +- ObjectUtil.checkNotNull(keys, "keys"); +- SessionTicketKey[] ticketKeys = new SessionTicketKey[keys.length]; +- for (int i = 0; i < ticketKeys.length; i++) { +- ticketKeys[i] = keys[i].key; +- } +- Lock writerLock = context.ctxLock.writeLock(); +- writerLock.lock(); +- try { +- SSLContext.clearOptions(context.ctx, SSL.SSL_OP_NO_TICKET); +- if (ticketKeys.length > 0) { +- SSLContext.setSessionTicketKeys(context.ctx, ticketKeys); +- } +- } finally { +- writerLock.unlock(); +- } - } - -- @Override -- public final int refCnt() { -- return refCnt.refCnt(); +- /** +- * Enable or disable caching of SSL sessions. +- */ +- public void setSessionCacheEnabled(boolean enabled) { +- long mode = enabled ? mask | SSL.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP | +- SSL.SSL_SESS_CACHE_NO_INTERNAL_STORE : SSL.SSL_SESS_CACHE_OFF; +- Lock writerLock = context.ctxLock.writeLock(); +- writerLock.lock(); +- try { +- SSLContext.setSessionCacheMode(context.ctx, mode); +- if (!enabled) { +- sessionCache.clear(); +- } +- } finally { +- writerLock.unlock(); +- } - } - -- @Override -- public final ReferenceCounted retain() { -- refCnt.retain(); -- return this; +- /** +- * Return {@code true} if caching of SSL sessions is enabled, {@code false} otherwise. +- */ +- public boolean isSessionCacheEnabled() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return (SSLContext.getSessionCacheMode(context.ctx) & mask) != 0; +- } finally { +- readerLock.unlock(); +- } - } - -- @Override -- public final ReferenceCounted retain(int increment) { -- refCnt.retain(increment); -- return this; +- /** +- * Returns the stats of this context. +- */ +- public OpenSslSessionStats stats() { +- return stats; - } - -- @Override -- public final ReferenceCounted touch() { -- refCnt.touch(); -- return this; +- /** +- * Remove the given {@link OpenSslSession} from the cache, and so not re-use it for new connections. +- */ +- final void removeFromCache(OpenSslSessionId id) { +- sessionCache.removeSessionWithId(id); - } - -- @Override -- public final ReferenceCounted touch(Object hint) { -- refCnt.touch(hint); -- return this; +- final boolean isInCache(OpenSslSessionId id) { +- return sessionCache.containsSessionWithId(id); - } - -- @Override -- public final boolean release() { -- return refCnt.release(); +- boolean setSessionFromCache(long ssl, OpenSslSession session, String host, int port) { +- return sessionCache.setSession(ssl, session, host, port); - } - -- @Override -- public final boolean release(int decrement) { -- return refCnt.release(decrement); +- final void destroy() { +- if (provider != null) { +- provider.destroy(); +- } +- sessionCache.clear(); - } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java +deleted file mode 100644 +index 85a0ee8..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionStats.java ++++ /dev/null +@@ -1,253 +0,0 @@ +-/* +- * Copyright 2014 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ - -- abstract static class AbstractCertificateVerifier extends CertificateVerifier { -- private final OpenSslEngineMap engineMap; -- -- AbstractCertificateVerifier(OpenSslEngineMap engineMap) { -- this.engineMap = engineMap; -- } +-package io.netty.handler.ssl; - -- @Override -- public final int verify(long ssl, byte[][] chain, String auth) { -- X509Certificate[] peerCerts = certificates(chain); -- final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); -- try { -- verify(engine, peerCerts, auth); -- return CertificateVerifier.X509_V_OK; -- } catch (Throwable cause) { -- logger.debug("verification of certificate failed", cause); -- SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem"); -- e.initCause(cause); -- engine.handshakeException = e; +-import io.netty.internal.tcnative.SSLContext; - -- // Try to extract the correct error code that should be used. -- if (cause instanceof OpenSslCertificateException) { -- // This will never return a negative error code as its validated when constructing the -- // OpenSslCertificateException. -- return ((OpenSslCertificateException) cause).errorCode(); -- } -- if (cause instanceof CertificateExpiredException) { -- return CertificateVerifier.X509_V_ERR_CERT_HAS_EXPIRED; -- } -- if (cause instanceof CertificateNotYetValidException) { -- return CertificateVerifier.X509_V_ERR_CERT_NOT_YET_VALID; -- } -- if (PlatformDependent.javaVersion() >= 7) { -- if (cause instanceof CertificateRevokedException) { -- return CertificateVerifier.X509_V_ERR_CERT_REVOKED; -- } +-import java.util.concurrent.locks.Lock; - -- // The X509TrustManagerImpl uses a Validator which wraps a CertPathValidatorException into -- // an CertificateException. So we need to handle the wrapped CertPathValidatorException to be -- // able to send the correct alert. -- Throwable wrapped = cause.getCause(); -- while (wrapped != null) { -- if (wrapped instanceof CertPathValidatorException) { -- CertPathValidatorException ex = (CertPathValidatorException) wrapped; -- CertPathValidatorException.Reason reason = ex.getReason(); -- if (reason == CertPathValidatorException.BasicReason.EXPIRED) { -- return CertificateVerifier.X509_V_ERR_CERT_HAS_EXPIRED; -- } -- if (reason == CertPathValidatorException.BasicReason.NOT_YET_VALID) { -- return CertificateVerifier.X509_V_ERR_CERT_NOT_YET_VALID; -- } -- if (reason == CertPathValidatorException.BasicReason.REVOKED) { -- return CertificateVerifier.X509_V_ERR_CERT_REVOKED; -- } -- } -- wrapped = wrapped.getCause(); -- } -- } +-/** +- * Stats exposed by an OpenSSL session context. +- * +- * @see SSL_CTX_sess_number +- */ +-public final class OpenSslSessionStats { - -- // Could not detect a specific error code to use, so fallback to a default code. -- return CertificateVerifier.X509_V_ERR_UNSPECIFIED; -- } -- } +- private final ReferenceCountedOpenSslContext context; - -- abstract void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, -- String auth) throws Exception; +- // IMPORTANT: We take the OpenSslContext and not just the long (which points the native instance) to prevent +- // the GC to collect OpenSslContext as this would also free the pointer and so could result in a +- // segfault when the user calls any of the methods here that try to pass the pointer down to the native +- // level. +- OpenSslSessionStats(ReferenceCountedOpenSslContext context) { +- this.context = context; - } - -- private static final class DefaultOpenSslEngineMap implements OpenSslEngineMap { -- private final Map engines = PlatformDependent.newConcurrentHashMap(); +- /** +- * Returns the current number of sessions in the internal session cache. +- */ +- public long number() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionNumber(context.ctx); +- } finally { +- readerLock.unlock(); +- } +- } - -- @Override -- public ReferenceCountedOpenSslEngine remove(long ssl) { -- return engines.remove(ssl); +- /** +- * Returns the number of started SSL/TLS handshakes in client mode. +- */ +- public long connect() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionConnect(context.ctx); +- } finally { +- readerLock.unlock(); - } +- } - -- @Override -- public void add(ReferenceCountedOpenSslEngine engine) { -- engines.put(engine.sslPointer(), engine); +- /** +- * Returns the number of successfully established SSL/TLS sessions in client mode. +- */ +- public long connectGood() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionConnectGood(context.ctx); +- } finally { +- readerLock.unlock(); - } +- } - -- @Override -- public ReferenceCountedOpenSslEngine get(long ssl) { -- return engines.get(ssl); +- /** +- * Returns the number of start renegotiations in client mode. +- */ +- public long connectRenegotiate() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionConnectRenegotiate(context.ctx); +- } finally { +- readerLock.unlock(); - } - } - -- static void setKeyMaterial(long ctx, X509Certificate[] keyCertChain, PrivateKey key, String keyPassword) -- throws SSLException { -- /* Load the certificate file and private key. */ -- long keyBio = 0; -- long keyCertChainBio = 0; -- long keyCertChainBio2 = 0; -- PemEncoded encoded = null; +- /** +- * Returns the number of started SSL/TLS handshakes in server mode. +- */ +- public long accept() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); - try { -- // Only encode one time -- encoded = PemX509Certificate.toPEM(ByteBufAllocator.DEFAULT, true, keyCertChain); -- keyCertChainBio = toBIO(ByteBufAllocator.DEFAULT, encoded.retain()); -- keyCertChainBio2 = toBIO(ByteBufAllocator.DEFAULT, encoded.retain()); +- return SSLContext.sessionAccept(context.ctx); +- } finally { +- readerLock.unlock(); +- } +- } - -- if (key != null) { -- keyBio = toBIO(key); -- } +- /** +- * Returns the number of successfully established SSL/TLS sessions in server mode. +- */ +- public long acceptGood() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionAcceptGood(context.ctx); +- } finally { +- readerLock.unlock(); +- } +- } - -- SSLContext.setCertificateBio( -- ctx, keyCertChainBio, keyBio, -- keyPassword == null ? StringUtil.EMPTY_STRING : keyPassword); -- // We may have more then one cert in the chain so add all of them now. -- SSLContext.setCertificateChainBio(ctx, keyCertChainBio2, true); -- } catch (SSLException e) { -- throw e; -- } catch (Exception e) { -- throw new SSLException("failed to set certificate and key", e); +- /** +- * Returns the number of start renegotiations in server mode. +- */ +- public long acceptRenegotiate() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionAcceptRenegotiate(context.ctx); - } finally { -- freeBio(keyBio); -- freeBio(keyCertChainBio); -- freeBio(keyCertChainBio2); -- if (encoded != null) { -- encoded.release(); -- } +- readerLock.unlock(); - } - } - -- static void freeBio(long bio) { -- if (bio != 0) { -- SSL.freeBIO(bio); +- /** +- * Returns the number of successfully reused sessions. In client mode, a session set with {@code SSL_set_session} +- * successfully reused is counted as a hit. In server mode, a session successfully retrieved from internal or +- * external cache is counted as a hit. +- */ +- public long hits() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionHits(context.ctx); +- } finally { +- readerLock.unlock(); - } - } - - /** -- * Return the pointer to a in-memory BIO -- * or {@code 0} if the {@code key} is {@code null}. The BIO contains the content of the {@code key}. +- * Returns the number of successfully retrieved sessions from the external session cache in server mode. - */ -- static long toBIO(PrivateKey key) throws Exception { -- if (key == null) { -- return 0; +- public long cbHits() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionCbHits(context.ctx); +- } finally { +- readerLock.unlock(); - } +- } - -- ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; -- PemEncoded pem = PemPrivateKey.toPEM(allocator, true, key); +- /** +- * Returns the number of sessions proposed by clients that were not found in the internal session cache +- * in server mode. +- */ +- public long misses() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); - try { -- return toBIO(allocator, pem.retain()); +- return SSLContext.sessionMisses(context.ctx); - } finally { -- pem.release(); +- readerLock.unlock(); - } - } - - /** -- * Return the pointer to a in-memory BIO -- * or {@code 0} if the {@code certChain} is {@code null}. The BIO contains the content of the {@code certChain}. +- * Returns the number of sessions proposed by clients and either found in the internal or external session cache +- * in server mode, but that were invalid due to timeout. These sessions are not included in the {@link #hits()} +- * count. - */ -- static long toBIO(X509Certificate... certChain) throws Exception { -- if (certChain == null) { -- return 0; +- public long timeouts() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionTimeouts(context.ctx); +- } finally { +- readerLock.unlock(); - } +- } - -- if (certChain.length == 0) { -- throw new IllegalArgumentException("certChain can't be empty"); +- /** +- * Returns the number of sessions that were removed because the maximum session cache size was exceeded. +- */ +- public long cacheFull() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionCacheFull(context.ctx); +- } finally { +- readerLock.unlock(); - } +- } - -- ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; -- PemEncoded pem = PemX509Certificate.toPEM(allocator, true, certChain); +- /** +- * Returns the number of times a client presented a ticket that did not match any key in the list. +- */ +- public long ticketKeyFail() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); - try { -- return toBIO(allocator, pem.retain()); +- return SSLContext.sessionTicketKeyFail(context.ctx); - } finally { -- pem.release(); +- readerLock.unlock(); - } - } - -- static long toBIO(ByteBufAllocator allocator, PemEncoded pem) throws Exception { +- /** +- * Returns the number of times a client did not present a ticket and we issued a new one +- */ +- public long ticketKeyNew() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); - try { -- // We can turn direct buffers straight into BIOs. No need to -- // make a yet another copy. -- ByteBuf content = pem.content(); -- -- if (content.isDirect()) { -- return newBIO(content.retainedSlice()); -- } +- return SSLContext.sessionTicketKeyNew(context.ctx); +- } finally { +- readerLock.unlock(); +- } +- } - -- ByteBuf buffer = allocator.directBuffer(content.readableBytes()); -- try { -- buffer.writeBytes(content, content.readerIndex(), content.readableBytes()); -- return newBIO(buffer.retainedSlice()); -- } finally { -- try { -- // If the contents of the ByteBuf is sensitive (e.g. a PrivateKey) we -- // need to zero out the bytes of the copy before we're releasing it. -- if (pem.isSensitive()) { -- SslUtils.zeroout(buffer); -- } -- } finally { -- buffer.release(); -- } -- } +- /** +- * Returns the number of times a client presented a ticket derived from an older key, +- * and we upgraded to the primary key. +- */ +- public long ticketKeyRenew() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- try { +- return SSLContext.sessionTicketKeyRenew(context.ctx); - } finally { -- pem.release(); +- readerLock.unlock(); - } - } - -- private static long newBIO(ByteBuf buffer) throws Exception { +- /** +- * Returns the number of times a client presented a ticket derived from the primary key. +- */ +- public long ticketKeyResume() { +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); - try { -- long bio = SSL.newMemBIO(); -- int readable = buffer.readableBytes(); -- if (SSL.bioWrite(bio, OpenSsl.memoryAddress(buffer) + buffer.readerIndex(), readable) != readable) { -- SSL.freeBIO(bio); -- throw new IllegalStateException("Could not write data to memory BIO"); -- } -- return bio; +- return SSLContext.sessionTicketKeyResume(context.ctx); - } finally { -- buffer.release(); +- readerLock.unlock(); - } - } -} -diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionTicketKey.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionTicketKey.java deleted file mode 100644 -index 27460c7..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java +index 175a37e..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslSessionTicketKey.java +++ /dev/null -@@ -1,2037 +0,0 @@ +@@ -1,78 +0,0 @@ -/* -- * Copyright 2016 The Netty Project +- * Copyright 2015 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -3485,4638 +4898,4770 @@ index 27460c7..0000000 - */ -package io.netty.handler.ssl; - --import io.netty.buffer.ByteBuf; --import io.netty.buffer.ByteBufAllocator; --import io.netty.internal.tcnative.Buffer; --import io.netty.internal.tcnative.SSL; --import io.netty.util.AbstractReferenceCounted; --import io.netty.util.ReferenceCounted; --import io.netty.util.ResourceLeakDetector; --import io.netty.util.ResourceLeakDetectorFactory; --import io.netty.util.ResourceLeakTracker; --import io.netty.util.internal.EmptyArrays; --import io.netty.util.internal.PlatformDependent; --import io.netty.util.internal.StringUtil; --import io.netty.util.internal.ThrowableUtil; --import io.netty.util.internal.UnstableApi; --import io.netty.util.internal.logging.InternalLogger; --import io.netty.util.internal.logging.InternalLoggerFactory; -- --import java.nio.ByteBuffer; --import java.nio.ReadOnlyBufferException; --import java.security.Principal; --import java.security.cert.Certificate; --import java.util.ArrayList; --import java.util.Arrays; --import java.util.Collection; --import java.util.HashMap; --import java.util.List; --import java.util.Map; --import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; -- --import java.util.concurrent.locks.Lock; --import javax.net.ssl.SSLEngine; --import javax.net.ssl.SSLEngineResult; --import javax.net.ssl.SSLException; --import javax.net.ssl.SSLHandshakeException; --import javax.net.ssl.SSLParameters; --import javax.net.ssl.SSLPeerUnverifiedException; --import javax.net.ssl.SSLSession; --import javax.net.ssl.SSLSessionBindingEvent; --import javax.net.ssl.SSLSessionBindingListener; --import javax.net.ssl.SSLSessionContext; --import javax.security.cert.X509Certificate; -- --import static io.netty.handler.ssl.OpenSsl.memoryAddress; --import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH; --import static io.netty.util.internal.EmptyArrays.EMPTY_CERTIFICATES; --import static io.netty.util.internal.EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES; --import static io.netty.util.internal.ObjectUtil.checkNotNull; --import static java.lang.Math.min; --import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; --import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP; --import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_WRAP; --import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; --import static javax.net.ssl.SSLEngineResult.Status.BUFFER_OVERFLOW; --import static javax.net.ssl.SSLEngineResult.Status.BUFFER_UNDERFLOW; --import static javax.net.ssl.SSLEngineResult.Status.CLOSED; --import static javax.net.ssl.SSLEngineResult.Status.OK; +-import io.netty.internal.tcnative.SessionTicketKey; - -/** -- * Implements a {@link SSLEngine} using -- * OpenSSL BIO abstractions. -- *

Instances of this class must be {@link #release() released} or else native memory will leak! -- * -- *

Instances of this class must be released before the {@link ReferenceCountedOpenSslContext} -- * the instance depends upon are released. Otherwise if any method of this class is called which uses the -- * the {@link ReferenceCountedOpenSslContext} JNI resources the JVM may crash. +- * Session Ticket Key - */ --public class ReferenceCountedOpenSslEngine extends SSLEngine implements ReferenceCounted { +-public final class OpenSslSessionTicketKey { - -- private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReferenceCountedOpenSslEngine.class); +- /** +- * Size of session ticket key name +- */ +- public static final int NAME_SIZE = SessionTicketKey.NAME_SIZE; +- /** +- * Size of session ticket key HMAC key +- */ +- public static final int HMAC_KEY_SIZE = SessionTicketKey.HMAC_KEY_SIZE; +- /** +- * Size of session ticket key AES key +- */ +- public static final int AES_KEY_SIZE = SessionTicketKey.AES_KEY_SIZE; +- /** +- * Size of session ticker key +- */ +- public static final int TICKET_KEY_SIZE = SessionTicketKey.TICKET_KEY_SIZE; +- +- final SessionTicketKey key; +- +- /** +- * Construct a OpenSslSessionTicketKey. +- * +- * @param name the name of the session ticket key +- * @param hmacKey the HMAC key of the session ticket key +- * @param aesKey the AES key of the session ticket key +- */ +- public OpenSslSessionTicketKey(byte[] name, byte[] hmacKey, byte[] aesKey) { +- key = new SessionTicketKey(name.clone(), hmacKey.clone(), aesKey.clone()); +- } +- +- /** +- * Get name. +- * @return the name of the session ticket key +- */ +- public byte[] name() { +- return key.getName().clone(); +- } - -- private static final SSLException BEGIN_HANDSHAKE_ENGINE_CLOSED = ThrowableUtil.unknownStackTrace( -- new SSLException("engine closed"), ReferenceCountedOpenSslEngine.class, "beginHandshake()"); -- private static final SSLException HANDSHAKE_ENGINE_CLOSED = ThrowableUtil.unknownStackTrace( -- new SSLException("engine closed"), ReferenceCountedOpenSslEngine.class, "handshake()"); -- private static final SSLException RENEGOTIATION_UNSUPPORTED = ThrowableUtil.unknownStackTrace( -- new SSLException("renegotiation unsupported"), ReferenceCountedOpenSslEngine.class, "beginHandshake()"); -- private static final ResourceLeakDetector leakDetector = -- ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslEngine.class); - /** -- * The flags argument is usually 0. +- * Get HMAC key. +- * @return the HMAC key of the session ticket key - */ -- private static final int DEFAULT_HOSTNAME_VALIDATION_FLAGS = 0; -- -- static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14 +- public byte[] hmacKey() { +- return key.getHmacKey().clone(); +- } - - /** -- * This is the maximum overhead when encrypting plaintext as defined by -- * rfc5264, -- * rfc5289 and openssl implementation itself. -- * -- * Please note that we use a padding of 16 here as openssl uses PKC#5 which uses 16 bytes while the spec itself -- * allow up to 255 bytes. 16 bytes is the max for PKC#5 (which handles it the same way as PKC#7) as we use a block -- * size of 16. See rfc5652#section-6.3. -- * -- * TLS Header (5) + 16 (IV) + 48 (MAC) + 1 (Padding_length field) + 15 (Padding) + 1 (ContentType) + -- * 2 (ProtocolVersion) + 2 (Length) -- * -- * TODO: We may need to review this calculation once TLS 1.3 becomes available. +- * Get AES Key. +- * @return the AES key of the session ticket key - */ -- static final int MAX_TLS_RECORD_OVERHEAD_LENGTH = SSL_RECORD_HEADER_LENGTH + 16 + 48 + 1 + 15 + 1 + 2 + 2; +- public byte[] aesKey() { +- return key.getAesKey().clone(); +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/OpenSslX509KeyManagerFactory.java b/handler/src/main/java/io/netty/handler/ssl/OpenSslX509KeyManagerFactory.java +deleted file mode 100644 +index df711a0..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/OpenSslX509KeyManagerFactory.java ++++ /dev/null +@@ -1,416 +0,0 @@ +-/* +- * Copyright 2018 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; - -- static final int MAX_ENCRYPTED_PACKET_LENGTH = MAX_PLAINTEXT_LENGTH + MAX_TLS_RECORD_OVERHEAD_LENGTH; +-import static io.netty.util.internal.ObjectUtil.checkNonEmpty; +-import static io.netty.util.internal.ObjectUtil.checkNotNull; - -- private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER = -- AtomicIntegerFieldUpdater.newUpdater(ReferenceCountedOpenSslEngine.class, "destroyed"); +-import io.netty.buffer.ByteBufAllocator; +-import io.netty.buffer.UnpooledByteBufAllocator; +-import io.netty.internal.tcnative.SSL; +-import io.netty.util.ReferenceCountUtil; +-import io.netty.util.internal.ObjectUtil; - -- private static final String INVALID_CIPHER = "SSL_NULL_WITH_NULL_NULL"; -- private static final SSLEngineResult NEED_UNWRAP_OK = new SSLEngineResult(OK, NEED_UNWRAP, 0, 0); -- private static final SSLEngineResult NEED_UNWRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_UNWRAP, 0, 0); -- private static final SSLEngineResult NEED_WRAP_OK = new SSLEngineResult(OK, NEED_WRAP, 0, 0); -- private static final SSLEngineResult NEED_WRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_WRAP, 0, 0); -- private static final SSLEngineResult CLOSED_NOT_HANDSHAKING = new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0); +-import javax.net.ssl.KeyManager; +-import javax.net.ssl.KeyManagerFactory; +-import javax.net.ssl.KeyManagerFactorySpi; +-import javax.net.ssl.ManagerFactoryParameters; +-import javax.net.ssl.X509KeyManager; +-import java.io.File; +-import java.io.IOException; +-import java.io.InputStream; +-import java.io.OutputStream; +-import java.security.InvalidAlgorithmParameterException; +-import java.security.Key; +-import java.security.KeyStore; +-import java.security.KeyStoreException; +-import java.security.KeyStoreSpi; +-import java.security.NoSuchAlgorithmException; +-import java.security.PrivateKey; +-import java.security.Provider; +-import java.security.UnrecoverableKeyException; +-import java.security.cert.Certificate; +-import java.security.cert.CertificateException; +-import java.security.cert.X509Certificate; +-import java.util.Collections; +-import java.util.Date; +-import java.util.Enumeration; +-import java.util.HashMap; +-import java.util.Map; - -- // OpenSSL state -- private long ssl; -- private long networkBIO; -- private boolean certificateSet; +-/** +- * Special {@link KeyManagerFactory} that pre-compute the keymaterial used when {@link SslProvider#OPENSSL} or +- * {@link SslProvider#OPENSSL_REFCNT} is used and so will improve handshake times and its performance. +- * +- * +- * +- * Because the keymaterial is pre-computed any modification to the {@link KeyStore} is ignored after +- * {@link #init(KeyStore, char[])} is called. +- * +- * {@link #init(ManagerFactoryParameters)} is not supported by this implementation and so a call to it will always +- * result in an {@link InvalidAlgorithmParameterException}. +- */ +-public final class OpenSslX509KeyManagerFactory extends KeyManagerFactory { - -- private enum HandshakeState { -- /** -- * Not started yet. -- */ -- NOT_STARTED, -- /** -- * Started via unwrap/wrap. -- */ -- STARTED_IMPLICITLY, -- /** -- * Started via {@link #beginHandshake()}. -- */ -- STARTED_EXPLICITLY, +- private final OpenSslKeyManagerFactorySpi spi; - -- /** -- * Handshake is finished. -- */ -- FINISHED +- public OpenSslX509KeyManagerFactory() { +- this(newOpenSslKeyManagerFactorySpi(null)); - } - -- private HandshakeState handshakeState = HandshakeState.NOT_STARTED; -- private boolean renegotiationPending; -- private boolean receivedShutdown; -- private volatile int destroyed; +- public OpenSslX509KeyManagerFactory(Provider provider) { +- this(newOpenSslKeyManagerFactorySpi(provider)); +- } - -- // Reference Counting -- private final ResourceLeakTracker leak; -- private final AbstractReferenceCounted refCnt = new AbstractReferenceCounted() { -- @Override -- public ReferenceCounted touch(Object hint) { -- if (leak != null) { -- leak.record(hint); -- } +- public OpenSslX509KeyManagerFactory(String algorithm, Provider provider) throws NoSuchAlgorithmException { +- this(newOpenSslKeyManagerFactorySpi(algorithm, provider)); +- } - -- return ReferenceCountedOpenSslEngine.this; -- } +- private OpenSslX509KeyManagerFactory(OpenSslKeyManagerFactorySpi spi) { +- super(spi, spi.kmf.getProvider(), spi.kmf.getAlgorithm()); +- this.spi = spi; +- } - -- @Override -- protected void deallocate() { -- shutdown(); -- if (leak != null) { -- boolean closed = leak.close(ReferenceCountedOpenSslEngine.this); -- assert closed; -- } +- private static OpenSslKeyManagerFactorySpi newOpenSslKeyManagerFactorySpi(Provider provider) { +- try { +- return newOpenSslKeyManagerFactorySpi(null, provider); +- } catch (NoSuchAlgorithmException e) { +- // This should never happen as we use the default algorithm. +- throw new IllegalStateException(e); - } -- }; -- -- private volatile ClientAuth clientAuth = ClientAuth.NONE; -- -- // Updated once a new handshake is started and so the SSLSession reused. -- private volatile long lastAccessed = -1; -- -- private String endPointIdentificationAlgorithm; -- // Store as object as AlgorithmConstraints only exists since java 7. -- private Object algorithmConstraints; -- private List sniHostNames; -- -- // Mark as volatile as accessed by checkSniHostnameMatch(...) and also not specify the SNIMatcher type to allow us -- // using it with java7. -- private volatile Collection matchers; -- -- // SSL Engine status variables -- private boolean isInboundDone; -- private boolean outboundClosed; +- } - -- private final boolean clientMode; -- private final ByteBufAllocator alloc; -- private final OpenSslEngineMap engineMap; -- private final OpenSslApplicationProtocolNegotiator apn; -- private final boolean rejectRemoteInitiatedRenegotiation; -- private final OpenSslSession session; -- private final Certificate[] localCerts; -- private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1]; -- private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1]; -- private final OpenSslKeyMaterialManager keyMaterialManager; -- private final boolean enableOcsp; +- private static OpenSslKeyManagerFactorySpi newOpenSslKeyManagerFactorySpi(String algorithm, Provider provider) +- throws NoSuchAlgorithmException { +- if (algorithm == null) { +- algorithm = KeyManagerFactory.getDefaultAlgorithm(); +- } +- return new OpenSslKeyManagerFactorySpi( +- provider == null ? KeyManagerFactory.getInstance(algorithm) : +- KeyManagerFactory.getInstance(algorithm, provider)); +- } - -- // This is package-private as we set it from OpenSslContext if an exception is thrown during -- // the verification step. -- SSLHandshakeException handshakeException; +- OpenSslKeyMaterialProvider newProvider() { +- return spi.newProvider(); +- } - -- /** -- * Create a new instance. -- * @param context Reference count release responsibility is not transferred! The callee still owns this object. -- * @param alloc The allocator to use. -- * @param peerHost The peer host name. -- * @param peerPort The peer port. -- * @param leakDetection {@code true} to enable leak detection of this object. -- */ -- ReferenceCountedOpenSslEngine(ReferenceCountedOpenSslContext context, ByteBufAllocator alloc, String peerHost, -- int peerPort, boolean leakDetection) { -- super(peerHost, peerPort); -- OpenSsl.ensureAvailability(); -- leak = leakDetection ? leakDetector.track(this) : null; -- this.alloc = checkNotNull(alloc, "alloc"); -- apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator(); -- session = new OpenSslSession(context.sessionContext()); -- clientMode = context.isClient(); -- engineMap = context.engineMap; -- rejectRemoteInitiatedRenegotiation = context.getRejectRemoteInitiatedRenegotiation(); -- localCerts = context.keyCertChain; -- keyMaterialManager = context.keyMaterialManager(); -- enableOcsp = context.enableOcsp; +- private static final class OpenSslKeyManagerFactorySpi extends KeyManagerFactorySpi { +- final KeyManagerFactory kmf; +- private volatile ProviderFactory providerFactory; - -- Lock readerLock = context.ctxLock.readLock(); -- readerLock.lock(); -- try { -- ssl = SSL.newSSL(context.ctx, !context.isClient()); -- } finally { -- readerLock.unlock(); +- OpenSslKeyManagerFactorySpi(KeyManagerFactory kmf) { +- this.kmf = ObjectUtil.checkNotNull(kmf, "kmf"); - } -- try { -- networkBIO = SSL.bioNewByteBuffer(ssl, context.getBioNonApplicationBufferSize()); -- -- // Set the client auth mode, this needs to be done via setClientAuth(...) method so we actually call the -- // needed JNI methods. -- setClientAuth(clientMode ? ClientAuth.NONE : context.clientAuth); -- -- if (context.protocols != null) { -- setEnabledProtocols(context.protocols); -- } - -- // Use SNI if peerHost was specified -- // See https://github.com/netty/netty/issues/4746 -- if (clientMode && peerHost != null) { -- SSL.setTlsExtHostName(ssl, peerHost); +- @Override +- protected synchronized void engineInit(KeyStore keyStore, char[] chars) +- throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { +- if (providerFactory != null) { +- throw new KeyStoreException("Already initialized"); - } -- -- if (enableOcsp) { -- SSL.enableOcsp(ssl); +- if (!keyStore.aliases().hasMoreElements()) { +- throw new KeyStoreException("No aliases found"); - } -- } catch (Throwable cause) { -- SSL.freeSSL(ssl); -- PlatformDependent.throwException(cause); -- } -- } -- -- /** -- * Sets the OCSP response. -- */ -- @UnstableApi -- public void setOcspResponse(byte[] response) { -- if (!enableOcsp) { -- throw new IllegalStateException("OCSP stapling is not enabled"); -- } - -- if (clientMode) { -- throw new IllegalStateException("Not a server SSLEngine"); +- kmf.init(keyStore, chars); +- providerFactory = new ProviderFactory(ReferenceCountedOpenSslContext.chooseX509KeyManager( +- kmf.getKeyManagers()), password(chars), Collections.list(keyStore.aliases())); - } - -- synchronized (this) { -- SSL.setOcspResponse(ssl, response); +- private static String password(char[] password) { +- if (password == null || password.length == 0) { +- return null; +- } +- return new String(password); - } -- } - -- /** -- * Returns the OCSP response or {@code null} if the server didn't provide a stapled OCSP response. -- */ -- @UnstableApi -- public byte[] getOcspResponse() { -- if (!enableOcsp) { -- throw new IllegalStateException("OCSP stapling is not enabled"); +- @Override +- protected void engineInit(ManagerFactoryParameters managerFactoryParameters) +- throws InvalidAlgorithmParameterException { +- throw new InvalidAlgorithmParameterException("Not supported"); - } - -- if (!clientMode) { -- throw new IllegalStateException("Not a client SSLEngine"); +- @Override +- protected KeyManager[] engineGetKeyManagers() { +- ProviderFactory providerFactory = this.providerFactory; +- if (providerFactory == null) { +- throw new IllegalStateException("engineInit(...) not called yet"); +- } +- return new KeyManager[] { providerFactory.keyManager }; - } - -- synchronized (this) { -- return SSL.getOcspResponse(ssl); +- OpenSslKeyMaterialProvider newProvider() { +- ProviderFactory providerFactory = this.providerFactory; +- if (providerFactory == null) { +- throw new IllegalStateException("engineInit(...) not called yet"); +- } +- return providerFactory.newProvider(); - } -- } -- -- @Override -- public final int refCnt() { -- return refCnt.refCnt(); -- } -- -- @Override -- public final ReferenceCounted retain() { -- refCnt.retain(); -- return this; -- } - -- @Override -- public final ReferenceCounted retain(int increment) { -- refCnt.retain(increment); -- return this; -- } +- private static final class ProviderFactory { +- private final X509KeyManager keyManager; +- private final String password; +- private final Iterable aliases; - -- @Override -- public final ReferenceCounted touch() { -- refCnt.touch(); -- return this; -- } +- ProviderFactory(X509KeyManager keyManager, String password, Iterable aliases) { +- this.keyManager = keyManager; +- this.password = password; +- this.aliases = aliases; +- } - -- @Override -- public final ReferenceCounted touch(Object hint) { -- refCnt.touch(hint); -- return this; -- } +- OpenSslKeyMaterialProvider newProvider() { +- return new OpenSslPopulatedKeyMaterialProvider(keyManager, +- password, aliases); +- } - -- @Override -- public final boolean release() { -- return refCnt.release(); -- } +- /** +- * {@link OpenSslKeyMaterialProvider} implementation that pre-compute the {@link OpenSslKeyMaterial} for +- * all aliases. +- */ +- private static final class OpenSslPopulatedKeyMaterialProvider extends OpenSslKeyMaterialProvider { +- private final Map materialMap; - -- @Override -- public final boolean release(int decrement) { -- return refCnt.release(decrement); -- } +- OpenSslPopulatedKeyMaterialProvider( +- X509KeyManager keyManager, String password, Iterable aliases) { +- super(keyManager, password); +- materialMap = new HashMap(); +- boolean initComplete = false; +- try { +- for (String alias: aliases) { +- if (alias != null && !materialMap.containsKey(alias)) { +- try { +- materialMap.put(alias, super.chooseKeyMaterial( +- UnpooledByteBufAllocator.DEFAULT, alias)); +- } catch (Exception e) { +- // Just store the exception and rethrow it when we try to choose the keymaterial +- // for this alias later on. +- materialMap.put(alias, e); +- } +- } +- } +- initComplete = true; +- } finally { +- if (!initComplete) { +- destroy(); +- } +- } +- checkNonEmpty(materialMap, "materialMap"); +- } - -- @Override -- public final synchronized SSLSession getHandshakeSession() { -- // Javadocs state return value should be: -- // null if this instance is not currently handshaking, or if the current handshake has not -- // progressed far enough to create a basic SSLSession. Otherwise, this method returns the -- // SSLSession currently being negotiated. -- switch(handshakeState) { -- case NOT_STARTED: -- case FINISHED: -- return null; -- default: -- return session; +- @Override +- OpenSslKeyMaterial chooseKeyMaterial(ByteBufAllocator allocator, String alias) throws Exception { +- Object value = materialMap.get(alias); +- if (value == null) { +- // There is no keymaterial for the requested alias, return null +- return null; +- } +- if (value instanceof OpenSslKeyMaterial) { +- return ((OpenSslKeyMaterial) value).retain(); +- } +- throw (Exception) value; +- } +- +- @Override +- void destroy() { +- for (Object material: materialMap.values()) { +- ReferenceCountUtil.release(material); +- } +- materialMap.clear(); +- } +- } - } - } - - /** -- * Returns the pointer to the {@code SSL} object for this {@link ReferenceCountedOpenSslEngine}. -- * Be aware that it is freed as soon as the {@link #release()} or {@link #shutdown()} methods are called. -- * At this point {@code 0} will be returned. +- * Create a new initialized {@link OpenSslX509KeyManagerFactory} which loads its {@link PrivateKey} directly from +- * an {@code OpenSSL engine} via the +- * ENGINE_load_private_key +- * function. - */ -- public final synchronized long sslPointer() { -- return ssl; +- public static OpenSslX509KeyManagerFactory newEngineBased(File certificateChain, String password) +- throws CertificateException, IOException, +- KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { +- return newEngineBased(SslContext.toX509Certificates(certificateChain), password); - } - - /** -- * Destroys this engine. +- * Create a new initialized {@link OpenSslX509KeyManagerFactory} which loads its {@link PrivateKey} directly from +- * an {@code OpenSSL engine} via the +- * ENGINE_load_private_key +- * function. - */ -- public final synchronized void shutdown() { -- if (DESTROYED_UPDATER.compareAndSet(this, 0, 1)) { -- engineMap.remove(ssl); -- SSL.freeSSL(ssl); -- ssl = networkBIO = 0; +- public static OpenSslX509KeyManagerFactory newEngineBased(X509Certificate[] certificateChain, String password) +- throws CertificateException, IOException, +- KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { +- checkNotNull(certificateChain, "certificateChain"); +- KeyStore store = new OpenSslKeyStore(certificateChain.clone(), false); +- store.load(null, null); +- OpenSslX509KeyManagerFactory factory = new OpenSslX509KeyManagerFactory(); +- factory.init(store, password == null ? null : password.toCharArray()); +- return factory; +- } - -- isInboundDone = outboundClosed = true; -- } +- /** +- * See {@link OpenSslX509KeyManagerFactory#newEngineBased(X509Certificate[], String)}. +- */ +- public static OpenSslX509KeyManagerFactory newKeyless(File chain) +- throws CertificateException, IOException, +- KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { +- return newKeyless(SslContext.toX509Certificates(chain)); +- } - -- // On shutdown clear all errors -- SSL.clearError(); +- /** +- * See {@link OpenSslX509KeyManagerFactory#newEngineBased(X509Certificate[], String)}. +- */ +- public static OpenSslX509KeyManagerFactory newKeyless(InputStream chain) +- throws CertificateException, IOException, +- KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { +- return newKeyless(SslContext.toX509Certificates(chain)); - } - - /** -- * Write plaintext data to the OpenSSL internal BIO -- * -- * Calling this function with src.remaining == 0 is undefined. +- * Returns a new initialized {@link OpenSslX509KeyManagerFactory} which will provide its private key by using the +- * {@link OpenSslPrivateKeyMethod}. - */ -- private int writePlaintextData(final ByteBuffer src, int len) { -- final int pos = src.position(); -- final int limit = src.limit(); -- final int sslWrote; +- public static OpenSslX509KeyManagerFactory newKeyless(X509Certificate... certificateChain) +- throws CertificateException, IOException, +- KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { +- checkNotNull(certificateChain, "certificateChain"); +- KeyStore store = new OpenSslKeyStore(certificateChain.clone(), true); +- store.load(null, null); +- OpenSslX509KeyManagerFactory factory = new OpenSslX509KeyManagerFactory(); +- factory.init(store, null); +- return factory; +- } - -- if (src.isDirect()) { -- sslWrote = SSL.writeToSSL(ssl, Buffer.address(src) + pos, len); -- if (sslWrote > 0) { -- src.position(pos + sslWrote); -- } -- } else { -- ByteBuf buf = alloc.directBuffer(len); -- try { -- src.limit(pos + len); +- private static final class OpenSslKeyStore extends KeyStore { +- private OpenSslKeyStore(final X509Certificate[] certificateChain, final boolean keyless) { +- super(new KeyStoreSpi() { - -- buf.setBytes(0, src); -- src.limit(limit); +- private final Date creationDate = new Date(); - -- sslWrote = SSL.writeToSSL(ssl, memoryAddress(buf), len); -- if (sslWrote > 0) { -- src.position(pos + sslWrote); -- } else { -- src.position(pos); +- @Override +- public Key engineGetKey(String alias, char[] password) throws UnrecoverableKeyException { +- if (engineContainsAlias(alias)) { +- final long privateKeyAddress; +- if (keyless) { +- privateKeyAddress = 0; +- } else { +- try { +- privateKeyAddress = SSL.loadPrivateKeyFromEngine( +- alias, password == null ? null : new String(password)); +- } catch (Exception e) { +- UnrecoverableKeyException keyException = +- new UnrecoverableKeyException("Unable to load key from engine"); +- keyException.initCause(e); +- throw keyException; +- } +- } +- return new OpenSslPrivateKey(privateKeyAddress); +- } +- return null; - } -- } finally { -- buf.release(); -- } +- +- @Override +- public Certificate[] engineGetCertificateChain(String alias) { +- return engineContainsAlias(alias)? certificateChain.clone() : null; +- } +- +- @Override +- public Certificate engineGetCertificate(String alias) { +- return engineContainsAlias(alias)? certificateChain[0] : null; +- } +- +- @Override +- public Date engineGetCreationDate(String alias) { +- return engineContainsAlias(alias)? creationDate : null; +- } +- +- @Override +- public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) +- throws KeyStoreException { +- throw new KeyStoreException("Not supported"); +- } +- +- @Override +- public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException { +- throw new KeyStoreException("Not supported"); +- } +- +- @Override +- public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { +- throw new KeyStoreException("Not supported"); +- } +- +- @Override +- public void engineDeleteEntry(String alias) throws KeyStoreException { +- throw new KeyStoreException("Not supported"); +- } +- +- @Override +- public Enumeration engineAliases() { +- return Collections.enumeration(Collections.singleton(SslContext.ALIAS)); +- } +- +- @Override +- public boolean engineContainsAlias(String alias) { +- return SslContext.ALIAS.equals(alias); +- } +- +- @Override +- public int engineSize() { +- return 1; +- } +- +- @Override +- public boolean engineIsKeyEntry(String alias) { +- return engineContainsAlias(alias); +- } +- +- @Override +- public boolean engineIsCertificateEntry(String alias) { +- return engineContainsAlias(alias); +- } +- +- @Override +- public String engineGetCertificateAlias(Certificate cert) { +- if (cert instanceof X509Certificate) { +- for (X509Certificate x509Certificate : certificateChain) { +- if (x509Certificate.equals(cert)) { +- return SslContext.ALIAS; +- } +- } +- } +- return null; +- } +- +- @Override +- public void engineStore(OutputStream stream, char[] password) { +- throw new UnsupportedOperationException(); +- } +- +- @Override +- public void engineLoad(InputStream stream, char[] password) { +- if (stream != null && password != null) { +- throw new UnsupportedOperationException(); +- } +- } +- }, null, "native"); +- +- OpenSsl.ensureAvailability(); - } -- return sslWrote; - } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java +deleted file mode 100644 +index 3eb233d..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java ++++ /dev/null +@@ -1,324 +0,0 @@ +-/* +- * Copyright 2016 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; - -- /** -- * Write encrypted data to the OpenSSL network BIO. -- */ -- private ByteBuf writeEncryptedData(final ByteBuffer src, int len) { -- final int pos = src.position(); -- if (src.isDirect()) { -- SSL.bioSetByteBuffer(networkBIO, Buffer.address(src) + pos, len, false); -- } else { -- final ByteBuf buf = alloc.directBuffer(len); -- try { -- final int limit = src.limit(); -- src.limit(pos + len); -- buf.writeBytes(src); -- // Restore the original position and limit because we don't want to consume from `src`. -- src.position(pos); -- src.limit(limit); +-import io.netty.internal.tcnative.CertificateCallback; +-import io.netty.util.internal.EmptyArrays; +-import io.netty.util.internal.SuppressJava6Requirement; +-import io.netty.internal.tcnative.SSL; +-import io.netty.internal.tcnative.SSLContext; - -- SSL.bioSetByteBuffer(networkBIO, memoryAddress(buf), len, false); -- return buf; -- } catch (Throwable cause) { -- buf.release(); -- PlatformDependent.throwException(cause); +-import java.security.KeyStore; +-import java.security.PrivateKey; +-import java.security.cert.X509Certificate; +- +-import java.util.Arrays; +-import java.util.Collections; +-import java.util.HashSet; +-import java.util.LinkedHashSet; +-import java.util.Map; +-import java.util.Set; +- +-import javax.net.ssl.KeyManagerFactory; +-import javax.net.ssl.SSLException; +-import javax.net.ssl.TrustManagerFactory; +-import javax.net.ssl.X509ExtendedTrustManager; +-import javax.net.ssl.X509TrustManager; +-import javax.security.auth.x500.X500Principal; +- +-/** +- * A client-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. +- *

Instances of this class must be {@link #release() released} or else native memory will leak! +- * +- *

Instances of this class must not be released before any {@link ReferenceCountedOpenSslEngine} +- * which depends upon the instance of this class is released. Otherwise if any method of +- * {@link ReferenceCountedOpenSslEngine} is called which uses this class's JNI resources the JVM may crash. +- */ +-public final class ReferenceCountedOpenSslClientContext extends ReferenceCountedOpenSslContext { +- +- private static final Set SUPPORTED_KEY_TYPES = Collections.unmodifiableSet(new LinkedHashSet( +- Arrays.asList(OpenSslKeyMaterialManager.KEY_TYPE_RSA, +- OpenSslKeyMaterialManager.KEY_TYPE_DH_RSA, +- OpenSslKeyMaterialManager.KEY_TYPE_EC, +- OpenSslKeyMaterialManager.KEY_TYPE_EC_RSA, +- OpenSslKeyMaterialManager.KEY_TYPE_EC_EC))); +- +- private final OpenSslSessionContext sessionContext; +- +- ReferenceCountedOpenSslClientContext(X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, +- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, +- KeyManagerFactory keyManagerFactory, Iterable ciphers, +- CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, +- String[] protocols, long sessionCacheSize, long sessionTimeout, +- boolean enableOcsp, String keyStore, String endpointIdentificationAlgorithm, +- ResumptionController resumptionController, +- Map.Entry, Object>... options) throws SSLException { +- super(ciphers, cipherFilter, toNegotiator(apn), SSL.SSL_MODE_CLIENT, keyCertChain, +- ClientAuth.NONE, protocols, false, endpointIdentificationAlgorithm, enableOcsp, true, +- resumptionController, options); +- boolean success = false; +- try { +- sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, +- keyCertChain, key, keyPassword, keyManagerFactory, keyStore, +- sessionCacheSize, sessionTimeout, resumptionController); +- success = true; +- } finally { +- if (!success) { +- release(); - } - } -- return null; - } - -- /** -- * Read plaintext data from the OpenSSL internal BIO -- */ -- private int readPlaintextData(final ByteBuffer dst) { -- final int sslRead; -- final int pos = dst.position(); -- if (dst.isDirect()) { -- sslRead = SSL.readFromSSL(ssl, Buffer.address(dst) + pos, dst.limit() - pos); -- if (sslRead > 0) { -- dst.position(pos + sslRead); +- @Override +- public OpenSslSessionContext sessionContext() { +- return sessionContext; +- } +- +- static OpenSslSessionContext newSessionContext(ReferenceCountedOpenSslContext thiz, long ctx, +- OpenSslEngineMap engineMap, +- X509Certificate[] trustCertCollection, +- TrustManagerFactory trustManagerFactory, +- X509Certificate[] keyCertChain, PrivateKey key, +- String keyPassword, KeyManagerFactory keyManagerFactory, +- String keyStore, long sessionCacheSize, long sessionTimeout, +- ResumptionController resumptionController) +- throws SSLException { +- if (key == null && keyCertChain != null || key != null && keyCertChain == null) { +- throw new IllegalArgumentException( +- "Either both keyCertChain and key needs to be null or none of them"); +- } +- OpenSslKeyMaterialProvider keyMaterialProvider = null; +- try { +- try { +- if (!OpenSsl.useKeyManagerFactory()) { +- if (keyManagerFactory != null) { +- throw new IllegalArgumentException( +- "KeyManagerFactory not supported"); +- } +- if (keyCertChain != null/* && key != null*/) { +- setKeyMaterial(ctx, keyCertChain, key, keyPassword); +- } +- } else { +- // javadocs state that keyManagerFactory has precedent over keyCertChain +- if (keyManagerFactory == null && keyCertChain != null) { +- char[] keyPasswordChars = keyStorePassword(keyPassword); +- KeyStore ks = buildKeyStore(keyCertChain, key, keyPasswordChars, keyStore); +- if (ks.aliases().hasMoreElements()) { +- keyManagerFactory = new OpenSslX509KeyManagerFactory(); +- } else { +- keyManagerFactory = new OpenSslCachingX509KeyManagerFactory( +- KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())); +- } +- keyManagerFactory.init(ks, keyPasswordChars); +- keyMaterialProvider = providerFor(keyManagerFactory, keyPassword); +- } else if (keyManagerFactory != null) { +- keyMaterialProvider = providerFor(keyManagerFactory, keyPassword); +- } +- +- if (keyMaterialProvider != null) { +- OpenSslKeyMaterialManager materialManager = new OpenSslKeyMaterialManager(keyMaterialProvider); +- SSLContext.setCertificateCallback(ctx, new OpenSslClientCertificateCallback( +- engineMap, materialManager)); +- } +- } +- } catch (Exception e) { +- throw new SSLException("failed to set certificate and key", e); - } -- } else { -- final int limit = dst.limit(); -- final int len = min(MAX_ENCRYPTED_PACKET_LENGTH, limit - pos); -- final ByteBuf buf = alloc.directBuffer(len); +- +- // On the client side we always need to use SSL_CVERIFY_OPTIONAL (which will translate to SSL_VERIFY_PEER) +- // to ensure that when the TrustManager throws we will produce the correct alert back to the server. +- // +- // See: +- // - https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html +- // - https://github.com/netty/netty/issues/8942 +- SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_OPTIONAL, VERIFY_DEPTH); +- - try { -- sslRead = SSL.readFromSSL(ssl, memoryAddress(buf), len); -- if (sslRead > 0) { -- dst.limit(pos + sslRead); -- buf.getBytes(buf.readerIndex(), dst); -- dst.limit(limit); +- if (trustCertCollection != null) { +- trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory, keyStore); +- } else if (trustManagerFactory == null) { +- trustManagerFactory = TrustManagerFactory.getInstance( +- TrustManagerFactory.getDefaultAlgorithm()); +- trustManagerFactory.init((KeyStore) null); - } -- } finally { -- buf.release(); +- final X509TrustManager manager = chooseTrustManager( +- trustManagerFactory.getTrustManagers(), resumptionController); +- +- // IMPORTANT: The callbacks set for verification must be static to prevent memory leak as +- // otherwise the context can never be collected. This is because the JNI code holds +- // a global reference to the callbacks. +- // +- // See https://github.com/netty/netty/issues/5372 +- +- setVerifyCallback(ctx, engineMap, manager); +- } catch (Exception e) { +- if (keyMaterialProvider != null) { +- keyMaterialProvider.destroy(); +- } +- throw new SSLException("unable to setup trustmanager", e); +- } +- OpenSslClientSessionContext context = new OpenSslClientSessionContext(thiz, keyMaterialProvider); +- context.setSessionCacheEnabled(CLIENT_ENABLE_SESSION_CACHE); +- if (sessionCacheSize > 0) { +- context.setSessionCacheSize((int) Math.min(sessionCacheSize, Integer.MAX_VALUE)); +- } +- if (sessionTimeout > 0) { +- context.setSessionTimeout((int) Math.min(sessionTimeout, Integer.MAX_VALUE)); +- } +- +- if (CLIENT_ENABLE_SESSION_TICKET) { +- context.setTicketKeys(); +- } +- +- keyMaterialProvider = null; +- return context; +- } finally { +- if (keyMaterialProvider != null) { +- keyMaterialProvider.destroy(); - } - } +- } - -- return sslRead; +- @SuppressJava6Requirement(reason = "Guarded by java version check") +- private static void setVerifyCallback(long ctx, OpenSslEngineMap engineMap, X509TrustManager manager) { +- // Use this to prevent an error when running on java < 7 +- if (useExtendedTrustManager(manager)) { +- SSLContext.setCertVerifyCallback(ctx, +- new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager)); +- } else { +- SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager)); +- } - } - -- @Override -- public final SSLEngineResult wrap( -- final ByteBuffer[] srcs, int offset, final int length, final ByteBuffer dst) throws SSLException { -- // Throw required runtime exceptions -- if (srcs == null) { -- throw new IllegalArgumentException("srcs is null"); +- static final class OpenSslClientSessionContext extends OpenSslSessionContext { +- OpenSslClientSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) { +- super(context, provider, SSL.SSL_SESS_CACHE_CLIENT, new OpenSslClientSessionCache(context.engineMap)); - } -- if (dst == null) { -- throw new IllegalArgumentException("dst is null"); +- } +- +- private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier { +- private final X509TrustManager manager; +- +- TrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509TrustManager manager) { +- super(engineMap); +- this.manager = manager; - } - -- if (offset >= srcs.length || offset + length > srcs.length) { -- throw new IndexOutOfBoundsException( -- "offset: " + offset + ", length: " + length + -- " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))"); +- @Override +- void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth) +- throws Exception { +- manager.checkServerTrusted(peerCerts, auth); - } +- } - -- if (dst.isReadOnly()) { -- throw new ReadOnlyBufferException(); +- @SuppressJava6Requirement(reason = "Usage guarded by java version check") +- private static final class ExtendedTrustManagerVerifyCallback extends AbstractCertificateVerifier { +- private final X509ExtendedTrustManager manager; +- +- ExtendedTrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509ExtendedTrustManager manager) { +- super(engineMap); +- this.manager = manager; - } - -- synchronized (this) { -- if (isOutboundDone()) { -- // All drained in the outbound buffer -- return isInboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_UNWRAP_CLOSED; -- } +- @Override +- void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth) +- throws Exception { +- manager.checkServerTrusted(peerCerts, auth, engine); +- } +- } - -- int bytesProduced = 0; -- ByteBuf bioReadCopyBuf = null; +- private static final class OpenSslClientCertificateCallback implements CertificateCallback { +- private final OpenSslEngineMap engineMap; +- private final OpenSslKeyMaterialManager keyManagerHolder; +- +- OpenSslClientCertificateCallback(OpenSslEngineMap engineMap, OpenSslKeyMaterialManager keyManagerHolder) { +- this.engineMap = engineMap; +- this.keyManagerHolder = keyManagerHolder; +- } +- +- @Override +- public void handle(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals) throws Exception { +- final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); +- // May be null if it was destroyed in the meantime. +- if (engine == null) { +- return; +- } - try { -- // Setup the BIO buffer so that we directly write the encryption results into dst. -- if (dst.isDirect()) { -- SSL.bioSetByteBuffer(networkBIO, Buffer.address(dst) + dst.position(), dst.remaining(), -- true); +- final Set keyTypesSet = supportedClientKeyTypes(keyTypeBytes); +- final String[] keyTypes = keyTypesSet.toArray(EmptyArrays.EMPTY_STRINGS); +- final X500Principal[] issuers; +- if (asn1DerEncodedPrincipals == null) { +- issuers = null; - } else { -- bioReadCopyBuf = alloc.directBuffer(dst.remaining()); -- SSL.bioSetByteBuffer(networkBIO, memoryAddress(bioReadCopyBuf), bioReadCopyBuf.writableBytes(), -- true); +- issuers = new X500Principal[asn1DerEncodedPrincipals.length]; +- for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) { +- issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]); +- } - } +- keyManagerHolder.setKeyMaterialClientSide(engine, keyTypes, issuers); +- } catch (Throwable cause) { +- engine.initHandshakeException(cause); +- if (cause instanceof Exception) { +- throw (Exception) cause; +- } +- throw new SSLException(cause); +- } +- } - -- int bioLengthBefore = SSL.bioLengthByteBuffer(networkBIO); -- -- // Explicit use outboundClosed as we want to drain any bytes that are still present. -- if (outboundClosed) { -- // There is something left to drain. -- // See https://github.com/netty/netty/issues/6260 -- bytesProduced = SSL.bioFlushByteBuffer(networkBIO); -- if (bytesProduced <= 0) { -- return newResultMayFinishHandshake(NOT_HANDSHAKING, 0, 0); -- } -- // It is possible when the outbound was closed there was not enough room in the non-application -- // buffers to hold the close_notify. We should keep trying to close until we consume all the data -- // OpenSSL can give us. -- if (!doSSLShutdown()) { -- return newResultMayFinishHandshake(NOT_HANDSHAKING, 0, bytesProduced); -- } -- bytesProduced = bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); -- return newResultMayFinishHandshake(NEED_WRAP, 0, bytesProduced); +- /** +- * Gets the supported key types for client certificates. +- * +- * @param clientCertificateTypes {@code ClientCertificateType} values provided by the server. +- * See https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml. +- * @return supported key types that can be used in {@code X509KeyManager.chooseClientAlias} and +- * {@code X509ExtendedKeyManager.chooseEngineClientAlias}. +- */ +- private static Set supportedClientKeyTypes(byte[] clientCertificateTypes) { +- if (clientCertificateTypes == null) { +- // Try all of the supported key types. +- return SUPPORTED_KEY_TYPES; +- } +- Set result = new HashSet(clientCertificateTypes.length); +- for (byte keyTypeCode : clientCertificateTypes) { +- String keyType = clientKeyType(keyTypeCode); +- if (keyType == null) { +- // Unsupported client key type -- ignore +- continue; - } +- result.add(keyType); +- } +- return result; +- } - -- // Flush any data that may be implicitly generated by OpenSSL (handshake, close, etc..). -- SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING; -- // Prepare OpenSSL to work in server mode and receive handshake -- if (handshakeState != HandshakeState.FINISHED) { -- if (handshakeState != HandshakeState.STARTED_EXPLICITLY) { -- // Update accepted so we know we triggered the handshake via wrap -- handshakeState = HandshakeState.STARTED_IMPLICITLY; -- } +- private static String clientKeyType(byte clientCertificateType) { +- // See also https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml +- switch (clientCertificateType) { +- case CertificateCallback.TLS_CT_RSA_SIGN: +- return OpenSslKeyMaterialManager.KEY_TYPE_RSA; // RFC rsa_sign +- case CertificateCallback.TLS_CT_RSA_FIXED_DH: +- return OpenSslKeyMaterialManager.KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh +- case CertificateCallback.TLS_CT_ECDSA_SIGN: +- return OpenSslKeyMaterialManager.KEY_TYPE_EC; // RFC ecdsa_sign +- case CertificateCallback.TLS_CT_RSA_FIXED_ECDH: +- return OpenSslKeyMaterialManager.KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh +- case CertificateCallback.TLS_CT_ECDSA_FIXED_ECDH: +- return OpenSslKeyMaterialManager.KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh +- default: +- return null; +- } +- } +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java +deleted file mode 100644 +index 59c5cb0..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslContext.java ++++ /dev/null +@@ -1,1173 +0,0 @@ +-/* +- * Copyright 2016 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; - -- // Flush any data that may have been written implicitly during the handshake by OpenSSL. -- bytesProduced = SSL.bioFlushByteBuffer(networkBIO); +-import io.netty.buffer.ByteBuf; +-import io.netty.buffer.ByteBufAllocator; +-import io.netty.handler.ssl.util.LazyX509Certificate; +-import io.netty.internal.tcnative.AsyncSSLPrivateKeyMethod; +-import io.netty.internal.tcnative.CertificateCompressionAlgo; +-import io.netty.internal.tcnative.CertificateVerifier; +-import io.netty.internal.tcnative.ResultCallback; +-import io.netty.internal.tcnative.SSL; +-import io.netty.internal.tcnative.SSLContext; +-import io.netty.internal.tcnative.SSLPrivateKeyMethod; +-import io.netty.util.AbstractReferenceCounted; +-import io.netty.util.ReferenceCounted; +-import io.netty.util.ResourceLeakDetector; +-import io.netty.util.ResourceLeakDetectorFactory; +-import io.netty.util.ResourceLeakTracker; +-import io.netty.util.concurrent.Future; +-import io.netty.util.concurrent.FutureListener; +-import io.netty.util.concurrent.ImmediateExecutor; +-import io.netty.util.internal.EmptyArrays; +-import io.netty.util.internal.PlatformDependent; +-import io.netty.util.internal.StringUtil; +-import io.netty.util.internal.SuppressJava6Requirement; +-import io.netty.util.internal.SystemPropertyUtil; +-import io.netty.util.internal.UnstableApi; +-import io.netty.util.internal.logging.InternalLogger; +-import io.netty.util.internal.logging.InternalLoggerFactory; - -- if (bytesProduced > 0 && handshakeException != null) { -- // TODO(scott): It is possible that when the handshake failed there was not enough room in the -- // non-application buffers to hold the alert. We should get all the data before progressing on. -- // However I'm not aware of a way to do this with the OpenSSL APIs. -- // See https://github.com/netty/netty/issues/6385. +-import java.security.PrivateKey; +-import java.security.SignatureException; +-import java.security.cert.CertPathValidatorException; +-import java.security.cert.Certificate; +-import java.security.cert.CertificateExpiredException; +-import java.security.cert.CertificateNotYetValidException; +-import java.security.cert.CertificateRevokedException; +-import java.security.cert.X509Certificate; +-import java.util.ArrayList; +-import java.util.Collections; +-import java.util.LinkedHashSet; +-import java.util.List; +-import java.util.Map; +-import java.util.concurrent.Executor; +-import java.util.concurrent.locks.Lock; +-import java.util.concurrent.locks.ReadWriteLock; +-import java.util.concurrent.locks.ReentrantReadWriteLock; - -- // We produced / consumed some data during the handshake, signal back to the caller. -- // If there is a handshake exception and we have produced data, we should send the data before -- // we allow handshake() to throw the handshake exception. -- return newResult(NEED_WRAP, 0, bytesProduced); -- } +-import javax.net.ssl.KeyManager; +-import javax.net.ssl.KeyManagerFactory; +-import javax.net.ssl.SSLEngine; +-import javax.net.ssl.SSLException; +-import javax.net.ssl.SSLHandshakeException; +-import javax.net.ssl.TrustManager; +-import javax.net.ssl.X509ExtendedTrustManager; +-import javax.net.ssl.X509KeyManager; +-import javax.net.ssl.X509TrustManager; - -- status = handshake(); +-import static io.netty.handler.ssl.OpenSsl.DEFAULT_CIPHERS; +-import static io.netty.handler.ssl.OpenSsl.availableJavaCipherSuites; +-import static io.netty.util.internal.ObjectUtil.checkNotNull; +-import static io.netty.util.internal.ObjectUtil.checkNonEmpty; +-import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; - -- if (renegotiationPending && status == FINISHED) { -- // If renegotiationPending is true that means when we attempted to start renegotiation -- // the BIO buffer didn't have enough space to hold the HelloRequest which prompts the -- // client to initiate a renegotiation. At this point the HelloRequest has been written -- // so we can actually start the handshake process. -- renegotiationPending = false; -- SSL.setState(ssl, SSL.SSL_ST_ACCEPT); -- handshakeState = HandshakeState.STARTED_EXPLICITLY; -- status = handshake(); -- } +-/** +- * An implementation of {@link SslContext} which works with libraries that support the +- * OpenSsl C library API. +- *

Instances of this class must be {@link #release() released} or else native memory will leak! +- * +- *

Instances of this class must not be released before any {@link ReferenceCountedOpenSslEngine} +- * which depends upon the instance of this class is released. Otherwise if any method of +- * {@link ReferenceCountedOpenSslEngine} is called which uses this class's JNI resources the JVM may crash. +- */ +-public abstract class ReferenceCountedOpenSslContext extends SslContext implements ReferenceCounted { +- private static final InternalLogger logger = +- InternalLoggerFactory.getInstance(ReferenceCountedOpenSslContext.class); - -- // Handshake may have generated more data, for example if the internal SSL buffer is small -- // we may have freed up space by flushing above. -- bytesProduced = bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); +- private static final int DEFAULT_BIO_NON_APPLICATION_BUFFER_SIZE = Math.max(1, +- SystemPropertyUtil.getInt("io.netty.handler.ssl.openssl.bioNonApplicationBufferSize", +- 2048)); +- // Let's use tasks by default but still allow the user to disable it via system property just in case. +- static final boolean USE_TASKS = +- SystemPropertyUtil.getBoolean("io.netty.handler.ssl.openssl.useTasks", true); +- private static final Integer DH_KEY_LENGTH; +- private static final ResourceLeakDetector leakDetector = +- ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslContext.class); - -- if (bytesProduced > 0) { -- // If we have filled up the dst buffer and we have not finished the handshake we should try to -- // wrap again. Otherwise we should only try to wrap again if there is still data pending in -- // SSL buffers. -- return newResult(mayFinishHandshake(status != FINISHED ? -- bytesProduced == bioLengthBefore ? NEED_WRAP : -- getHandshakeStatus(SSL.bioLengthNonApplication(networkBIO)) : FINISHED), -- 0, bytesProduced); -- } +- // TODO: Maybe make configurable ? +- protected static final int VERIFY_DEPTH = 10; - -- if (status == NEED_UNWRAP) { -- // Signal if the outbound is done or not. -- return isOutboundDone() ? NEED_UNWRAP_CLOSED : NEED_UNWRAP_OK; -- } +- static final boolean CLIENT_ENABLE_SESSION_TICKET = +- SystemPropertyUtil.getBoolean("jdk.tls.client.enableSessionTicketExtension", false); - -- // Explicit use outboundClosed and not outboundClosed() as we want to drain any bytes that are -- // still present. -- if (outboundClosed) { -- bytesProduced = SSL.bioFlushByteBuffer(networkBIO); -- return newResultMayFinishHandshake(status, 0, bytesProduced); -- } -- } +- static final boolean CLIENT_ENABLE_SESSION_TICKET_TLSV13 = +- SystemPropertyUtil.getBoolean("jdk.tls.client.enableSessionTicketExtension", true); - -- int srcsLen = 0; -- final int endOffset = offset + length; -- for (int i = offset; i < endOffset; ++i) { -- final ByteBuffer src = srcs[i]; -- if (src == null) { -- throw new IllegalArgumentException("srcs[" + i + "] is null"); -- } -- if (srcsLen == MAX_PLAINTEXT_LENGTH) { -- continue; -- } +- static final boolean SERVER_ENABLE_SESSION_TICKET = +- SystemPropertyUtil.getBoolean("jdk.tls.server.enableSessionTicketExtension", false); - -- srcsLen += src.remaining(); -- if (srcsLen > MAX_PLAINTEXT_LENGTH || srcsLen < 0) { -- // If srcLen > MAX_PLAINTEXT_LENGTH or secLen < 0 just set it to MAX_PLAINTEXT_LENGTH. -- // This also help us to guard against overflow. -- // We not break out here as we still need to check for null entries in srcs[]. -- srcsLen = MAX_PLAINTEXT_LENGTH; -- } -- } +- static final boolean SERVER_ENABLE_SESSION_TICKET_TLSV13 = +- SystemPropertyUtil.getBoolean("jdk.tls.server.enableSessionTicketExtension", true); - -- // we will only produce a single TLS packet, and we don't aggregate src buffers, -- // so we always fix the number of buffers to 1 when checking if the dst buffer is large enough. -- if (dst.remaining() < calculateOutNetBufSize(srcsLen, 1)) { -- return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0); -- } +- static final boolean SERVER_ENABLE_SESSION_CACHE = +- SystemPropertyUtil.getBoolean("io.netty.handler.ssl.openssl.sessionCacheServer", true); +- static final boolean CLIENT_ENABLE_SESSION_CACHE = +- SystemPropertyUtil.getBoolean("io.netty.handler.ssl.openssl.sessionCacheClient", true); - -- // There was no pending data in the network BIO -- encrypt any application data -- int bytesConsumed = 0; -- // Flush any data that may have been written implicitly by OpenSSL in case a shutdown/alert occurs. -- bytesProduced = SSL.bioFlushByteBuffer(networkBIO); -- for (; offset < endOffset; ++offset) { -- final ByteBuffer src = srcs[offset]; -- final int remaining = src.remaining(); -- if (remaining == 0) { -- continue; -- } +- /** +- * The OpenSSL SSL_CTX object. +- * +- * {@link #ctxLock} must be hold while using ctx! +- */ +- protected long ctx; +- private final List unmodifiableCiphers; +- private final OpenSslApplicationProtocolNegotiator apn; +- private final int mode; - -- // Write plaintext application data to the SSL engine -- int bytesWritten = writePlaintextData(src, min(remaining, MAX_PLAINTEXT_LENGTH - bytesConsumed)); +- // Reference Counting +- private final ResourceLeakTracker leak; +- private final AbstractReferenceCounted refCnt = new AbstractReferenceCounted() { +- @Override +- public ReferenceCounted touch(Object hint) { +- if (leak != null) { +- leak.record(hint); +- } - -- if (bytesWritten > 0) { -- bytesConsumed += bytesWritten; +- return ReferenceCountedOpenSslContext.this; +- } - -- // Determine how much encrypted data was generated: -- final int pendingNow = SSL.bioLengthByteBuffer(networkBIO); -- bytesProduced += bioLengthBefore - pendingNow; -- bioLengthBefore = pendingNow; +- @Override +- protected void deallocate() { +- destroy(); +- if (leak != null) { +- boolean closed = leak.close(ReferenceCountedOpenSslContext.this); +- assert closed; +- } +- } +- }; - -- return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); -- } else { -- int sslError = SSL.getError(ssl, bytesWritten); -- if (sslError == SSL.SSL_ERROR_ZERO_RETURN) { -- // This means the connection was shutdown correctly, close inbound and outbound -- if (!receivedShutdown) { -- closeAll(); +- final Certificate[] keyCertChain; +- final ClientAuth clientAuth; +- final String[] protocols; +- final String endpointIdentificationAlgorithm; +- final boolean hasTLSv13Cipher; - -- bytesProduced += bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); +- final boolean enableOcsp; +- final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap(); +- final ReadWriteLock ctxLock = new ReentrantReadWriteLock(); - -- // If we have filled up the dst buffer and we have not finished the handshake we should -- // try to wrap again. Otherwise we should only try to wrap again if there is still data -- // pending in SSL buffers. -- SSLEngineResult.HandshakeStatus hs = mayFinishHandshake( -- status != FINISHED ? bytesProduced == dst.remaining() ? NEED_WRAP -- : getHandshakeStatus(SSL.bioLengthNonApplication(networkBIO)) -- : FINISHED); -- return newResult(hs, bytesConsumed, bytesProduced); -- } +- private volatile int bioNonApplicationBufferSize = DEFAULT_BIO_NON_APPLICATION_BUFFER_SIZE; - -- return newResult(NOT_HANDSHAKING, bytesConsumed, bytesProduced); -- } else if (sslError == SSL.SSL_ERROR_WANT_READ) { -- // If there is no pending data to read from BIO we should go back to event loop and try -- // to read more data [1]. It is also possible that event loop will detect the socket has -- // been closed. [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html -- return newResult(NEED_UNWRAP, bytesConsumed, bytesProduced); -- } else if (sslError == SSL.SSL_ERROR_WANT_WRITE) { -- // SSL_ERROR_WANT_WRITE typically means that the underlying transport is not writable -- // and we should set the "want write" flag on the selector and try again when the -- // underlying transport is writable [1]. However we are not directly writing to the -- // underlying transport and instead writing to a BIO buffer. The OpenSsl documentation -- // says we should do the following [1]: -- // -- // "When using a buffering BIO, like a BIO pair, data must be written into or retrieved -- // out of the BIO before being able to continue." -- // -- // So we attempt to drain the BIO buffer below, but if there is no data this condition -- // is undefined and we assume their is a fatal error with the openssl engine and close. -- // [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html -- return newResult(NEED_WRAP, bytesConsumed, bytesProduced); -- } else { -- // Everything else is considered as error -- throw shutdownWithError("SSL_write"); -- } -- } +- @SuppressWarnings("deprecation") +- static final OpenSslApplicationProtocolNegotiator NONE_PROTOCOL_NEGOTIATOR = +- new OpenSslApplicationProtocolNegotiator() { +- @Override +- public ApplicationProtocolConfig.Protocol protocol() { +- return ApplicationProtocolConfig.Protocol.NONE; - } -- return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); -- } finally { -- SSL.bioClearByteBuffer(networkBIO); -- if (bioReadCopyBuf == null) { -- dst.position(dst.position() + bytesProduced); -- } else { -- assert bioReadCopyBuf.readableBytes() <= dst.remaining() : "The destination buffer " + dst + -- " didn't have enough remaining space to hold the encrypted content in " + bioReadCopyBuf; -- dst.put(bioReadCopyBuf.internalNioBuffer(bioReadCopyBuf.readerIndex(), bytesProduced)); -- bioReadCopyBuf.release(); +- +- @Override +- public List protocols() { +- return Collections.emptyList(); - } -- } -- } -- } - -- private SSLEngineResult newResult(SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) { -- return newResult(OK, hs, bytesConsumed, bytesProduced); -- } +- @Override +- public ApplicationProtocolConfig.SelectorFailureBehavior selectorFailureBehavior() { +- return ApplicationProtocolConfig.SelectorFailureBehavior.CHOOSE_MY_LAST_PROTOCOL; +- } - -- private SSLEngineResult newResult(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus hs, -- int bytesConsumed, int bytesProduced) { -- // If isOutboundDone, then the data from the network BIO -- // was the close_notify message and all was consumed we are not required to wait -- // for the receipt the peer's close_notify message -- shutdown. -- if (isOutboundDone()) { -- if (isInboundDone()) { -- // If the inbound was done as well, we need to ensure we return NOT_HANDSHAKING to signal we are done. -- hs = NOT_HANDSHAKING; +- @Override +- public ApplicationProtocolConfig.SelectedListenerFailureBehavior selectedListenerFailureBehavior() { +- return ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT; +- } +- }; - -- // As the inbound and the outbound is done we can shutdown the engine now. -- shutdown(); +- static { +- Integer dhLen = null; +- +- try { +- String dhKeySize = SystemPropertyUtil.get("jdk.tls.ephemeralDHKeySize"); +- if (dhKeySize != null) { +- try { +- dhLen = Integer.valueOf(dhKeySize); +- } catch (NumberFormatException e) { +- logger.debug("ReferenceCountedOpenSslContext supports -Djdk.tls.ephemeralDHKeySize={int}, but got: " +- + dhKeySize); +- } - } -- return new SSLEngineResult(CLOSED, hs, bytesConsumed, bytesProduced); +- } catch (Throwable ignore) { +- // ignore - } -- return new SSLEngineResult(status, hs, bytesConsumed, bytesProduced); +- DH_KEY_LENGTH = dhLen; - } - -- private SSLEngineResult newResultMayFinishHandshake(SSLEngineResult.HandshakeStatus hs, -- int bytesConsumed, int bytesProduced) throws SSLException { -- return newResult(mayFinishHandshake(hs != FINISHED ? getHandshakeStatus() : FINISHED), -- bytesConsumed, bytesProduced); -- } +- final boolean tlsFalseStart; - -- private SSLEngineResult newResultMayFinishHandshake(SSLEngineResult.Status status, -- SSLEngineResult.HandshakeStatus hs, -- int bytesConsumed, int bytesProduced) throws SSLException { -- return newResult(status, mayFinishHandshake(hs != FINISHED ? getHandshakeStatus() : FINISHED), -- bytesConsumed, bytesProduced); -- } +- ReferenceCountedOpenSslContext(Iterable ciphers, CipherSuiteFilter cipherFilter, +- OpenSslApplicationProtocolNegotiator apn, int mode, Certificate[] keyCertChain, +- ClientAuth clientAuth, String[] protocols, boolean startTls, +- String endpointIdentificationAlgorithm, boolean enableOcsp, +- boolean leakDetection, ResumptionController resumptionController, +- Map.Entry, Object>... ctxOptions) +- throws SSLException { +- super(startTls, resumptionController); - -- /** -- * Log the error, shutdown the engine and throw an exception. -- */ -- private SSLException shutdownWithError(String operations) { -- String err = SSL.getLastError(); -- return shutdownWithError(operations, err); -- } +- OpenSsl.ensureAvailability(); - -- private SSLException shutdownWithError(String operation, String err) { -- if (logger.isDebugEnabled()) { -- logger.debug("{} failed: OpenSSL error: {}", operation, err); +- if (enableOcsp && !OpenSsl.isOcspSupported()) { +- throw new IllegalStateException("OCSP is not supported."); - } - -- // There was an internal error -- shutdown -- shutdown(); -- if (handshakeState == HandshakeState.FINISHED) { -- return new SSLException(err); +- if (mode != SSL.SSL_MODE_SERVER && mode != SSL.SSL_MODE_CLIENT) { +- throw new IllegalArgumentException("mode most be either SSL.SSL_MODE_SERVER or SSL.SSL_MODE_CLIENT"); - } -- return new SSLHandshakeException(err); -- } -- -- public final SSLEngineResult unwrap( -- final ByteBuffer[] srcs, int srcsOffset, final int srcsLength, -- final ByteBuffer[] dsts, int dstsOffset, final int dstsLength) throws SSLException { - -- // Throw required runtime exceptions -- if (srcs == null) { -- throw new NullPointerException("srcs"); -- } -- if (srcsOffset >= srcs.length -- || srcsOffset + srcsLength > srcs.length) { -- throw new IndexOutOfBoundsException( -- "offset: " + srcsOffset + ", length: " + srcsLength + -- " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))"); -- } -- if (dsts == null) { -- throw new IllegalArgumentException("dsts is null"); -- } -- if (dstsOffset >= dsts.length || dstsOffset + dstsLength > dsts.length) { -- throw new IndexOutOfBoundsException( -- "offset: " + dstsOffset + ", length: " + dstsLength + -- " (expected: offset <= offset + length <= dsts.length (" + dsts.length + "))"); -- } -- long capacity = 0; -- final int dstsEndOffset = dstsOffset + dstsLength; -- for (int i = dstsOffset; i < dstsEndOffset; i ++) { -- ByteBuffer dst = dsts[i]; -- if (dst == null) { -- throw new IllegalArgumentException("dsts[" + i + "] is null"); -- } -- if (dst.isReadOnly()) { -- throw new ReadOnlyBufferException(); +- boolean tlsFalseStart = false; +- boolean useTasks = USE_TASKS; +- OpenSslPrivateKeyMethod privateKeyMethod = null; +- OpenSslAsyncPrivateKeyMethod asyncPrivateKeyMethod = null; +- OpenSslCertificateCompressionConfig certCompressionConfig = null; +- Integer maxCertificateList = null; +- +- if (ctxOptions != null) { +- for (Map.Entry, Object> ctxOpt : ctxOptions) { +- SslContextOption option = ctxOpt.getKey(); +- +- if (option == OpenSslContextOption.TLS_FALSE_START) { +- tlsFalseStart = (Boolean) ctxOpt.getValue(); +- } else if (option == OpenSslContextOption.USE_TASKS) { +- useTasks = (Boolean) ctxOpt.getValue(); +- } else if (option == OpenSslContextOption.PRIVATE_KEY_METHOD) { +- privateKeyMethod = (OpenSslPrivateKeyMethod) ctxOpt.getValue(); +- } else if (option == OpenSslContextOption.ASYNC_PRIVATE_KEY_METHOD) { +- asyncPrivateKeyMethod = (OpenSslAsyncPrivateKeyMethod) ctxOpt.getValue(); +- } else if (option == OpenSslContextOption.CERTIFICATE_COMPRESSION_ALGORITHMS) { +- certCompressionConfig = (OpenSslCertificateCompressionConfig) ctxOpt.getValue(); +- } else if (option == OpenSslContextOption.MAX_CERTIFICATE_LIST_BYTES) { +- maxCertificateList = (Integer) ctxOpt.getValue(); +- } else { +- logger.debug("Skipping unsupported " + SslContextOption.class.getSimpleName() +- + ": " + ctxOpt.getKey()); +- } - } -- capacity += dst.remaining(); - } -- -- final int srcsEndOffset = srcsOffset + srcsLength; -- long len = 0; -- for (int i = srcsOffset; i < srcsEndOffset; i++) { -- ByteBuffer src = srcs[i]; -- if (src == null) { -- throw new IllegalArgumentException("srcs[" + i + "] is null"); -- } -- len += src.remaining(); +- if (privateKeyMethod != null && asyncPrivateKeyMethod != null) { +- throw new IllegalArgumentException("You can either only use " +- + OpenSslAsyncPrivateKeyMethod.class.getSimpleName() + " or " +- + OpenSslPrivateKeyMethod.class.getSimpleName()); - } - -- synchronized (this) { -- if (isInboundDone()) { -- return isOutboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_WRAP_CLOSED; -- } +- this.tlsFalseStart = tlsFalseStart; - -- SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING; -- // Prepare OpenSSL to work in server mode and receive handshake -- if (handshakeState != HandshakeState.FINISHED) { -- if (handshakeState != HandshakeState.STARTED_EXPLICITLY) { -- // Update accepted so we know we triggered the handshake via wrap -- handshakeState = HandshakeState.STARTED_IMPLICITLY; -- } +- leak = leakDetection ? leakDetector.track(this) : null; +- this.mode = mode; +- this.clientAuth = isServer() ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE; +- this.protocols = protocols == null ? OpenSsl.defaultProtocols(mode == SSL.SSL_MODE_CLIENT) : protocols; +- this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm; +- this.enableOcsp = enableOcsp; - -- status = handshake(); -- if (status == NEED_WRAP) { -- return NEED_WRAP_OK; -- } -- // Check if the inbound is considered to be closed if so let us try to wrap again. -- if (isInboundDone) { -- return NEED_WRAP_CLOSED; +- this.keyCertChain = keyCertChain == null ? null : keyCertChain.clone(); +- +- String[] suites = checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites( +- ciphers, DEFAULT_CIPHERS, availableJavaCipherSuites()); +- // Filter out duplicates. +- LinkedHashSet suitesSet = new LinkedHashSet(suites.length); +- Collections.addAll(suitesSet, suites); +- unmodifiableCiphers = new ArrayList(suitesSet); +- +- this.apn = checkNotNull(apn, "apn"); +- +- // Create a new SSL_CTX and configure it. +- boolean success = false; +- try { +- boolean tlsv13Supported = OpenSsl.isTlsv13Supported(); +- boolean anyTlsv13Ciphers = false; +- try { +- int protocolOpts = SSL.SSL_PROTOCOL_SSLV3 | SSL.SSL_PROTOCOL_TLSV1 | +- SSL.SSL_PROTOCOL_TLSV1_1 | SSL.SSL_PROTOCOL_TLSV1_2; +- if (tlsv13Supported) { +- protocolOpts |= SSL.SSL_PROTOCOL_TLSV1_3; - } +- ctx = SSLContext.make(protocolOpts, mode); +- } catch (Exception e) { +- throw new SSLException("failed to create an SSL_CTX", e); - } - -- if (len < SSL_RECORD_HEADER_LENGTH) { -- return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); +- StringBuilder cipherBuilder = new StringBuilder(); +- StringBuilder cipherTLSv13Builder = new StringBuilder(); +- +- /* List the ciphers that are permitted to negotiate. */ +- try { +- if (unmodifiableCiphers.isEmpty()) { +- // Set non TLSv1.3 ciphers. +- SSLContext.setCipherSuite(ctx, StringUtil.EMPTY_STRING, false); +- if (tlsv13Supported) { +- // Set TLSv1.3 ciphers. +- SSLContext.setCipherSuite(ctx, StringUtil.EMPTY_STRING, true); +- } +- } else { +- CipherSuiteConverter.convertToCipherStrings( +- unmodifiableCiphers, cipherBuilder, cipherTLSv13Builder, OpenSsl.isBoringSSL()); +- +- // Set non TLSv1.3 ciphers. +- SSLContext.setCipherSuite(ctx, cipherBuilder.toString(), false); +- if (tlsv13Supported) { +- // Set TLSv1.3 ciphers. +- String tlsv13Ciphers = OpenSsl.checkTls13Ciphers(logger, cipherTLSv13Builder.toString()); +- SSLContext.setCipherSuite(ctx, tlsv13Ciphers, true); +- if (!tlsv13Ciphers.isEmpty()) { +- anyTlsv13Ciphers = true; +- } +- } +- } +- } catch (SSLException e) { +- throw e; +- } catch (Exception e) { +- throw new SSLException("failed to set cipher suite: " + unmodifiableCiphers, e); - } - -- int packetLength = SslUtils.getEncryptedPacketLength(srcs, srcsOffset); +- int options = SSLContext.getOptions(ctx) | +- SSL.SSL_OP_NO_SSLv2 | +- SSL.SSL_OP_NO_SSLv3 | +- // Disable TLSv1 and TLSv1.1 by default as these are not considered secure anymore +- // and the JDK is doing the same: +- // https://www.oracle.com/java/technologies/javase/8u291-relnotes.html +- SSL.SSL_OP_NO_TLSv1 | +- SSL.SSL_OP_NO_TLSv1_1 | +- +- SSL.SSL_OP_CIPHER_SERVER_PREFERENCE | +- +- // We do not support compression at the moment so we should explicitly disable it. +- SSL.SSL_OP_NO_COMPRESSION | - -- if (packetLength == SslUtils.NOT_ENCRYPTED) { -- throw new NotSslRecordException("not an SSL/TLS record"); -- } +- // Disable ticket support by default to be more inline with SSLEngineImpl of the JDK. +- // This also let SSLSession.getId() work the same way for the JDK implementation and the +- // OpenSSLEngine. If tickets are supported SSLSession.getId() will only return an ID on the +- // server-side if it could make use of tickets. +- SSL.SSL_OP_NO_TICKET; - -- if (packetLength - SSL_RECORD_HEADER_LENGTH > capacity) { -- // No enough space in the destination buffer so signal the caller -- // that the buffer needs to be increased. -- return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0); +- if (cipherBuilder.length() == 0) { +- // No ciphers that are compatible with SSLv2 / SSLv3 / TLSv1 / TLSv1.1 / TLSv1.2 +- options |= SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 +- | SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2; - } - -- if (len < packetLength) { -- // We either have no enough data to read the packet length at all or not enough for reading -- // the whole packet. -- return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); +- if (!tlsv13Supported) { +- // Explicit disable TLSv1.3 +- // See: +- // - https://github.com/netty/netty/issues/12968 +- options |= SSL.SSL_OP_NO_TLSv1_3; - } - -- // This must always be the case when we reached here as if not we returned BUFFER_UNDERFLOW. -- assert srcsOffset < srcsEndOffset; +- hasTLSv13Cipher = anyTlsv13Ciphers; +- SSLContext.setOptions(ctx, options); - -- // This must always be the case if we reached here. -- assert capacity > 0; +- // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address may change between +- // calling OpenSSLEngine.wrap(...). +- // See https://github.com/netty/netty-tcnative/issues/100 +- SSLContext.setMode(ctx, SSLContext.getMode(ctx) | SSL.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - -- // Number of produced bytes -- int bytesProduced = 0; -- int bytesConsumed = 0; -- try { -- for (; srcsOffset < srcsEndOffset; ++srcsOffset) { -- ByteBuffer src = srcs[srcsOffset]; -- int remaining = src.remaining(); -- if (remaining == 0) { -- // We must skip empty buffers as BIO_write will return 0 if asked to write something -- // with length 0. -- continue; -- } -- // Write more encrypted data into the BIO. Ensure we only read one packet at a time as -- // stated in the SSLEngine javadocs. -- int pendingEncryptedBytes = min(packetLength, remaining); -- ByteBuf bioWriteCopyBuf = writeEncryptedData(src, pendingEncryptedBytes); -- try { -- readLoop: -- for (; dstsOffset < dstsEndOffset; ++dstsOffset) { -- ByteBuffer dst = dsts[dstsOffset]; -- if (!dst.hasRemaining()) { -- // No space left in the destination buffer, skip it. -- continue; -- } +- if (DH_KEY_LENGTH != null) { +- SSLContext.setTmpDHLength(ctx, DH_KEY_LENGTH); +- } - -- int bytesRead = readPlaintextData(dst); -- // We are directly using the ByteBuffer memory for the write, and so we only know what -- // has been consumed after we let SSL decrypt the data. At this point we should update -- // the number of bytes consumed, update the ByteBuffer position, and release temp -- // ByteBuf. -- int localBytesConsumed = pendingEncryptedBytes - SSL.bioLengthByteBuffer(networkBIO); -- bytesConsumed += localBytesConsumed; -- packetLength -= localBytesConsumed; -- pendingEncryptedBytes -= localBytesConsumed; -- src.position(src.position() + localBytesConsumed); +- List nextProtoList = apn.protocols(); +- /* Set next protocols for next protocol negotiation extension, if specified */ +- if (!nextProtoList.isEmpty()) { +- String[] appProtocols = nextProtoList.toArray(EmptyArrays.EMPTY_STRINGS); +- int selectorBehavior = opensslSelectorFailureBehavior(apn.selectorFailureBehavior()); - -- if (bytesRead > 0) { -- bytesProduced += bytesRead; +- switch (apn.protocol()) { +- case NPN: +- SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior); +- break; +- case ALPN: +- SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior); +- break; +- case NPN_AND_ALPN: +- SSLContext.setNpnProtos(ctx, appProtocols, selectorBehavior); +- SSLContext.setAlpnProtos(ctx, appProtocols, selectorBehavior); +- break; +- default: +- throw new Error(); +- } +- } - -- if (!dst.hasRemaining()) { -- // Move to the next dst buffer as this one is full. -- continue; -- } -- if (packetLength == 0) { -- // We read everything return now. -- return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, -- bytesConsumed, bytesProduced); -- } -- // try to write again to the BIO. stop reading from it by break out of the readLoop. -- break; -- } else { -- int sslError = SSL.getError(ssl, bytesRead); -- if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) { -- // break to the outer loop as we want to read more data which means we need to -- // write more to the BIO. -- break readLoop; -- } else if (sslError == SSL.SSL_ERROR_ZERO_RETURN) { -- // This means the connection was shutdown correctly, close inbound and outbound -- if (!receivedShutdown) { -- closeAll(); -- } -- return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, -- bytesConsumed, bytesProduced); -- } else { -- return sslReadErrorResult(SSL.getLastErrorNumber(), bytesConsumed, -- bytesProduced); -- } -- } -- } +- if (enableOcsp) { +- SSLContext.enableOcsp(ctx, isClient()); +- } - -- // Either we have no more dst buffers to put the data, or no more data to generate; we are done. -- if (dstsOffset >= dstsEndOffset || packetLength == 0) { +- SSLContext.setUseTasks(ctx, useTasks); +- if (privateKeyMethod != null) { +- SSLContext.setPrivateKeyMethod(ctx, new PrivateKeyMethod(engineMap, privateKeyMethod)); +- } +- if (asyncPrivateKeyMethod != null) { +- SSLContext.setPrivateKeyMethod(ctx, new AsyncPrivateKeyMethod(engineMap, asyncPrivateKeyMethod)); +- } +- if (certCompressionConfig != null) { +- for (OpenSslCertificateCompressionConfig.AlgorithmConfig configPair : certCompressionConfig) { +- final CertificateCompressionAlgo algo = new CompressionAlgorithm(engineMap, configPair.algorithm()); +- switch (configPair.mode()) { +- case Decompress: +- SSLContext.addCertificateCompressionAlgorithm( +- ctx, SSL.SSL_CERT_COMPRESSION_DIRECTION_DECOMPRESS, algo); - break; -- } -- } finally { -- if (bioWriteCopyBuf != null) { -- bioWriteCopyBuf.release(); -- } +- case Compress: +- SSLContext.addCertificateCompressionAlgorithm( +- ctx, SSL.SSL_CERT_COMPRESSION_DIRECTION_COMPRESS, algo); +- break; +- case Both: +- SSLContext.addCertificateCompressionAlgorithm( +- ctx, SSL.SSL_CERT_COMPRESSION_DIRECTION_BOTH, algo); +- break; +- default: +- throw new IllegalStateException(); - } - } -- } finally { -- SSL.bioClearByteBuffer(networkBIO); -- rejectRemoteInitiatedRenegotiation(); - } -- -- // Check to see if we received a close_notify message from the peer. -- if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) { -- closeAll(); +- if (maxCertificateList != null) { +- SSLContext.setMaxCertList(ctx, maxCertificateList); +- } +- // Set the curves. +- SSLContext.setCurvesList(ctx, OpenSsl.NAMED_GROUPS); +- success = true; +- } finally { +- if (!success) { +- release(); - } +- } +- } - -- return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced); +- private static int opensslSelectorFailureBehavior(ApplicationProtocolConfig.SelectorFailureBehavior behavior) { +- switch (behavior) { +- case NO_ADVERTISE: +- return SSL.SSL_SELECTOR_FAILURE_NO_ADVERTISE; +- case CHOOSE_MY_LAST_PROTOCOL: +- return SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL; +- default: +- throw new Error(); - } - } - -- private SSLEngineResult sslReadErrorResult(int err, int bytesConsumed, int bytesProduced) throws SSLException { -- String errStr = SSL.getErrorString(err); +- @Override +- public final List cipherSuites() { +- return unmodifiableCiphers; +- } - -- // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the -- // BIO first or can just shutdown and throw it now. -- // This is needed so we ensure close_notify etc is correctly send to the remote peer. -- // See https://github.com/netty/netty/issues/3900 -- if (SSL.bioLengthNonApplication(networkBIO) > 0) { -- if (handshakeException == null && handshakeState != HandshakeState.FINISHED) { -- // we seems to have data left that needs to be transfered and so the user needs -- // call wrap(...). Store the error so we can pick it up later. -- handshakeException = new SSLHandshakeException(errStr); -- } -- return new SSLEngineResult(OK, NEED_WRAP, bytesConsumed, bytesProduced); -- } -- throw shutdownWithError("SSL_read", errStr); +- @Override +- public ApplicationProtocolNegotiator applicationProtocolNegotiator() { +- return apn; - } - -- private void closeAll() throws SSLException { -- receivedShutdown = true; -- closeOutbound(); -- closeInbound(); +- @Override +- public final boolean isClient() { +- return mode == SSL.SSL_MODE_CLIENT; - } - -- private void rejectRemoteInitiatedRenegotiation() throws SSLHandshakeException { -- if (rejectRemoteInitiatedRenegotiation && SSL.getHandshakeCount(ssl) > 1) { -- // TODO: In future versions me may also want to send a fatal_alert to the client and so notify it -- // that the renegotiation failed. -- shutdown(); -- throw new SSLHandshakeException("remote-initiated renegotiation not allowed"); -- } +- @Override +- public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { +- return newEngine0(alloc, peerHost, peerPort, true); - } - -- public final SSLEngineResult unwrap(final ByteBuffer[] srcs, final ByteBuffer[] dsts) throws SSLException { -- return unwrap(srcs, 0, srcs.length, dsts, 0, dsts.length); +- @Override +- protected final SslHandler newHandler(ByteBufAllocator alloc, boolean startTls) { +- return new SslHandler(newEngine0(alloc, null, -1, false), startTls, ImmediateExecutor.INSTANCE, +- resumptionController); - } - -- private ByteBuffer[] singleSrcBuffer(ByteBuffer src) { -- singleSrcBuffer[0] = src; -- return singleSrcBuffer; +- @Override +- protected final SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, boolean startTls) { +- return new SslHandler(newEngine0(alloc, peerHost, peerPort, false), startTls, ImmediateExecutor.INSTANCE, +- resumptionController); - } - -- private void resetSingleSrcBuffer() { -- singleSrcBuffer[0] = null; +- @Override +- protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls, Executor executor) { +- return new SslHandler(newEngine0(alloc, null, -1, false), startTls, executor, resumptionController); - } - -- private ByteBuffer[] singleDstBuffer(ByteBuffer src) { -- singleDstBuffer[0] = src; -- return singleDstBuffer; +- @Override +- protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, +- boolean startTls, Executor executor) { +- return new SslHandler(newEngine0(alloc, peerHost, peerPort, false), false, executor, resumptionController); - } - -- private void resetSingleDstBuffer() { -- singleDstBuffer[0] = null; +- SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort, boolean jdkCompatibilityMode) { +- return new ReferenceCountedOpenSslEngine(this, alloc, peerHost, peerPort, jdkCompatibilityMode, true, +- endpointIdentificationAlgorithm); - } - +- /** +- * Returns a new server-side {@link SSLEngine} with the current configuration. +- */ - @Override -- public final synchronized SSLEngineResult unwrap( -- final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException { -- try { -- return unwrap(singleSrcBuffer(src), 0, 1, dsts, offset, length); -- } finally { -- resetSingleSrcBuffer(); +- public final SSLEngine newEngine(ByteBufAllocator alloc) { +- return newEngine(alloc, null, -1); +- } +- +- /** +- * Returns the pointer to the {@code SSL_CTX} object for this {@link ReferenceCountedOpenSslContext}. +- * Be aware that it is freed as soon as the {@link #finalize()} method is called. +- * At this point {@code 0} will be returned. +- * +- * @deprecated this method is considered unsafe as the returned pointer may be released later. Dont use it! +- */ +- @Deprecated +- public final long context() { +- return sslCtxPointer(); +- } +- +- /** +- * Returns the stats of this context. +- * +- * @deprecated use {@link #sessionContext#stats()} +- */ +- @Deprecated +- public final OpenSslSessionStats stats() { +- return sessionContext().stats(); +- } +- +- /** +- * {@deprecated Renegotiation is not supported} +- * Specify if remote initiated renegotiation is supported or not. If not supported and the remote side tries +- * to initiate a renegotiation a {@link SSLHandshakeException} will be thrown during decoding. +- */ +- @Deprecated +- public void setRejectRemoteInitiatedRenegotiation(boolean rejectRemoteInitiatedRenegotiation) { +- if (!rejectRemoteInitiatedRenegotiation) { +- throw new UnsupportedOperationException("Renegotiation is not supported"); - } - } - +- /** +- * {@deprecated Renegotiation is not supported} +- * @return {@code true} because renegotiation is not supported. +- */ +- @Deprecated +- public boolean getRejectRemoteInitiatedRenegotiation() { +- return true; +- } +- +- /** +- * Set the size of the buffer used by the BIO for non-application based writes +- * (e.g. handshake, renegotiation, etc...). +- */ +- public void setBioNonApplicationBufferSize(int bioNonApplicationBufferSize) { +- this.bioNonApplicationBufferSize = +- checkPositiveOrZero(bioNonApplicationBufferSize, "bioNonApplicationBufferSize"); +- } +- +- /** +- * Returns the size of the buffer used by the BIO for non-application based writes +- */ +- public int getBioNonApplicationBufferSize() { +- return bioNonApplicationBufferSize; +- } +- +- /** +- * Sets the SSL session ticket keys of this context. +- * +- * @deprecated use {@link OpenSslSessionContext#setTicketKeys(byte[])} +- */ +- @Deprecated +- public final void setTicketKeys(byte[] keys) { +- sessionContext().setTicketKeys(keys); +- } +- - @Override -- public final synchronized SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) throws SSLException { +- public abstract OpenSslSessionContext sessionContext(); +- +- /** +- * Returns the pointer to the {@code SSL_CTX} object for this {@link ReferenceCountedOpenSslContext}. +- * Be aware that it is freed as soon as the {@link #release()} method is called. +- * At this point {@code 0} will be returned. +- * +- * @deprecated this method is considered unsafe as the returned pointer may be released later. Dont use it! +- */ +- @Deprecated +- public final long sslCtxPointer() { +- Lock readerLock = ctxLock.readLock(); +- readerLock.lock(); - try { -- return wrap(singleSrcBuffer(src), dst); +- return SSLContext.getSslCtx(ctx); - } finally { -- resetSingleSrcBuffer(); +- readerLock.unlock(); - } - } - -- @Override -- public final synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) throws SSLException { +- /** +- * Set the {@link OpenSslPrivateKeyMethod} to use. This allows to offload private-key operations +- * if needed. +- * +- * This method is currently only supported when {@code BoringSSL} is used. +- * +- * @param method method to use. +- * @deprecated use {@link SslContextBuilder#option(SslContextOption, Object)} with +- * {@link OpenSslContextOption#PRIVATE_KEY_METHOD}. +- */ +- @Deprecated +- @UnstableApi +- public final void setPrivateKeyMethod(OpenSslPrivateKeyMethod method) { +- checkNotNull(method, "method"); +- Lock writerLock = ctxLock.writeLock(); +- writerLock.lock(); - try { -- return unwrap(singleSrcBuffer(src), singleDstBuffer(dst)); +- SSLContext.setPrivateKeyMethod(ctx, new PrivateKeyMethod(engineMap, method)); - } finally { -- resetSingleSrcBuffer(); -- resetSingleDstBuffer(); +- writerLock.unlock(); - } - } - -- @Override -- public final synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts) throws SSLException { +- /** +- * @deprecated use {@link SslContextBuilder#option(SslContextOption, Object)} with +- * {@link OpenSslContextOption#USE_TASKS}. +- */ +- @Deprecated +- public final void setUseTasks(boolean useTasks) { +- Lock writerLock = ctxLock.writeLock(); +- writerLock.lock(); - try { -- return unwrap(singleSrcBuffer(src), dsts); +- SSLContext.setUseTasks(ctx, useTasks); - } finally { -- resetSingleSrcBuffer(); +- writerLock.unlock(); - } - } - -- @Override -- public final Runnable getDelegatedTask() { -- // Currently, we do not delegate SSL computation tasks -- // TODO: in the future, possibly create tasks to do encrypt / decrypt async -- -- return null; -- } -- -- @Override -- public final synchronized void closeInbound() throws SSLException { -- if (isInboundDone) { -- return; -- } +- // IMPORTANT: This method must only be called from either the constructor or the finalizer as a user MUST never +- // get access to an OpenSslSessionContext after this method was called to prevent the user from +- // producing a segfault. +- private void destroy() { +- Lock writerLock = ctxLock.writeLock(); +- writerLock.lock(); +- try { +- if (ctx != 0) { +- if (enableOcsp) { +- SSLContext.disableOcsp(ctx); +- } - -- isInboundDone = true; +- SSLContext.free(ctx); +- ctx = 0; - -- if (isOutboundDone()) { -- // Only call shutdown if there is no outbound data pending. -- // See https://github.com/netty/netty/issues/6167 -- shutdown(); +- OpenSslSessionContext context = sessionContext(); +- if (context != null) { +- context.destroy(); +- } +- } +- } finally { +- writerLock.unlock(); - } +- } - -- if (handshakeState != HandshakeState.NOT_STARTED && !receivedShutdown) { -- throw new SSLException( -- "Inbound closed before receiving peer's close_notify: possible truncation attack?"); +- protected static X509Certificate[] certificates(byte[][] chain) { +- X509Certificate[] peerCerts = new X509Certificate[chain.length]; +- for (int i = 0; i < peerCerts.length; i++) { +- peerCerts[i] = new LazyX509Certificate(chain[i]); - } +- return peerCerts; - } - -- @Override -- public final synchronized boolean isInboundDone() { -- return isInboundDone; +- /** +- * @deprecated This method is kept for API backwards compatibility. +- */ +- @Deprecated +- protected static X509TrustManager chooseTrustManager(TrustManager[] managers) { +- return chooseTrustManager(managers, null); - } - -- @Override -- public final synchronized void closeOutbound() { -- if (outboundClosed) { -- return; +- static X509TrustManager chooseTrustManager(TrustManager[] managers, +- ResumptionController resumptionController) { +- for (TrustManager m : managers) { +- if (m instanceof X509TrustManager) { +- X509TrustManager tm = (X509TrustManager) m; +- if (PlatformDependent.javaVersion() >= 7) { +- if (resumptionController != null) { +- tm = (X509TrustManager) resumptionController.wrapIfNeeded(tm); +- } +- tm = OpenSslX509TrustManagerWrapper.wrapIfNeeded(tm); +- if (useExtendedTrustManager(tm)) { +- // Wrap the TrustManager to provide a better exception message for users to debug hostname +- // validation failures. +- tm = new EnhancingX509ExtendedTrustManager(tm); +- } +- } +- return tm; +- } - } +- throw new IllegalStateException("no X509TrustManager found"); +- } - -- outboundClosed = true; -- -- if (handshakeState != HandshakeState.NOT_STARTED && !isDestroyed()) { -- int mode = SSL.getShutdown(ssl); -- if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) { -- doSSLShutdown(); +- protected static X509KeyManager chooseX509KeyManager(KeyManager[] kms) { +- for (KeyManager km : kms) { +- if (km instanceof X509KeyManager) { +- return (X509KeyManager) km; - } -- } else { -- // engine closing before initial handshake -- shutdown(); - } +- throw new IllegalStateException("no X509KeyManager found"); - } - - /** -- * Attempt to call {@link SSL#shutdownSSL(long)}. -- * @return {@code false} if the call to {@link SSL#shutdownSSL(long)} was not attempted or returned an error. +- * Translate a {@link ApplicationProtocolConfig} object to a +- * {@link OpenSslApplicationProtocolNegotiator} object. +- * +- * @param config The configuration which defines the translation +- * @return The results of the translation - */ -- private boolean doSSLShutdown() { -- if (SSL.isInInit(ssl) != 0) { -- // Only try to call SSL_shutdown if we are not in the init state anymore. -- // Otherwise we will see 'error:140E0197:SSL routines:SSL_shutdown:shutdown while in init' in our logs. -- // -- // See also http://hg.nginx.org/nginx/rev/062c189fee20 -- return false; +- @SuppressWarnings("deprecation") +- static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config) { +- if (config == null) { +- return NONE_PROTOCOL_NEGOTIATOR; - } -- int err = SSL.shutdownSSL(ssl); -- if (err < 0) { -- int sslErr = SSL.getError(ssl, err); -- if (sslErr == SSL.SSL_ERROR_SYSCALL || sslErr == SSL.SSL_ERROR_SSL) { -- if (logger.isDebugEnabled()) { -- logger.debug("SSL_shutdown failed: OpenSSL error: {}", SSL.getLastError()); +- +- switch (config.protocol()) { +- case NONE: +- return NONE_PROTOCOL_NEGOTIATOR; +- case ALPN: +- case NPN: +- case NPN_AND_ALPN: +- switch (config.selectedListenerFailureBehavior()) { +- case CHOOSE_MY_LAST_PROTOCOL: +- case ACCEPT: +- switch (config.selectorFailureBehavior()) { +- case CHOOSE_MY_LAST_PROTOCOL: +- case NO_ADVERTISE: +- return new OpenSslDefaultApplicationProtocolNegotiator( +- config); +- default: +- throw new UnsupportedOperationException( +- new StringBuilder("OpenSSL provider does not support ") +- .append(config.selectorFailureBehavior()) +- .append(" behavior").toString()); +- } +- default: +- throw new UnsupportedOperationException( +- new StringBuilder("OpenSSL provider does not support ") +- .append(config.selectedListenerFailureBehavior()) +- .append(" behavior").toString()); - } -- // There was an internal error -- shutdown -- shutdown(); -- return false; -- } -- SSL.clearError(); +- default: +- throw new Error(); - } -- return true; - } - -- @Override -- public final synchronized boolean isOutboundDone() { -- // Check if there is anything left in the outbound buffer. -- // We need to ensure we only call SSL.pendingWrittenBytesInBIO(...) if the engine was not destroyed yet. -- return outboundClosed && (networkBIO == 0 || SSL.bioLengthNonApplication(networkBIO) == 0); +- @SuppressJava6Requirement(reason = "Guarded by java version check") +- static boolean useExtendedTrustManager(X509TrustManager trustManager) { +- return PlatformDependent.javaVersion() >= 7 && trustManager instanceof X509ExtendedTrustManager; - } - - @Override -- public final String[] getSupportedCipherSuites() { -- return OpenSsl.AVAILABLE_CIPHER_SUITES.toArray(new String[OpenSsl.AVAILABLE_CIPHER_SUITES.size()]); +- public final int refCnt() { +- return refCnt.refCnt(); - } - - @Override -- public final String[] getEnabledCipherSuites() { -- final String[] enabled; -- synchronized (this) { -- if (!isDestroyed()) { -- enabled = SSL.getCiphers(ssl); -- } else { -- return EmptyArrays.EMPTY_STRINGS; -- } -- } -- if (enabled == null) { -- return EmptyArrays.EMPTY_STRINGS; -- } else { -- synchronized (this) { -- for (int i = 0; i < enabled.length; i++) { -- String mapped = toJavaCipherSuite(enabled[i]); -- if (mapped != null) { -- enabled[i] = mapped; -- } -- } -- } -- return enabled; -- } +- public final ReferenceCounted retain() { +- refCnt.retain(); +- return this; - } - - @Override -- public final void setEnabledCipherSuites(String[] cipherSuites) { -- checkNotNull(cipherSuites, "cipherSuites"); -- -- final StringBuilder buf = new StringBuilder(); -- for (String c: cipherSuites) { -- if (c == null) { -- break; -- } -- -- String converted = CipherSuiteConverter.toOpenSsl(c); -- if (converted == null) { -- converted = c; -- } -- -- if (!OpenSsl.isCipherSuiteAvailable(converted)) { -- throw new IllegalArgumentException("unsupported cipher suite: " + c + '(' + converted + ')'); -- } -- -- buf.append(converted); -- buf.append(':'); -- } -- -- if (buf.length() == 0) { -- throw new IllegalArgumentException("empty cipher suites"); -- } -- buf.setLength(buf.length() - 1); -- -- final String cipherSuiteSpec = buf.toString(); -- -- synchronized (this) { -- if (!isDestroyed()) { -- try { -- SSL.setCipherSuites(ssl, cipherSuiteSpec); -- } catch (Exception e) { -- throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec, e); -- } -- } else { -- throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec); -- } -- } +- public final ReferenceCounted retain(int increment) { +- refCnt.retain(increment); +- return this; - } - - @Override -- public final String[] getSupportedProtocols() { -- return OpenSsl.SUPPORTED_PROTOCOLS_SET.toArray(new String[OpenSsl.SUPPORTED_PROTOCOLS_SET.size()]); +- public final ReferenceCounted touch() { +- refCnt.touch(); +- return this; - } - - @Override -- public final String[] getEnabledProtocols() { -- List enabled = new ArrayList(6); -- // Seems like there is no way to explicit disable SSLv2Hello in openssl so it is always enabled -- enabled.add(OpenSsl.PROTOCOL_SSL_V2_HELLO); -- -- int opts; -- synchronized (this) { -- if (!isDestroyed()) { -- opts = SSL.getOptions(ssl); -- } else { -- return enabled.toArray(new String[1]); -- } -- } -- if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1, OpenSsl.PROTOCOL_TLS_V1)) { -- enabled.add(OpenSsl.PROTOCOL_TLS_V1); -- } -- if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_1, OpenSsl.PROTOCOL_TLS_V1_1)) { -- enabled.add(OpenSsl.PROTOCOL_TLS_V1_1); -- } -- if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_2, OpenSsl.PROTOCOL_TLS_V1_2)) { -- enabled.add(OpenSsl.PROTOCOL_TLS_V1_2); -- } -- if (isProtocolEnabled(opts, SSL.SSL_OP_NO_SSLv2, OpenSsl.PROTOCOL_SSL_V2)) { -- enabled.add(OpenSsl.PROTOCOL_SSL_V2); -- } -- if (isProtocolEnabled(opts, SSL.SSL_OP_NO_SSLv3, OpenSsl.PROTOCOL_SSL_V3)) { -- enabled.add(OpenSsl.PROTOCOL_SSL_V3); -- } -- return enabled.toArray(new String[enabled.size()]); +- public final ReferenceCounted touch(Object hint) { +- refCnt.touch(hint); +- return this; - } - -- private static boolean isProtocolEnabled(int opts, int disableMask, String protocolString) { -- // We also need to check if the actual protocolString is supported as depending on the openssl API -- // implementations it may use a disableMask of 0 (BoringSSL is doing this for example). -- return (opts & disableMask) == 0 && OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(protocolString); +- @Override +- public final boolean release() { +- return refCnt.release(); - } - - @Override -- public final void setEnabledProtocols(String[] protocols) { -- if (protocols == null) { -- // This is correct from the API docs -- throw new IllegalArgumentException(); -- } -- boolean sslv2 = false; -- boolean sslv3 = false; -- boolean tlsv1 = false; -- boolean tlsv1_1 = false; -- boolean tlsv1_2 = false; -- for (String p: protocols) { -- if (!OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(p)) { -- throw new IllegalArgumentException("Protocol " + p + " is not supported."); -- } -- if (p.equals(OpenSsl.PROTOCOL_SSL_V2)) { -- sslv2 = true; -- } else if (p.equals(OpenSsl.PROTOCOL_SSL_V3)) { -- sslv3 = true; -- } else if (p.equals(OpenSsl.PROTOCOL_TLS_V1)) { -- tlsv1 = true; -- } else if (p.equals(OpenSsl.PROTOCOL_TLS_V1_1)) { -- tlsv1_1 = true; -- } else if (p.equals(OpenSsl.PROTOCOL_TLS_V1_2)) { -- tlsv1_2 = true; -- } +- public final boolean release(int decrement) { +- return refCnt.release(decrement); +- } +- +- abstract static class AbstractCertificateVerifier extends CertificateVerifier { +- private final OpenSslEngineMap engineMap; +- +- AbstractCertificateVerifier(OpenSslEngineMap engineMap) { +- this.engineMap = engineMap; - } -- synchronized (this) { -- if (!isDestroyed()) { -- // Clear out options which disable protocols -- SSL.clearOptions(ssl, SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 | -- SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2); - -- int opts = 0; -- if (!sslv2) { -- opts |= SSL.SSL_OP_NO_SSLv2; -- } -- if (!sslv3) { -- opts |= SSL.SSL_OP_NO_SSLv3; +- @Override +- public final int verify(long ssl, byte[][] chain, String auth) { +- final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); +- if (engine == null) { +- // May be null if it was destroyed in the meantime. +- return CertificateVerifier.X509_V_ERR_UNSPECIFIED; +- } +- X509Certificate[] peerCerts = certificates(chain); +- try { +- verify(engine, peerCerts, auth); +- return CertificateVerifier.X509_V_OK; +- } catch (Throwable cause) { +- logger.debug("verification of certificate failed", cause); +- engine.initHandshakeException(cause); +- +- // Try to extract the correct error code that should be used. +- if (cause instanceof OpenSslCertificateException) { +- // This will never return a negative error code as its validated when constructing the +- // OpenSslCertificateException. +- return ((OpenSslCertificateException) cause).errorCode(); - } -- if (!tlsv1) { -- opts |= SSL.SSL_OP_NO_TLSv1; +- if (cause instanceof CertificateExpiredException) { +- return CertificateVerifier.X509_V_ERR_CERT_HAS_EXPIRED; - } -- if (!tlsv1_1) { -- opts |= SSL.SSL_OP_NO_TLSv1_1; +- if (cause instanceof CertificateNotYetValidException) { +- return CertificateVerifier.X509_V_ERR_CERT_NOT_YET_VALID; - } -- if (!tlsv1_2) { -- opts |= SSL.SSL_OP_NO_TLSv1_2; +- if (PlatformDependent.javaVersion() >= 7) { +- return translateToError(cause); - } - -- // Disable protocols we do not want -- SSL.setOptions(ssl, opts); -- } else { -- throw new IllegalStateException("failed to enable protocols: " + Arrays.asList(protocols)); +- // Could not detect a specific error code to use, so fallback to a default code. +- return CertificateVerifier.X509_V_ERR_UNSPECIFIED; - } - } -- } -- -- @Override -- public final SSLSession getSession() { -- return session; -- } -- -- @Override -- public final synchronized void beginHandshake() throws SSLException { -- switch (handshakeState) { -- case STARTED_IMPLICITLY: -- checkEngineClosed(BEGIN_HANDSHAKE_ENGINE_CLOSED); - -- // A user did not start handshake by calling this method by him/herself, -- // but handshake has been started already by wrap() or unwrap() implicitly. -- // Because it's the user's first time to call this method, it is unfair to -- // raise an exception. From the user's standpoint, he or she never asked -- // for renegotiation. +- @SuppressJava6Requirement(reason = "Usage guarded by java version check") +- private static int translateToError(Throwable cause) { +- if (cause instanceof CertificateRevokedException) { +- return CertificateVerifier.X509_V_ERR_CERT_REVOKED; +- } - -- handshakeState = HandshakeState.STARTED_EXPLICITLY; // Next time this method is invoked by the user, -- // we should raise an exception. -- break; -- case STARTED_EXPLICITLY: -- // Nothing to do as the handshake is not done yet. -- break; -- case FINISHED: -- if (clientMode) { -- // Only supported for server mode at the moment. -- throw RENEGOTIATION_UNSUPPORTED; -- } -- // For renegotiate on the server side we need to issue the following command sequence with openssl: -- // -- // SSL_renegotiate(ssl) -- // SSL_do_handshake(ssl) -- // ssl->state = SSL_ST_ACCEPT -- // SSL_do_handshake(ssl) -- // -- // Because of this we fall-through to call handshake() after setting the state, as this will also take -- // care of updating the internal OpenSslSession object. -- // -- // See also: -- // https://github.com/apache/httpd/blob/2.4.16/modules/ssl/ssl_engine_kernel.c#L812 -- // http://h71000.www7.hp.com/doc/83final/ba554_90007/ch04s03.html -- int status; -- if ((status = SSL.renegotiate(ssl)) != 1 || (status = SSL.doHandshake(ssl)) != 1) { -- int err = SSL.getError(ssl, status); -- if (err == SSL.SSL_ERROR_WANT_READ || err == SSL.SSL_ERROR_WANT_WRITE) { -- // If the internal SSL buffer is small it is possible that doHandshake may "fail" because -- // there is not enough room to write, so we should wait until the renegotiation has been. -- renegotiationPending = true; -- handshakeState = HandshakeState.STARTED_EXPLICITLY; -- lastAccessed = System.currentTimeMillis(); -- return; -- } else { -- throw shutdownWithError("renegotiation failed"); +- // The X509TrustManagerImpl uses a Validator which wraps a CertPathValidatorException into +- // an CertificateException. So we need to handle the wrapped CertPathValidatorException to be +- // able to send the correct alert. +- Throwable wrapped = cause.getCause(); +- while (wrapped != null) { +- if (wrapped instanceof CertPathValidatorException) { +- CertPathValidatorException ex = (CertPathValidatorException) wrapped; +- CertPathValidatorException.Reason reason = ex.getReason(); +- if (reason == CertPathValidatorException.BasicReason.EXPIRED) { +- return CertificateVerifier.X509_V_ERR_CERT_HAS_EXPIRED; +- } +- if (reason == CertPathValidatorException.BasicReason.NOT_YET_VALID) { +- return CertificateVerifier.X509_V_ERR_CERT_NOT_YET_VALID; +- } +- if (reason == CertPathValidatorException.BasicReason.REVOKED) { +- return CertificateVerifier.X509_V_ERR_CERT_REVOKED; - } - } +- wrapped = wrapped.getCause(); +- } +- return CertificateVerifier.X509_V_ERR_UNSPECIFIED; +- } - -- SSL.setState(ssl, SSL.SSL_ST_ACCEPT); +- abstract void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, +- String auth) throws Exception; +- } - -- lastAccessed = System.currentTimeMillis(); +- private static final class DefaultOpenSslEngineMap implements OpenSslEngineMap { +- private final Map engines = PlatformDependent.newConcurrentHashMap(); - -- // fall-through -- case NOT_STARTED: -- handshakeState = HandshakeState.STARTED_EXPLICITLY; -- handshake(); -- break; -- default: -- throw new Error(); +- @Override +- public ReferenceCountedOpenSslEngine remove(long ssl) { +- return engines.remove(ssl); - } -- } - -- private void checkEngineClosed(SSLException cause) throws SSLException { -- if (isDestroyed()) { -- throw cause; +- @Override +- public void add(ReferenceCountedOpenSslEngine engine) { +- engines.put(engine.sslPointer(), engine); - } -- } - -- private static SSLEngineResult.HandshakeStatus pendingStatus(int pendingStatus) { -- // Depending on if there is something left in the BIO we need to WRAP or UNWRAP -- return pendingStatus > 0 ? NEED_WRAP : NEED_UNWRAP; +- @Override +- public ReferenceCountedOpenSslEngine get(long ssl) { +- return engines.get(ssl); +- } - } - -- private static boolean isEmpty(Object[] arr) { -- return arr == null || arr.length == 0; -- } +- static void setKeyMaterial(long ctx, X509Certificate[] keyCertChain, PrivateKey key, String keyPassword) +- throws SSLException { +- /* Load the certificate file and private key. */ +- long keyBio = 0; +- long keyCertChainBio = 0; +- long keyCertChainBio2 = 0; +- PemEncoded encoded = null; +- try { +- // Only encode one time +- encoded = PemX509Certificate.toPEM(ByteBufAllocator.DEFAULT, true, keyCertChain); +- keyCertChainBio = toBIO(ByteBufAllocator.DEFAULT, encoded.retain()); +- keyCertChainBio2 = toBIO(ByteBufAllocator.DEFAULT, encoded.retain()); - -- private static boolean isEmpty(byte[] cert) { -- return cert == null || cert.length == 0; +- if (key != null) { +- keyBio = toBIO(ByteBufAllocator.DEFAULT, key); +- } +- +- SSLContext.setCertificateBio( +- ctx, keyCertChainBio, keyBio, +- keyPassword == null ? StringUtil.EMPTY_STRING : keyPassword); +- // We may have more then one cert in the chain so add all of them now. +- SSLContext.setCertificateChainBio(ctx, keyCertChainBio2, true); +- } catch (SSLException e) { +- throw e; +- } catch (Exception e) { +- throw new SSLException("failed to set certificate and key", e); +- } finally { +- freeBio(keyBio); +- freeBio(keyCertChainBio); +- freeBio(keyCertChainBio2); +- if (encoded != null) { +- encoded.release(); +- } +- } - } - -- private SSLEngineResult.HandshakeStatus handshake() throws SSLException { -- if (handshakeState == HandshakeState.FINISHED) { -- return FINISHED; +- static void freeBio(long bio) { +- if (bio != 0) { +- SSL.freeBIO(bio); - } -- checkEngineClosed(HANDSHAKE_ENGINE_CLOSED); +- } - -- // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the -- // BIO first or can just shutdown and throw it now. -- // This is needed so we ensure close_notify etc is correctly send to the remote peer. -- // See https://github.com/netty/netty/issues/3900 -- SSLHandshakeException exception = handshakeException; -- if (exception != null) { -- if (SSL.bioLengthNonApplication(networkBIO) > 0) { -- // There is something pending, we need to consume it first via a WRAP so we don't loose anything. -- return NEED_WRAP; -- } -- // No more data left to send to the remote peer, so null out the exception field, shutdown and throw -- // the exception. -- handshakeException = null; -- shutdown(); -- throw exception; +- /** +- * Return the pointer to a in-memory BIO +- * or {@code 0} if the {@code key} is {@code null}. The BIO contains the content of the {@code key}. +- */ +- static long toBIO(ByteBufAllocator allocator, PrivateKey key) throws Exception { +- if (key == null) { +- return 0; - } - -- // Adding the OpenSslEngine to the OpenSslEngineMap so it can be used in the AbstractCertificateVerifier. -- engineMap.add(this); -- if (lastAccessed == -1) { -- lastAccessed = System.currentTimeMillis(); +- PemEncoded pem = PemPrivateKey.toPEM(allocator, true, key); +- try { +- return toBIO(allocator, pem.retain()); +- } finally { +- pem.release(); - } +- } - -- if (!certificateSet && keyMaterialManager != null) { -- certificateSet = true; -- keyMaterialManager.setKeyMaterial(this); +- /** +- * Return the pointer to a in-memory BIO +- * or {@code 0} if the {@code certChain} is {@code null}. The BIO contains the content of the {@code certChain}. +- */ +- static long toBIO(ByteBufAllocator allocator, X509Certificate... certChain) throws Exception { +- if (certChain == null) { +- return 0; - } - -- int code = SSL.doHandshake(ssl); -- if (code <= 0) { -- // Check if we have a pending exception that was created during the handshake and if so throw it after -- // shutdown the connection. -- if (handshakeException != null) { -- exception = handshakeException; -- handshakeException = null; -- shutdown(); -- throw exception; -- } +- checkNonEmpty(certChain, "certChain"); - -- int sslError = SSL.getError(ssl, code); -- if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) { -- return pendingStatus(SSL.bioLengthNonApplication(networkBIO)); -- } else { -- // Everything else is considered as error -- throw shutdownWithError("SSL_do_handshake"); -- } +- PemEncoded pem = PemX509Certificate.toPEM(allocator, true, certChain); +- try { +- return toBIO(allocator, pem.retain()); +- } finally { +- pem.release(); - } -- // if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished. -- session.handshakeFinished(); -- engineMap.remove(ssl); -- return FINISHED; - } - -- private SSLEngineResult.HandshakeStatus mayFinishHandshake(SSLEngineResult.HandshakeStatus status) -- throws SSLException { -- if (status == NOT_HANDSHAKING && handshakeState != HandshakeState.FINISHED) { -- // If the status was NOT_HANDSHAKING and we not finished the handshake we need to call -- // SSL_do_handshake() again -- return handshake(); -- } -- return status; -- } +- static long toBIO(ByteBufAllocator allocator, PemEncoded pem) throws Exception { +- try { +- // We can turn direct buffers straight into BIOs. No need to +- // make a yet another copy. +- ByteBuf content = pem.content(); - -- @Override -- public final synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() { -- // Check if we are in the initial handshake phase or shutdown phase -- return needPendingStatus() ? pendingStatus(SSL.bioLengthNonApplication(networkBIO)) : NOT_HANDSHAKING; -- } +- if (content.isDirect()) { +- return newBIO(content.retainedSlice()); +- } - -- private SSLEngineResult.HandshakeStatus getHandshakeStatus(int pending) { -- // Check if we are in the initial handshake phase or shutdown phase -- return needPendingStatus() ? pendingStatus(pending) : NOT_HANDSHAKING; +- ByteBuf buffer = allocator.directBuffer(content.readableBytes()); +- try { +- buffer.writeBytes(content, content.readerIndex(), content.readableBytes()); +- return newBIO(buffer.retainedSlice()); +- } finally { +- try { +- // If the contents of the ByteBuf is sensitive (e.g. a PrivateKey) we +- // need to zero out the bytes of the copy before we're releasing it. +- if (pem.isSensitive()) { +- SslUtils.zeroout(buffer); +- } +- } finally { +- buffer.release(); +- } +- } +- } finally { +- pem.release(); +- } - } - -- private boolean needPendingStatus() { -- return handshakeState != HandshakeState.NOT_STARTED && !isDestroyed() -- && (handshakeState != HandshakeState.FINISHED || isInboundDone() || isOutboundDone()); +- private static long newBIO(ByteBuf buffer) throws Exception { +- try { +- long bio = SSL.newMemBIO(); +- int readable = buffer.readableBytes(); +- if (SSL.bioWrite(bio, OpenSsl.memoryAddress(buffer) + buffer.readerIndex(), readable) != readable) { +- SSL.freeBIO(bio); +- throw new IllegalStateException("Could not write data to memory BIO"); +- } +- return bio; +- } finally { +- buffer.release(); +- } - } - - /** -- * Converts the specified OpenSSL cipher suite to the Java cipher suite. +- * Returns the {@link OpenSslKeyMaterialProvider} that should be used for OpenSSL. Depending on the given +- * {@link KeyManagerFactory} this may cache the {@link OpenSslKeyMaterial} for better performance if it can +- * ensure that the same material is always returned for the same alias. - */ -- private String toJavaCipherSuite(String openSslCipherSuite) { -- if (openSslCipherSuite == null) { -- return null; +- static OpenSslKeyMaterialProvider providerFor(KeyManagerFactory factory, String password) { +- if (factory instanceof OpenSslX509KeyManagerFactory) { +- return ((OpenSslX509KeyManagerFactory) factory).newProvider(); - } - -- String prefix = toJavaCipherSuitePrefix(SSL.getVersion(ssl)); -- return CipherSuiteConverter.toJava(openSslCipherSuite, prefix); -- } -- -- /** -- * Converts the protocol version string returned by {@link SSL#getVersion(long)} to protocol family string. -- */ -- private static String toJavaCipherSuitePrefix(String protocolVersion) { -- final char c; -- if (protocolVersion == null || protocolVersion.isEmpty()) { -- c = 0; -- } else { -- c = protocolVersion.charAt(0); +- if (factory instanceof OpenSslCachingX509KeyManagerFactory) { +- // The user explicit used OpenSslCachingX509KeyManagerFactory which signals us that its fine to cache. +- return ((OpenSslCachingX509KeyManagerFactory) factory).newProvider(password); - } +- // We can not be sure if the material may change at runtime so we will not cache it. +- return new OpenSslKeyMaterialProvider(chooseX509KeyManager(factory.getKeyManagers()), password); +- } - -- switch (c) { -- case 'T': -- return "TLS"; -- case 'S': -- return "SSL"; -- default: -- return "UNKNOWN"; +- private static ReferenceCountedOpenSslEngine retrieveEngine(OpenSslEngineMap engineMap, long ssl) +- throws SSLException { +- ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); +- if (engine == null) { +- throw new SSLException("Could not find a " + +- StringUtil.simpleClassName(ReferenceCountedOpenSslEngine.class) + " for sslPointer " + ssl); - } +- return engine; - } - -- @Override -- public final void setUseClientMode(boolean clientMode) { -- if (clientMode != this.clientMode) { -- throw new UnsupportedOperationException(); +- private static final class PrivateKeyMethod implements SSLPrivateKeyMethod { +- +- private final OpenSslEngineMap engineMap; +- private final OpenSslPrivateKeyMethod keyMethod; +- PrivateKeyMethod(OpenSslEngineMap engineMap, OpenSslPrivateKeyMethod keyMethod) { +- this.engineMap = engineMap; +- this.keyMethod = keyMethod; - } -- } - -- @Override -- public final boolean getUseClientMode() { -- return clientMode; -- } +- @Override +- public byte[] sign(long ssl, int signatureAlgorithm, byte[] digest) throws Exception { +- ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); +- try { +- return verifyResult(keyMethod.sign(engine, signatureAlgorithm, digest)); +- } catch (Exception e) { +- engine.initHandshakeException(e); +- throw e; +- } +- } - -- @Override -- public final void setNeedClientAuth(boolean b) { -- setClientAuth(b ? ClientAuth.REQUIRE : ClientAuth.NONE); +- @Override +- public byte[] decrypt(long ssl, byte[] input) throws Exception { +- ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); +- try { +- return verifyResult(keyMethod.decrypt(engine, input)); +- } catch (Exception e) { +- engine.initHandshakeException(e); +- throw e; +- } +- } - } - -- @Override -- public final boolean getNeedClientAuth() { -- return clientAuth == ClientAuth.REQUIRE; -- } +- private static final class AsyncPrivateKeyMethod implements AsyncSSLPrivateKeyMethod { - -- @Override -- public final void setWantClientAuth(boolean b) { -- setClientAuth(b ? ClientAuth.OPTIONAL : ClientAuth.NONE); -- } +- private final OpenSslEngineMap engineMap; +- private final OpenSslAsyncPrivateKeyMethod keyMethod; - -- @Override -- public final boolean getWantClientAuth() { -- return clientAuth == ClientAuth.OPTIONAL; -- } +- AsyncPrivateKeyMethod(OpenSslEngineMap engineMap, OpenSslAsyncPrivateKeyMethod keyMethod) { +- this.engineMap = engineMap; +- this.keyMethod = keyMethod; +- } - -- /** -- * See SSL_set_verify and -- * {@link SSL#setVerify(long, int, int)}. -- */ -- @UnstableApi -- public final synchronized void setVerify(int verifyMode, int depth) { -- SSL.setVerify(ssl, verifyMode, depth); -- } +- @Override +- public void sign(long ssl, int signatureAlgorithm, byte[] bytes, ResultCallback resultCallback) { +- try { +- ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); +- keyMethod.sign(engine, signatureAlgorithm, bytes) +- .addListener(new ResultCallbackListener(engine, ssl, resultCallback)); +- } catch (SSLException e) { +- resultCallback.onError(ssl, e); +- } +- } - -- private void setClientAuth(ClientAuth mode) { -- if (clientMode) { -- return; +- @Override +- public void decrypt(long ssl, byte[] bytes, ResultCallback resultCallback) { +- try { +- ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); +- keyMethod.decrypt(engine, bytes) +- .addListener(new ResultCallbackListener(engine, ssl, resultCallback)); +- } catch (SSLException e) { +- resultCallback.onError(ssl, e); +- } - } -- synchronized (this) { -- if (clientAuth == mode) { -- // No need to issue any JNI calls if the mode is the same -- return; +- +- private static final class ResultCallbackListener implements FutureListener { +- private final ReferenceCountedOpenSslEngine engine; +- private final long ssl; +- private final ResultCallback resultCallback; +- +- ResultCallbackListener(ReferenceCountedOpenSslEngine engine, long ssl, +- ResultCallback resultCallback) { +- this.engine = engine; +- this.ssl = ssl; +- this.resultCallback = resultCallback; - } -- switch (mode) { -- case NONE: -- SSL.setVerify(ssl, SSL.SSL_CVERIFY_NONE, ReferenceCountedOpenSslContext.VERIFY_DEPTH); -- break; -- case REQUIRE: -- SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRED, ReferenceCountedOpenSslContext.VERIFY_DEPTH); -- break; -- case OPTIONAL: -- SSL.setVerify(ssl, SSL.SSL_CVERIFY_OPTIONAL, ReferenceCountedOpenSslContext.VERIFY_DEPTH); -- break; -- default: -- throw new Error(mode.toString()); +- +- @Override +- public void operationComplete(Future future) { +- Throwable cause = future.cause(); +- if (cause == null) { +- try { +- byte[] result = verifyResult(future.getNow()); +- resultCallback.onSuccess(ssl, result); +- return; +- } catch (SignatureException e) { +- cause = e; +- engine.initHandshakeException(e); +- } +- } +- resultCallback.onError(ssl, cause); - } -- clientAuth = mode; - } - } - -- @Override -- public final void setEnableSessionCreation(boolean b) { -- if (b) { -- throw new UnsupportedOperationException(); +- private static byte[] verifyResult(byte[] result) throws SignatureException { +- if (result == null) { +- throw new SignatureException(); - } +- return result; - } - -- @Override -- public final boolean getEnableSessionCreation() { -- return false; -- } +- private static final class CompressionAlgorithm implements CertificateCompressionAlgo { +- private final OpenSslEngineMap engineMap; +- private final OpenSslCertificateCompressionAlgorithm compressionAlgorithm; - -- @Override -- public final synchronized SSLParameters getSSLParameters() { -- SSLParameters sslParameters = super.getSSLParameters(); +- CompressionAlgorithm(OpenSslEngineMap engineMap, OpenSslCertificateCompressionAlgorithm compressionAlgorithm) { +- this.engineMap = engineMap; +- this.compressionAlgorithm = compressionAlgorithm; +- } - -- int version = PlatformDependent.javaVersion(); -- if (version >= 7) { -- sslParameters.setEndpointIdentificationAlgorithm(endPointIdentificationAlgorithm); -- Java7SslParametersUtils.setAlgorithmConstraints(sslParameters, algorithmConstraints); -- if (version >= 8) { -- if (sniHostNames != null) { -- Java8SslUtils.setSniHostNames(sslParameters, sniHostNames); -- } -- if (!isDestroyed()) { -- Java8SslUtils.setUseCipherSuitesOrder( -- sslParameters, (SSL.getOptions(ssl) & SSL.SSL_OP_CIPHER_SERVER_PREFERENCE) != 0); -- } +- @Override +- public byte[] compress(long ssl, byte[] bytes) throws Exception { +- ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); +- return compressionAlgorithm.compress(engine, bytes); +- } - -- Java8SslUtils.setSNIMatchers(sslParameters, matchers); -- } +- @Override +- public byte[] decompress(long ssl, int len, byte[] bytes) throws Exception { +- ReferenceCountedOpenSslEngine engine = retrieveEngine(engineMap, ssl); +- return compressionAlgorithm.decompress(engine, len, bytes); +- } +- +- @Override +- public int algorithmId() { +- return compressionAlgorithm.algorithmId(); - } -- return sslParameters; - } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java +deleted file mode 100644 +index 937b469..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java ++++ /dev/null +@@ -1,2760 +0,0 @@ +-/* +- * Copyright 2016 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +-package io.netty.handler.ssl; - -- @Override -- public final synchronized void setSSLParameters(SSLParameters sslParameters) { -- int version = PlatformDependent.javaVersion(); -- if (version >= 7) { -- if (sslParameters.getAlgorithmConstraints() != null) { -- throw new IllegalArgumentException("AlgorithmConstraints are not supported."); -- } +-import io.netty.buffer.ByteBuf; +-import io.netty.buffer.ByteBufAllocator; +-import io.netty.handler.ssl.util.LazyJavaxX509Certificate; +-import io.netty.handler.ssl.util.LazyX509Certificate; +-import io.netty.internal.tcnative.AsyncTask; +-import io.netty.internal.tcnative.Buffer; +-import io.netty.internal.tcnative.SSL; +-import io.netty.util.AbstractReferenceCounted; +-import io.netty.util.CharsetUtil; +-import io.netty.util.ReferenceCounted; +-import io.netty.util.ResourceLeakDetector; +-import io.netty.util.ResourceLeakDetectorFactory; +-import io.netty.util.ResourceLeakTracker; +-import io.netty.util.internal.EmptyArrays; +-import io.netty.util.internal.PlatformDependent; +-import io.netty.util.internal.StringUtil; +-import io.netty.util.internal.SuppressJava6Requirement; +-import io.netty.util.internal.ThrowableUtil; +-import io.netty.util.internal.UnstableApi; +-import io.netty.util.internal.logging.InternalLogger; +-import io.netty.util.internal.logging.InternalLoggerFactory; - -- if (version >= 8) { -- if (!isDestroyed()) { -- if (clientMode) { -- final List sniHostNames = Java8SslUtils.getSniHostNames(sslParameters); -- for (String name: sniHostNames) { -- SSL.setTlsExtHostName(ssl, name); -- } -- this.sniHostNames = sniHostNames; -- } -- if (Java8SslUtils.getUseCipherSuitesOrder(sslParameters)) { -- SSL.setOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); -- } else { -- SSL.clearOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); -- } -- } -- matchers = sslParameters.getSNIMatchers(); -- } +-import java.nio.ByteBuffer; +-import java.nio.ReadOnlyBufferException; +-import java.security.Principal; +-import java.security.cert.Certificate; +-import java.util.Arrays; +-import java.util.Collection; +-import java.util.Collections; +-import java.util.HashSet; +-import java.util.LinkedHashSet; +-import java.util.List; +-import java.util.Map; +-import java.util.Set; +-import java.util.concurrent.ConcurrentHashMap; +-import java.util.concurrent.locks.Lock; +- +-import javax.crypto.spec.SecretKeySpec; +-import javax.net.ssl.SSLEngine; +-import javax.net.ssl.SSLEngineResult; +-import javax.net.ssl.SSLException; +-import javax.net.ssl.SSLHandshakeException; +-import javax.net.ssl.SSLParameters; +-import javax.net.ssl.SSLPeerUnverifiedException; +-import javax.net.ssl.SSLSession; +-import javax.net.ssl.SSLSessionBindingEvent; +-import javax.net.ssl.SSLSessionBindingListener; +-import javax.security.cert.X509Certificate; +- +-import static io.netty.handler.ssl.OpenSsl.memoryAddress; +-import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH; +-import static io.netty.util.internal.EmptyArrays.EMPTY_STRINGS; +-import static io.netty.util.internal.ObjectUtil.checkNotNull; +-import static io.netty.util.internal.ObjectUtil.checkNotNullArrayParam; +-import static io.netty.util.internal.ObjectUtil.checkNotNullWithIAE; +-import static java.lang.Integer.MAX_VALUE; +-import static java.lang.Math.min; +-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; +-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_TASK; +-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP; +-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_WRAP; +-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; +-import static javax.net.ssl.SSLEngineResult.Status.BUFFER_OVERFLOW; +-import static javax.net.ssl.SSLEngineResult.Status.BUFFER_UNDERFLOW; +-import static javax.net.ssl.SSLEngineResult.Status.CLOSED; +-import static javax.net.ssl.SSLEngineResult.Status.OK; +- +-/** +- * Implements a {@link SSLEngine} using +- * OpenSSL BIO abstractions. +- *

Instances of this class must be {@link #release() released} or else native memory will leak! +- * +- *

Instances of this class must be released before the {@link ReferenceCountedOpenSslContext} +- * the instance depends upon are released. Otherwise if any method of this class is called which uses the +- * the {@link ReferenceCountedOpenSslContext} JNI resources the JVM may crash. +- */ +-public class ReferenceCountedOpenSslEngine extends SSLEngine implements ReferenceCounted, ApplicationProtocolAccessor { +- +- private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReferenceCountedOpenSslEngine.class); +- +- private static final ResourceLeakDetector leakDetector = +- ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslEngine.class); +- private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2 = 0; +- private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3 = 1; +- private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1 = 2; +- private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1 = 3; +- private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2 = 4; +- private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3 = 5; +- private static final int[] OPENSSL_OP_NO_PROTOCOLS = { +- SSL.SSL_OP_NO_SSLv2, +- SSL.SSL_OP_NO_SSLv3, +- SSL.SSL_OP_NO_TLSv1, +- SSL.SSL_OP_NO_TLSv1_1, +- SSL.SSL_OP_NO_TLSv1_2, +- SSL.SSL_OP_NO_TLSv1_3 +- }; +- +- /** +- * Depends upon tcnative ... only use if tcnative is available! +- */ +- static final int MAX_PLAINTEXT_LENGTH = SSL.SSL_MAX_PLAINTEXT_LENGTH; +- /** +- * Depends upon tcnative ... only use if tcnative is available! +- */ +- static final int MAX_RECORD_SIZE = SSL.SSL_MAX_RECORD_LENGTH; +- +- private static final SSLEngineResult NEED_UNWRAP_OK = new SSLEngineResult(OK, NEED_UNWRAP, 0, 0); +- private static final SSLEngineResult NEED_UNWRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_UNWRAP, 0, 0); +- private static final SSLEngineResult NEED_WRAP_OK = new SSLEngineResult(OK, NEED_WRAP, 0, 0); +- private static final SSLEngineResult NEED_WRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_WRAP, 0, 0); +- private static final SSLEngineResult CLOSED_NOT_HANDSHAKING = new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0); +- +- // OpenSSL state +- private long ssl; +- private long networkBIO; +- +- private enum HandshakeState { +- /** +- * Not started yet. +- */ +- NOT_STARTED, +- /** +- * Started via unwrap/wrap. +- */ +- STARTED_IMPLICITLY, +- /** +- * Started via {@link #beginHandshake()}. +- */ +- STARTED_EXPLICITLY, +- /** +- * Handshake is finished. +- */ +- FINISHED +- } - -- final String endPointIdentificationAlgorithm = sslParameters.getEndpointIdentificationAlgorithm(); -- final boolean endPointVerificationEnabled = endPointIdentificationAlgorithm != null && -- !endPointIdentificationAlgorithm.isEmpty(); -- SSL.setHostNameValidation(ssl, DEFAULT_HOSTNAME_VALIDATION_FLAGS, -- endPointVerificationEnabled ? getPeerHost() : null); -- // If the user asks for hostname verification we must ensure we verify the peer. -- // If the user disables hostname verification we leave it up to the user to change the mode manually. -- if (clientMode && endPointVerificationEnabled) { -- SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRED, -1); +- private HandshakeState handshakeState = HandshakeState.NOT_STARTED; +- private boolean receivedShutdown; +- private volatile boolean destroyed; +- private volatile String applicationProtocol; +- private volatile boolean needTask; +- private boolean hasTLSv13Cipher; +- private boolean sessionSet; +- +- // Reference Counting +- private final ResourceLeakTracker leak; +- private final AbstractReferenceCounted refCnt = new AbstractReferenceCounted() { +- @Override +- public ReferenceCounted touch(Object hint) { +- if (leak != null) { +- leak.record(hint); - } - -- this.endPointIdentificationAlgorithm = endPointIdentificationAlgorithm; -- algorithmConstraints = sslParameters.getAlgorithmConstraints(); +- return ReferenceCountedOpenSslEngine.this; - } -- super.setSSLParameters(sslParameters); -- } - -- private boolean isDestroyed() { -- return destroyed != 0; -- } +- @Override +- protected void deallocate() { +- shutdown(); +- if (leak != null) { +- boolean closed = leak.close(ReferenceCountedOpenSslEngine.this); +- assert closed; +- } +- parentContext.release(); +- } +- }; - -- static int calculateOutNetBufSize(int pendingBytes, int numComponents) { -- return (int) min(MAX_ENCRYPTED_PACKET_LENGTH, -- pendingBytes + (long) MAX_TLS_RECORD_OVERHEAD_LENGTH * numComponents); -- } +- private final Set enabledProtocols = new LinkedHashSet(); - -- final boolean checkSniHostnameMatch(String hostname) { -- return Java8SslUtils.checkSniHostnameMatch(matchers, hostname); -- } +- private volatile ClientAuth clientAuth = ClientAuth.NONE; - -- private final class OpenSslSession implements SSLSession, ApplicationProtocolAccessor { -- private final OpenSslSessionContext sessionContext; +- private String endpointIdentificationAlgorithm; +- // Store as object as AlgorithmConstraints only exists since java 7. +- private Object algorithmConstraints; +- private List sniHostNames; - -- // These are guarded by synchronized(OpenSslEngine.this) as handshakeFinished() may be triggered by any -- // thread. -- private X509Certificate[] x509PeerCerts; -- private Certificate[] peerCerts; -- private String protocol; -- private String applicationProtocol; -- private String cipher; -- private byte[] id; -- private long creationTime; +- // Mark as volatile as accessed by checkSniHostnameMatch(...) and also not specify the SNIMatcher type to allow us +- // using it with java7. +- private volatile Collection matchers; - -- // lazy init for memory reasons -- private Map values; +- // SSL Engine status variables +- private boolean isInboundDone; +- private boolean outboundClosed; - -- OpenSslSession(OpenSslSessionContext sessionContext) { -- this.sessionContext = sessionContext; -- } +- final boolean jdkCompatibilityMode; +- private final boolean clientMode; +- final ByteBufAllocator alloc; +- private final OpenSslEngineMap engineMap; +- private final OpenSslApplicationProtocolNegotiator apn; +- private final ReferenceCountedOpenSslContext parentContext; +- private final OpenSslSession session; +- private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1]; +- private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1]; +- private final boolean enableOcsp; +- private int maxWrapOverhead; +- private int maxWrapBufferSize; +- private Throwable pendingException; - -- @Override -- public byte[] getId() { -- synchronized (ReferenceCountedOpenSslEngine.this) { -- if (id == null) { -- return EmptyArrays.EMPTY_BYTES; -- } -- return id.clone(); -- } -- } +- /** +- * Create a new instance. +- * @param context Reference count release responsibility is not transferred! The callee still owns this object. +- * @param alloc The allocator to use. +- * @param peerHost The peer host name. +- * @param peerPort The peer port. +- * @param jdkCompatibilityMode {@code true} to behave like described in +- * https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html. +- * {@code false} allows for partial and/or multiple packets to be process in a single +- * wrap or unwrap call. +- * @param leakDetection {@code true} to enable leak detection of this object. +- */ +- ReferenceCountedOpenSslEngine(ReferenceCountedOpenSslContext context, final ByteBufAllocator alloc, String peerHost, +- int peerPort, boolean jdkCompatibilityMode, boolean leakDetection, +- String endpointIdentificationAlgorithm) { +- super(peerHost, peerPort); +- OpenSsl.ensureAvailability(); +- engineMap = context.engineMap; +- enableOcsp = context.enableOcsp; +- this.jdkCompatibilityMode = jdkCompatibilityMode; +- this.alloc = checkNotNull(alloc, "alloc"); +- apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator(); +- clientMode = context.isClient(); +- this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm; - -- @Override -- public SSLSessionContext getSessionContext() { -- return sessionContext; -- } +- if (PlatformDependent.javaVersion() >= 7) { +- session = new ExtendedOpenSslSession(new DefaultOpenSslSession(context.sessionContext())) { +- private String[] peerSupportedSignatureAlgorithms; +- private List requestedServerNames; - -- @Override -- public long getCreationTime() { -- synchronized (ReferenceCountedOpenSslEngine.this) { -- if (creationTime == 0 && !isDestroyed()) { -- creationTime = SSL.getTime(ssl) * 1000L; +- @Override +- public List getRequestedServerNames() { +- if (clientMode) { +- return Java8SslUtils.getSniHostNames(sniHostNames); +- } else { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- if (requestedServerNames == null) { +- if (isDestroyed()) { +- requestedServerNames = Collections.emptyList(); +- } else { +- String name = SSL.getSniHostname(ssl); +- if (name == null) { +- requestedServerNames = Collections.emptyList(); +- } else { +- // Convert to bytes as we do not want to do any strict validation of the +- // SNIHostName while creating it. +- requestedServerNames = +- Java8SslUtils.getSniHostName( +- SSL.getSniHostname(ssl).getBytes(CharsetUtil.UTF_8)); +- } +- } +- } +- return requestedServerNames; +- } +- } - } -- } -- return creationTime; -- } - -- @Override -- public long getLastAccessedTime() { -- long lastAccessed = ReferenceCountedOpenSslEngine.this.lastAccessed; -- // if lastAccessed is -1 we will just return the creation time as the handshake was not started yet. -- return lastAccessed == -1 ? getCreationTime() : lastAccessed; -- } +- @Override +- public String[] getPeerSupportedSignatureAlgorithms() { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- if (peerSupportedSignatureAlgorithms == null) { +- if (isDestroyed()) { +- peerSupportedSignatureAlgorithms = EMPTY_STRINGS; +- } else { +- String[] algs = SSL.getSigAlgs(ssl); +- if (algs == null) { +- peerSupportedSignatureAlgorithms = EMPTY_STRINGS; +- } else { +- Set algorithmList = new LinkedHashSet(algs.length); +- for (String alg: algs) { +- String converted = SignatureAlgorithmConverter.toJavaName(alg); - -- @Override -- public void invalidate() { -- synchronized (ReferenceCountedOpenSslEngine.this) { -- if (!isDestroyed()) { -- SSL.setTimeout(ssl, 0); +- if (converted != null) { +- algorithmList.add(converted); +- } +- } +- peerSupportedSignatureAlgorithms = algorithmList.toArray(EMPTY_STRINGS); +- } +- } +- } +- return peerSupportedSignatureAlgorithms.clone(); +- } - } -- } -- } - -- @Override -- public boolean isValid() { -- synchronized (ReferenceCountedOpenSslEngine.this) { -- if (!isDestroyed()) { -- return System.currentTimeMillis() - (SSL.getTimeout(ssl) * 1000L) < (SSL.getTime(ssl) * 1000L); +- @Override +- public List getStatusResponses() { +- byte[] ocspResponse = null; +- if (enableOcsp && clientMode) { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- if (!isDestroyed()) { +- ocspResponse = SSL.getOcspResponse(ssl); +- } +- } +- } +- return ocspResponse == null ? +- Collections.emptyList() : Collections.singletonList(ocspResponse); - } -- } -- return false; -- } -- -- @Override -- public void putValue(String name, Object value) { -- if (name == null) { -- throw new NullPointerException("name"); -- } -- if (value == null) { -- throw new NullPointerException("value"); -- } -- Map values = this.values; -- if (values == null) { -- // Use size of 2 to keep the memory overhead small -- values = this.values = new HashMap(2); -- } -- Object old = values.put(name, value); -- if (value instanceof SSLSessionBindingListener) { -- ((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name)); -- } -- notifyUnbound(old, name); +- }; +- } else { +- session = new DefaultOpenSslSession(context.sessionContext()); - } - -- @Override -- public Object getValue(String name) { -- if (name == null) { -- throw new NullPointerException("name"); -- } -- if (values == null) { -- return null; -- } -- return values.get(name); +- if (!context.sessionContext().useKeyManager()) { +- session.setLocalCertificate(context.keyCertChain); - } - -- @Override -- public void removeValue(String name) { -- if (name == null) { -- throw new NullPointerException("name"); -- } -- Map values = this.values; -- if (values == null) { -- return; -- } -- Object old = values.remove(name); -- notifyUnbound(old, name); +- Lock readerLock = context.ctxLock.readLock(); +- readerLock.lock(); +- final long finalSsl; +- try { +- finalSsl = SSL.newSSL(context.ctx, !context.isClient()); +- } finally { +- readerLock.unlock(); - } +- synchronized (this) { +- ssl = finalSsl; +- try { +- networkBIO = SSL.bioNewByteBuffer(ssl, context.getBioNonApplicationBufferSize()); - -- @Override -- public String[] getValueNames() { -- Map values = this.values; -- if (values == null || values.isEmpty()) { -- return EmptyArrays.EMPTY_STRINGS; -- } -- return values.keySet().toArray(new String[values.size()]); -- } +- // Set the client auth mode, this needs to be done via setClientAuth(...) method so we actually call the +- // needed JNI methods. +- setClientAuth(clientMode ? ClientAuth.NONE : context.clientAuth); - -- private void notifyUnbound(Object value, String name) { -- if (value instanceof SSLSessionBindingListener) { -- ((SSLSessionBindingListener) value).valueUnbound(new SSLSessionBindingEvent(this, name)); -- } -- } +- assert context.protocols != null; +- this.hasTLSv13Cipher = context.hasTLSv13Cipher; - -- /** -- * Finish the handshake and so init everything in the {@link OpenSslSession} that should be accessible by -- * the user. -- */ -- void handshakeFinished() throws SSLException { -- synchronized (ReferenceCountedOpenSslEngine.this) { -- if (!isDestroyed()) { -- id = SSL.getSessionId(ssl); -- cipher = toJavaCipherSuite(SSL.getCipherForSSL(ssl)); -- protocol = SSL.getVersion(ssl); +- setEnabledProtocols(context.protocols); - -- initPeerCerts(); -- selectApplicationProtocol(); +- // Use SNI if peerHost was specified and a valid hostname +- // See https://github.com/netty/netty/issues/4746 +- if (clientMode && SslUtils.isValidHostNameForSNI(peerHost)) { +- // If on java8 and later we should do some extra validation to ensure we can construct the +- // SNIHostName later again. +- if (PlatformDependent.javaVersion() >= 8) { +- if (Java8SslUtils.isValidHostNameForSNI(peerHost)) { +- SSL.setTlsExtHostName(ssl, peerHost); +- sniHostNames = Collections.singletonList(peerHost); +- } +- } else { +- SSL.setTlsExtHostName(ssl, peerHost); +- sniHostNames = Collections.singletonList(peerHost); +- } +- } - -- handshakeState = HandshakeState.FINISHED; -- } else { -- throw new SSLException("Already closed"); +- if (enableOcsp) { +- SSL.enableOcsp(ssl); - } -- } -- } - -- /** -- * Init peer certificates that can be obtained via {@link #getPeerCertificateChain()} -- * and {@link #getPeerCertificates()}. -- */ -- private void initPeerCerts() { -- // Return the full chain from the JNI layer. -- byte[][] chain = SSL.getPeerCertChain(ssl); -- if (clientMode) { -- if (isEmpty(chain)) { -- peerCerts = EMPTY_CERTIFICATES; -- x509PeerCerts = EMPTY_JAVAX_X509_CERTIFICATES; -- } else { -- peerCerts = new Certificate[chain.length]; -- x509PeerCerts = new X509Certificate[chain.length]; -- initCerts(chain, 0); +- if (!jdkCompatibilityMode) { +- SSL.setMode(ssl, SSL.getMode(ssl) | SSL.SSL_MODE_ENABLE_PARTIAL_WRITE); - } -- } else { -- // if used on the server side SSL_get_peer_cert_chain(...) will not include the remote peer -- // certificate. We use SSL_get_peer_certificate to get it in this case and add it to our -- // array later. -- // -- // See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html -- byte[] clientCert = SSL.getPeerCertificate(ssl); -- if (isEmpty(clientCert)) { -- peerCerts = EMPTY_CERTIFICATES; -- x509PeerCerts = EMPTY_JAVAX_X509_CERTIFICATES; -- } else { -- if (isEmpty(chain)) { -- peerCerts = new Certificate[] {new OpenSslX509Certificate(clientCert)}; -- x509PeerCerts = new X509Certificate[] {new OpenSslJavaxX509Certificate(clientCert)}; -- } else { -- peerCerts = new Certificate[chain.length + 1]; -- x509PeerCerts = new X509Certificate[chain.length + 1]; -- peerCerts[0] = new OpenSslX509Certificate(clientCert); -- x509PeerCerts[0] = new OpenSslJavaxX509Certificate(clientCert); -- initCerts(chain, 1); +- +- if (isProtocolEnabled(SSL.getOptions(ssl), SSL.SSL_OP_NO_TLSv1_3, SslProtocols.TLS_v1_3)) { +- final boolean enableTickets = clientMode ? +- ReferenceCountedOpenSslContext.CLIENT_ENABLE_SESSION_TICKET_TLSV13 : +- ReferenceCountedOpenSslContext.SERVER_ENABLE_SESSION_TICKET_TLSV13; +- if (enableTickets) { +- // We should enable session tickets for stateless resumption when TLSv1.3 is enabled. This +- // is also done by OpenJDK and without this session resumption does not work at all with +- // BoringSSL when TLSv1.3 is used as BoringSSL only supports stateless resumption with TLSv1.3: +- // +- // See: +- // - https://bugs.openjdk.java.net/browse/JDK-8223922 +- // - https://boringssl.googlesource.com/boringssl/+/refs/heads/master/ssl/tls13_server.cc#104 +- SSL.clearOptions(ssl, SSL.SSL_OP_NO_TICKET); - } - } +- +- if (OpenSsl.isBoringSSL() && clientMode) { +- // If in client-mode and BoringSSL let's allow to renegotiate once as the server may use this +- // for client auth. +- // +- // See https://github.com/netty/netty/issues/11529 +- SSL.setRenegotiateMode(ssl, SSL.SSL_RENEGOTIATE_ONCE); +- } +- // setMode may impact the overhead. +- calculateMaxWrapOverhead(); +- +- // Configure any endpoint verification specified by the SslContext. +- configureEndpointVerification(endpointIdentificationAlgorithm); +- } catch (Throwable cause) { +- // Call shutdown so we are sure we correctly release all native memory and also guard against the +- // case when shutdown() will be called by the finalizer again. +- shutdown(); +- +- PlatformDependent.throwException(cause); - } - } - -- private void initCerts(byte[][] chain, int startPos) { -- for (int i = 0; i < chain.length; i++) { -- int certPos = startPos + i; -- peerCerts[certPos] = new OpenSslX509Certificate(chain[i]); -- x509PeerCerts[certPos] = new OpenSslJavaxX509Certificate(chain[i]); -- } +- // Now that everything looks good and we're going to successfully return the +- // object so we need to retain a reference to the parent context. +- parentContext = context; +- parentContext.retain(); +- +- // Only create the leak after everything else was executed and so ensure we don't produce a false-positive for +- // the ResourceLeakDetector. +- leak = leakDetection ? leakDetector.track(this) : null; +- } +- +- final synchronized String[] authMethods() { +- if (isDestroyed()) { +- return EMPTY_STRINGS; - } +- return SSL.authenticationMethods(ssl); +- } - -- /** -- * Select the application protocol used. -- */ -- private void selectApplicationProtocol() throws SSLException { -- ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior = apn.selectedListenerFailureBehavior(); -- List protocols = apn.protocols(); -- String applicationProtocol; -- switch (apn.protocol()) { -- case NONE: -- break; -- // We always need to check for applicationProtocol == null as the remote peer may not support -- // the TLS extension or may have returned an empty selection. -- case ALPN: -- applicationProtocol = SSL.getAlpnSelected(ssl); -- if (applicationProtocol != null) { -- this.applicationProtocol = selectApplicationProtocol( -- protocols, behavior, applicationProtocol); -- } -- break; -- case NPN: -- applicationProtocol = SSL.getNextProtoNegotiated(ssl); -- if (applicationProtocol != null) { -- this.applicationProtocol = selectApplicationProtocol( -- protocols, behavior, applicationProtocol); -- } -- break; -- case NPN_AND_ALPN: -- applicationProtocol = SSL.getAlpnSelected(ssl); -- if (applicationProtocol == null) { -- applicationProtocol = SSL.getNextProtoNegotiated(ssl); -- } -- if (applicationProtocol != null) { -- this.applicationProtocol = selectApplicationProtocol( -- protocols, behavior, applicationProtocol); -- } -- break; -- default: -- throw new Error(); +- final boolean setKeyMaterial(OpenSslKeyMaterial keyMaterial) throws Exception { +- synchronized (this) { +- if (isDestroyed()) { +- return false; - } +- SSL.setKeyMaterial(ssl, keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress()); - } +- session.setLocalCertificate(keyMaterial.certificateChain()); +- return true; +- } - -- private String selectApplicationProtocol(List protocols, -- ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior, -- String applicationProtocol) throws SSLException { -- if (behavior == ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT) { -- return applicationProtocol; -- } else { -- int size = protocols.size(); -- assert size > 0; -- if (protocols.contains(applicationProtocol)) { -- return applicationProtocol; -- } else { -- if (behavior == ApplicationProtocolConfig.SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL) { -- return protocols.get(size - 1); -- } else { -- throw new SSLException("unknown protocol " + applicationProtocol); -- } -- } -- } +- final synchronized SecretKeySpec masterKey() { +- if (isDestroyed()) { +- return null; - } +- return new SecretKeySpec(SSL.getMasterKey(ssl), "AES"); +- } - -- @Override -- public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { -- synchronized (ReferenceCountedOpenSslEngine.this) { -- if (isEmpty(peerCerts)) { -- throw new SSLPeerUnverifiedException("peer not verified"); -- } -- return peerCerts.clone(); -- } +- synchronized boolean isSessionReused() { +- if (isDestroyed()) { +- return false; +- } +- return SSL.isSessionReused(ssl); +- } +- +- /** +- * Sets the OCSP response. +- */ +- @UnstableApi +- public void setOcspResponse(byte[] response) { +- if (!enableOcsp) { +- throw new IllegalStateException("OCSP stapling is not enabled"); - } - -- @Override -- public Certificate[] getLocalCertificates() { -- if (localCerts == null) { -- return null; -- } -- return localCerts.clone(); +- if (clientMode) { +- throw new IllegalStateException("Not a server SSLEngine"); - } - -- @Override -- public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { -- synchronized (ReferenceCountedOpenSslEngine.this) { -- if (isEmpty(x509PeerCerts)) { -- throw new SSLPeerUnverifiedException("peer not verified"); -- } -- return x509PeerCerts.clone(); +- synchronized (this) { +- if (!isDestroyed()) { +- SSL.setOcspResponse(ssl, response); - } - } +- } - -- @Override -- public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { -- Certificate[] peer = getPeerCertificates(); -- // No need for null or length > 0 is needed as this is done in getPeerCertificates() -- // already. -- return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal(); +- /** +- * Returns the OCSP response or {@code null} if the server didn't provide a stapled OCSP response. +- */ +- @UnstableApi +- public byte[] getOcspResponse() { +- if (!enableOcsp) { +- throw new IllegalStateException("OCSP stapling is not enabled"); - } - -- @Override -- public Principal getLocalPrincipal() { -- Certificate[] local = localCerts; -- if (local == null || local.length == 0) { -- return null; -- } -- return ((java.security.cert.X509Certificate) local[0]).getIssuerX500Principal(); +- if (!clientMode) { +- throw new IllegalStateException("Not a client SSLEngine"); - } - -- @Override -- public String getCipherSuite() { -- synchronized (ReferenceCountedOpenSslEngine.this) { -- if (cipher == null) { -- return INVALID_CIPHER; -- } -- return cipher; +- synchronized (this) { +- if (isDestroyed()) { +- return EmptyArrays.EMPTY_BYTES; - } +- return SSL.getOcspResponse(ssl); - } +- } - -- @Override -- public String getProtocol() { -- String protocol = this.protocol; -- if (protocol == null) { -- synchronized (ReferenceCountedOpenSslEngine.this) { -- if (!isDestroyed()) { -- protocol = SSL.getVersion(ssl); -- } else { -- protocol = StringUtil.EMPTY_STRING; -- } -- } -- } -- return protocol; -- } +- @Override +- public final int refCnt() { +- return refCnt.refCnt(); +- } - -- @Override -- public String getApplicationProtocol() { -- synchronized (ReferenceCountedOpenSslEngine.this) { -- return applicationProtocol; -- } -- } +- @Override +- public final ReferenceCounted retain() { +- refCnt.retain(); +- return this; +- } - -- @Override -- public String getPeerHost() { -- return ReferenceCountedOpenSslEngine.this.getPeerHost(); -- } +- @Override +- public final ReferenceCounted retain(int increment) { +- refCnt.retain(increment); +- return this; +- } - -- @Override -- public int getPeerPort() { -- return ReferenceCountedOpenSslEngine.this.getPeerPort(); -- } +- @Override +- public final ReferenceCounted touch() { +- refCnt.touch(); +- return this; +- } - -- @Override -- public int getPacketBufferSize() { -- return MAX_ENCRYPTED_PACKET_LENGTH; +- @Override +- public final ReferenceCounted touch(Object hint) { +- refCnt.touch(hint); +- return this; +- } +- +- @Override +- public final boolean release() { +- return refCnt.release(); +- } +- +- @Override +- public final boolean release(int decrement) { +- return refCnt.release(decrement); +- } +- +- // These method will override the method defined by Java 8u251 and later. As we may compile with an earlier +- // java8 version we don't use @Override annotations here. +- public String getApplicationProtocol() { +- return applicationProtocol; +- } +- +- // These method will override the method defined by Java 8u251 and later. As we may compile with an earlier +- // java8 version we don't use @Override annotations here. +- public String getHandshakeApplicationProtocol() { +- return applicationProtocol; +- } +- +- @Override +- public final synchronized SSLSession getHandshakeSession() { +- // Javadocs state return value should be: +- // null if this instance is not currently handshaking, or if the current handshake has not +- // progressed far enough to create a basic SSLSession. Otherwise, this method returns the +- // SSLSession currently being negotiated. +- switch(handshakeState) { +- case NOT_STARTED: +- case FINISHED: +- return null; +- default: +- return session; - } +- } - -- @Override -- public int getApplicationBufferSize() { -- return MAX_PLAINTEXT_LENGTH; +- /** +- * Returns the pointer to the {@code SSL} object for this {@link ReferenceCountedOpenSslEngine}. +- * Be aware that it is freed as soon as the {@link #release()} or {@link #shutdown()} methods are called. +- * At this point {@code 0} will be returned. +- */ +- public final synchronized long sslPointer() { +- return ssl; +- } +- +- /** +- * Destroys this engine. +- */ +- public final synchronized void shutdown() { +- if (!destroyed) { +- destroyed = true; +- // Let's check if engineMap is null as it could be in theory if we throw an OOME during the construction of +- // ReferenceCountedOpenSslEngine (before we assign the field). This is needed as shutdown() is called from +- // the finalizer as well. +- if (engineMap != null) { +- engineMap.remove(ssl); +- } +- SSL.freeSSL(ssl); +- ssl = networkBIO = 0; +- +- isInboundDone = outboundClosed = true; - } +- +- // On shutdown clear all errors +- SSL.clearError(); - } --} -diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java -deleted file mode 100644 -index 4c9df31..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java -+++ /dev/null -@@ -1,239 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; - --import io.netty.internal.tcnative.SSL; --import io.netty.internal.tcnative.SSLContext; --import io.netty.internal.tcnative.SniHostNameMatcher; --import io.netty.util.internal.PlatformDependent; --import io.netty.util.internal.logging.InternalLogger; --import io.netty.util.internal.logging.InternalLoggerFactory; +- /** +- * Write plaintext data to the OpenSSL internal BIO +- * +- * Calling this function with src.remaining == 0 is undefined. +- */ +- private int writePlaintextData(final ByteBuffer src, int len) { +- final int pos = src.position(); +- final int limit = src.limit(); +- final int sslWrote; - --import java.security.KeyStore; --import java.security.PrivateKey; --import java.security.cert.X509Certificate; --import javax.net.ssl.KeyManagerFactory; --import javax.net.ssl.SSLException; --import javax.net.ssl.TrustManagerFactory; --import javax.net.ssl.X509ExtendedKeyManager; --import javax.net.ssl.X509ExtendedTrustManager; --import javax.net.ssl.X509KeyManager; --import javax.net.ssl.X509TrustManager; +- if (src.isDirect()) { +- sslWrote = SSL.writeToSSL(ssl, bufferAddress(src) + pos, len); +- if (sslWrote > 0) { +- src.position(pos + sslWrote); +- } +- } else { +- ByteBuf buf = alloc.directBuffer(len); +- try { +- src.limit(pos + len); - --import static io.netty.util.internal.ObjectUtil.checkNotNull; +- buf.setBytes(0, src); +- src.limit(limit); - --/** -- * A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. -- *

Instances of this class must be {@link #release() released} or else native memory will leak! -- * -- *

Instances of this class must not be released before any {@link ReferenceCountedOpenSslEngine} -- * which depends upon the instance of this class is released. Otherwise if any method of -- * {@link ReferenceCountedOpenSslEngine} is called which uses this class's JNI resources the JVM may crash. -- */ --public final class ReferenceCountedOpenSslServerContext extends ReferenceCountedOpenSslContext { -- private static final InternalLogger logger = -- InternalLoggerFactory.getInstance(ReferenceCountedOpenSslServerContext.class); -- private static final byte[] ID = {'n', 'e', 't', 't', 'y'}; -- private final OpenSslServerSessionContext sessionContext; -- private final OpenSslKeyMaterialManager keyMaterialManager; +- sslWrote = SSL.writeToSSL(ssl, memoryAddress(buf), len); +- if (sslWrote > 0) { +- src.position(pos + sslWrote); +- } else { +- src.position(pos); +- } +- } finally { +- buf.release(); +- } +- } +- return sslWrote; +- } - -- ReferenceCountedOpenSslServerContext( -- X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, -- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory, -- Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, -- long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls, -- boolean enableOcsp) throws SSLException { -- this(trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory, ciphers, -- cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls, -- enableOcsp); +- synchronized void bioSetFd(int fd) { +- if (!isDestroyed()) { +- SSL.bioSetFd(this.ssl, fd); +- } - } - -- private ReferenceCountedOpenSslServerContext( -- X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, -- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory, -- Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, -- long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls, -- boolean enableOcsp) throws SSLException { -- super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain, -- clientAuth, protocols, startTls, enableOcsp, true); -- // Create a new SSL_CTX and configure it. -- boolean success = false; -- try { -- ServerContext context = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, -- keyCertChain, key, keyPassword, keyManagerFactory); -- sessionContext = context.sessionContext; -- keyMaterialManager = context.keyMaterialManager; -- success = true; -- } finally { -- if (!success) { -- release(); +- /** +- * Write encrypted data to the OpenSSL network BIO. +- */ +- private ByteBuf writeEncryptedData(final ByteBuffer src, int len) throws SSLException { +- final int pos = src.position(); +- if (src.isDirect()) { +- SSL.bioSetByteBuffer(networkBIO, bufferAddress(src) + pos, len, false); +- } else { +- final ByteBuf buf = alloc.directBuffer(len); +- try { +- final int limit = src.limit(); +- src.limit(pos + len); +- buf.writeBytes(src); +- // Restore the original position and limit because we don't want to consume from `src`. +- src.position(pos); +- src.limit(limit); +- +- SSL.bioSetByteBuffer(networkBIO, memoryAddress(buf), len, false); +- return buf; +- } catch (Throwable cause) { +- buf.release(); +- PlatformDependent.throwException(cause); - } - } +- return null; - } - -- @Override -- public OpenSslServerSessionContext sessionContext() { -- return sessionContext; +- /** +- * Read plaintext data from the OpenSSL internal BIO +- */ +- private int readPlaintextData(final ByteBuffer dst) throws SSLException { +- final int sslRead; +- final int pos = dst.position(); +- if (dst.isDirect()) { +- sslRead = SSL.readFromSSL(ssl, bufferAddress(dst) + pos, dst.limit() - pos); +- if (sslRead > 0) { +- dst.position(pos + sslRead); +- } +- } else { +- final int limit = dst.limit(); +- final int len = min(maxEncryptedPacketLength0(), limit - pos); +- final ByteBuf buf = alloc.directBuffer(len); +- try { +- sslRead = SSL.readFromSSL(ssl, memoryAddress(buf), len); +- if (sslRead > 0) { +- dst.limit(pos + sslRead); +- buf.getBytes(buf.readerIndex(), dst); +- dst.limit(limit); +- } +- } finally { +- buf.release(); +- } +- } +- +- return sslRead; - } - -- @Override -- OpenSslKeyMaterialManager keyMaterialManager() { -- return keyMaterialManager; +- /** +- * Visible only for testing! +- */ +- final synchronized int maxWrapOverhead() { +- return maxWrapOverhead; - } - -- static final class ServerContext { -- OpenSslServerSessionContext sessionContext; -- OpenSslKeyMaterialManager keyMaterialManager; +- /** +- * Visible only for testing! +- */ +- final synchronized int maxEncryptedPacketLength() { +- return maxEncryptedPacketLength0(); - } - -- static ServerContext newSessionContext(ReferenceCountedOpenSslContext thiz, long ctx, OpenSslEngineMap engineMap, -- X509Certificate[] trustCertCollection, -- TrustManagerFactory trustManagerFactory, -- X509Certificate[] keyCertChain, PrivateKey key, -- String keyPassword, KeyManagerFactory keyManagerFactory) -- throws SSLException { -- ServerContext result = new ServerContext(); -- try { -- SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH); -- if (!OpenSsl.useKeyManagerFactory()) { -- if (keyManagerFactory != null) { -- throw new IllegalArgumentException( -- "KeyManagerFactory not supported"); -- } -- checkNotNull(keyCertChain, "keyCertChain"); +- /** +- * This method is intentionally not synchronized, only use if you know you are in the EventLoop +- * thread and visibility on {@link #maxWrapOverhead} is achieved via other synchronized blocks. +- */ +- final int maxEncryptedPacketLength0() { +- return maxWrapOverhead + MAX_PLAINTEXT_LENGTH; +- } - -- setKeyMaterial(ctx, keyCertChain, key, keyPassword); -- } else { -- // javadocs state that keyManagerFactory has precedent over keyCertChain, and we must have a -- // keyManagerFactory for the server so build one if it is not specified. -- if (keyManagerFactory == null) { -- keyManagerFactory = buildKeyManagerFactory( -- keyCertChain, key, keyPassword, keyManagerFactory); -- } -- X509KeyManager keyManager = chooseX509KeyManager(keyManagerFactory.getKeyManagers()); -- result.keyMaterialManager = useExtendedKeyManager(keyManager) ? -- new OpenSslExtendedKeyMaterialManager( -- (X509ExtendedKeyManager) keyManager, keyPassword) : -- new OpenSslKeyMaterialManager(keyManager, keyPassword); -- } -- } catch (Exception e) { -- throw new SSLException("failed to set certificate and key", e); -- } -- try { -- if (trustCertCollection != null) { -- trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory); -- } else if (trustManagerFactory == null) { -- // Mimic the way SSLContext.getInstance(KeyManager[], null, null) works -- trustManagerFactory = TrustManagerFactory.getInstance( -- TrustManagerFactory.getDefaultAlgorithm()); -- trustManagerFactory.init((KeyStore) null); -- } +- /** +- * This method is intentionally not synchronized, only use if you know you are in the EventLoop +- * thread and visibility on {@link #maxWrapBufferSize} and {@link #maxWrapOverhead} is achieved +- * via other synchronized blocks. +- *
+- * Calculates the max size of a single wrap operation for the given plaintextLength and +- * numComponents. +- */ +- final int calculateMaxLengthForWrap(int plaintextLength, int numComponents) { +- return (int) min(maxWrapBufferSize, plaintextLength + (long) maxWrapOverhead * numComponents); +- } - -- final X509TrustManager manager = chooseTrustManager(trustManagerFactory.getTrustManagers()); +- /** +- * This method is intentionally not synchronized, only use if you know you are in the EventLoop +- * thread and visibility on {@link #maxWrapOverhead} is achieved via other synchronized blocks. +- *
+- * Calculates the size of the out net buf to create for the given plaintextLength and numComponents. +- * This is not related to the max size per wrap, as we can wrap chunks at a time into one out net buf. +- */ +- final int calculateOutNetBufSize(int plaintextLength, int numComponents) { +- return (int) min(MAX_VALUE, plaintextLength + (long) maxWrapOverhead * numComponents); +- } - -- // IMPORTANT: The callbacks set for verification must be static to prevent memory leak as -- // otherwise the context can never be collected. This is because the JNI code holds -- // a global reference to the callbacks. -- // -- // See https://github.com/netty/netty/issues/5372 +- final synchronized int sslPending() { +- return sslPending0(); +- } - -- // Use this to prevent an error when running on java < 7 -- if (useExtendedTrustManager(manager)) { -- SSLContext.setCertVerifyCallback(ctx, -- new ExtendedTrustManagerVerifyCallback(engineMap, (X509ExtendedTrustManager) manager)); -- } else { -- SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager)); -- } +- /** +- * It is assumed this method is called in a synchronized block (or the constructor)! +- */ +- private void calculateMaxWrapOverhead() { +- maxWrapOverhead = SSL.getMaxWrapOverhead(ssl); - -- X509Certificate[] issuers = manager.getAcceptedIssuers(); -- if (issuers != null && issuers.length > 0) { -- long bio = 0; -- try { -- bio = toBIO(issuers); -- if (!SSLContext.setCACertificateBio(ctx, bio)) { -- throw new SSLException("unable to setup accepted issuers for trustmanager " + manager); -- } -- } finally { -- freeBio(bio); -- } -- } +- // maxWrapBufferSize must be set after maxWrapOverhead because there is a dependency on this value. +- // If jdkCompatibility mode is off we allow enough space to encrypt 16 buffers at a time. This could be +- // configurable in the future if necessary. +- maxWrapBufferSize = jdkCompatibilityMode ? maxEncryptedPacketLength0() : maxEncryptedPacketLength0() << 4; +- } - -- if (PlatformDependent.javaVersion() >= 8) { -- // Only do on Java8+ as SNIMatcher is not supported in earlier releases. -- // IMPORTANT: The callbacks set for hostname matching must be static to prevent memory leak as -- // otherwise the context can never be collected. This is because the JNI code holds -- // a global reference to the matcher. -- SSLContext.setSniHostnameMatcher(ctx, new OpenSslSniHostnameMatcher(engineMap)); -- } -- } catch (SSLException e) { -- throw e; -- } catch (Exception e) { -- throw new SSLException("unable to setup trustmanager", e); -- } +- private int sslPending0() { +- // OpenSSL has a limitation where if you call SSL_pending before the handshake is complete OpenSSL will throw a +- // "called a function you should not call" error. Using the TLS_method instead of SSLv23_method may solve this +- // issue but this API is only available in 1.1.0+ [1]. +- // [1] https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_new.html +- return handshakeState != HandshakeState.FINISHED ? 0 : SSL.sslPending(ssl); +- } - -- result.sessionContext = new OpenSslServerSessionContext(thiz); -- result.sessionContext.setSessionIdContext(ID); -- return result; +- private boolean isBytesAvailableEnoughForWrap(int bytesAvailable, int plaintextLength, int numComponents) { +- return bytesAvailable - (long) maxWrapOverhead * numComponents >= plaintextLength; - } - -- private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier { -- private final X509TrustManager manager; +- @Override +- public final SSLEngineResult wrap( +- final ByteBuffer[] srcs, int offset, final int length, final ByteBuffer dst) throws SSLException { +- // Throw required runtime exceptions +- checkNotNullWithIAE(srcs, "srcs"); +- checkNotNullWithIAE(dst, "dst"); - -- TrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509TrustManager manager) { -- super(engineMap); -- this.manager = manager; +- if (offset >= srcs.length || offset + length > srcs.length) { +- throw new IndexOutOfBoundsException( +- "offset: " + offset + ", length: " + length + +- " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))"); - } - -- @Override -- void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth) -- throws Exception { -- manager.checkClientTrusted(peerCerts, auth); +- if (dst.isReadOnly()) { +- throw new ReadOnlyBufferException(); - } -- } - -- private static final class ExtendedTrustManagerVerifyCallback extends AbstractCertificateVerifier { -- private final X509ExtendedTrustManager manager; +- synchronized (this) { +- if (isOutboundDone()) { +- // All drained in the outbound buffer +- return isInboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_UNWRAP_CLOSED; +- } - -- ExtendedTrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509ExtendedTrustManager manager) { -- super(engineMap); -- this.manager = manager; -- } +- int bytesProduced = 0; +- ByteBuf bioReadCopyBuf = null; +- try { +- // Setup the BIO buffer so that we directly write the encryption results into dst. +- if (dst.isDirect()) { +- SSL.bioSetByteBuffer(networkBIO, bufferAddress(dst) + dst.position(), dst.remaining(), +- true); +- } else { +- bioReadCopyBuf = alloc.directBuffer(dst.remaining()); +- SSL.bioSetByteBuffer(networkBIO, memoryAddress(bioReadCopyBuf), bioReadCopyBuf.writableBytes(), +- true); +- } - -- @Override -- void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth) -- throws Exception { -- manager.checkClientTrusted(peerCerts, auth, engine); -- } -- } +- int bioLengthBefore = SSL.bioLengthByteBuffer(networkBIO); - -- private static final class OpenSslSniHostnameMatcher implements SniHostNameMatcher { -- private final OpenSslEngineMap engineMap; +- // Explicitly use outboundClosed as we want to drain any bytes that are still present. +- if (outboundClosed) { +- // If the outbound was closed we want to ensure we can produce the alert to the destination buffer. +- // This is true even if we not using jdkCompatibilityMode. +- // +- // We use a plaintextLength of 2 as we at least want to have an alert fit into it. +- // https://tools.ietf.org/html/rfc5246#section-7.2 +- if (!isBytesAvailableEnoughForWrap(dst.remaining(), 2, 1)) { +- return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0); +- } - -- OpenSslSniHostnameMatcher(OpenSslEngineMap engineMap) { -- this.engineMap = engineMap; -- } +- // There is something left to drain. +- // See https://github.com/netty/netty/issues/6260 +- bytesProduced = SSL.bioFlushByteBuffer(networkBIO); +- if (bytesProduced <= 0) { +- return newResultMayFinishHandshake(NOT_HANDSHAKING, 0, 0); +- } +- // It is possible when the outbound was closed there was not enough room in the non-application +- // buffers to hold the close_notify. We should keep trying to close until we consume all the data +- // OpenSSL can give us. +- if (!doSSLShutdown()) { +- return newResultMayFinishHandshake(NOT_HANDSHAKING, 0, bytesProduced); +- } +- bytesProduced = bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); +- return newResultMayFinishHandshake(NEED_WRAP, 0, bytesProduced); +- } - -- @Override -- public boolean match(long ssl, String hostname) { -- ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); -- if (engine != null) { -- return engine.checkSniHostnameMatch(hostname); -- } -- logger.warn("No ReferenceCountedOpenSslEngine found for SSL pointer: {}", ssl); -- return false; -- } -- } --} -diff --git a/handler/src/main/java/io/netty/handler/ssl/SslContext.java b/handler/src/main/java/io/netty/handler/ssl/SslContext.java -index 4998d0d..8dbc3cf 100644 ---- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java -+++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java -@@ -115,11 +115,7 @@ public abstract class SslContext { - } - - private static SslProvider defaultProvider() { -- if (OpenSsl.isAvailable()) { -- return SslProvider.OPENSSL; -- } else { -- return SslProvider.JDK; -- } -+ return SslProvider.JDK; - } - - /** -@@ -416,18 +412,6 @@ public abstract class SslContext { - trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, - keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, - clientAuth, protocols, startTls); -- case OPENSSL: -- verifyNullSslContextProvider(provider, sslContextProvider); -- return new OpenSslServerContext( -- trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, -- keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, -- clientAuth, protocols, startTls, enableOcsp); -- case OPENSSL_REFCNT: -- verifyNullSslContextProvider(provider, sslContextProvider); -- return new ReferenceCountedOpenSslServerContext( -- trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, -- keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, -- clientAuth, protocols, startTls, enableOcsp); - default: - throw new Error(provider.toString()); - } -@@ -770,18 +754,6 @@ public abstract class SslContext { - return new JdkSslClientContext(sslContextProvider, - trustCert, trustManagerFactory, keyCertChain, key, keyPassword, - keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout); -- case OPENSSL: -- verifyNullSslContextProvider(provider, sslContextProvider); -- return new OpenSslClientContext( -- trustCert, trustManagerFactory, keyCertChain, key, keyPassword, -- keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, -- enableOcsp); -- case OPENSSL_REFCNT: -- verifyNullSslContextProvider(provider, sslContextProvider); -- return new ReferenceCountedOpenSslClientContext( -- trustCert, trustManagerFactory, keyCertChain, key, keyPassword, -- keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, -- enableOcsp); - default: - throw new Error(provider.toString()); - } -diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java -index c054964..05c451a 100644 ---- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java -+++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java -@@ -159,6 +159,12 @@ import static io.netty.handler.ssl.SslUtils.getEncryptedPacketLength; - * #832 in our issue tracker. - */ - public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundHandler { -+ private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14 -+ private static final int MAX_COMPRESSED_LENGTH = MAX_PLAINTEXT_LENGTH + 1024; -+ private static final int MAX_CIPHERTEXT_LENGTH = MAX_COMPRESSED_LENGTH + 1024; -+ // Header (5) + Data (2^14) + Compression (1024) + Encryption (1024) + MAC (20) + Padding (256) -+ static final int MAX_ENCRYPTED_PACKET_LENGTH = MAX_CIPHERTEXT_LENGTH + 5 + 20 + 256; -+ static final int MAX_ENCRYPTION_OVERHEAD_LENGTH = MAX_ENCRYPTED_PACKET_LENGTH - MAX_PLAINTEXT_LENGTH; - - private static final InternalLogger logger = - InternalLoggerFactory.getInstance(SslHandler.class); -@@ -181,40 +187,6 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH - new ClosedChannelException(), SslHandler.class, "channelInactive(...)"); - - private enum SslEngineType { -- TCNATIVE(true, COMPOSITE_CUMULATOR) { -- @Override -- SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int readerIndex, int len, ByteBuf out) -- throws SSLException { -- int nioBufferCount = in.nioBufferCount(); -- int writerIndex = out.writerIndex(); -- final SSLEngineResult result; -- if (nioBufferCount > 1) { -- /* -- * If {@link OpenSslEngine} is in use, -- * we can use a special {@link OpenSslEngine#unwrap(ByteBuffer[], ByteBuffer[])} method -- * that accepts multiple {@link ByteBuffer}s without additional memory copies. -- */ -- ReferenceCountedOpenSslEngine opensslEngine = (ReferenceCountedOpenSslEngine) handler.engine; -- try { -- handler.singleBuffer[0] = toByteBuffer(out, writerIndex, -- out.writableBytes()); -- result = opensslEngine.unwrap(in.nioBuffers(readerIndex, len), handler.singleBuffer); -- } finally { -- handler.singleBuffer[0] = null; +- // Flush any data that may be implicitly generated by OpenSSL (handshake, close, etc..). +- SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING; +- HandshakeState oldHandshakeState = handshakeState; +- +- // Prepare OpenSSL to work in server mode and receive handshake +- if (handshakeState != HandshakeState.FINISHED) { +- if (handshakeState != HandshakeState.STARTED_EXPLICITLY) { +- // Update accepted so we know we triggered the handshake via wrap +- handshakeState = HandshakeState.STARTED_IMPLICITLY; +- } +- +- // Flush any data that may have been written implicitly during the handshake by OpenSSL. +- bytesProduced = SSL.bioFlushByteBuffer(networkBIO); +- +- if (pendingException != null) { +- // TODO(scott): It is possible that when the handshake failed there was not enough room in the +- // non-application buffers to hold the alert. We should get all the data before progressing on. +- // However I'm not aware of a way to do this with the OpenSSL APIs. +- // See https://github.com/netty/netty/issues/6385. +- +- // We produced / consumed some data during the handshake, signal back to the caller. +- // If there is a handshake exception and we have produced data, we should send the data before +- // we allow handshake() to throw the handshake exception. +- // +- // When the user calls wrap() again we will propagate the handshake error back to the user as +- // soon as there is no more data to was produced (as part of an alert etc). +- if (bytesProduced > 0) { +- return newResult(NEED_WRAP, 0, bytesProduced); +- } +- // Nothing was produced see if there is a handshakeException that needs to be propagated +- // to the caller by calling handshakeException() which will return the right HandshakeStatus +- // if it can "recover" from the exception for now. +- return newResult(handshakeException(), 0, 0); - } -- } else { -- result = handler.engine.unwrap(toByteBuffer(in, readerIndex, len), -- toByteBuffer(out, writerIndex, out.writableBytes())); -- } -- out.writerIndex(writerIndex + result.bytesProduced()); -- return result; -- } - -- @Override -- int calculateWrapBufferCapacity(SslHandler handler, int pendingBytes, int numComponents) { -- return ReferenceCountedOpenSslEngine.calculateOutNetBufSize(pendingBytes, numComponents); -- } -- }, - CONSCRYPT(true, COMPOSITE_CUMULATOR) { - @Override - SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int readerIndex, int len, ByteBuf out) -@@ -265,9 +237,6 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH - }; - - static SslEngineType forEngine(SSLEngine engine) { -- if (engine instanceof ReferenceCountedOpenSslEngine) { -- return TCNATIVE; -- } - if (engine instanceof ConscryptAlpnSslEngine) { - return CONSCRYPT; - } -@@ -1034,7 +1003,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH - - boolean nonSslRecord = false; - -- while (totalLength < ReferenceCountedOpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) { -+ while (totalLength < MAX_ENCRYPTED_PACKET_LENGTH) { - final int readableBytes = endOffset - offset; - if (readableBytes < SslUtils.SSL_RECORD_HEADER_LENGTH) { - break; -@@ -1055,7 +1024,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH - } - - int newTotalLength = totalLength + packetLength; -- if (newTotalLength > ReferenceCountedOpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) { -+ if (newTotalLength > MAX_ENCRYPTED_PACKET_LENGTH) { - // Don't read too much. - break; - } -diff --git a/handler/src/main/java/io/netty/handler/ssl/ocsp/OcspClientHandler.java b/handler/src/main/java/io/netty/handler/ssl/ocsp/OcspClientHandler.java -deleted file mode 100644 -index aff0949..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/ocsp/OcspClientHandler.java -+++ /dev/null -@@ -1,65 +0,0 @@ --/* -- * Copyright 2017 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl.ocsp; +- status = handshake(); - --import io.netty.channel.ChannelHandlerContext; --import io.netty.channel.ChannelInboundHandlerAdapter; --import io.netty.handler.ssl.ReferenceCountedOpenSslContext; --import io.netty.handler.ssl.ReferenceCountedOpenSslEngine; --import io.netty.handler.ssl.SslHandshakeCompletionEvent; --import io.netty.util.internal.ObjectUtil; --import io.netty.util.internal.ThrowableUtil; --import io.netty.util.internal.UnstableApi; +- // Handshake may have generated more data, for example if the internal SSL buffer is small +- // we may have freed up space by flushing above. +- bytesProduced = bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); - --import javax.net.ssl.SSLHandshakeException; +- if (status == NEED_TASK) { +- return newResult(status, 0, bytesProduced); +- } - --/** -- * A handler for SSL clients to handle and act upon stapled OCSP responses. -- * -- * @see ReferenceCountedOpenSslContext#enableOcsp() -- * @see ReferenceCountedOpenSslEngine#getOcspResponse() -- */ --@UnstableApi --public abstract class OcspClientHandler extends ChannelInboundHandlerAdapter { +- if (bytesProduced > 0) { +- // If we have filled up the dst buffer and we have not finished the handshake we should try to +- // wrap again. Otherwise we should only try to wrap again if there is still data pending in +- // SSL buffers. +- return newResult(mayFinishHandshake(status != FINISHED ? +- bytesProduced == bioLengthBefore ? NEED_WRAP : +- getHandshakeStatus(SSL.bioLengthNonApplication(networkBIO)) : FINISHED), +- 0, bytesProduced); +- } - -- private static final SSLHandshakeException OCSP_VERIFICATION_EXCEPTION = ThrowableUtil.unknownStackTrace( -- new SSLHandshakeException("Bad OCSP response"), OcspClientHandler.class, "verify(...)"); +- if (status == NEED_UNWRAP) { +- // Signal if the outbound is done or not. +- return isOutboundDone() ? NEED_UNWRAP_CLOSED : NEED_UNWRAP_OK; +- } - -- private final ReferenceCountedOpenSslEngine engine; +- // Explicit use outboundClosed and not outboundClosed() as we want to drain any bytes that are +- // still present. +- if (outboundClosed) { +- bytesProduced = SSL.bioFlushByteBuffer(networkBIO); +- return newResultMayFinishHandshake(status, 0, bytesProduced); +- } +- } - -- protected OcspClientHandler(ReferenceCountedOpenSslEngine engine) { -- this.engine = ObjectUtil.checkNotNull(engine, "engine"); -- } +- final int endOffset = offset + length; +- if (jdkCompatibilityMode || +- // If the handshake was not finished before we entered the method, we also ensure we only +- // wrap one record. We do this to ensure we not produce any extra data before the caller +- // of the method is able to observe handshake completion and react on it. +- oldHandshakeState != HandshakeState.FINISHED) { +- int srcsLen = 0; +- for (int i = offset; i < endOffset; ++i) { +- final ByteBuffer src = srcs[i]; +- if (src == null) { +- throw new IllegalArgumentException("srcs[" + i + "] is null"); +- } +- if (srcsLen == MAX_PLAINTEXT_LENGTH) { +- continue; +- } - -- /** -- * @see ReferenceCountedOpenSslEngine#getOcspResponse() -- */ -- protected abstract boolean verify(ChannelHandlerContext ctx, ReferenceCountedOpenSslEngine engine) throws Exception; +- srcsLen += src.remaining(); +- if (srcsLen > MAX_PLAINTEXT_LENGTH || srcsLen < 0) { +- // If srcLen > MAX_PLAINTEXT_LENGTH or secLen < 0 just set it to MAX_PLAINTEXT_LENGTH. +- // This also help us to guard against overflow. +- // We not break out here as we still need to check for null entries in srcs[]. +- srcsLen = MAX_PLAINTEXT_LENGTH; +- } +- } - -- @Override -- public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { -- if (evt instanceof SslHandshakeCompletionEvent) { -- ctx.pipeline().remove(this); +- // jdkCompatibilityMode will only produce a single TLS packet, and we don't aggregate src buffers, +- // so we always fix the number of buffers to 1 when checking if the dst buffer is large enough. +- if (!isBytesAvailableEnoughForWrap(dst.remaining(), srcsLen, 1)) { +- return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0); +- } +- } - -- SslHandshakeCompletionEvent event = (SslHandshakeCompletionEvent) evt; -- if (event.isSuccess() && !verify(ctx, engine)) { -- throw OCSP_VERIFICATION_EXCEPTION; -- } -- } +- // There was no pending data in the network BIO -- encrypt any application data +- int bytesConsumed = 0; +- assert bytesProduced == 0; - -- ctx.fireUserEventTriggered(evt); -- } --} -diff --git a/handler/src/main/java/io/netty/handler/ssl/ocsp/package-info.java b/handler/src/main/java/io/netty/handler/ssl/ocsp/package-info.java -deleted file mode 100644 -index 2883ff4..0000000 ---- a/handler/src/main/java/io/netty/handler/ssl/ocsp/package-info.java -+++ /dev/null -@@ -1,23 +0,0 @@ --/* -- * Copyright 2017 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ +- // Flush any data that may have been written implicitly by OpenSSL in case a shutdown/alert occurs. +- bytesProduced = SSL.bioFlushByteBuffer(networkBIO); - --/** -- * OCSP stapling, -- * formally known as the TLS Certificate Status Request extension, is an -- * alternative approach to the Online Certificate Status Protocol (OCSP) -- * for checking the revocation status of X.509 digital certificates. -- */ --package io.netty.handler.ssl.ocsp; -diff --git a/handler/src/test/java/io/netty/handler/ssl/JdkOpenSslEngineInteroptTest.java b/handler/src/test/java/io/netty/handler/ssl/JdkOpenSslEngineInteroptTest.java -deleted file mode 100644 -index d696d6b..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/JdkOpenSslEngineInteroptTest.java -+++ /dev/null -@@ -1,108 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; +- if (bytesProduced > 0) { +- return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); +- } +- // There was a pending exception that we just delayed because there was something to produce left. +- // Throw it now and shutdown the engine. +- if (pendingException != null) { +- Throwable error = pendingException; +- pendingException = null; +- shutdown(); +- // Throw a new exception wrapping the pending exception, so the stacktrace is meaningful and +- // contains all the details. +- throw new SSLException(error); +- } +- +- for (; offset < endOffset; ++offset) { +- final ByteBuffer src = srcs[offset]; +- final int remaining = src.remaining(); +- if (remaining == 0) { +- continue; +- } - --import org.junit.BeforeClass; --import org.junit.Test; --import org.junit.runner.RunWith; --import org.junit.runners.Parameterized; +- final int bytesWritten; +- if (jdkCompatibilityMode) { +- // Write plaintext application data to the SSL engine. We don't have to worry about checking +- // if there is enough space if jdkCompatibilityMode because we only wrap at most +- // MAX_PLAINTEXT_LENGTH and we loop over the input before hand and check if there is space. +- bytesWritten = writePlaintextData(src, min(remaining, MAX_PLAINTEXT_LENGTH - bytesConsumed)); +- } else { +- // OpenSSL's SSL_write keeps state between calls. We should make sure the amount we attempt to +- // write is guaranteed to succeed so we don't have to worry about keeping state consistent +- // between calls. +- final int availableCapacityForWrap = dst.remaining() - bytesProduced - maxWrapOverhead; +- if (availableCapacityForWrap <= 0) { +- return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, +- bytesProduced); +- } +- bytesWritten = writePlaintextData(src, min(remaining, availableCapacityForWrap)); +- } - --import java.util.ArrayList; --import java.util.Collection; --import java.util.List; +- // Determine how much encrypted data was generated. +- // +- // Even if SSL_write doesn't consume any application data it is possible that OpenSSL will +- // produce non-application data into the BIO. For example session tickets.... +- // See https://github.com/netty/netty/issues/10041 +- final int pendingNow = SSL.bioLengthByteBuffer(networkBIO); +- bytesProduced += bioLengthBefore - pendingNow; +- bioLengthBefore = pendingNow; +- +- if (bytesWritten > 0) { +- bytesConsumed += bytesWritten; +- +- if (jdkCompatibilityMode || bytesProduced == dst.remaining()) { +- return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); +- } +- } else { +- int sslError = SSL.getError(ssl, bytesWritten); +- if (sslError == SSL.SSL_ERROR_ZERO_RETURN) { +- // This means the connection was shutdown correctly, close inbound and outbound +- if (!receivedShutdown) { +- closeAll(); - --import static io.netty.handler.ssl.OpenSslTestUtils.checkShouldUseKeyManagerFactory; --import static io.netty.internal.tcnative.SSL.SSL_CVERIFY_IGNORED; --import static org.junit.Assume.assumeTrue; +- bytesProduced += bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); +- +- // If we have filled up the dst buffer and we have not finished the handshake we should +- // try to wrap again. Otherwise we should only try to wrap again if there is still data +- // pending in SSL buffers. +- SSLEngineResult.HandshakeStatus hs = mayFinishHandshake( +- status != FINISHED ? bytesProduced == dst.remaining() ? NEED_WRAP +- : getHandshakeStatus(SSL.bioLengthNonApplication(networkBIO)) +- : FINISHED); +- return newResult(hs, bytesConsumed, bytesProduced); +- } - --@RunWith(Parameterized.class) --public class JdkOpenSslEngineInteroptTest extends SSLEngineTest { +- return newResult(NOT_HANDSHAKING, bytesConsumed, bytesProduced); +- } else if (sslError == SSL.SSL_ERROR_WANT_READ) { +- // If there is no pending data to read from BIO we should go back to event loop and try +- // to read more data [1]. It is also possible that event loop will detect the socket has +- // been closed. [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html +- return newResult(NEED_UNWRAP, bytesConsumed, bytesProduced); +- } else if (sslError == SSL.SSL_ERROR_WANT_WRITE) { +- // SSL_ERROR_WANT_WRITE typically means that the underlying transport is not writable +- // and we should set the "want write" flag on the selector and try again when the +- // underlying transport is writable [1]. However we are not directly writing to the +- // underlying transport and instead writing to a BIO buffer. The OpenSsl documentation +- // says we should do the following [1]: +- // +- // "When using a buffering BIO, like a BIO pair, data must be written into or retrieved +- // out of the BIO before being able to continue." +- // +- // In practice this means the destination buffer doesn't have enough space for OpenSSL +- // to write encrypted data to. This is an OVERFLOW condition. +- // [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html +- if (bytesProduced > 0) { +- // If we produced something we should report this back and let the user call +- // wrap again. +- return newResult(NEED_WRAP, bytesConsumed, bytesProduced); +- } +- return newResult(BUFFER_OVERFLOW, status, bytesConsumed, bytesProduced); +- } else if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || +- sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY || +- sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) { - -- @Parameterized.Parameters(name = "{index}: bufferType = {0}") -- public static Collection data() { -- List params = new ArrayList(); -- for (BufferType type: BufferType.values()) { -- params.add(type); +- return newResult(NEED_TASK, bytesConsumed, bytesProduced); +- } else { +- // Everything else is considered as error +- throw shutdownWithError("SSL_write", sslError); +- } +- } +- } +- return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); +- } finally { +- SSL.bioClearByteBuffer(networkBIO); +- if (bioReadCopyBuf == null) { +- dst.position(dst.position() + bytesProduced); +- } else { +- assert bioReadCopyBuf.readableBytes() <= dst.remaining() : "The destination buffer " + dst + +- " didn't have enough remaining space to hold the encrypted content in " + bioReadCopyBuf; +- dst.put(bioReadCopyBuf.internalNioBuffer(bioReadCopyBuf.readerIndex(), bytesProduced)); +- bioReadCopyBuf.release(); +- } +- } - } -- return params; - } - -- public JdkOpenSslEngineInteroptTest(BufferType type) { -- super(type); +- private SSLEngineResult newResult(SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) { +- return newResult(OK, hs, bytesConsumed, bytesProduced); +- } +- +- private SSLEngineResult newResult(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus hs, +- int bytesConsumed, int bytesProduced) { +- // If isOutboundDone, then the data from the network BIO +- // was the close_notify message and all was consumed we are not required to wait +- // for the receipt the peer's close_notify message -- shutdown. +- if (isOutboundDone()) { +- if (isInboundDone()) { +- // If the inbound was done as well, we need to ensure we return NOT_HANDSHAKING to signal we are done. +- hs = NOT_HANDSHAKING; +- +- // As the inbound and the outbound is done we can shutdown the engine now. +- shutdown(); +- } +- return new SSLEngineResult(CLOSED, hs, bytesConsumed, bytesProduced); +- } +- if (hs == NEED_TASK) { +- // Set needTask to true so getHandshakeStatus() will return the correct value. +- needTask = true; +- } +- return new SSLEngineResult(status, hs, bytesConsumed, bytesProduced); - } - -- @BeforeClass -- public static void checkOpenSsl() { -- assumeTrue(OpenSsl.isAvailable()); +- private SSLEngineResult newResultMayFinishHandshake(SSLEngineResult.HandshakeStatus hs, +- int bytesConsumed, int bytesProduced) throws SSLException { +- return newResult(mayFinishHandshake(hs, bytesConsumed, bytesProduced), bytesConsumed, bytesProduced); - } - -- @Override -- protected SslProvider sslClientProvider() { -- return SslProvider.JDK; +- private SSLEngineResult newResultMayFinishHandshake(SSLEngineResult.Status status, +- SSLEngineResult.HandshakeStatus hs, +- int bytesConsumed, int bytesProduced) throws SSLException { +- return newResult(status, mayFinishHandshake(hs, bytesConsumed, bytesProduced), bytesConsumed, bytesProduced); - } - -- @Override -- protected SslProvider sslServerProvider() { -- return SslProvider.OPENSSL; +- /** +- * Log the error, shutdown the engine and throw an exception. +- */ +- private SSLException shutdownWithError(String operations, int sslError) { +- return shutdownWithError(operations, sslError, SSL.getLastErrorNumber()); - } - -- @Override -- @Test -- public void testMutualAuthInvalidIntermediateCASucceedWithOptionalClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthInvalidIntermediateCASucceedWithOptionalClientAuth(); -- } +- private SSLException shutdownWithError(String operation, int sslError, int error) { +- if (logger.isDebugEnabled()) { +- String errorString = SSL.getErrorString(error); +- logger.debug("{} failed with {}: OpenSSL error: {} {}", +- operation, sslError, error, errorString); +- } - -- @Override -- @Test -- public void testMutualAuthInvalidIntermediateCAFailWithOptionalClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthInvalidIntermediateCAFailWithOptionalClientAuth(); -- } +- // There was an internal error -- shutdown +- shutdown(); - -- @Override -- @Test -- public void testMutualAuthInvalidIntermediateCAFailWithRequiredClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthInvalidIntermediateCAFailWithRequiredClientAuth(); +- SSLException exception = newSSLExceptionForError(error); +- // If we have a pendingException stored already we should include it as well to help the user debug things. +- if (pendingException != null) { +- exception.initCause(pendingException); +- pendingException = null; +- } +- return exception; - } - -- @Override -- @Test -- public void testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth(); +- private SSLEngineResult handleUnwrapException(int bytesConsumed, int bytesProduced, SSLException e) +- throws SSLException { +- int lastError = SSL.getLastErrorNumber(); +- if (lastError != 0) { +- return sslReadErrorResult(SSL.SSL_ERROR_SSL, lastError, bytesConsumed, +- bytesProduced); +- } +- throw e; - } - -- @Override -- @Test -- public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthValidClientCertChainTooLongFailRequireClientAuth(); -- } +- public final SSLEngineResult unwrap( +- final ByteBuffer[] srcs, int srcsOffset, final int srcsLength, +- final ByteBuffer[] dsts, int dstsOffset, final int dstsLength) throws SSLException { - -- @Override -- protected void mySetupMutualAuthServerInitSslHandler(SslHandler handler) { -- ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) handler.engine(); -- engine.setVerify(SSL_CVERIFY_IGNORED, 1); -- } +- // Throw required runtime exceptions +- checkNotNullWithIAE(srcs, "srcs"); +- if (srcsOffset >= srcs.length +- || srcsOffset + srcsLength > srcs.length) { +- throw new IndexOutOfBoundsException( +- "offset: " + srcsOffset + ", length: " + srcsLength + +- " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))"); +- } +- checkNotNullWithIAE(dsts, "dsts"); +- if (dstsOffset >= dsts.length || dstsOffset + dstsLength > dsts.length) { +- throw new IndexOutOfBoundsException( +- "offset: " + dstsOffset + ", length: " + dstsLength + +- " (expected: offset <= offset + length <= dsts.length (" + dsts.length + "))"); +- } +- long capacity = 0; +- final int dstsEndOffset = dstsOffset + dstsLength; +- for (int i = dstsOffset; i < dstsEndOffset; i ++) { +- ByteBuffer dst = checkNotNullArrayParam(dsts[i], i, "dsts"); +- if (dst.isReadOnly()) { +- throw new ReadOnlyBufferException(); +- } +- capacity += dst.remaining(); +- } - -- @Override -- protected boolean mySetupMutualAuthServerIsValidClientException(Throwable cause) { -- // TODO(scott): work around for a JDK issue. The exception should be SSLHandshakeException. -- return super.mySetupMutualAuthServerIsValidClientException(cause) || causedBySSLException(cause); -- } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslCertificateExceptionTest.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslCertificateExceptionTest.java -deleted file mode 100644 -index 229e853..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/OpenSslCertificateExceptionTest.java -+++ /dev/null -@@ -1,49 +0,0 @@ --/* -- * Copyright 2017 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; +- final int srcsEndOffset = srcsOffset + srcsLength; +- long len = 0; +- for (int i = srcsOffset; i < srcsEndOffset; i++) { +- ByteBuffer src = checkNotNullArrayParam(srcs[i], i, "srcs"); +- len += src.remaining(); +- } - --import io.netty.internal.tcnative.CertificateVerifier; --import org.junit.Assert; --import org.junit.Assume; --import org.junit.BeforeClass; --import org.junit.Test; +- synchronized (this) { +- if (isInboundDone()) { +- return isOutboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_WRAP_CLOSED; +- } - --import java.lang.reflect.Field; +- SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING; +- HandshakeState oldHandshakeState = handshakeState; +- // Prepare OpenSSL to work in server mode and receive handshake +- if (handshakeState != HandshakeState.FINISHED) { +- if (handshakeState != HandshakeState.STARTED_EXPLICITLY) { +- // Update accepted so we know we triggered the handshake via wrap +- handshakeState = HandshakeState.STARTED_IMPLICITLY; +- } - --public class OpenSslCertificateExceptionTest { +- status = handshake(); - -- @BeforeClass -- public static void assumeOpenSsl() { -- Assume.assumeTrue(OpenSsl.isAvailable()); -- } +- if (status == NEED_TASK) { +- return newResult(status, 0, 0); +- } - -- @Test -- public void testValidErrorCode() throws Exception { -- Field[] fields = CertificateVerifier.class.getFields(); -- for (Field field : fields) { -- if (field.isAccessible()) { -- int errorCode = field.getInt(null); -- OpenSslCertificateException exception = new OpenSslCertificateException(errorCode); -- Assert.assertEquals(errorCode, exception.errorCode()); +- if (status == NEED_WRAP) { +- return NEED_WRAP_OK; +- } +- // Check if the inbound is considered to be closed if so let us try to wrap again. +- if (isInboundDone) { +- return NEED_WRAP_CLOSED; +- } - } -- } -- } - -- @Test(expected = IllegalArgumentException.class) -- public void testNonValidErrorCode() { -- new OpenSslCertificateException(Integer.MIN_VALUE); -- } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslClientContextTest.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslClientContextTest.java -deleted file mode 100644 -index 6011cf7..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/OpenSslClientContextTest.java -+++ /dev/null -@@ -1,38 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; +- int sslPending = sslPending0(); +- int packetLength; +- // The JDK implies that only a single SSL packet should be processed per unwrap call [1]. If we are in +- // JDK compatibility mode then we should honor this, but if not we just wrap as much as possible. If there +- // are multiple records or partial records this may reduce thrashing events through the pipeline. +- // [1] https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html +- if (jdkCompatibilityMode || +- // If the handshake was not finished before we entered the method, we also ensure we only +- // unwrap one record. We do this to ensure we not produce any extra data before the caller +- // of the method is able to observe handshake completion and react on it. +- oldHandshakeState != HandshakeState.FINISHED) { +- if (len < SSL_RECORD_HEADER_LENGTH) { +- return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); +- } - --import io.netty.handler.ssl.util.InsecureTrustManagerFactory; --import org.junit.BeforeClass; +- packetLength = SslUtils.getEncryptedPacketLength(srcs, srcsOffset); +- if (packetLength == SslUtils.NOT_ENCRYPTED) { +- throw new NotSslRecordException("not an SSL/TLS record"); +- } - --import javax.net.ssl.SSLException; --import java.io.File; +- final int packetLengthDataOnly = packetLength - SSL_RECORD_HEADER_LENGTH; +- if (packetLengthDataOnly > capacity) { +- // Not enough space in the destination buffer so signal the caller that the buffer needs to be +- // increased. +- if (packetLengthDataOnly > MAX_RECORD_SIZE) { +- // The packet length MUST NOT exceed 2^14 [1]. However we do accommodate more data to support +- // legacy use cases which may violate this condition (e.g. OpenJDK's SslEngineImpl). If the max +- // length is exceeded we fail fast here to avoid an infinite loop due to the fact that we +- // won't allocate a buffer large enough. +- // [1] https://tools.ietf.org/html/rfc5246#section-6.2.1 +- throw new SSLException("Illegal packet length: " + packetLengthDataOnly + " > " + +- session.getApplicationBufferSize()); +- } else { +- session.tryExpandApplicationBufferSize(packetLengthDataOnly); +- } +- return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0); +- } - --import static org.junit.Assume.assumeTrue; +- if (len < packetLength) { +- // We either don't have enough data to read the packet length or not enough for reading the whole +- // packet. +- return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); +- } +- } else if (len == 0 && sslPending <= 0) { +- return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); +- } else if (capacity == 0) { +- return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0); +- } else { +- packetLength = (int) min(MAX_VALUE, len); +- } - --public class OpenSslClientContextTest extends SslContextTest { +- // This must always be the case when we reached here as if not we returned BUFFER_UNDERFLOW. +- assert srcsOffset < srcsEndOffset; - -- @BeforeClass -- public static void checkOpenSsl() { -- assumeTrue(OpenSsl.isAvailable()); -- } +- // This must always be the case if we reached here. +- assert capacity > 0; - -- @Override -- protected SslContext newServerContext(File crtFile, File keyFile, String pass) throws SSLException { -- return new OpenSslClientContext(crtFile, InsecureTrustManagerFactory.INSTANCE, crtFile, keyFile, pass, -- null, null, IdentityCipherSuiteFilter.INSTANCE, ApplicationProtocolConfig.DISABLED, 0, 0); -- } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java -deleted file mode 100644 -index 5939b66..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/OpenSslEngineTest.java -+++ /dev/null -@@ -1,661 +0,0 @@ --/* -- * Copyright 2015 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; +- // Number of produced bytes +- int bytesProduced = 0; +- int bytesConsumed = 0; +- try { +- srcLoop: +- for (;;) { +- ByteBuffer src = srcs[srcsOffset]; +- int remaining = src.remaining(); +- final ByteBuf bioWriteCopyBuf; +- int pendingEncryptedBytes; +- if (remaining == 0) { +- if (sslPending <= 0) { +- // We must skip empty buffers as BIO_write will return 0 if asked to write something +- // with length 0. +- if (++srcsOffset >= srcsEndOffset) { +- break; +- } +- continue; +- } else { +- bioWriteCopyBuf = null; +- pendingEncryptedBytes = SSL.bioLengthByteBuffer(networkBIO); +- } +- } else { +- // Write more encrypted data into the BIO. Ensure we only read one packet at a time as +- // stated in the SSLEngine javadocs. +- pendingEncryptedBytes = min(packetLength, remaining); +- try { +- bioWriteCopyBuf = writeEncryptedData(src, pendingEncryptedBytes); +- } catch (SSLException e) { +- // Ensure we correctly handle the error stack. +- return handleUnwrapException(bytesConsumed, bytesProduced, e); +- } +- } +- try { +- for (;;) { +- ByteBuffer dst = dsts[dstsOffset]; +- if (!dst.hasRemaining()) { +- // No space left in the destination buffer, skip it. +- if (++dstsOffset >= dstsEndOffset) { +- break srcLoop; +- } +- continue; +- } - --import io.netty.buffer.UnpooledByteBufAllocator; --import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol; --import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior; --import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior; --import io.netty.handler.ssl.util.InsecureTrustManagerFactory; --import io.netty.handler.ssl.util.SelfSignedCertificate; --import io.netty.util.internal.PlatformDependent; --import org.junit.Assume; --import org.junit.BeforeClass; --import org.junit.Test; --import org.junit.runner.RunWith; --import org.junit.runners.Parameterized; +- int bytesRead; +- try { +- bytesRead = readPlaintextData(dst); +- } catch (SSLException e) { +- // Ensure we correctly handle the error stack. +- return handleUnwrapException(bytesConsumed, bytesProduced, e); +- } +- // We are directly using the ByteBuffer memory for the write, and so we only know what has +- // been consumed after we let SSL decrypt the data. At this point we should update the +- // number of bytes consumed, update the ByteBuffer position, and release temp ByteBuf. +- int localBytesConsumed = pendingEncryptedBytes - SSL.bioLengthByteBuffer(networkBIO); +- bytesConsumed += localBytesConsumed; +- packetLength -= localBytesConsumed; +- pendingEncryptedBytes -= localBytesConsumed; +- src.position(src.position() + localBytesConsumed); - --import java.nio.ByteBuffer; --import java.security.AlgorithmConstraints; --import java.security.AlgorithmParameters; --import java.security.CryptoPrimitive; --import java.security.Key; --import java.util.ArrayList; --import java.util.Collection; --import java.util.List; --import java.util.Set; --import javax.net.ssl.SSLEngine; --import javax.net.ssl.SSLEngineResult; --import javax.net.ssl.SSLException; --import javax.net.ssl.SSLParameters; +- if (bytesRead > 0) { +- bytesProduced += bytesRead; - --import static io.netty.handler.ssl.OpenSslTestUtils.checkShouldUseKeyManagerFactory; --import static io.netty.handler.ssl.ReferenceCountedOpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH; --import static io.netty.handler.ssl.ReferenceCountedOpenSslEngine.MAX_TLS_RECORD_OVERHEAD_LENGTH; --import static io.netty.handler.ssl.ReferenceCountedOpenSslEngine.MAX_PLAINTEXT_LENGTH; --import static io.netty.internal.tcnative.SSL.SSL_CVERIFY_IGNORED; --import static java.lang.Integer.MAX_VALUE; --import static org.junit.Assert.assertEquals; --import static org.junit.Assert.assertFalse; --import static org.junit.Assert.assertNull; --import static org.junit.Assert.assertSame; --import static org.junit.Assert.assertTrue; --import static org.junit.Assume.assumeTrue; +- if (!dst.hasRemaining()) { +- sslPending = sslPending0(); +- // Move to the next dst buffer as this one is full. +- if (++dstsOffset >= dstsEndOffset) { +- return sslPending > 0 ? +- newResult(BUFFER_OVERFLOW, status, bytesConsumed, bytesProduced) : +- newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, +- bytesConsumed, bytesProduced); +- } +- } else if (packetLength == 0 || jdkCompatibilityMode) { +- // We either consumed all data or we are in jdkCompatibilityMode and have consumed +- // a single TLS packet and should stop consuming until this method is called again. +- break srcLoop; +- } +- } else { +- int sslError = SSL.getError(ssl, bytesRead); +- if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) { +- // break to the outer loop as we want to read more data which means we need to +- // write more to the BIO. +- break; +- } else if (sslError == SSL.SSL_ERROR_ZERO_RETURN) { +- // This means the connection was shutdown correctly, close inbound and outbound +- if (!receivedShutdown) { +- closeAll(); +- } +- return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, +- bytesConsumed, bytesProduced); +- } else if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || +- sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY || +- sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) { +- return newResult(isInboundDone() ? CLOSED : OK, +- NEED_TASK, bytesConsumed, bytesProduced); +- } else { +- return sslReadErrorResult(sslError, SSL.getLastErrorNumber(), bytesConsumed, +- bytesProduced); +- } +- } +- } - --@RunWith(Parameterized.class) --public class OpenSslEngineTest extends SSLEngineTest { -- private static final String PREFERRED_APPLICATION_LEVEL_PROTOCOL = "my-protocol-http2"; -- private static final String FALLBACK_APPLICATION_LEVEL_PROTOCOL = "my-protocol-http1_1"; +- if (++srcsOffset >= srcsEndOffset) { +- break; +- } +- } finally { +- if (bioWriteCopyBuf != null) { +- bioWriteCopyBuf.release(); +- } +- } +- } +- } finally { +- SSL.bioClearByteBuffer(networkBIO); +- rejectRemoteInitiatedRenegotiation(); +- } - -- @Parameterized.Parameters(name = "{index}: bufferType = {0}") -- public static Collection data() { -- List params = new ArrayList(); -- for (BufferType type: BufferType.values()) { -- params.add(type); -- } -- return params; -- } +- // Check to see if we received a close_notify message from the peer. +- if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) { +- closeAll(); +- } - -- public OpenSslEngineTest(BufferType type) { -- super(type); +- return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced); +- } - } - -- @BeforeClass -- public static void checkOpenSsl() { -- assumeTrue(OpenSsl.isAvailable()); +- private boolean needWrapAgain(int stackError) { +- // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the +- // BIO first or can just shutdown and throw it now. +- // This is needed so we ensure close_notify etc is correctly send to the remote peer. +- // See https://github.com/netty/netty/issues/3900 +- if (SSL.bioLengthNonApplication(networkBIO) > 0) { +- // we seem to have data left that needs to be transferred and so the user needs +- // call wrap(...). Store the error so we can pick it up later. +- if (pendingException == null) { +- pendingException = newSSLExceptionForError(stackError); +- } else if (shouldAddSuppressed(pendingException, stackError)) { +- ThrowableUtil.addSuppressed(pendingException, newSSLExceptionForError(stackError)); +- } +- // We need to clear all errors so we not pick up anything that was left on the stack on the next +- // operation. Note that shutdownWithError(...) will cleanup the stack as well so its only needed here. +- SSL.clearError(); +- return true; +- } +- return false; - } - -- @Override -- @Test -- public void testMutualAuthInvalidIntermediateCASucceedWithOptionalClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthInvalidIntermediateCASucceedWithOptionalClientAuth(); +- private SSLException newSSLExceptionForError(int stackError) { +- String message = SSL.getErrorString(stackError); +- return handshakeState == HandshakeState.FINISHED ? +- new OpenSslException(message, stackError) : new OpenSslHandshakeException(message, stackError); - } - -- @Override -- @Test -- public void testMutualAuthInvalidIntermediateCAFailWithOptionalClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthInvalidIntermediateCAFailWithOptionalClientAuth(); +- private static boolean shouldAddSuppressed(Throwable target, int errorCode) { +- for (Throwable suppressed: ThrowableUtil.getSuppressed(target)) { +- if (suppressed instanceof NativeSslException && +- ((NativeSslException) suppressed).errorCode() == errorCode) { +- /// An exception with this errorCode was already added before. +- return false; +- } +- } +- return true; - } - -- @Override -- @Test -- public void testMutualAuthInvalidIntermediateCAFailWithRequiredClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthInvalidIntermediateCAFailWithRequiredClientAuth(); +- private SSLEngineResult sslReadErrorResult(int error, int stackError, int bytesConsumed, int bytesProduced) +- throws SSLException { +- if (needWrapAgain(stackError)) { +- // There is something that needs to be send to the remote peer before we can teardown. +- // This is most likely some alert. +- return new SSLEngineResult(OK, NEED_WRAP, bytesConsumed, bytesProduced); +- } +- throw shutdownWithError("SSL_read", error, stackError); - } - -- @Override -- @Test -- public void testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth(); +- private void closeAll() throws SSLException { +- receivedShutdown = true; +- closeOutbound(); +- closeInbound(); - } - -- @Override -- @Test -- public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthValidClientCertChainTooLongFailRequireClientAuth(); +- private void rejectRemoteInitiatedRenegotiation() throws SSLHandshakeException { +- // As rejectRemoteInitiatedRenegotiation() is called in a finally block we also need to check if we shutdown +- // the engine before as otherwise SSL.getHandshakeCount(ssl) will throw an NPE if the passed in ssl is 0. +- // See https://github.com/netty/netty/issues/7353 +- if (!isDestroyed() && (!clientMode && SSL.getHandshakeCount(ssl) > 1 || +- // Let's allow to renegotiate once for client auth. +- clientMode && SSL.getHandshakeCount(ssl) > 2) && +- // As we may count multiple handshakes when TLSv1.3 is used we should just ignore this here as +- // renegotiation is not supported in TLSv1.3 as per spec. +- !SslProtocols.TLS_v1_3.equals(session.getProtocol()) && handshakeState == HandshakeState.FINISHED) { +- // TODO: In future versions me may also want to send a fatal_alert to the client and so notify it +- // that the renegotiation failed. +- shutdown(); +- throw new SSLHandshakeException("remote-initiated renegotiation not allowed"); +- } - } - -- @Override -- @Test -- public void testClientHostnameValidationSuccess() throws InterruptedException, SSLException { -- assumeTrue(OpenSsl.supportsHostnameValidation()); -- super.testClientHostnameValidationSuccess(); +- public final SSLEngineResult unwrap(final ByteBuffer[] srcs, final ByteBuffer[] dsts) throws SSLException { +- return unwrap(srcs, 0, srcs.length, dsts, 0, dsts.length); - } - -- @Override -- @Test -- public void testClientHostnameValidationFail() throws InterruptedException, SSLException { -- assumeTrue(OpenSsl.supportsHostnameValidation()); -- super.testClientHostnameValidationFail(); +- private ByteBuffer[] singleSrcBuffer(ByteBuffer src) { +- singleSrcBuffer[0] = src; +- return singleSrcBuffer; - } - -- @Test -- public void testNpn() throws Exception { -- ApplicationProtocolConfig apn = acceptingNegotiator(Protocol.NPN, -- PREFERRED_APPLICATION_LEVEL_PROTOCOL); -- setupHandlers(apn); -- runTest(PREFERRED_APPLICATION_LEVEL_PROTOCOL); +- private void resetSingleSrcBuffer() { +- singleSrcBuffer[0] = null; - } - -- @Test -- public void testAlpn() throws Exception { -- assumeTrue(OpenSsl.isAlpnSupported()); -- ApplicationProtocolConfig apn = acceptingNegotiator(Protocol.ALPN, -- PREFERRED_APPLICATION_LEVEL_PROTOCOL); -- setupHandlers(apn); -- runTest(PREFERRED_APPLICATION_LEVEL_PROTOCOL); +- private ByteBuffer[] singleDstBuffer(ByteBuffer src) { +- singleDstBuffer[0] = src; +- return singleDstBuffer; - } - -- @Test -- public void testAlpnCompatibleProtocolsDifferentClientOrder() throws Exception { -- assumeTrue(OpenSsl.isAlpnSupported()); -- ApplicationProtocolConfig clientApn = acceptingNegotiator(Protocol.ALPN, -- FALLBACK_APPLICATION_LEVEL_PROTOCOL, PREFERRED_APPLICATION_LEVEL_PROTOCOL); -- ApplicationProtocolConfig serverApn = acceptingNegotiator(Protocol.ALPN, -- PREFERRED_APPLICATION_LEVEL_PROTOCOL, FALLBACK_APPLICATION_LEVEL_PROTOCOL); -- setupHandlers(serverApn, clientApn); -- assertNull(serverException); -- runTest(PREFERRED_APPLICATION_LEVEL_PROTOCOL); +- private void resetSingleDstBuffer() { +- singleDstBuffer[0] = null; - } - -- @Test -- public void testEnablingAnAlreadyDisabledSslProtocol() throws Exception { -- testEnablingAnAlreadyDisabledSslProtocol(new String[]{PROTOCOL_SSL_V2_HELLO}, -- new String[]{PROTOCOL_SSL_V2_HELLO, PROTOCOL_TLS_V1_2}); -- } -- @Test -- public void testWrapBuffersNoWritePendingError() throws Exception { -- clientSslCtx = SslContextBuilder.forClient() -- .trustManager(InsecureTrustManagerFactory.INSTANCE) -- .sslProvider(sslClientProvider()) -- .build(); -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslServerProvider()) -- .build(); -- SSLEngine clientEngine = null; -- SSLEngine serverEngine = null; -- try { -- clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- serverEngine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- handshake(clientEngine, serverEngine); -- -- ByteBuffer src = allocateBuffer(1024 * 10); -- byte[] data = new byte[src.capacity()]; -- PlatformDependent.threadLocalRandom().nextBytes(data); -- src.put(data).flip(); -- ByteBuffer dst = allocateBuffer(1); -- // Try to wrap multiple times so we are more likely to hit the issue. -- for (int i = 0; i < 100; i++) { -- src.position(0); -- dst.position(0); -- assertSame(SSLEngineResult.Status.BUFFER_OVERFLOW, clientEngine.wrap(src, dst).getStatus()); -- } -- } finally { -- cleanupClientSslEngine(clientEngine); -- cleanupServerSslEngine(serverEngine); -- } -- } -- -- @Test -- public void testOnlySmallBufferNeededForWrap() throws Exception { -- clientSslCtx = SslContextBuilder.forClient() -- .trustManager(InsecureTrustManagerFactory.INSTANCE) -- .sslProvider(sslClientProvider()) -- .build(); -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslServerProvider()) -- .build(); -- SSLEngine clientEngine = null; -- SSLEngine serverEngine = null; +- @Override +- public final synchronized SSLEngineResult unwrap( +- final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException { - try { -- clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- serverEngine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- handshake(clientEngine, serverEngine); -- -- // Allocate a buffer which is small enough and set the limit to the capacity to mark its whole content -- // as readable. -- int srcLen = 1024; -- ByteBuffer src = allocateBuffer(srcLen); -- -- ByteBuffer dstTooSmall = allocateBuffer( -- src.capacity() + MAX_TLS_RECORD_OVERHEAD_LENGTH - 1); -- ByteBuffer dst = allocateBuffer( -- src.capacity() + MAX_TLS_RECORD_OVERHEAD_LENGTH); -- -- // Check that we fail to wrap if the dst buffers capacity is not at least -- // src.capacity() + ReferenceCountedOpenSslEngine.MAX_TLS_RECORD_OVERHEAD_LENGTH -- SSLEngineResult result = clientEngine.wrap(src, dstTooSmall); -- assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus()); -- assertEquals(0, result.bytesConsumed()); -- assertEquals(0, result.bytesProduced()); -- assertEquals(src.remaining(), src.capacity()); -- assertEquals(dst.remaining(), dst.capacity()); -- -- // Check that we can wrap with a dst buffer that has the capacity of -- // src.capacity() + ReferenceCountedOpenSslEngine.MAX_TLS_RECORD_OVERHEAD_LENGTH -- result = clientEngine.wrap(src, dst); -- assertEquals(SSLEngineResult.Status.OK, result.getStatus()); -- assertEquals(srcLen, result.bytesConsumed()); -- assertEquals(0, src.remaining()); -- assertTrue(result.bytesProduced() > srcLen); -- assertEquals(src.capacity() - result.bytesConsumed(), src.remaining()); -- assertEquals(dst.capacity() - result.bytesProduced(), dst.remaining()); +- return unwrap(singleSrcBuffer(src), 0, 1, dsts, offset, length); - } finally { -- cleanupClientSslEngine(clientEngine); -- cleanupServerSslEngine(serverEngine); -- } -- } -- -- @Test -- public void testNeededDstCapacityIsCorrectlyCalculated() throws Exception { -- clientSslCtx = SslContextBuilder.forClient() -- .trustManager(InsecureTrustManagerFactory.INSTANCE) -- .sslProvider(sslClientProvider()) -- .build(); -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslServerProvider()) -- .build(); -- SSLEngine clientEngine = null; -- SSLEngine serverEngine = null; +- resetSingleSrcBuffer(); +- } +- } +- +- @Override +- public final synchronized SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) throws SSLException { - try { -- clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- serverEngine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- handshake(clientEngine, serverEngine); -- -- ByteBuffer src = allocateBuffer(1024); -- ByteBuffer src2 = src.duplicate(); -- -- ByteBuffer dst = allocateBuffer(src.capacity() -- + MAX_TLS_RECORD_OVERHEAD_LENGTH); -- -- SSLEngineResult result = clientEngine.wrap(new ByteBuffer[] { src, src2 }, dst); -- assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus()); -- assertEquals(0, src.position()); -- assertEquals(0, src2.position()); -- assertEquals(0, dst.position()); -- assertEquals(0, result.bytesConsumed()); -- assertEquals(0, result.bytesProduced()); +- return wrap(singleSrcBuffer(src), dst); - } finally { -- cleanupClientSslEngine(clientEngine); -- cleanupServerSslEngine(serverEngine); -- } -- } -- -- @Test -- public void testSrcsLenOverFlowCorrectlyHandled() throws Exception { -- clientSslCtx = SslContextBuilder.forClient() -- .trustManager(InsecureTrustManagerFactory.INSTANCE) -- .sslProvider(sslClientProvider()) -- .build(); -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslServerProvider()) -- .build(); -- SSLEngine clientEngine = null; -- SSLEngine serverEngine = null; -- try { -- clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- serverEngine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- handshake(clientEngine, serverEngine); -- -- ByteBuffer src = allocateBuffer(1024); -- List srcList = new ArrayList(); -- long srcsLen = 0; -- long maxLen = ((long) MAX_VALUE) * 2; -- -- while (srcsLen < maxLen) { -- ByteBuffer dup = src.duplicate(); -- srcList.add(dup); -- srcsLen += dup.capacity(); -- } -- -- ByteBuffer[] srcs = srcList.toArray(new ByteBuffer[srcList.size()]); -- -- ByteBuffer dst = allocateBuffer(MAX_ENCRYPTED_PACKET_LENGTH - 1); -- -- SSLEngineResult result = clientEngine.wrap(srcs, dst); -- assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus()); +- resetSingleSrcBuffer(); +- } +- } - -- for (ByteBuffer buffer : srcs) { -- assertEquals(0, buffer.position()); -- } -- assertEquals(0, dst.position()); -- assertEquals(0, result.bytesConsumed()); -- assertEquals(0, result.bytesProduced()); +- @Override +- public final synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) throws SSLException { +- try { +- return unwrap(singleSrcBuffer(src), singleDstBuffer(dst)); - } finally { -- cleanupClientSslEngine(clientEngine); -- cleanupServerSslEngine(serverEngine); -- } -- } -- -- @Test -- public void testCalculateOutNetBufSizeOverflow() { -- assertEquals(MAX_ENCRYPTED_PACKET_LENGTH, -- ReferenceCountedOpenSslEngine.calculateOutNetBufSize(MAX_VALUE, 1)); -- } -- -- @Test -- public void testCalculateOutNetBufSize0() { -- assertEquals(MAX_TLS_RECORD_OVERHEAD_LENGTH, -- ReferenceCountedOpenSslEngine.calculateOutNetBufSize(0, 1)); -- } -- -- @Test -- public void testCalculateOutNetBufSizeMaxEncryptedPacketLength() { -- assertEquals(MAX_ENCRYPTED_PACKET_LENGTH, -- ReferenceCountedOpenSslEngine.calculateOutNetBufSize(MAX_ENCRYPTED_PACKET_LENGTH + 1, 2)); -- } -- -- @Override -- protected void mySetupMutualAuthServerInitSslHandler(SslHandler handler) { -- ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) handler.engine(); -- engine.setVerify(SSL_CVERIFY_IGNORED, 1); -- } -- -- @Test -- public void testWrapWithDifferentSizesTLSv1() throws Exception { -- clientSslCtx = SslContextBuilder.forClient() -- .trustManager(InsecureTrustManagerFactory.INSTANCE) -- .sslProvider(sslClientProvider()) -- .build(); -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslServerProvider()) -- .build(); -- -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ADH-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ECDHE-RSA-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ADH-CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "AECDH-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "AECDH-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "DHE-RSA-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "RC4-MD5"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ADH-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ADH-SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ADH-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "EDH-RSA-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ADH-RC4-MD5"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "IDEA-CBC-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "DHE-RSA-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "RC4-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "AECDH-RC4-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "DHE-RSA-SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "AECDH-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ECDHE-RSA-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ADH-CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "DHE-RSA-CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ECDHE-RSA-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "DHE-RSA-CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1, "ECDHE-RSA-RC4-SHA"); -- } -- -- @Test -- public void testWrapWithDifferentSizesTLSv1_1() throws Exception { -- clientSslCtx = SslContextBuilder.forClient() -- .trustManager(InsecureTrustManagerFactory.INSTANCE) -- .sslProvider(sslClientProvider()) -- .build(); -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslServerProvider()) -- .build(); -- -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "ECDHE-RSA-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "DHE-RSA-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "DHE-RSA-CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "ADH-CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "ADH-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "AECDH-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "DHE-RSA-CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "ECDHE-RSA-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "ADH-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "ADH-SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "ADH-CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "IDEA-CBC-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "AECDH-RC4-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "ADH-RC4-MD5"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "RC4-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "ECDHE-RSA-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "EDH-RSA-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "AECDH-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "ADH-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_1, "DES-CBC3-SHA"); -- } -- -- @Test -- public void testWrapWithDifferentSizesTLSv1_2() throws Exception { -- clientSslCtx = SslContextBuilder.forClient() -- .trustManager(InsecureTrustManagerFactory.INSTANCE) -- .sslProvider(sslClientProvider()) -- .build(); -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslServerProvider()) -- .build(); -- -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ECDHE-RSA-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "DHE-RSA-AES256-GCM-SHA384"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "AECDH-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "AES128-GCM-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "DHE-RSA-AES128-GCM-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ECDHE-RSA-AES256-SHA384"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "AECDH-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "AES256-GCM-SHA384"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "AES256-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ECDHE-RSA-AES128-GCM-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ECDHE-RSA-AES128-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "DHE-RSA-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "RC4-MD5"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "DHE-RSA-AES128-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "EDH-RSA-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-RC4-MD5"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "IDEA-CBC-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "DHE-RSA-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "RC4-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-AES128-GCM-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "AES128-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "AECDH-RC4-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-AES256-GCM-SHA384"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "DHE-RSA-SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "DHE-RSA-AES256-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "AECDH-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ECDHE-RSA-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ECDHE-RSA-AES256-GCM-SHA384"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "DHE-RSA-CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-AES256-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ADH-AES128-SHA256"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ECDHE-RSA-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "DHE-RSA-CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_TLS_V1_2, "ECDHE-RSA-RC4-SHA"); -- } -- -- @Test -- public void testWrapWithDifferentSizesSSLv3() throws Exception { -- clientSslCtx = SslContextBuilder.forClient() -- .trustManager(InsecureTrustManagerFactory.INSTANCE) -- .sslProvider(sslClientProvider()) -- .build(); -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslServerProvider()) -- .build(); -- -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ADH-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ADH-CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "AECDH-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "AECDH-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "DHE-RSA-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "RC4-MD5"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ADH-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ADH-SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ADH-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "EDH-RSA-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ADH-RC4-MD5"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "IDEA-CBC-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "DHE-RSA-AES128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "RC4-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "AECDH-RC4-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "DHE-RSA-SEED-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "AECDH-AES256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ECDHE-RSA-DES-CBC3-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ADH-CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "DHE-RSA-CAMELLIA256-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "DHE-RSA-CAMELLIA128-SHA"); -- testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ECDHE-RSA-RC4-SHA"); -- } -- -- private void testWrapWithDifferentSizes(String protocol, String cipher) throws Exception { -- assumeTrue(OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(protocol)); -- if (!OpenSsl.isCipherSuiteAvailable(cipher)) { -- return; +- resetSingleSrcBuffer(); +- resetSingleDstBuffer(); - } +- } - -- SSLEngine clientEngine = null; -- SSLEngine serverEngine = null; +- @Override +- public final synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts) throws SSLException { - try { -- clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- serverEngine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- clientEngine.setEnabledCipherSuites(new String[] { cipher }); -- clientEngine.setEnabledProtocols(new String[] { protocol }); -- serverEngine.setEnabledCipherSuites(new String[] { cipher }); -- serverEngine.setEnabledProtocols(new String[] { protocol }); -- -- try { -- handshake(clientEngine, serverEngine); -- } catch (SSLException e) { -- if (e.getMessage().contains("unsupported protocol")) { -- Assume.assumeNoException(protocol + " not supported with cipher " + cipher, e); -- } -- throw e; -- } -- -- int srcLen = 64; -- do { -- testWrapDstBigEnough(clientEngine, srcLen); -- srcLen += 64; -- } while (srcLen < MAX_PLAINTEXT_LENGTH); -- -- testWrapDstBigEnough(clientEngine, MAX_PLAINTEXT_LENGTH); +- return unwrap(singleSrcBuffer(src), dsts); - } finally { -- cleanupClientSslEngine(clientEngine); -- cleanupServerSslEngine(serverEngine); +- resetSingleSrcBuffer(); - } - } - -- private void testWrapDstBigEnough(SSLEngine engine, int srcLen) throws SSLException { -- ByteBuffer src = allocateBuffer(srcLen); -- ByteBuffer dst = allocateBuffer(srcLen + MAX_TLS_RECORD_OVERHEAD_LENGTH); -- -- SSLEngineResult result = engine.wrap(src, dst); -- assertEquals(SSLEngineResult.Status.OK, result.getStatus()); -- int consumed = result.bytesConsumed(); -- int produced = result.bytesProduced(); -- assertEquals(srcLen, consumed); -- assertTrue(produced > consumed); +- private class TaskDecorator implements Runnable { +- protected final R task; +- TaskDecorator(R task) { +- this.task = task; +- } - -- dst.flip(); -- assertEquals(produced, dst.remaining()); -- assertFalse(src.hasRemaining()); +- @Override +- public void run() { +- runAndResetNeedTask(task); +- } - } - -- @Test -- public void testSNIMatchersDoesNotThrow() throws Exception { -- assumeTrue(PlatformDependent.javaVersion() >= 8); -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslServerProvider()) -- .build(); +- private final class AsyncTaskDecorator extends TaskDecorator implements AsyncRunnable { +- AsyncTaskDecorator(AsyncTask task) { +- super(task); +- } - -- SSLEngine engine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- try { -- SSLParameters parameters = new SSLParameters(); -- Java8SslTestUtils.setSNIMatcher(parameters); -- engine.setSSLParameters(parameters); -- } finally { -- cleanupServerSslEngine(engine); -- ssc.delete(); +- @Override +- public void run(final Runnable runnable) { +- if (isDestroyed()) { +- // The engine was destroyed in the meantime, just return. +- return; +- } +- task.runAsync(new TaskDecorator(runnable)); - } - } - -- @Test(expected = IllegalArgumentException.class) -- public void testAlgorithmConstraintsThrows() throws Exception { -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- serverSslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslServerProvider()) -- .build(); -- -- SSLEngine engine = serverSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT); -- try { -- SSLParameters parameters = new SSLParameters(); -- parameters.setAlgorithmConstraints(new AlgorithmConstraints() { -- @Override -- public boolean permits( -- Set primitives, String algorithm, AlgorithmParameters parameters) { -- return false; -- } -- -- @Override -- public boolean permits(Set primitives, Key key) { -- return false; +- private void runAndResetNeedTask(Runnable task) { +- // We need to synchronize on the ReferenceCountedOpenSslEngine, we are sure the SSL object +- // will not be freed by the user calling for example shutdown() concurrently. +- synchronized (ReferenceCountedOpenSslEngine.this) { +- try { +- if (isDestroyed()) { +- // The engine was destroyed in the meantime, just return. +- return; - } -- -- @Override -- public boolean permits( -- Set primitives, String algorithm, Key key, AlgorithmParameters parameters) { -- return false; +- task.run(); +- if (handshakeState != HandshakeState.FINISHED && !isDestroyed()) { +- // Call SSL.doHandshake(...) If the handshake was not finished yet. This might be needed +- // to fill the application buffer and so have getHandshakeStatus() return the right value +- // in this case. +- if (SSL.doHandshake(ssl) <= 0) { +- SSL.clearError(); +- } - } -- }); -- engine.setSSLParameters(parameters); -- } finally { -- cleanupServerSslEngine(engine); -- ssc.delete(); +- } finally { +- // The task was run, reset needTask to false so getHandshakeStatus() returns the correct value. +- needTask = false; +- } - } - } - - @Override -- protected SslProvider sslClientProvider() { -- return SslProvider.OPENSSL; +- public final synchronized Runnable getDelegatedTask() { +- if (isDestroyed()) { +- return null; +- } +- final Runnable task = SSL.getTask(ssl); +- if (task == null) { +- return null; +- } +- if (task instanceof AsyncTask) { +- return new AsyncTaskDecorator((AsyncTask) task); +- } +- return new TaskDecorator(task); - } - - @Override -- protected SslProvider sslServerProvider() { -- return SslProvider.OPENSSL; -- } +- public final synchronized void closeInbound() throws SSLException { +- if (isInboundDone) { +- return; +- } - -- private static ApplicationProtocolConfig acceptingNegotiator(Protocol protocol, -- String... supportedProtocols) { -- return new ApplicationProtocolConfig(protocol, -- SelectorFailureBehavior.NO_ADVERTISE, -- SelectedListenerFailureBehavior.ACCEPT, -- supportedProtocols); -- } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslJdkSslEngineInteroptTest.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslJdkSslEngineInteroptTest.java -deleted file mode 100644 -index f63a16f..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/OpenSslJdkSslEngineInteroptTest.java -+++ /dev/null -@@ -1,114 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; +- isInboundDone = true; - --import org.junit.BeforeClass; --import org.junit.Ignore; --import org.junit.Test; +- if (isOutboundDone()) { +- // Only call shutdown if there is no outbound data pending. +- // See https://github.com/netty/netty/issues/6167 +- shutdown(); +- } - --import javax.net.ssl.SSLException; --import org.junit.runner.RunWith; --import org.junit.runners.Parameterized; +- if (handshakeState != HandshakeState.NOT_STARTED && !receivedShutdown) { +- throw new SSLException( +- "Inbound closed before receiving peer's close_notify: possible truncation attack?"); +- } +- } - --import java.util.ArrayList; --import java.util.Collection; --import java.util.List; +- @Override +- public final synchronized boolean isInboundDone() { +- return isInboundDone; +- } - --import static io.netty.handler.ssl.OpenSslTestUtils.checkShouldUseKeyManagerFactory; --import static org.junit.Assume.assumeTrue; +- @Override +- public final synchronized void closeOutbound() { +- if (outboundClosed) { +- return; +- } - --@RunWith(Parameterized.class) --public class OpenSslJdkSslEngineInteroptTest extends SSLEngineTest { +- outboundClosed = true; - -- @Parameterized.Parameters(name = "{index}: bufferType = {0}") -- public static Collection data() { -- List params = new ArrayList(); -- for (BufferType type: BufferType.values()) { -- params.add(type); +- if (handshakeState != HandshakeState.NOT_STARTED && !isDestroyed()) { +- int mode = SSL.getShutdown(ssl); +- if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) { +- doSSLShutdown(); +- } +- } else { +- // engine closing before initial handshake +- shutdown(); - } -- return params; -- } -- -- public OpenSslJdkSslEngineInteroptTest(BufferType type) { -- super(type); - } - -- @BeforeClass -- public static void checkOpenSsl() { -- assumeTrue(OpenSsl.isAvailable()); +- /** +- * Attempt to call {@link SSL#shutdownSSL(long)}. +- * @return {@code false} if the call to {@link SSL#shutdownSSL(long)} was not attempted or returned an error. +- */ +- private boolean doSSLShutdown() { +- if (SSL.isInInit(ssl) != 0) { +- // Only try to call SSL_shutdown if we are not in the init state anymore. +- // Otherwise we will see 'error:140E0197:SSL routines:SSL_shutdown:shutdown while in init' in our logs. +- // +- // See also https://hg.nginx.org/nginx/rev/062c189fee20 +- return false; +- } +- int err = SSL.shutdownSSL(ssl); +- if (err < 0) { +- int sslErr = SSL.getError(ssl, err); +- if (sslErr == SSL.SSL_ERROR_SYSCALL || sslErr == SSL.SSL_ERROR_SSL) { +- if (logger.isDebugEnabled()) { +- int error = SSL.getLastErrorNumber(); +- logger.debug("SSL_shutdown failed: OpenSSL error: {} {}", error, SSL.getErrorString(error)); +- } +- // There was an internal error -- shutdown +- shutdown(); +- return false; +- } +- SSL.clearError(); +- } +- return true; - } - - @Override -- protected SslProvider sslClientProvider() { -- return SslProvider.OPENSSL; +- public final synchronized boolean isOutboundDone() { +- // Check if there is anything left in the outbound buffer. +- // We need to ensure we only call SSL.pendingWrittenBytesInBIO(...) if the engine was not destroyed yet. +- return outboundClosed && (networkBIO == 0 || SSL.bioLengthNonApplication(networkBIO) == 0); - } - - @Override -- protected SslProvider sslServerProvider() { -- return SslProvider.JDK; +- public final String[] getSupportedCipherSuites() { +- return OpenSsl.AVAILABLE_CIPHER_SUITES.toArray(EMPTY_STRINGS); - } - -- @Ignore /* Does the JDK support a "max certificate chain length"? */ - @Override -- public void testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth() throws Exception { +- public final String[] getEnabledCipherSuites() { +- final String[] extraCiphers; +- final String[] enabled; +- final boolean tls13Enabled; +- synchronized (this) { +- if (!isDestroyed()) { +- enabled = SSL.getCiphers(ssl); +- int opts = SSL.getOptions(ssl); +- if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_3, SslProtocols.TLS_v1_3)) { +- extraCiphers = OpenSsl.EXTRA_SUPPORTED_TLS_1_3_CIPHERS; +- tls13Enabled = true; +- } else { +- extraCiphers = EMPTY_STRINGS; +- tls13Enabled = false; +- } +- } else { +- return EMPTY_STRINGS; +- } +- } +- if (enabled == null) { +- return EMPTY_STRINGS; +- } else { +- Set enabledSet = new LinkedHashSet(enabled.length + extraCiphers.length); +- synchronized (this) { +- for (int i = 0; i < enabled.length; i++) { +- String mapped = toJavaCipherSuite(enabled[i]); +- final String cipher = mapped == null ? enabled[i] : mapped; +- if ((!tls13Enabled || !OpenSsl.isTlsv13Supported()) && SslUtils.isTLSv13Cipher(cipher)) { +- continue; +- } +- enabledSet.add(cipher); +- } +- Collections.addAll(enabledSet, extraCiphers); +- } +- return enabledSet.toArray(EMPTY_STRINGS); +- } - } - -- @Ignore /* Does the JDK support a "max certificate chain length"? */ - @Override -- public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth() throws Exception { -- } +- public final void setEnabledCipherSuites(String[] cipherSuites) { +- checkNotNull(cipherSuites, "cipherSuites"); - -- @Override -- @Test -- public void testMutualAuthInvalidIntermediateCASucceedWithOptionalClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthInvalidIntermediateCASucceedWithOptionalClientAuth(); -- } +- final StringBuilder buf = new StringBuilder(); +- final StringBuilder bufTLSv13 = new StringBuilder(); - -- @Override -- @Test -- public void testMutualAuthInvalidIntermediateCAFailWithOptionalClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthInvalidIntermediateCAFailWithOptionalClientAuth(); -- } +- CipherSuiteConverter.convertToCipherStrings(Arrays.asList(cipherSuites), buf, bufTLSv13, OpenSsl.isBoringSSL()); +- final String cipherSuiteSpec = buf.toString(); +- final String cipherSuiteSpecTLSv13 = bufTLSv13.toString(); - -- @Override -- @Test -- public void testMutualAuthInvalidIntermediateCAFailWithRequiredClientAuth() throws Exception { -- checkShouldUseKeyManagerFactory(); -- super.testMutualAuthInvalidIntermediateCAFailWithRequiredClientAuth(); +- if (!OpenSsl.isTlsv13Supported() && !cipherSuiteSpecTLSv13.isEmpty()) { +- throw new IllegalArgumentException("TLSv1.3 is not supported by this java version."); +- } +- synchronized (this) { +- hasTLSv13Cipher = !cipherSuiteSpecTLSv13.isEmpty(); +- if (!isDestroyed()) { +- try { +- // Set non TLSv1.3 ciphers. +- SSL.setCipherSuites(ssl, cipherSuiteSpec, false); +- if (OpenSsl.isTlsv13Supported()) { +- // Set TLSv1.3 ciphers. +- SSL.setCipherSuites(ssl, OpenSsl.checkTls13Ciphers(logger, cipherSuiteSpecTLSv13), true); +- } +- +- // We also need to update the enabled protocols to ensure we disable the protocol if there are +- // no compatible ciphers left. +- Set protocols = new HashSet(enabledProtocols); +- +- // We have no ciphers that are compatible with none-TLSv1.3, let us explicit disable all other +- // protocols. +- if (cipherSuiteSpec.isEmpty()) { +- protocols.remove(SslProtocols.TLS_v1); +- protocols.remove(SslProtocols.TLS_v1_1); +- protocols.remove(SslProtocols.TLS_v1_2); +- protocols.remove(SslProtocols.SSL_v3); +- protocols.remove(SslProtocols.SSL_v2); +- protocols.remove(SslProtocols.SSL_v2_HELLO); +- } +- // We have no ciphers that are compatible with TLSv1.3, let us explicit disable it. +- if (cipherSuiteSpecTLSv13.isEmpty()) { +- protocols.remove(SslProtocols.TLS_v1_3); +- } +- // Update the protocols but not cache the value. We only cache when we call it from the user +- // code or when we construct the engine. +- setEnabledProtocols0(protocols.toArray(EMPTY_STRINGS), !hasTLSv13Cipher); +- } catch (Exception e) { +- throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec, e); +- } +- } else { +- throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec); +- } +- } - } - - @Override -- @Test -- public void testClientHostnameValidationSuccess() throws InterruptedException, SSLException { -- assumeTrue(OpenSsl.supportsHostnameValidation()); -- super.testClientHostnameValidationSuccess(); +- public final String[] getSupportedProtocols() { +- return OpenSsl.SUPPORTED_PROTOCOLS_SET.toArray(EMPTY_STRINGS); - } - - @Override -- @Test -- public void testClientHostnameValidationFail() throws InterruptedException, SSLException { -- assumeTrue(OpenSsl.supportsHostnameValidation()); -- super.testClientHostnameValidationFail(); +- public final String[] getEnabledProtocols() { +- return enabledProtocols.toArray(EMPTY_STRINGS); - } - -- @Override -- protected boolean mySetupMutualAuthServerIsValidServerException(Throwable cause) { -- // TODO(scott): work around for a JDK issue. The exception should be SSLHandshakeException. -- return super.mySetupMutualAuthServerIsValidServerException(cause) || causedBySSLException(cause); +- private static boolean isProtocolEnabled(int opts, int disableMask, String protocolString) { +- // We also need to check if the actual protocolString is supported as depending on the openssl API +- // implementations it may use a disableMask of 0 (BoringSSL is doing this for example). +- return (opts & disableMask) == 0 && OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(protocolString); - } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslRenegotiateSmallBIOTest.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslRenegotiateSmallBIOTest.java -deleted file mode 100644 -index 3959e64..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/OpenSslRenegotiateSmallBIOTest.java -+++ /dev/null -@@ -1,23 +0,0 @@ --/* -- * Copyright 2017 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; - --public class OpenSslRenegotiateSmallBIOTest extends OpenSslRenegotiateTest { +- /** +- * {@inheritDoc} +- * TLS doesn't support a way to advertise non-contiguous versions from the client's perspective, and the client +- * just advertises the max supported version. The TLS protocol also doesn't support all different combinations of +- * discrete protocols, and instead assumes contiguous ranges. OpenSSL has some unexpected behavior +- * (e.g. handshake failures) if non-contiguous protocols are used even where there is a compatible set of protocols +- * and ciphers. For these reasons this method will determine the minimum protocol and the maximum protocol and +- * enabled a contiguous range from [min protocol, max protocol] in OpenSSL. +- */ - @Override -- protected void initSslServerContext(SslContext context) { -- ((ReferenceCountedOpenSslContext) context).setBioNonApplicationBufferSize(1); -- } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslRenegotiateTest.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslRenegotiateTest.java -deleted file mode 100644 -index 8f3dfee..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/OpenSslRenegotiateTest.java -+++ /dev/null -@@ -1,36 +0,0 @@ --/* -- * Copyright 2015 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; +- public final void setEnabledProtocols(String[] protocols) { +- checkNotNullWithIAE(protocols, "protocols"); +- synchronized (this) { +- enabledProtocols.clear(); +- // Seems like there is no way to explicit disable SSLv2Hello in openssl, so it is always enabled +- enabledProtocols.add(SslProtocols.SSL_v2_HELLO); - --import org.junit.BeforeClass; +- Collections.addAll(enabledProtocols, protocols); +- +- setEnabledProtocols0(protocols, !hasTLSv13Cipher); +- } +- } - --import static org.junit.Assume.assumeFalse; --import static org.junit.Assume.assumeTrue; +- private void setEnabledProtocols0(String[] protocols, boolean explicitDisableTLSv13) { +- assert Thread.holdsLock(this); +- // This is correct from the API docs +- int minProtocolIndex = OPENSSL_OP_NO_PROTOCOLS.length; +- int maxProtocolIndex = 0; +- for (String p: protocols) { +- if (!OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(p)) { +- throw new IllegalArgumentException("Protocol " + p + " is not supported."); +- } +- if (p.equals(SslProtocols.SSL_v2)) { +- if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2) { +- minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2; +- } +- if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2) { +- maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2; +- } +- } else if (p.equals(SslProtocols.SSL_v3)) { +- if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3) { +- minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3; +- } +- if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3) { +- maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3; +- } +- } else if (p.equals(SslProtocols.TLS_v1)) { +- if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1) { +- minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1; +- } +- if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1) { +- maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1; +- } +- } else if (p.equals(SslProtocols.TLS_v1_1)) { +- if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1) { +- minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1; +- } +- if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1) { +- maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1; +- } +- } else if (p.equals(SslProtocols.TLS_v1_2)) { +- if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2) { +- minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2; +- } +- if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2) { +- maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2; +- } +- } else if (!explicitDisableTLSv13 && p.equals(SslProtocols.TLS_v1_3)) { +- if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3) { +- minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3; +- } +- if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3) { +- maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3; +- } +- } +- } +- if (!isDestroyed()) { +- // Clear out options which disable protocols +- SSL.clearOptions(ssl, SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 | +- SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2 | SSL.SSL_OP_NO_TLSv1_3); - --public class OpenSslRenegotiateTest extends RenegotiateTest { +- int opts = 0; +- for (int i = 0; i < minProtocolIndex; ++i) { +- opts |= OPENSSL_OP_NO_PROTOCOLS[i]; +- } +- assert maxProtocolIndex != MAX_VALUE; +- for (int i = maxProtocolIndex + 1; i < OPENSSL_OP_NO_PROTOCOLS.length; ++i) { +- opts |= OPENSSL_OP_NO_PROTOCOLS[i]; +- } - -- @BeforeClass -- public static void checkOpenSsl() { -- assumeTrue(OpenSsl.isAvailable()); -- // BoringSSL does not support renegotiation intentionally. -- assumeFalse("BoringSSL".equals(OpenSsl.versionString())); +- // Disable protocols we do not want +- SSL.setOptions(ssl, opts); +- } else { +- throw new IllegalStateException("failed to enable protocols: " + Arrays.asList(protocols)); +- } - } - - @Override -- protected SslProvider serverSslProvider() { -- return SslProvider.OPENSSL; +- public final SSLSession getSession() { +- return session; - } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslServerContextTest.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslServerContextTest.java -deleted file mode 100644 -index f22d045..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/OpenSslServerContextTest.java -+++ /dev/null -@@ -1,39 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ - --package io.netty.handler.ssl; +- @Override +- public final synchronized void beginHandshake() throws SSLException { +- switch (handshakeState) { +- case STARTED_IMPLICITLY: +- checkEngineClosed(); - --import org.junit.Assume; --import org.junit.BeforeClass; +- // A user did not start handshake by calling this method by him/herself, +- // but handshake has been started already by wrap() or unwrap() implicitly. +- // Because it's the user's first time to call this method, it is unfair to +- // raise an exception. From the user's standpoint, he or she never asked +- // for renegotiation. - --import javax.net.ssl.SSLException; --import java.io.File; +- handshakeState = HandshakeState.STARTED_EXPLICITLY; // Next time this method is invoked by the user, +- calculateMaxWrapOverhead(); +- // we should raise an exception. +- break; +- case STARTED_EXPLICITLY: +- // Nothing to do as the handshake is not done yet. +- break; +- case FINISHED: +- throw new SSLException("renegotiation unsupported"); +- case NOT_STARTED: +- handshakeState = HandshakeState.STARTED_EXPLICITLY; +- if (handshake() == NEED_TASK) { +- // Set needTask to true so getHandshakeStatus() will return the correct value. +- needTask = true; +- } +- calculateMaxWrapOverhead(); +- break; +- default: +- throw new Error(); +- } +- } - --import static org.junit.Assume.assumeTrue; +- private void checkEngineClosed() throws SSLException { +- if (isDestroyed()) { +- throw new SSLException("engine closed"); +- } +- } - --public class OpenSslServerContextTest extends SslContextTest { +- private static SSLEngineResult.HandshakeStatus pendingStatus(int pendingStatus) { +- // Depending on if there is something left in the BIO we need to WRAP or UNWRAP +- return pendingStatus > 0 ? NEED_WRAP : NEED_UNWRAP; +- } - -- @BeforeClass -- public static void checkOpenSsl() { -- assumeTrue(OpenSsl.isAvailable()); +- private static boolean isEmpty(Object[] arr) { +- return arr == null || arr.length == 0; - } - -- @Override -- protected SslContext newServerContext(File crtFile, File keyFile, String pass) throws SSLException { -- Assume.assumeTrue(OpenSsl.isAvailable()); -- return new OpenSslServerContext(crtFile, keyFile, pass); +- private static boolean isEmpty(byte[] cert) { +- return cert == null || cert.length == 0; - } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/OpenSslTestUtils.java b/handler/src/test/java/io/netty/handler/ssl/OpenSslTestUtils.java -deleted file mode 100644 -index 7882a61..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/OpenSslTestUtils.java -+++ /dev/null -@@ -1,27 +0,0 @@ --/* -- * Copyright 2017 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; - --import static org.junit.Assume.assumeTrue; +- private SSLEngineResult.HandshakeStatus handshakeException() throws SSLException { +- if (SSL.bioLengthNonApplication(networkBIO) > 0) { +- // There is something pending, we need to consume it first via a WRAP so we don't loose anything. +- return NEED_WRAP; +- } - --final class OpenSslTestUtils { -- private OpenSslTestUtils() { +- Throwable exception = pendingException; +- assert exception != null; +- pendingException = null; +- shutdown(); +- if (exception instanceof SSLHandshakeException) { +- throw (SSLHandshakeException) exception; +- } +- SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem"); +- e.initCause(exception); +- throw e; - } - -- static void checkShouldUseKeyManagerFactory() { -- assumeTrue(OpenSsl.supportsKeyManagerFactory() && OpenSsl.useKeyManagerFactory()); +- /** +- * Should be called if the handshake will be failed due a callback that throws an exception. +- * This cause will then be used to give more details as part of the {@link SSLHandshakeException}. +- */ +- final void initHandshakeException(Throwable cause) { +- if (pendingException == null) { +- pendingException = cause; +- } else { +- ThrowableUtil.addSuppressed(pendingException, cause); +- } - } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/PemEncodedTest.java b/handler/src/test/java/io/netty/handler/ssl/PemEncodedTest.java -deleted file mode 100644 -index 793f772..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/PemEncodedTest.java -+++ /dev/null -@@ -1,95 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ - --package io.netty.handler.ssl; +- private SSLEngineResult.HandshakeStatus handshake() throws SSLException { +- if (needTask) { +- return NEED_TASK; +- } +- if (handshakeState == HandshakeState.FINISHED) { +- return FINISHED; +- } - --import static org.junit.Assert.assertEquals; --import static org.junit.Assert.assertTrue; --import static org.junit.Assume.assumeFalse; --import static org.junit.Assume.assumeTrue; +- checkEngineClosed(); - --import java.io.ByteArrayOutputStream; --import java.io.File; --import java.io.FileInputStream; +- if (pendingException != null) { +- // Let's call SSL.doHandshake(...) again in case there is some async operation pending that would fill the +- // outbound buffer. +- if (SSL.doHandshake(ssl) <= 0) { +- // Clear any error that was put on the stack by the handshake +- SSL.clearError(); +- } +- return handshakeException(); +- } - --import org.junit.Test; +- // Adding the OpenSslEngine to the OpenSslEngineMap so it can be used in the AbstractCertificateVerifier. +- engineMap.add(this); - --import io.netty.handler.ssl.util.SelfSignedCertificate; --import io.netty.util.ReferenceCountUtil; +- if (!sessionSet) { +- if (!parentContext.sessionContext().setSessionFromCache(ssl, session, getPeerHost(), getPeerPort())) { +- // The session was not reused via the cache. Call prepareHandshake() to ensure we remove all previous +- // stored key-value pairs. +- session.prepareHandshake(); +- } +- sessionSet = true; +- } - --public class PemEncodedTest { +- int code = SSL.doHandshake(ssl); +- if (code <= 0) { +- int sslError = SSL.getError(ssl, code); +- if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) { +- return pendingStatus(SSL.bioLengthNonApplication(networkBIO)); +- } - -- @Test -- public void testPemEncodedOpenSsl() throws Exception { -- testPemEncoded(SslProvider.OPENSSL); -- } +- if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || +- sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY || +- sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) { +- return NEED_TASK; +- } - -- @Test -- public void testPemEncodedOpenSslRef() throws Exception { -- testPemEncoded(SslProvider.OPENSSL_REFCNT); -- } +- if (needWrapAgain(SSL.getLastErrorNumber())) { +- // There is something that needs to be send to the remote peer before we can teardown. +- // This is most likely some alert. +- return NEED_WRAP; +- } +- // Check if we have a pending exception that was created during the handshake and if so throw it after +- // shutdown the connection. +- if (pendingException != null) { +- return handshakeException(); +- } - -- private static void testPemEncoded(SslProvider provider) throws Exception { -- assumeTrue(OpenSsl.isAvailable()); -- assumeFalse(OpenSsl.useKeyManagerFactory()); -- PemPrivateKey pemKey; -- PemX509Certificate pemCert; -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- try { -- pemKey = PemPrivateKey.valueOf(toByteArray(ssc.privateKey())); -- pemCert = PemX509Certificate.valueOf(toByteArray(ssc.certificate())); -- } finally { -- ssc.delete(); +- // Everything else is considered as error +- throw shutdownWithError("SSL_do_handshake", sslError); - } -- -- SslContext context = SslContextBuilder.forServer(pemKey, pemCert) -- .sslProvider(provider) -- .build(); -- assertEquals(1, pemKey.refCnt()); -- assertEquals(1, pemCert.refCnt()); -- try { -- assertTrue(context instanceof ReferenceCountedOpenSslContext); -- } finally { -- ReferenceCountUtil.release(context); -- assertRelease(pemKey); -- assertRelease(pemCert); +- // We have produced more data as part of the handshake if this is the case the user should call wrap(...) +- if (SSL.bioLengthNonApplication(networkBIO) > 0) { +- return NEED_WRAP; - } +- // if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished. +- session.handshakeFinished(SSL.getSessionId(ssl), SSL.getCipherForSSL(ssl), SSL.getVersion(ssl), +- SSL.getPeerCertificate(ssl), SSL.getPeerCertChain(ssl), +- SSL.getTime(ssl) * 1000L, parentContext.sessionTimeout() * 1000L); +- selectApplicationProtocol(); +- return FINISHED; - } - -- private static void assertRelease(PemEncoded encoded) { -- assertTrue(encoded.release()); +- private SSLEngineResult.HandshakeStatus mayFinishHandshake( +- SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) throws SSLException { +- return hs == NEED_UNWRAP && bytesProduced > 0 || hs == NEED_WRAP && bytesConsumed > 0 ? +- handshake() : mayFinishHandshake(hs != FINISHED ? getHandshakeStatus() : FINISHED); - } - -- private static byte[] toByteArray(File file) throws Exception { -- FileInputStream in = new FileInputStream(file); -- try { -- ByteArrayOutputStream baos = new ByteArrayOutputStream(); -- try { -- byte[] buf = new byte[1024]; -- int len; -- while ((len = in.read(buf)) != -1) { -- baos.write(buf, 0, len); -- } -- } finally { -- baos.close(); +- private SSLEngineResult.HandshakeStatus mayFinishHandshake(SSLEngineResult.HandshakeStatus status) +- throws SSLException { +- if (status == NOT_HANDSHAKING) { +- if (handshakeState != HandshakeState.FINISHED) { +- // If the status was NOT_HANDSHAKING and we not finished the handshake we need to call +- // SSL_do_handshake() again +- return handshake(); - } +- if (!isDestroyed() && SSL.bioLengthNonApplication(networkBIO) > 0) { +- // We have something left that needs to be wrapped. +- return NEED_WRAP; +- } +- } +- return status; +- } - -- return baos.toByteArray(); -- } finally { -- in.close(); +- @Override +- public final synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() { +- // Check if we are in the initial handshake phase or shutdown phase +- if (needPendingStatus()) { +- if (needTask) { +- // There is a task outstanding +- return NEED_TASK; +- } +- return pendingStatus(SSL.bioLengthNonApplication(networkBIO)); - } +- return NOT_HANDSHAKING; - } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngineTest.java -deleted file mode 100644 -index 6d38940..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngineTest.java -+++ /dev/null -@@ -1,57 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; - --import io.netty.util.ReferenceCountUtil; +- private SSLEngineResult.HandshakeStatus getHandshakeStatus(int pending) { +- // Check if we are in the initial handshake phase or shutdown phase +- if (needPendingStatus()) { +- if (needTask) { +- // There is a task outstanding +- return NEED_TASK; +- } +- return pendingStatus(pending); +- } +- return NOT_HANDSHAKING; +- } - --import javax.net.ssl.SSLEngine; +- private boolean needPendingStatus() { +- return handshakeState != HandshakeState.NOT_STARTED && !isDestroyed() +- && (handshakeState != HandshakeState.FINISHED || isInboundDone() || isOutboundDone()); +- } +- +- /** +- * Converts the specified OpenSSL cipher suite to the Java cipher suite. +- */ +- private String toJavaCipherSuite(String openSslCipherSuite) { +- if (openSslCipherSuite == null) { +- return null; +- } +- +- String version = SSL.getVersion(ssl); +- String prefix = toJavaCipherSuitePrefix(version); +- return CipherSuiteConverter.toJava(openSslCipherSuite, prefix); +- } - --public class ReferenceCountedOpenSslEngineTest extends OpenSslEngineTest { +- /** +- * Converts the protocol version string returned by {@link SSL#getVersion(long)} to protocol family string. +- */ +- private static String toJavaCipherSuitePrefix(String protocolVersion) { +- final char c; +- if (protocolVersion == null || protocolVersion.isEmpty()) { +- c = 0; +- } else { +- c = protocolVersion.charAt(0); +- } - -- public ReferenceCountedOpenSslEngineTest(BufferType type) { -- super(type); +- switch (c) { +- case 'T': +- return "TLS"; +- case 'S': +- return "SSL"; +- default: +- return "UNKNOWN"; +- } - } - - @Override -- protected SslProvider sslClientProvider() { -- return SslProvider.OPENSSL_REFCNT; +- public final void setUseClientMode(boolean clientMode) { +- if (clientMode != this.clientMode) { +- throw new UnsupportedOperationException(); +- } - } - - @Override -- protected SslProvider sslServerProvider() { -- return SslProvider.OPENSSL_REFCNT; +- public final boolean getUseClientMode() { +- return clientMode; - } - - @Override -- protected void cleanupClientSslContext(SslContext ctx) { -- ReferenceCountUtil.release(ctx); +- public final void setNeedClientAuth(boolean b) { +- setClientAuth(b ? ClientAuth.REQUIRE : ClientAuth.NONE); - } - - @Override -- protected void cleanupClientSslEngine(SSLEngine engine) { -- ReferenceCountUtil.release(engine); +- public final boolean getNeedClientAuth() { +- return clientAuth == ClientAuth.REQUIRE; - } - - @Override -- protected void cleanupServerSslContext(SslContext ctx) { -- ReferenceCountUtil.release(ctx); +- public final void setWantClientAuth(boolean b) { +- setClientAuth(b ? ClientAuth.OPTIONAL : ClientAuth.NONE); - } - - @Override -- protected void cleanupServerSslEngine(SSLEngine engine) { -- ReferenceCountUtil.release(engine); +- public final boolean getWantClientAuth() { +- return clientAuth == ClientAuth.OPTIONAL; - } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/SniClientTest.java b/handler/src/test/java/io/netty/handler/ssl/SniClientTest.java -deleted file mode 100644 -index 3193d20..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/SniClientTest.java -+++ /dev/null -@@ -1,161 +0,0 @@ --/* -- * Copyright 2016 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; -- --import io.netty.bootstrap.Bootstrap; --import io.netty.bootstrap.ServerBootstrap; --import io.netty.buffer.ByteBufAllocator; --import io.netty.channel.Channel; --import io.netty.channel.ChannelInitializer; --import io.netty.channel.DefaultEventLoopGroup; --import io.netty.channel.EventLoopGroup; --import io.netty.channel.local.LocalAddress; --import io.netty.channel.local.LocalChannel; --import io.netty.channel.local.LocalServerChannel; --import io.netty.handler.ssl.util.InsecureTrustManagerFactory; --import io.netty.handler.ssl.util.SelfSignedCertificate; --import io.netty.util.Mapping; --import io.netty.util.concurrent.Promise; --import io.netty.util.internal.PlatformDependent; --import org.junit.Assert; --import org.junit.Assume; --import org.junit.Test; -- --import java.nio.channels.ClosedChannelException; - --public class SniClientTest { -- -- @Test(timeout = 30000) -- public void testSniClientJdkSslServerJdkSsl() throws Exception { -- testSniClient(SslProvider.JDK, SslProvider.JDK); +- /** +- * See SSL_set_verify and +- * {@link SSL#setVerify(long, int, int)}. +- */ +- @UnstableApi +- public final synchronized void setVerify(int verifyMode, int depth) { +- if (!isDestroyed()) { +- SSL.setVerify(ssl, verifyMode, depth); +- } - } - -- @Test(timeout = 30000) -- public void testSniClientOpenSslServerOpenSsl() throws Exception { -- Assume.assumeTrue(OpenSsl.isAvailable()); -- testSniClient(SslProvider.OPENSSL, SslProvider.OPENSSL); +- private void setClientAuth(ClientAuth mode) { +- if (clientMode) { +- return; +- } +- synchronized (this) { +- if (clientAuth == mode) { +- // No need to issue any JNI calls if the mode is the same +- return; +- } +- if (!isDestroyed()) { +- switch (mode) { +- case NONE: +- SSL.setVerify(ssl, SSL.SSL_CVERIFY_NONE, ReferenceCountedOpenSslContext.VERIFY_DEPTH); +- break; +- case REQUIRE: +- SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRED, ReferenceCountedOpenSslContext.VERIFY_DEPTH); +- break; +- case OPTIONAL: +- SSL.setVerify(ssl, SSL.SSL_CVERIFY_OPTIONAL, ReferenceCountedOpenSslContext.VERIFY_DEPTH); +- break; +- default: +- throw new Error(mode.toString()); +- } +- } +- clientAuth = mode; +- } - } - -- @Test(timeout = 30000) -- public void testSniClientJdkSslServerOpenSsl() throws Exception { -- Assume.assumeTrue(OpenSsl.isAvailable()); -- testSniClient(SslProvider.JDK, SslProvider.OPENSSL); +- @Override +- public final void setEnableSessionCreation(boolean b) { +- if (b) { +- throw new UnsupportedOperationException(); +- } - } - -- @Test(timeout = 30000) -- public void testSniClientOpenSslServerJdkSsl() throws Exception { -- Assume.assumeTrue(OpenSsl.isAvailable()); -- testSniClient(SslProvider.OPENSSL, SslProvider.JDK); +- @Override +- public final boolean getEnableSessionCreation() { +- return false; - } - -- @Test(timeout = 30000) -- public void testSniSNIMatcherMatchesClientJdkSslServerJdkSsl() throws Exception { -- Assume.assumeTrue(PlatformDependent.javaVersion() >= 8); -- SniClientJava8TestUtil.testSniClient(SslProvider.JDK, SslProvider.JDK, true); -- } +- @SuppressJava6Requirement(reason = "Usage guarded by java version check") +- @Override +- public final synchronized SSLParameters getSSLParameters() { +- SSLParameters sslParameters = super.getSSLParameters(); +- +- int version = PlatformDependent.javaVersion(); +- if (version >= 7) { +- Java7SslParametersUtils.setEndpointIdentificationAlgorithm(sslParameters, endpointIdentificationAlgorithm); +- Java7SslParametersUtils.setAlgorithmConstraints(sslParameters, algorithmConstraints); +- if (version >= 8) { +- if (sniHostNames != null) { +- Java8SslUtils.setSniHostNames(sslParameters, sniHostNames); +- } +- if (!isDestroyed()) { +- Java8SslUtils.setUseCipherSuitesOrder( +- sslParameters, (SSL.getOptions(ssl) & SSL.SSL_OP_CIPHER_SERVER_PREFERENCE) != 0); +- } - -- @Test(timeout = 30000, expected = ClosedChannelException.class) -- public void testSniSNIMatcherDoesNotMatchClientJdkSslServerJdkSsl() throws Exception { -- Assume.assumeTrue(PlatformDependent.javaVersion() >= 8); -- SniClientJava8TestUtil.testSniClient(SslProvider.JDK, SslProvider.JDK, false); +- Java8SslUtils.setSNIMatchers(sslParameters, matchers); +- } +- } +- return sslParameters; - } - -- @Test(timeout = 30000) -- public void testSniSNIMatcherMatchesClientOpenSslServerOpenSsl() throws Exception { -- Assume.assumeTrue(PlatformDependent.javaVersion() >= 8); -- Assume.assumeTrue(OpenSsl.isAvailable()); -- SniClientJava8TestUtil.testSniClient(SslProvider.OPENSSL, SslProvider.OPENSSL, true); +- @SuppressJava6Requirement(reason = "Usage guarded by java version check") +- @Override +- public final synchronized void setSSLParameters(SSLParameters sslParameters) { +- int version = PlatformDependent.javaVersion(); +- if (version >= 7) { +- if (sslParameters.getAlgorithmConstraints() != null) { +- throw new IllegalArgumentException("AlgorithmConstraints are not supported."); +- } +- +- boolean isDestroyed = isDestroyed(); +- if (version >= 8) { +- if (!isDestroyed) { +- if (clientMode) { +- final List sniHostNames = Java8SslUtils.getSniHostNames(sslParameters); +- for (String name: sniHostNames) { +- SSL.setTlsExtHostName(ssl, name); +- } +- this.sniHostNames = sniHostNames; +- } +- if (Java8SslUtils.getUseCipherSuitesOrder(sslParameters)) { +- SSL.setOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); +- } else { +- SSL.clearOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); +- } +- } +- matchers = sslParameters.getSNIMatchers(); +- } +- +- final String endpointIdentificationAlgorithm = sslParameters.getEndpointIdentificationAlgorithm(); +- if (!isDestroyed) { +- configureEndpointVerification(endpointIdentificationAlgorithm); +- } +- this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm; +- algorithmConstraints = sslParameters.getAlgorithmConstraints(); +- } +- super.setSSLParameters(sslParameters); - } - -- @Test(timeout = 30000, expected = ClosedChannelException.class) -- public void testSniSNIMatcherDoesNotMatchClientOpenSslServerOpenSsl() throws Exception { -- Assume.assumeTrue(PlatformDependent.javaVersion() >= 8); -- Assume.assumeTrue(OpenSsl.isAvailable()); -- SniClientJava8TestUtil.testSniClient(SslProvider.OPENSSL, SslProvider.OPENSSL, false); +- private void configureEndpointVerification(String endpointIdentificationAlgorithm) { +- // If the user asks for hostname verification we must ensure we verify the peer. +- // If the user disables hostname verification we leave it up to the user to change the mode manually. +- if (clientMode && isEndPointVerificationEnabled(endpointIdentificationAlgorithm)) { +- SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRED, -1); +- } - } - -- @Test(timeout = 30000) -- public void testSniSNIMatcherMatchesClientJdkSslServerOpenSsl() throws Exception { -- Assume.assumeTrue(PlatformDependent.javaVersion() >= 8); -- Assume.assumeTrue(OpenSsl.isAvailable()); -- SniClientJava8TestUtil.testSniClient(SslProvider.JDK, SslProvider.OPENSSL, true); +- private static boolean isEndPointVerificationEnabled(String endPointIdentificationAlgorithm) { +- return endPointIdentificationAlgorithm != null && !endPointIdentificationAlgorithm.isEmpty(); - } - -- @Test(timeout = 30000, expected = ClosedChannelException.class) -- public void testSniSNIMatcherDoesNotMatchClientJdkSslServerOpenSsl() throws Exception { -- Assume.assumeTrue(PlatformDependent.javaVersion() >= 8); -- Assume.assumeTrue(OpenSsl.isAvailable()); -- SniClientJava8TestUtil.testSniClient(SslProvider.JDK, SslProvider.OPENSSL, false); +- private boolean isDestroyed() { +- return destroyed; - } - -- @Test(timeout = 30000) -- public void testSniSNIMatcherMatchesClientOpenSslServerJdkSsl() throws Exception { -- Assume.assumeTrue(PlatformDependent.javaVersion() >= 8); -- Assume.assumeTrue(OpenSsl.isAvailable()); -- SniClientJava8TestUtil.testSniClient(SslProvider.OPENSSL, SslProvider.JDK, true); +- final boolean checkSniHostnameMatch(byte[] hostname) { +- return Java8SslUtils.checkSniHostnameMatch(matchers, hostname); - } - -- @Test(timeout = 30000, expected = ClosedChannelException.class) -- public void testSniSNIMatcherDoesNotMatchClientOpenSslServerJdkSsl() throws Exception { -- Assume.assumeTrue(PlatformDependent.javaVersion() >= 8); -- Assume.assumeTrue(OpenSsl.isAvailable()); -- SniClientJava8TestUtil.testSniClient(SslProvider.OPENSSL, SslProvider.JDK, false); +- @Override +- public String getNegotiatedApplicationProtocol() { +- return applicationProtocol; - } - -- private static void testSniClient(SslProvider sslClientProvider, SslProvider sslServerProvider) throws Exception { -- final String sniHost = "sni.netty.io"; -- LocalAddress address = new LocalAddress("test"); -- EventLoopGroup group = new DefaultEventLoopGroup(1); -- Channel sc = null; -- Channel cc = null; -- try { -- SelfSignedCertificate cert = new SelfSignedCertificate(); -- final SslContext sslServerContext = SslContextBuilder.forServer(cert.key(), cert.cert()) -- .sslProvider(sslServerProvider).build(); +- private static long bufferAddress(ByteBuffer b) { +- assert b.isDirect(); +- if (PlatformDependent.hasUnsafe()) { +- return PlatformDependent.directBufferAddress(b); +- } +- return Buffer.address(b); +- } - -- final Promise promise = group.next().newPromise(); -- ServerBootstrap sb = new ServerBootstrap(); -- sc = sb.group(group).channel(LocalServerChannel.class).childHandler(new ChannelInitializer() { -- @Override -- protected void initChannel(Channel ch) throws Exception { -- ch.pipeline().addFirst(new SniHandler(new Mapping() { -- @Override -- public SslContext map(String input) { -- promise.setSuccess(input); -- return sslServerContext; -- } -- })); +- /** +- * Select the application protocol used. +- */ +- private void selectApplicationProtocol() throws SSLException { +- ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior = apn.selectedListenerFailureBehavior(); +- List protocols = apn.protocols(); +- String applicationProtocol; +- switch (apn.protocol()) { +- case NONE: +- break; +- // We always need to check for applicationProtocol == null as the remote peer may not support +- // the TLS extension or may have returned an empty selection. +- case ALPN: +- applicationProtocol = SSL.getAlpnSelected(ssl); +- if (applicationProtocol != null) { +- ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( +- protocols, behavior, applicationProtocol); +- } +- break; +- case NPN: +- applicationProtocol = SSL.getNextProtoNegotiated(ssl); +- if (applicationProtocol != null) { +- ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( +- protocols, behavior, applicationProtocol); +- } +- break; +- case NPN_AND_ALPN: +- applicationProtocol = SSL.getAlpnSelected(ssl); +- if (applicationProtocol == null) { +- applicationProtocol = SSL.getNextProtoNegotiated(ssl); - } -- }).bind(address).syncUninterruptibly().channel(); +- if (applicationProtocol != null) { +- ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( +- protocols, behavior, applicationProtocol); +- } +- break; +- default: +- throw new Error(); +- } +- } - -- SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE) -- .sslProvider(sslClientProvider).build(); -- Bootstrap cb = new Bootstrap(); -- cc = cb.group(group).channel(LocalChannel.class).handler(new SslHandler( -- sslContext.newEngine(ByteBufAllocator.DEFAULT, sniHost, -1))) -- .connect(address).syncUninterruptibly().channel(); -- Assert.assertEquals(sniHost, promise.syncUninterruptibly().getNow()); -- } finally { -- if (cc != null) { -- cc.close().syncUninterruptibly(); -- } -- if (sc != null) { -- sc.close().syncUninterruptibly(); +- private String selectApplicationProtocol(List protocols, +- ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior, +- String applicationProtocol) throws SSLException { +- if (behavior == ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT) { +- return applicationProtocol; +- } else { +- int size = protocols.size(); +- assert size > 0; +- if (protocols.contains(applicationProtocol)) { +- return applicationProtocol; +- } else { +- if (behavior == ApplicationProtocolConfig.SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL) { +- return protocols.get(size - 1); +- } else { +- throw new SSLException("unknown protocol " + applicationProtocol); +- } - } -- group.shutdownGracefully(); - } - } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java b/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java -deleted file mode 100644 -index 07c87c6..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/SniHandlerTest.java -+++ /dev/null -@@ -1,496 +0,0 @@ --/* -- * Copyright 2014 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ -- --package io.netty.handler.ssl; - --import static org.hamcrest.CoreMatchers.is; --import static org.hamcrest.CoreMatchers.nullValue; --import static org.junit.Assert.assertEquals; --import static org.junit.Assert.assertThat; --import static org.junit.Assert.assertTrue; --import static org.junit.Assume.assumeTrue; +- private static final X509Certificate[] JAVAX_CERTS_NOT_SUPPORTED = new X509Certificate[0]; - --import java.io.File; --import java.net.InetSocketAddress; --import java.util.ArrayList; --import java.util.List; --import java.util.concurrent.CountDownLatch; --import java.util.concurrent.TimeUnit; +- private final class DefaultOpenSslSession implements OpenSslSession { +- private final OpenSslSessionContext sessionContext; - --import javax.net.ssl.SSLEngine; +- // These are guarded by synchronized(OpenSslEngine.this) as handshakeFinished() may be triggered by any +- // thread. +- private X509Certificate[] x509PeerCerts; +- private Certificate[] peerCerts; - --import org.junit.Test; +- private boolean valid = true; +- private String protocol; +- private String cipher; +- private OpenSslSessionId id = OpenSslSessionId.NULL_ID; +- private long creationTime; - --import io.netty.bootstrap.Bootstrap; --import io.netty.bootstrap.ServerBootstrap; --import io.netty.buffer.ByteBufAllocator; --import io.netty.buffer.Unpooled; --import io.netty.channel.Channel; --import io.netty.channel.ChannelFuture; --import io.netty.channel.ChannelHandlerContext; --import io.netty.channel.ChannelInitializer; --import io.netty.channel.ChannelPipeline; --import io.netty.channel.DefaultEventLoopGroup; --import io.netty.channel.EventLoopGroup; --import io.netty.channel.embedded.EmbeddedChannel; --import io.netty.channel.local.LocalAddress; --import io.netty.channel.local.LocalChannel; --import io.netty.channel.local.LocalServerChannel; --import io.netty.channel.nio.NioEventLoopGroup; --import io.netty.channel.socket.nio.NioServerSocketChannel; --import io.netty.channel.socket.nio.NioSocketChannel; --import io.netty.handler.codec.DecoderException; --import io.netty.handler.ssl.util.InsecureTrustManagerFactory; --import io.netty.handler.ssl.util.SelfSignedCertificate; --import io.netty.util.DomainNameMapping; --import io.netty.util.DomainNameMappingBuilder; --import io.netty.util.Mapping; --import io.netty.util.ReferenceCountUtil; --import io.netty.util.ReferenceCounted; --import io.netty.util.concurrent.Promise; --import io.netty.util.internal.ObjectUtil; --import io.netty.util.internal.StringUtil; --import org.junit.runner.RunWith; --import org.junit.runners.Parameterized; +- // Updated once a new handshake is started and so the SSLSession reused. +- private long lastAccessed = -1; - --@RunWith(Parameterized.class) --public class SniHandlerTest { +- private volatile int applicationBufferSize = MAX_PLAINTEXT_LENGTH; +- private volatile Certificate[] localCertificateChain; +- private volatile Map keyValueStorage = new ConcurrentHashMap(); - -- private static ApplicationProtocolConfig newApnConfig() { -- return new ApplicationProtocolConfig( -- ApplicationProtocolConfig.Protocol.ALPN, -- // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers. -- ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, -- // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. -- ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, -- "myprotocol"); -- } +- DefaultOpenSslSession(OpenSslSessionContext sessionContext) { +- this.sessionContext = sessionContext; +- } - -- private static void assumeApnSupported(SslProvider provider) { -- switch (provider) { -- case OPENSSL: -- case OPENSSL_REFCNT: -- assumeTrue(OpenSsl.isAlpnSupported()); -- break; -- case JDK: -- assumeTrue(JettyAlpnSslEngine.isAvailable()); -- break; -- default: -- throw new Error(); +- private SSLSessionBindingEvent newSSLSessionBindingEvent(String name) { +- return new SSLSessionBindingEvent(session, name); - } -- } - -- private static SslContext makeSslContext(SslProvider provider, boolean apn) throws Exception { -- if (apn) { -- assumeApnSupported(provider); +- @Override +- public void prepareHandshake() { +- keyValueStorage.clear(); - } - -- File keyFile = new File(SniHandlerTest.class.getResource("test_encrypted.pem").getFile()); -- File crtFile = new File(SniHandlerTest.class.getResource("test.crt").getFile()); +- @Override +- public void setSessionDetails( +- long creationTime, long lastAccessedTime, OpenSslSessionId sessionId, +- Map keyValueStorage) { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- if (this.id == OpenSslSessionId.NULL_ID) { +- this.id = sessionId; +- this.creationTime = creationTime; +- this.lastAccessed = lastAccessedTime; +- +- // Update the key value storage. It's fine to just drop the previous stored values on the floor +- // as the JDK does the same in the sense that it will use a new SSLSessionImpl instance once the +- // handshake was done +- this.keyValueStorage = keyValueStorage; +- } +- } +- } - -- SslContextBuilder sslCtxBuilder = SslContextBuilder.forServer(crtFile, keyFile, "12345") -- .sslProvider(provider); -- if (apn) { -- sslCtxBuilder.applicationProtocolConfig(newApnConfig()); +- @Override +- public Map keyValueStorage() { +- return keyValueStorage; - } -- return sslCtxBuilder.build(); -- } - -- private static SslContext makeSslClientContext(SslProvider provider, boolean apn) throws Exception { -- if (apn) { -- assumeApnSupported(provider); +- @Override +- public OpenSslSessionId sessionId() { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- if (this.id == OpenSslSessionId.NULL_ID && !isDestroyed()) { +- byte[] sessionId = SSL.getSessionId(ssl); +- if (sessionId != null) { +- id = new OpenSslSessionId(sessionId); +- } +- } +- +- return id; +- } - } - -- File crtFile = new File(SniHandlerTest.class.getResource("test.crt").getFile()); +- @Override +- public void setLocalCertificate(Certificate[] localCertificate) { +- this.localCertificateChain = localCertificate; +- } - -- SslContextBuilder sslCtxBuilder = SslContextBuilder.forClient().trustManager(crtFile).sslProvider(provider); -- if (apn) { -- sslCtxBuilder.applicationProtocolConfig(newApnConfig()); +- @Override +- public byte[] getId() { +- return sessionId().cloneBytes(); - } -- return sslCtxBuilder.build(); -- } - -- @Parameterized.Parameters(name = "{index}: sslProvider={0}") -- public static Iterable data() { -- List params = new ArrayList(3); -- if (OpenSsl.isAvailable()) { -- params.add(SslProvider.OPENSSL); -- params.add(SslProvider.OPENSSL_REFCNT); +- @Override +- public OpenSslSessionContext getSessionContext() { +- return sessionContext; - } -- params.add(SslProvider.JDK); -- return params; -- } - -- private final SslProvider provider; +- @Override +- public long getCreationTime() { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- return creationTime; +- } +- } - -- public SniHandlerTest(SslProvider provider) { -- this.provider = provider; -- } +- @Override +- public void setLastAccessedTime(long time) { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- this.lastAccessed = time; +- } +- } - -- @Test -- public void testServerNameParsing() throws Exception { -- SslContext nettyContext = makeSslContext(provider, false); -- SslContext leanContext = makeSslContext(provider, false); -- SslContext leanContext2 = makeSslContext(provider, false); +- @Override +- public long getLastAccessedTime() { +- // if lastAccessed is -1 we will just return the creation time as the handshake was not started yet. +- synchronized (ReferenceCountedOpenSslEngine.this) { +- return lastAccessed == -1 ? creationTime : lastAccessed; +- } +- } - -- try { -- DomainNameMapping mapping = new DomainNameMappingBuilder(nettyContext) -- .add("*.netty.io", nettyContext) -- // input with custom cases -- .add("*.LEANCLOUD.CN", leanContext) -- // a hostname conflict with previous one, since we are using order-sensitive config, -- // the engine won't be used with the handler. -- .add("chat4.leancloud.cn", leanContext2) -- .build(); -- -- SniHandler handler = new SniHandler(mapping); -- EmbeddedChannel ch = new EmbeddedChannel(handler); +- @Override +- public void invalidate() { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- valid = false; +- sessionContext.removeFromCache(id); +- } +- } - -- try { -- // hex dump of a client hello packet, which contains hostname "CHAT4.LEANCLOUD.CN" -- String tlsHandshakeMessageHex1 = "16030100"; -- // part 2 -- String tlsHandshakeMessageHex = "c6010000c20303bb0855d66532c05a0ef784f7c384feeafa68b3" + -- "b655ac7288650d5eed4aa3fb52000038c02cc030009fcca9cca8ccaac02b" + -- "c02f009ec024c028006bc023c0270067c00ac0140039c009c0130033009d" + -- "009c003d003c0035002f00ff010000610000001700150000124348415434" + -- "2e4c45414e434c4f55442e434e000b000403000102000a000a0008001d00" + -- "170019001800230000000d0020001e060106020603050105020503040104" + -- "0204030301030203030201020202030016000000170000"; -- -- ch.writeInbound(Unpooled.wrappedBuffer(StringUtil.decodeHexDump(tlsHandshakeMessageHex1))); -- ch.writeInbound(Unpooled.wrappedBuffer(StringUtil.decodeHexDump(tlsHandshakeMessageHex))); -- -- // This should produce an alert -- assertTrue(ch.finish()); -- -- assertThat(handler.hostname(), is("chat4.leancloud.cn")); -- assertThat(handler.sslContext(), is(leanContext)); -- } finally { -- ch.finishAndReleaseAll(); +- @Override +- public boolean isValid() { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- return valid || sessionContext.isInCache(id); - } -- } finally { -- releaseAll(leanContext, leanContext2, nettyContext); - } -- } -- -- @Test(expected = DecoderException.class) -- public void testNonAsciiServerNameParsing() throws Exception { -- SslContext nettyContext = makeSslContext(provider, false); -- SslContext leanContext = makeSslContext(provider, false); -- SslContext leanContext2 = makeSslContext(provider, false); - -- try { -- DomainNameMapping mapping = new DomainNameMappingBuilder(nettyContext) -- .add("*.netty.io", nettyContext) -- // input with custom cases -- .add("*.LEANCLOUD.CN", leanContext) -- // a hostname conflict with previous one, since we are using order-sensitive config, -- // the engine won't be used with the handler. -- .add("chat4.leancloud.cn", leanContext2) -- .build(); -- -- SniHandler handler = new SniHandler(mapping); -- EmbeddedChannel ch = new EmbeddedChannel(handler); +- @Override +- public void putValue(String name, Object value) { +- checkNotNull(name, "name"); +- checkNotNull(value, "value"); - -- try { -- // hex dump of a client hello packet, which contains an invalid hostname "CHAT4。LEANCLOUD。CN" -- String tlsHandshakeMessageHex1 = "16030100"; -- // part 2 -- String tlsHandshakeMessageHex = "bd010000b90303a74225676d1814ba57faff3b366" + -- "3656ed05ee9dbb2a4dbb1bb1c32d2ea5fc39e0000000100008c0000001700150000164348" + -- "415434E380824C45414E434C4F5544E38082434E000b000403000102000a00340032000e0" + -- "00d0019000b000c00180009000a0016001700080006000700140015000400050012001300" + -- "0100020003000f0010001100230000000d0020001e0601060206030501050205030401040" + -- "20403030103020303020102020203000f00010133740000"; -- -- // Push the handshake message. -- // Decode should fail because of the badly encoded "HostName" string in the SNI extension -- // that isn't ASCII as per RFC 6066 - https://tools.ietf.org/html/rfc6066#page-6 -- ch.writeInbound(Unpooled.wrappedBuffer(StringUtil.decodeHexDump(tlsHandshakeMessageHex1))); -- ch.writeInbound(Unpooled.wrappedBuffer(StringUtil.decodeHexDump(tlsHandshakeMessageHex))); -- } finally { -- ch.finishAndReleaseAll(); +- final Object old = keyValueStorage.put(name, value); +- if (value instanceof SSLSessionBindingListener) { +- // Use newSSLSessionBindingEvent so we always use the wrapper if needed. +- ((SSLSessionBindingListener) value).valueBound(newSSLSessionBindingEvent(name)); - } -- } finally { -- releaseAll(leanContext, leanContext2, nettyContext); +- notifyUnbound(old, name); - } -- } -- -- @Test -- public void testFallbackToDefaultContext() throws Exception { -- SslContext nettyContext = makeSslContext(provider, false); -- SslContext leanContext = makeSslContext(provider, false); -- SslContext leanContext2 = makeSslContext(provider, false); - -- try { -- DomainNameMapping mapping = new DomainNameMappingBuilder(nettyContext) -- .add("*.netty.io", nettyContext) -- // input with custom cases -- .add("*.LEANCLOUD.CN", leanContext) -- // a hostname conflict with previous one, since we are using order-sensitive config, -- // the engine won't be used with the handler. -- .add("chat4.leancloud.cn", leanContext2) -- .build(); +- @Override +- public Object getValue(String name) { +- checkNotNull(name, "name"); +- return keyValueStorage.get(name); +- } - -- SniHandler handler = new SniHandler(mapping); -- EmbeddedChannel ch = new EmbeddedChannel(handler); +- @Override +- public void removeValue(String name) { +- checkNotNull(name, "name"); +- final Object old = keyValueStorage.remove(name); +- notifyUnbound(old, name); +- } - -- // invalid -- byte[] message = {22, 3, 1, 0, 0}; +- @Override +- public String[] getValueNames() { +- return keyValueStorage.keySet().toArray(EMPTY_STRINGS); +- } - -- try { -- // Push the handshake message. -- ch.writeInbound(Unpooled.wrappedBuffer(message)); -- } catch (Exception e) { -- // expected +- private void notifyUnbound(Object value, String name) { +- if (value instanceof SSLSessionBindingListener) { +- // Use newSSLSessionBindingEvent so we always use the wrapper if needed. +- ((SSLSessionBindingListener) value).valueUnbound(newSSLSessionBindingEvent(name)); - } -- -- assertThat(ch.finish(), is(false)); -- assertThat(handler.hostname(), nullValue()); -- assertThat(handler.sslContext(), is(nettyContext)); -- } finally { -- releaseAll(leanContext, leanContext2, nettyContext); - } -- } - -- @Test -- public void testSniWithApnHandler() throws Exception { -- SslContext nettyContext = makeSslContext(provider, true); -- SslContext sniContext = makeSslContext(provider, true); -- final SslContext clientContext = makeSslClientContext(provider, true); -- try { -- final CountDownLatch serverApnDoneLatch = new CountDownLatch(1); -- final CountDownLatch clientApnDoneLatch = new CountDownLatch(1); -- -- final DomainNameMapping mapping = new DomainNameMappingBuilder(nettyContext) -- .add("*.netty.io", nettyContext) -- .add("sni.fake.site", sniContext).build(); -- final SniHandler handler = new SniHandler(mapping); -- EventLoopGroup group = new NioEventLoopGroup(2); -- Channel serverChannel = null; -- Channel clientChannel = null; -- try { -- ServerBootstrap sb = new ServerBootstrap(); -- sb.group(group); -- sb.channel(NioServerSocketChannel.class); -- sb.childHandler(new ChannelInitializer() { -- @Override -- protected void initChannel(Channel ch) throws Exception { -- ChannelPipeline p = ch.pipeline(); -- // Server side SNI. -- p.addLast(handler); -- // Catch the notification event that APN has completed successfully. -- p.addLast(new ApplicationProtocolNegotiationHandler("foo") { -- @Override -- protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { -- serverApnDoneLatch.countDown(); -- } -- }); +- /** +- * Finish the handshake and so init everything in the {@link OpenSslSession} that should be accessible by +- * the user. +- */ +- @Override +- public void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate, +- byte[][] peerCertificateChain, long creationTime, long timeout) +- throws SSLException { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- if (!isDestroyed()) { +- if (this.id == OpenSslSessionId.NULL_ID) { +- // if the handshake finished and it was not a resumption let ensure we try to set the id +- +- this.id = id == null ? OpenSslSessionId.NULL_ID : new OpenSslSessionId(id); +- // Once the handshake was done the lastAccessed and creationTime should be the same if we +- // did not set it earlier via setSessionDetails(...) +- this.creationTime = lastAccessed = creationTime; - } -- }); -- -- Bootstrap cb = new Bootstrap(); -- cb.group(group); -- cb.channel(NioSocketChannel.class); -- cb.handler(new ChannelInitializer() { -- @Override -- protected void initChannel(Channel ch) throws Exception { -- ch.pipeline().addLast(new SslHandler(clientContext.newEngine( -- ch.alloc(), "sni.fake.site", -1))); -- // Catch the notification event that APN has completed successfully. -- ch.pipeline().addLast(new ApplicationProtocolNegotiationHandler("foo") { -- @Override -- protected void configurePipeline(ChannelHandlerContext ctx, String protocol) { -- clientApnDoneLatch.countDown(); +- this.cipher = toJavaCipherSuite(cipher); +- this.protocol = protocol; +- +- if (clientMode) { +- if (isEmpty(peerCertificateChain)) { +- peerCerts = EmptyArrays.EMPTY_CERTIFICATES; +- if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { +- x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES; +- } else { +- x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; +- } +- } else { +- peerCerts = new Certificate[peerCertificateChain.length]; +- if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { +- x509PeerCerts = new X509Certificate[peerCertificateChain.length]; +- } else { +- x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; +- } +- initCerts(peerCertificateChain, 0); +- } +- } else { +- // if used on the server side SSL_get_peer_cert_chain(...) will not include the remote peer +- // certificate. We use SSL_get_peer_certificate to get it in this case and add it to our +- // array later. +- // +- // See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html +- if (isEmpty(peerCertificate)) { +- peerCerts = EmptyArrays.EMPTY_CERTIFICATES; +- x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES; +- } else { +- if (isEmpty(peerCertificateChain)) { +- peerCerts = new Certificate[] {new LazyX509Certificate(peerCertificate)}; +- if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { +- x509PeerCerts = new X509Certificate[] { +- new LazyJavaxX509Certificate(peerCertificate) +- }; +- } else { +- x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; +- } +- } else { +- peerCerts = new Certificate[peerCertificateChain.length + 1]; +- peerCerts[0] = new LazyX509Certificate(peerCertificate); +- +- if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { +- x509PeerCerts = new X509Certificate[peerCertificateChain.length + 1]; +- x509PeerCerts[0] = new LazyJavaxX509Certificate(peerCertificate); +- } else { +- x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; +- } +- +- initCerts(peerCertificateChain, 1); - } -- }); +- } - } -- }); - -- serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel(); +- calculateMaxWrapOverhead(); - -- ChannelFuture ccf = cb.connect(serverChannel.localAddress()); -- assertTrue(ccf.awaitUninterruptibly().isSuccess()); -- clientChannel = ccf.channel(); +- handshakeState = HandshakeState.FINISHED; +- } else { +- throw new SSLException("Already closed"); +- } +- } +- } - -- assertTrue(serverApnDoneLatch.await(5, TimeUnit.SECONDS)); -- assertTrue(clientApnDoneLatch.await(5, TimeUnit.SECONDS)); -- assertThat(handler.hostname(), is("sni.fake.site")); -- assertThat(handler.sslContext(), is(sniContext)); -- } finally { -- if (serverChannel != null) { -- serverChannel.close().sync(); +- private void initCerts(byte[][] chain, int startPos) { +- for (int i = 0; i < chain.length; i++) { +- int certPos = startPos + i; +- peerCerts[certPos] = new LazyX509Certificate(chain[i]); +- if (x509PeerCerts != JAVAX_CERTS_NOT_SUPPORTED) { +- x509PeerCerts[certPos] = new LazyJavaxX509Certificate(chain[i]); - } -- if (clientChannel != null) { -- clientChannel.close().sync(); +- } +- } +- +- @Override +- public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- if (isEmpty(peerCerts)) { +- throw new SSLPeerUnverifiedException("peer not verified"); - } -- group.shutdownGracefully(0, 0, TimeUnit.MICROSECONDS); +- return peerCerts.clone(); - } -- } finally { -- releaseAll(clientContext, nettyContext, sniContext); - } -- } - -- @Test(timeout = 30000) -- public void testReplaceHandler() throws Exception { -- switch (provider) { -- case OPENSSL: -- case OPENSSL_REFCNT: -- final String sniHost = "sni.netty.io"; -- LocalAddress address = new LocalAddress("testReplaceHandler-" + Math.random()); -- EventLoopGroup group = new DefaultEventLoopGroup(1); -- Channel sc = null; -- Channel cc = null; -- SslContext sslContext = null; +- @Override +- public Certificate[] getLocalCertificates() { +- Certificate[] localCerts = this.localCertificateChain; +- if (localCerts == null) { +- return null; +- } +- return localCerts.clone(); +- } - -- SelfSignedCertificate cert = new SelfSignedCertificate(); +- @Override +- public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- if (x509PeerCerts == JAVAX_CERTS_NOT_SUPPORTED) { +- // Not supported by the underlying JDK, so just throw. This is fine in terms of the API +- // contract. See SSLSession.html#getPeerCertificateChain(). +- throw new UnsupportedOperationException(); +- } +- if (isEmpty(x509PeerCerts)) { +- throw new SSLPeerUnverifiedException("peer not verified"); +- } +- return x509PeerCerts.clone(); +- } +- } - -- try { -- final SslContext sslServerContext = SslContextBuilder -- .forServer(cert.key(), cert.cert()) -- .sslProvider(provider) -- .build(); -- -- final Mapping mapping = new Mapping() { -- @Override -- public SslContext map(String input) { -- return sslServerContext; -- } -- }; +- @Override +- public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { +- Certificate[] peer = getPeerCertificates(); +- // No need for null or length > 0 is needed as this is done in getPeerCertificates() +- // already. +- return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal(); +- } - -- final Promise releasePromise = group.next().newPromise(); +- @Override +- public Principal getLocalPrincipal() { +- Certificate[] local = this.localCertificateChain; +- if (local == null || local.length == 0) { +- return null; +- } +- return ((java.security.cert.X509Certificate) local[0]).getSubjectX500Principal(); +- } - -- final SniHandler handler = new SniHandler(mapping) { -- @Override -- protected void replaceHandler(ChannelHandlerContext ctx, -- String hostname, final SslContext sslContext) -- throws Exception { +- @Override +- public String getCipherSuite() { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- if (cipher == null) { +- return SslUtils.INVALID_CIPHER; +- } +- return cipher; +- } +- } - -- boolean success = false; -- try { -- // The SniHandler's replaceHandler() method allows us to implement custom behavior. -- // As an example, we want to release() the SslContext upon channelInactive() or rather -- // when the SslHandler closes it's SslEngine. If you take a close look at SslHandler -- // you'll see that it's doing it in the #handlerRemoved0() method. +- @Override +- public String getProtocol() { +- String protocol = this.protocol; +- if (protocol == null) { +- synchronized (ReferenceCountedOpenSslEngine.this) { +- if (!isDestroyed()) { +- protocol = SSL.getVersion(ssl); +- } else { +- protocol = StringUtil.EMPTY_STRING; +- } +- } +- } +- return protocol; +- } - -- SSLEngine sslEngine = sslContext.newEngine(ctx.alloc()); -- try { -- SslHandler customSslHandler = new CustomSslHandler(sslContext, sslEngine) { -- @Override -- public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { -- try { -- super.handlerRemoved0(ctx); -- } finally { -- releasePromise.trySuccess(null); -- } -- } -- }; -- ctx.pipeline().replace(this, CustomSslHandler.class.getName(), customSslHandler); -- success = true; -- } finally { -- if (!success) { -- ReferenceCountUtil.safeRelease(sslEngine); -- } -- } -- } finally { -- if (!success) { -- ReferenceCountUtil.safeRelease(sslContext); -- releasePromise.cancel(true); -- } -- } -- } -- }; -- -- ServerBootstrap sb = new ServerBootstrap(); -- sc = sb.group(group).channel(LocalServerChannel.class) -- .childHandler(new ChannelInitializer() { -- @Override -- protected void initChannel(Channel ch) throws Exception { -- ch.pipeline().addFirst(handler); -- } -- }).bind(address).syncUninterruptibly().channel(); +- @Override +- public String getPeerHost() { +- return ReferenceCountedOpenSslEngine.this.getPeerHost(); +- } - -- sslContext = SslContextBuilder.forClient().sslProvider(provider) -- .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); +- @Override +- public int getPeerPort() { +- return ReferenceCountedOpenSslEngine.this.getPeerPort(); +- } - -- Bootstrap cb = new Bootstrap(); -- cc = cb.group(group).channel(LocalChannel.class).handler(new SslHandler( -- sslContext.newEngine(ByteBufAllocator.DEFAULT, sniHost, -1))) -- .connect(address).syncUninterruptibly().channel(); +- @Override +- public int getPacketBufferSize() { +- return SSL.SSL_MAX_ENCRYPTED_LENGTH; +- } - -- cc.writeAndFlush(Unpooled.wrappedBuffer("Hello, World!".getBytes())) -- .syncUninterruptibly(); +- @Override +- public int getApplicationBufferSize() { +- return applicationBufferSize; +- } - -- // Notice how the server's SslContext refCnt is 1 -- assertEquals(1, ((ReferenceCounted) sslServerContext).refCnt()); +- @Override +- public void tryExpandApplicationBufferSize(int packetLengthDataOnly) { +- if (packetLengthDataOnly > MAX_PLAINTEXT_LENGTH && applicationBufferSize != MAX_RECORD_SIZE) { +- applicationBufferSize = MAX_RECORD_SIZE; +- } +- } - -- // The client disconnects -- cc.close().syncUninterruptibly(); -- if (!releasePromise.awaitUninterruptibly(10L, TimeUnit.SECONDS)) { -- throw new IllegalStateException("It doesn't seem #replaceHandler() got called."); -- } +- @Override +- public String toString() { +- return "DefaultOpenSslSession{" + +- "sessionContext=" + sessionContext + +- ", id=" + id + +- '}'; +- } - -- // We should have successfully release() the SslContext -- assertEquals(0, ((ReferenceCounted) sslServerContext).refCnt()); -- } finally { -- if (cc != null) { -- cc.close().syncUninterruptibly(); -- } -- if (sc != null) { -- sc.close().syncUninterruptibly(); -- } -- if (sslContext != null) { -- ReferenceCountUtil.release(sslContext); -- } -- group.shutdownGracefully(); +- @Override +- public int hashCode() { +- return sessionId().hashCode(); +- } - -- cert.delete(); -- } -- case JDK: -- return; -- default: -- throw new Error(); +- @Override +- public boolean equals(Object o) { +- if (o == this) { +- return true; +- } +- // We trust all sub-types as we use different types but the interface is package-private +- if (!(o instanceof OpenSslSession)) { +- return false; +- } +- return sessionId().equals(((OpenSslSession) o).sessionId()); - } - } - -- /** -- * This is a {@link SslHandler} that will call {@code release()} on the {@link SslContext} when -- * the client disconnects. -- * -- * @see SniHandlerTest#testReplaceHandler() -- */ -- private static class CustomSslHandler extends SslHandler { -- private final SslContext sslContext; +- private interface NativeSslException { +- int errorCode(); +- } - -- public CustomSslHandler(SslContext sslContext, SSLEngine sslEngine) { -- super(sslEngine); -- this.sslContext = ObjectUtil.checkNotNull(sslContext, "sslContext"); -- } +- private static final class OpenSslException extends SSLException implements NativeSslException { +- private final int errorCode; - -- @Override -- public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { -- super.handlerRemoved0(ctx); -- ReferenceCountUtil.release(sslContext); +- OpenSslException(String reason, int errorCode) { +- super(reason); +- this.errorCode = errorCode; - } -- } - -- private static void releaseAll(SslContext... contexts) { -- for (SslContext ctx: contexts) { -- ReferenceCountUtil.release(ctx); +- @Override +- public int errorCode() { +- return errorCode; - } - } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/SslContextBuilderTest.java b/handler/src/test/java/io/netty/handler/ssl/SslContextBuilderTest.java -deleted file mode 100644 -index 752424c..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/SslContextBuilderTest.java -+++ /dev/null -@@ -1,132 +0,0 @@ --/* -- * Copyright 2015 The Netty Project -- * -- * The Netty Project licenses this file to you under the Apache License, -- * version 2.0 (the "License"); you may not use this file except in compliance -- * with the License. You may obtain a copy of the License at: -- * -- * http://www.apache.org/licenses/LICENSE-2.0 -- * -- * Unless required by applicable law or agreed to in writing, software -- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- * License for the specific language governing permissions and limitations -- * under the License. -- */ --package io.netty.handler.ssl; -- --import static org.junit.Assert.assertFalse; --import static org.junit.Assert.assertTrue; - --import io.netty.buffer.UnpooledByteBufAllocator; --import io.netty.handler.ssl.util.SelfSignedCertificate; --import org.junit.Assume; --import org.junit.Test; +- private static final class OpenSslHandshakeException extends SSLHandshakeException implements NativeSslException { +- private final int errorCode; - --import javax.net.ssl.SSLEngine; +- OpenSslHandshakeException(String reason, int errorCode) { +- super(reason); +- this.errorCode = errorCode; +- } - --public class SslContextBuilderTest { -- -- @Test -- public void testClientContextFromFileJdk() throws Exception { -- testClientContextFromFile(SslProvider.JDK); -- } -- -- @Test -- public void testClientContextFromFileOpenssl() throws Exception { -- Assume.assumeTrue(OpenSsl.isAvailable()); -- testClientContextFromFile(SslProvider.OPENSSL); -- } -- -- @Test -- public void testClientContextJdk() throws Exception { -- testClientContext(SslProvider.JDK); -- } -- -- @Test -- public void testClientContextOpenssl() throws Exception { -- Assume.assumeTrue(OpenSsl.isAvailable()); -- testClientContext(SslProvider.OPENSSL); -- } -- -- @Test -- public void testServerContextFromFileJdk() throws Exception { -- testServerContextFromFile(SslProvider.JDK); -- } -- -- @Test -- public void testServerContextFromFileOpenssl() throws Exception { -- Assume.assumeTrue(OpenSsl.isAvailable()); -- testServerContextFromFile(SslProvider.OPENSSL); -- } -- -- @Test -- public void testServerContextJdk() throws Exception { -- testServerContext(SslProvider.JDK); -- } -- -- @Test -- public void testServerContextOpenssl() throws Exception { -- Assume.assumeTrue(OpenSsl.isAvailable()); -- testServerContext(SslProvider.OPENSSL); -- } -- -- private static void testClientContextFromFile(SslProvider provider) throws Exception { -- SelfSignedCertificate cert = new SelfSignedCertificate(); -- SslContextBuilder builder = SslContextBuilder.forClient() -- .sslProvider(provider) -- .keyManager(cert.certificate(), -- cert.privateKey()) -- .trustManager(cert.certificate()) -- .clientAuth(ClientAuth.OPTIONAL); -- SslContext context = builder.build(); -- SSLEngine engine = context.newEngine(UnpooledByteBufAllocator.DEFAULT); -- assertFalse(engine.getWantClientAuth()); -- assertFalse(engine.getNeedClientAuth()); -- engine.closeInbound(); -- engine.closeOutbound(); -- } -- -- private static void testClientContext(SslProvider provider) throws Exception { -- SelfSignedCertificate cert = new SelfSignedCertificate(); -- SslContextBuilder builder = SslContextBuilder.forClient() -- .sslProvider(provider) -- .keyManager(cert.key(), cert.cert()) -- .trustManager(cert.cert()) -- .clientAuth(ClientAuth.OPTIONAL); -- SslContext context = builder.build(); -- SSLEngine engine = context.newEngine(UnpooledByteBufAllocator.DEFAULT); -- assertFalse(engine.getWantClientAuth()); -- assertFalse(engine.getNeedClientAuth()); -- engine.closeInbound(); -- engine.closeOutbound(); -- } -- -- private static void testServerContextFromFile(SslProvider provider) throws Exception { -- SelfSignedCertificate cert = new SelfSignedCertificate(); -- SslContextBuilder builder = SslContextBuilder.forServer(cert.certificate(), cert.privateKey()) -- .sslProvider(provider) -- .trustManager(cert.certificate()) -- .clientAuth(ClientAuth.OPTIONAL); -- SslContext context = builder.build(); -- SSLEngine engine = context.newEngine(UnpooledByteBufAllocator.DEFAULT); -- assertTrue(engine.getWantClientAuth()); -- assertFalse(engine.getNeedClientAuth()); -- engine.closeInbound(); -- engine.closeOutbound(); -- } -- -- private static void testServerContext(SslProvider provider) throws Exception { -- SelfSignedCertificate cert = new SelfSignedCertificate(); -- SslContextBuilder builder = SslContextBuilder.forServer(cert.key(), cert.cert()) -- .sslProvider(provider) -- .trustManager(cert.cert()) -- .clientAuth(ClientAuth.REQUIRE); -- SslContext context = builder.build(); -- SSLEngine engine = context.newEngine(UnpooledByteBufAllocator.DEFAULT); -- assertFalse(engine.getWantClientAuth()); -- assertTrue(engine.getNeedClientAuth()); -- engine.closeInbound(); -- engine.closeOutbound(); +- @Override +- public int errorCode() { +- return errorCode; +- } - } -} -diff --git a/handler/src/test/java/io/netty/handler/ssl/SslErrorTest.java b/handler/src/test/java/io/netty/handler/ssl/SslErrorTest.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java deleted file mode 100644 -index aacdb69..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/SslErrorTest.java +index 862ab72..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslServerContext.java +++ /dev/null -@@ -1,255 +0,0 @@ +@@ -1,302 +0,0 @@ -/* - * Copyright 2016 The Netty Project - * @@ -8124,7 +9669,7 @@ index aacdb69..0000000 - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -8134,351 +9679,528 @@ index aacdb69..0000000 - */ -package io.netty.handler.ssl; - --import io.netty.bootstrap.Bootstrap; --import io.netty.bootstrap.ServerBootstrap; --import io.netty.channel.Channel; --import io.netty.channel.ChannelHandlerContext; --import io.netty.channel.ChannelInboundHandlerAdapter; --import io.netty.channel.ChannelInitializer; --import io.netty.channel.EventLoopGroup; --import io.netty.channel.nio.NioEventLoopGroup; --import io.netty.channel.socket.nio.NioServerSocketChannel; --import io.netty.channel.socket.nio.NioSocketChannel; --import io.netty.handler.logging.LogLevel; --import io.netty.handler.logging.LoggingHandler; --import io.netty.handler.ssl.util.InsecureTrustManagerFactory; --import io.netty.handler.ssl.util.SelfSignedCertificate; --import io.netty.handler.ssl.util.SimpleTrustManagerFactory; --import io.netty.util.ReferenceCountUtil; --import io.netty.util.concurrent.Promise; --import io.netty.util.internal.EmptyArrays; --import org.junit.Assume; --import org.junit.Test; --import org.junit.runner.RunWith; --import org.junit.runners.Parameterized; +-import io.netty.buffer.ByteBufAllocator; +-import io.netty.internal.tcnative.CertificateCallback; +-import io.netty.internal.tcnative.SSL; +-import io.netty.internal.tcnative.SSLContext; +-import io.netty.internal.tcnative.SniHostNameMatcher; +-import io.netty.util.CharsetUtil; +-import io.netty.util.internal.PlatformDependent; +-import io.netty.util.internal.SuppressJava6Requirement; +-import io.netty.util.internal.logging.InternalLogger; +-import io.netty.util.internal.logging.InternalLoggerFactory; - --import javax.net.ssl.ManagerFactoryParameters; --import javax.net.ssl.SSLException; --import javax.net.ssl.TrustManager; --import javax.net.ssl.X509TrustManager; --import javax.security.auth.x500.X500Principal; --import java.io.File; -import java.security.KeyStore; --import java.security.cert.CRLReason; --import java.security.cert.CertPathValidatorException; --import java.security.cert.CertificateException; --import java.security.cert.CertificateExpiredException; --import java.security.cert.CertificateNotYetValidException; --import java.security.cert.CertificateRevokedException; --import java.security.cert.Extension; +-import java.security.PrivateKey; -import java.security.cert.X509Certificate; --import java.util.ArrayList; --import java.util.Collection; --import java.util.Collections; --import java.util.Date; --import java.util.List; --import java.util.Locale; -- -- --@RunWith(Parameterized.class) --public class SslErrorTest { -- -- @Parameterized.Parameters(name = "{index}: serverProvider = {0}, clientProvider = {1}, exception = {2}") -- public static Collection data() { -- List serverProviders = new ArrayList(2); -- List clientProviders = new ArrayList(3); +-import java.util.Map; +-import javax.net.ssl.KeyManagerFactory; +-import javax.net.ssl.SSLException; +-import javax.net.ssl.TrustManagerFactory; +-import javax.net.ssl.X509ExtendedTrustManager; +-import javax.net.ssl.X509TrustManager; - -- if (OpenSsl.isAvailable()) { -- serverProviders.add(SslProvider.OPENSSL); -- serverProviders.add(SslProvider.OPENSSL_REFCNT); -- clientProviders.add(SslProvider.OPENSSL); -- clientProviders.add(SslProvider.OPENSSL_REFCNT); -- } -- // We not test with SslProvider.JDK on the server side as the JDK implementation currently just send the same -- // alert all the time, sigh..... -- clientProviders.add(SslProvider.JDK); +-import static io.netty.util.internal.ObjectUtil.checkNotNull; - -- List exceptions = new ArrayList(6); -- exceptions.add(new CertificateExpiredException()); -- exceptions.add(new CertificateNotYetValidException()); -- exceptions.add(new CertificateRevokedException( -- new Date(), CRLReason.AA_COMPROMISE, new X500Principal(""), -- Collections.emptyMap())); +-/** +- * A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation. +- *

Instances of this class must be {@link #release() released} or else native memory will leak! +- * +- *

Instances of this class must not be released before any {@link ReferenceCountedOpenSslEngine} +- * which depends upon the instance of this class is released. Otherwise if any method of +- * {@link ReferenceCountedOpenSslEngine} is called which uses this class's JNI resources the JVM may crash. +- */ +-public final class ReferenceCountedOpenSslServerContext extends ReferenceCountedOpenSslContext { +- private static final InternalLogger logger = +- InternalLoggerFactory.getInstance(ReferenceCountedOpenSslServerContext.class); +- private static final byte[] ID = {'n', 'e', 't', 't', 'y'}; +- private final OpenSslServerSessionContext sessionContext; - -- // Also use wrapped exceptions as this is what the JDK implementation of X509TrustManagerFactory is doing. -- exceptions.add(newCertificateException(CertPathValidatorException.BasicReason.EXPIRED)); -- exceptions.add(newCertificateException(CertPathValidatorException.BasicReason.NOT_YET_VALID)); -- exceptions.add(newCertificateException(CertPathValidatorException.BasicReason.REVOKED)); +- ReferenceCountedOpenSslServerContext( +- X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, +- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory, +- Iterable ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, +- long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls, +- boolean enableOcsp, String keyStore, ResumptionController resumptionController, +- Map.Entry, Object>... options) throws SSLException { +- this(trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory, ciphers, +- cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls, +- enableOcsp, keyStore, resumptionController, options); +- } - -- List params = new ArrayList(); -- for (SslProvider serverProvider: serverProviders) { -- for (SslProvider clientProvider: clientProviders) { -- for (CertificateException exception: exceptions) { -- params.add(new Object[] { serverProvider, clientProvider, exception}); -- } +- ReferenceCountedOpenSslServerContext( +- X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, +- X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory, +- Iterable ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn, +- long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls, +- boolean enableOcsp, String keyStore, ResumptionController resumptionController, +- Map.Entry, Object>... options) throws SSLException { +- super(ciphers, cipherFilter, apn, SSL.SSL_MODE_SERVER, keyCertChain, +- clientAuth, protocols, startTls, +- null, // No endpoint validation for servers. +- enableOcsp, true, resumptionController, options); +- // Create a new SSL_CTX and configure it. +- boolean success = false; +- try { +- sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory, +- keyCertChain, key, keyPassword, keyManagerFactory, keyStore, +- sessionCacheSize, sessionTimeout, resumptionController); +- if (SERVER_ENABLE_SESSION_TICKET) { +- sessionContext.setTicketKeys(); +- } +- success = true; +- } finally { +- if (!success) { +- release(); - } - } -- return params; - } - -- private static CertificateException newCertificateException(CertPathValidatorException.Reason reason) { -- return new TestCertificateException( -- new CertPathValidatorException("x", null, null, -1, reason)); +- @Override +- public OpenSslServerSessionContext sessionContext() { +- return sessionContext; - } - -- private final SslProvider serverProvider; -- private final SslProvider clientProvider; -- private final CertificateException exception; +- static OpenSslServerSessionContext newSessionContext(ReferenceCountedOpenSslContext thiz, long ctx, +- OpenSslEngineMap engineMap, +- X509Certificate[] trustCertCollection, +- TrustManagerFactory trustManagerFactory, +- X509Certificate[] keyCertChain, PrivateKey key, +- String keyPassword, KeyManagerFactory keyManagerFactory, +- String keyStore, long sessionCacheSize, long sessionTimeout, +- ResumptionController resumptionController) +- throws SSLException { +- OpenSslKeyMaterialProvider keyMaterialProvider = null; +- try { +- try { +- SSLContext.setVerify(ctx, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH); +- if (!OpenSsl.useKeyManagerFactory()) { +- if (keyManagerFactory != null) { +- throw new IllegalArgumentException( +- "KeyManagerFactory not supported"); +- } +- checkNotNull(keyCertChain, "keyCertChain"); - -- public SslErrorTest(SslProvider serverProvider, SslProvider clientProvider, CertificateException exception) { -- this.serverProvider = serverProvider; -- this.clientProvider = clientProvider; -- this.exception = exception; -- } +- setKeyMaterial(ctx, keyCertChain, key, keyPassword); +- } else { +- // javadocs state that keyManagerFactory has precedent over keyCertChain, and we must have a +- // keyManagerFactory for the server so build one if it is not specified. +- if (keyManagerFactory == null) { +- char[] keyPasswordChars = keyStorePassword(keyPassword); +- KeyStore ks = buildKeyStore(keyCertChain, key, keyPasswordChars, keyStore); +- if (ks.aliases().hasMoreElements()) { +- keyManagerFactory = new OpenSslX509KeyManagerFactory(); +- } else { +- keyManagerFactory = new OpenSslCachingX509KeyManagerFactory( +- KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())); +- } +- keyManagerFactory.init(ks, keyPasswordChars); +- } +- keyMaterialProvider = providerFor(keyManagerFactory, keyPassword); - -- @Test(timeout = 30000) -- public void testCorrectAlert() throws Exception { -- // As this only works correctly at the moment when OpenSslEngine is used on the server-side there is -- // no need to run it if there is no openssl is available at all. -- Assume.assumeTrue(OpenSsl.isAvailable()); +- SSLContext.setCertificateCallback(ctx, new OpenSslServerCertificateCallback( +- engineMap, new OpenSslKeyMaterialManager(keyMaterialProvider))); +- } +- } catch (Exception e) { +- throw new SSLException("failed to set certificate and key", e); +- } +- try { +- if (trustCertCollection != null) { +- trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory, keyStore); +- } else if (trustManagerFactory == null) { +- // Mimic the way SSLContext.getInstance(KeyManager[], null, null) works +- trustManagerFactory = TrustManagerFactory.getInstance( +- TrustManagerFactory.getDefaultAlgorithm()); +- trustManagerFactory.init((KeyStore) null); +- } - -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- final SslContext sslServerCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(serverProvider) -- .trustManager(new SimpleTrustManagerFactory() { -- @Override -- protected void engineInit(KeyStore keyStore) { } -- @Override -- protected void engineInit(ManagerFactoryParameters managerFactoryParameters) { } +- final X509TrustManager manager = chooseTrustManager( +- trustManagerFactory.getTrustManagers(), resumptionController); - -- @Override -- protected TrustManager[] engineGetTrustManagers() { -- return new TrustManager[] { new X509TrustManager() { +- // IMPORTANT: The callbacks set for verification must be static to prevent memory leak as +- // otherwise the context can never be collected. This is because the JNI code holds +- // a global reference to the callbacks. +- // +- // See https://github.com/netty/netty/issues/5372 - -- @Override -- public void checkClientTrusted(X509Certificate[] x509Certificates, String s) -- throws CertificateException { -- throw exception; -- } +- setVerifyCallback(ctx, engineMap, manager); - -- @Override -- public void checkServerTrusted(X509Certificate[] x509Certificates, String s) -- throws CertificateException { -- // NOOP +- X509Certificate[] issuers = manager.getAcceptedIssuers(); +- if (issuers != null && issuers.length > 0) { +- long bio = 0; +- try { +- bio = toBIO(ByteBufAllocator.DEFAULT, issuers); +- if (!SSLContext.setCACertificateBio(ctx, bio)) { +- throw new SSLException("unable to setup accepted issuers for trustmanager " + manager); +- } +- } finally { +- freeBio(bio); - } +- } - -- @Override -- public X509Certificate[] getAcceptedIssuers() { -- return EmptyArrays.EMPTY_X509_CERTIFICATES; -- } -- } }; +- if (PlatformDependent.javaVersion() >= 8) { +- // Only do on Java8+ as SNIMatcher is not supported in earlier releases. +- // IMPORTANT: The callbacks set for hostname matching must be static to prevent memory leak as +- // otherwise the context can never be collected. This is because the JNI code holds +- // a global reference to the matcher. +- SSLContext.setSniHostnameMatcher(ctx, new OpenSslSniHostnameMatcher(engineMap)); +- } +- } catch (SSLException e) { +- throw e; +- } catch (Exception e) { +- throw new SSLException("unable to setup trustmanager", e); - } -- }).clientAuth(ClientAuth.REQUIRE).build(); -- -- final SslContext sslClientCtx = SslContextBuilder.forClient() -- .trustManager(InsecureTrustManagerFactory.INSTANCE) -- .keyManager(new File(getClass().getResource("test.crt").getFile()), -- new File(getClass().getResource("test_unencrypted.pem").getFile())) -- .sslProvider(clientProvider).build(); - -- Channel serverChannel = null; -- Channel clientChannel = null; -- EventLoopGroup group = new NioEventLoopGroup(); -- try { -- serverChannel = new ServerBootstrap().group(group) -- .channel(NioServerSocketChannel.class) -- .handler(new LoggingHandler(LogLevel.INFO)) -- .childHandler(new ChannelInitializer() { -- @Override -- protected void initChannel(Channel ch) throws Exception { -- ch.pipeline().addLast(sslServerCtx.newHandler(ch.alloc())); -- ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { -- -- @Override -- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { -- ctx.close(); -- } -- }); -- } -- }).bind(0).sync().channel(); -- -- final Promise promise = group.next().newPromise(); -- -- clientChannel = new Bootstrap().group(group) -- .channel(NioSocketChannel.class) -- .handler(new ChannelInitializer() { -- @Override -- protected void initChannel(Channel ch) throws Exception { -- ch.pipeline().addLast(sslClientCtx.newHandler(ch.alloc())); -- ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { -- @Override -- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { -- // Unwrap as its wrapped by a DecoderException -- Throwable unwrappedCause = cause.getCause(); -- if (unwrappedCause instanceof SSLException) { -- if (exception instanceof TestCertificateException) { -- CertPathValidatorException.Reason reason = -- ((CertPathValidatorException) exception.getCause()).getReason(); -- if (reason == CertPathValidatorException.BasicReason.EXPIRED) { -- verifyException(unwrappedCause, "expired", promise); -- } else if (reason == CertPathValidatorException.BasicReason.NOT_YET_VALID) { -- verifyException(unwrappedCause, "bad", promise); -- } else if (reason == CertPathValidatorException.BasicReason.REVOKED) { -- verifyException(unwrappedCause, "revoked", promise); -- } -- } else if (exception instanceof CertificateExpiredException) { -- verifyException(unwrappedCause, "expired", promise); -- } else if (exception instanceof CertificateNotYetValidException) { -- verifyException(unwrappedCause, "bad", promise); -- } else if (exception instanceof CertificateRevokedException) { -- verifyException(unwrappedCause, "revoked", promise); -- } -- } -- } -- }); -- } -- }).connect(serverChannel.localAddress()).syncUninterruptibly().channel(); -- // Block until we received the correct exception -- promise.syncUninterruptibly(); -- } finally { -- if (clientChannel != null) { -- clientChannel.close().syncUninterruptibly(); +- OpenSslServerSessionContext sessionContext = new OpenSslServerSessionContext(thiz, keyMaterialProvider); +- sessionContext.setSessionIdContext(ID); +- // Enable session caching by default +- sessionContext.setSessionCacheEnabled(SERVER_ENABLE_SESSION_CACHE); +- if (sessionCacheSize > 0) { +- sessionContext.setSessionCacheSize((int) Math.min(sessionCacheSize, Integer.MAX_VALUE)); - } -- if (serverChannel != null) { -- serverChannel.close().syncUninterruptibly(); +- if (sessionTimeout > 0) { +- sessionContext.setSessionTimeout((int) Math.min(sessionTimeout, Integer.MAX_VALUE)); - } -- group.shutdownGracefully(); - -- ReferenceCountUtil.release(sslServerCtx); -- ReferenceCountUtil.release(sslClientCtx); +- keyMaterialProvider = null; +- +- return sessionContext; +- } finally { +- if (keyMaterialProvider != null) { +- keyMaterialProvider.destroy(); +- } - } - } - -- // Its a bit hacky to verify against the message that is part of the exception but there is no other way -- // at the moment as there are no different exceptions for the different alerts. -- private static void verifyException(Throwable cause, String messagePart, Promise promise) { -- String message = cause.getMessage(); -- if (message.toLowerCase(Locale.UK).contains(messagePart.toLowerCase(Locale.UK))) { -- promise.setSuccess(null); +- @SuppressJava6Requirement(reason = "Guarded by java version check") +- private static void setVerifyCallback(long ctx, OpenSslEngineMap engineMap, X509TrustManager manager) { +- // Use this to prevent an error when running on java < 7 +- if (useExtendedTrustManager(manager)) { +- SSLContext.setCertVerifyCallback(ctx, new ExtendedTrustManagerVerifyCallback( +- engineMap, (X509ExtendedTrustManager) manager)); - } else { -- promise.setFailure(new AssertionError("message not contains '" + messagePart + "': " + message)); +- SSLContext.setCertVerifyCallback(ctx, new TrustManagerVerifyCallback(engineMap, manager)); - } - } - -- private static final class TestCertificateException extends CertificateException { +- private static final class OpenSslServerCertificateCallback implements CertificateCallback { +- private final OpenSslEngineMap engineMap; +- private final OpenSslKeyMaterialManager keyManagerHolder; - -- public TestCertificateException(Throwable cause) { -- super(cause); +- OpenSslServerCertificateCallback(OpenSslEngineMap engineMap, OpenSslKeyMaterialManager keyManagerHolder) { +- this.engineMap = engineMap; +- this.keyManagerHolder = keyManagerHolder; - } -- } --} -diff --git a/handler/src/test/java/io/netty/handler/ssl/SslHandlerTest.java b/handler/src/test/java/io/netty/handler/ssl/SslHandlerTest.java -index 5ef43de..52c4d22 100644 ---- a/handler/src/test/java/io/netty/handler/ssl/SslHandlerTest.java -+++ b/handler/src/test/java/io/netty/handler/ssl/SslHandlerTest.java -@@ -121,35 +121,6 @@ public class SslHandlerTest { - } - } - -- @Test -- public void testReleaseSslEngine() throws Exception { -- assumeTrue(OpenSsl.isAvailable()); - -- SelfSignedCertificate cert = new SelfSignedCertificate(); -- try { -- SslContext sslContext = SslContextBuilder.forServer(cert.certificate(), cert.privateKey()) -- .sslProvider(SslProvider.OPENSSL) -- .build(); +- @Override +- public void handle(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals) throws Exception { +- final ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); +- if (engine == null) { +- // Maybe null if destroyed in the meantime. +- return; +- } - try { -- SSLEngine sslEngine = sslContext.newEngine(ByteBufAllocator.DEFAULT); -- EmbeddedChannel ch = new EmbeddedChannel(new SslHandler(sslEngine)); +- // For now we just ignore the asn1DerEncodedPrincipals as this is kind of inline with what the +- // OpenJDK SSLEngineImpl does. +- keyManagerHolder.setKeyMaterialServerSide(engine); +- } catch (Throwable cause) { +- engine.initHandshakeException(cause); - -- assertEquals(1, ((ReferenceCounted) sslContext).refCnt()); -- assertEquals(1, ((ReferenceCounted) sslEngine).refCnt()); +- if (cause instanceof Exception) { +- throw (Exception) cause; +- } +- throw new SSLException(cause); +- } +- } +- } - -- assertTrue(ch.finishAndReleaseAll()); -- ch.close().syncUninterruptibly(); +- private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier { +- private final X509TrustManager manager; - -- assertEquals(1, ((ReferenceCounted) sslContext).refCnt()); -- assertEquals(0, ((ReferenceCounted) sslEngine).refCnt()); -- } finally { -- ReferenceCountUtil.release(sslContext); -- } -- } finally { -- cert.delete(); +- TrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509TrustManager manager) { +- super(engineMap); +- this.manager = manager; +- } +- +- @Override +- void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth) +- throws Exception { +- manager.checkClientTrusted(peerCerts, auth); +- } +- } +- +- @SuppressJava6Requirement(reason = "Usage guarded by java version check") +- private static final class ExtendedTrustManagerVerifyCallback extends AbstractCertificateVerifier { +- private final X509ExtendedTrustManager manager; +- +- ExtendedTrustManagerVerifyCallback(OpenSslEngineMap engineMap, X509ExtendedTrustManager manager) { +- super(engineMap); +- this.manager = manager; +- } +- +- @Override +- void verify(ReferenceCountedOpenSslEngine engine, X509Certificate[] peerCerts, String auth) +- throws Exception { +- manager.checkClientTrusted(peerCerts, auth, engine); - } - } - - private static final class TlsReadTest extends ChannelOutboundHandlerAdapter { - private volatile boolean readIssued; +- private static final class OpenSslSniHostnameMatcher implements SniHostNameMatcher { +- private final OpenSslEngineMap engineMap; +- +- OpenSslSniHostnameMatcher(OpenSslEngineMap engineMap) { +- this.engineMap = engineMap; +- } +- +- @Override +- public boolean match(long ssl, String hostname) { +- ReferenceCountedOpenSslEngine engine = engineMap.get(ssl); +- if (engine != null) { +- // TODO: In the next release of tcnative we should pass the byte[] directly in and not use a String. +- return engine.checkSniHostnameMatch(hostname.getBytes(CharsetUtil.UTF_8)); +- } +- logger.warn("No ReferenceCountedOpenSslEngine found for SSL pointer: {}", ssl); +- return false; +- } +- } +-} +diff --git a/handler/src/main/java/io/netty/handler/ssl/SslContext.java b/handler/src/main/java/io/netty/handler/ssl/SslContext.java +index 137cbf3..7b649d9 100644 +--- a/handler/src/main/java/io/netty/handler/ssl/SslContext.java ++++ b/handler/src/main/java/io/netty/handler/ssl/SslContext.java +@@ -129,11 +129,7 @@ public abstract class SslContext { + } -@@ -279,13 +250,6 @@ public class SslHandlerTest { - testAlertProducedAndSend(SslProvider.JDK); + private static SslProvider defaultProvider() { +- if (OpenSsl.isAvailable()) { +- return SslProvider.OPENSSL; +- } else { +- return SslProvider.JDK; +- } ++ return SslProvider.JDK; } -- @Test(timeout = 30000) -- public void testAlertProducedAndSendOpenSsl() throws Exception { -- assumeTrue(OpenSsl.isAvailable()); -- testAlertProducedAndSend(SslProvider.OPENSSL); -- testAlertProducedAndSend(SslProvider.OPENSSL_REFCNT); -- } + /** +@@ -479,18 +475,6 @@ public abstract class SslContext { + trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, + keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, + clientAuth, protocols, startTls, secureRandom, keyStoreType, resumptionController); +- case OPENSSL: +- verifyNullSslContextProvider(provider, sslContextProvider); +- return new OpenSslServerContext( +- trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, +- keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, +- clientAuth, protocols, startTls, enableOcsp, keyStoreType, resumptionController, ctxOptions); +- case OPENSSL_REFCNT: +- verifyNullSslContextProvider(provider, sslContextProvider); +- return new ReferenceCountedOpenSslServerContext( +- trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, +- keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, +- clientAuth, protocols, startTls, enableOcsp, keyStoreType, resumptionController, ctxOptions); + default: + throw new Error(provider.toString()); + } +@@ -841,20 +825,6 @@ public abstract class SslContext { + keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, + sessionTimeout, secureRandom, keyStoreType, endpointIdentificationAlgorithm, + resumptionController); +- case OPENSSL: +- verifyNullSslContextProvider(provider, sslContextProvider); +- OpenSsl.ensureAvailability(); +- return new OpenSslClientContext( +- trustCert, trustManagerFactory, keyCertChain, key, keyPassword, +- keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, +- enableOcsp, keyStoreType, endpointIdentificationAlgorithm, resumptionController, options); +- case OPENSSL_REFCNT: +- verifyNullSslContextProvider(provider, sslContextProvider); +- OpenSsl.ensureAvailability(); +- return new ReferenceCountedOpenSslClientContext( +- trustCert, trustManagerFactory, keyCertChain, key, keyPassword, +- keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, +- enableOcsp, keyStoreType, endpointIdentificationAlgorithm, resumptionController, options); + default: + throw new Error(provider.toString()); + } +diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +index 40a46d2..53afec2 100644 +--- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java ++++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +@@ -200,57 +200,6 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH + private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; + + private enum SslEngineType { +- TCNATIVE(true, COMPOSITE_CUMULATOR) { +- @Override +- SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int len, ByteBuf out) throws SSLException { +- int nioBufferCount = in.nioBufferCount(); +- int writerIndex = out.writerIndex(); +- final SSLEngineResult result; +- if (nioBufferCount > 1) { +- /* +- * If {@link OpenSslEngine} is in use, +- * we can use a special {@link OpenSslEngine#unwrap(ByteBuffer[], ByteBuffer[])} method +- * that accepts multiple {@link ByteBuffer}s without additional memory copies. +- */ +- ReferenceCountedOpenSslEngine opensslEngine = (ReferenceCountedOpenSslEngine) handler.engine; +- try { +- handler.singleBuffer[0] = toByteBuffer(out, writerIndex, out.writableBytes()); +- result = opensslEngine.unwrap(in.nioBuffers(in.readerIndex(), len), handler.singleBuffer); +- } finally { +- handler.singleBuffer[0] = null; +- } +- } else { +- result = handler.engine.unwrap(toByteBuffer(in, in.readerIndex(), len), +- toByteBuffer(out, writerIndex, out.writableBytes())); +- } +- out.writerIndex(writerIndex + result.bytesProduced()); +- return result; +- } +- +- @Override +- ByteBuf allocateWrapBuffer(SslHandler handler, ByteBufAllocator allocator, +- int pendingBytes, int numComponents) { +- return allocator.directBuffer(((ReferenceCountedOpenSslEngine) handler.engine) +- .calculateOutNetBufSize(pendingBytes, numComponents)); +- } +- +- @Override +- int calculateRequiredOutBufSpace(SslHandler handler, int pendingBytes, int numComponents) { +- return ((ReferenceCountedOpenSslEngine) handler.engine) +- .calculateMaxLengthForWrap(pendingBytes, numComponents); +- } +- +- @Override +- int calculatePendingData(SslHandler handler, int guess) { +- int sslPending = ((ReferenceCountedOpenSslEngine) handler.engine).sslPending(); +- return sslPending > 0 ? sslPending : guess; +- } - - private void testAlertProducedAndSend(SslProvider provider) throws Exception { - SelfSignedCertificate ssc = new SelfSignedCertificate(); +- @Override +- boolean jdkCompatibilityMode(SSLEngine engine) { +- return ((ReferenceCountedOpenSslEngine) engine).jdkCompatibilityMode; +- } +- }, + JDK(false, MERGE_CUMULATOR) { + @Override + SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int len, ByteBuf out) throws SSLException { +@@ -309,7 +258,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH + }; -@@ -425,12 +389,6 @@ public class SslHandlerTest { - testCloseNotify(SslProvider.JDK, 5000, false); - } + static SslEngineType forEngine(SSLEngine engine) { +- return engine instanceof ReferenceCountedOpenSslEngine ? TCNATIVE : JDK; ++ return JDK; + } -- @Test(timeout = 30000) -- public void testCloseNotifyReceivedOpenSsl() throws Exception { -- assumeTrue(OpenSsl.isAvailable()); -- testCloseNotify(SslProvider.OPENSSL, 5000, false); -- testCloseNotify(SslProvider.OPENSSL_REFCNT, 5000, false); -- } + SslEngineType(boolean wantsDirectBuffer, Cumulator cumulator) { +@@ -2078,7 +2027,6 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH + } + }; - @Test(timeout = 30000) - public void testCloseNotifyReceivedJdkTimeout() throws Exception { -@@ -438,24 +396,10 @@ public class SslHandlerTest { +- setOpensslEngineSocketFd(channel); + boolean fastOpen = Boolean.TRUE.equals(channel.config().getOption(ChannelOption.TCP_FASTOPEN_CONNECT)); + boolean active = channel.isActive(); + if (active || fastOpen) { +@@ -2238,18 +2186,11 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH + ctx.flush(); } - @Test(timeout = 30000) -- public void testCloseNotifyReceivedOpenSslTimeout() throws Exception { -- assumeTrue(OpenSsl.isAvailable()); -- testCloseNotify(SslProvider.OPENSSL, 100, true); -- testCloseNotify(SslProvider.OPENSSL_REFCNT, 100, true); -- } +- private void setOpensslEngineSocketFd(Channel c) { +- if (c instanceof UnixChannel && engine instanceof ReferenceCountedOpenSslEngine) { +- ((ReferenceCountedOpenSslEngine) engine).bioSetFd(((UnixChannel) c).fd().intValue()); +- } +- } - -- @Test(timeout = 30000) - public void testCloseNotifyNotWaitForResponseJdk() throws Exception { - testCloseNotify(SslProvider.JDK, 0, false); - } + /** + * Issues an initial TLS handshake once connected when used in client-mode + */ + @Override + public void channelActive(final ChannelHandlerContext ctx) throws Exception { +- setOpensslEngineSocketFd(ctx.channel()); + if (!startTls) { + startHandshakeProcessing(true); + } +diff --git a/handler/src/main/java/io/netty/handler/ssl/SslMasterKeyHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslMasterKeyHandler.java +index 3d8628e..c477dda 100644 +--- a/handler/src/main/java/io/netty/handler/ssl/SslMasterKeyHandler.java ++++ b/handler/src/main/java/io/netty/handler/ssl/SslMasterKeyHandler.java +@@ -139,9 +139,6 @@ public abstract class SslMasterKeyHandler extends ChannelInboundHandlerAdapter { + "via reflection.", e); + } + accept(secretKey, sslSession); +- } else if (OpenSsl.isAvailable() && engine instanceof ReferenceCountedOpenSslEngine) { +- SecretKeySpec secretKey = ((ReferenceCountedOpenSslEngine) engine).masterKey(); +- accept(secretKey, sslSession); + } + } -- @Test(timeout = 30000) -- public void testCloseNotifyNotWaitForResponseOpenSsl() throws Exception { -- assumeTrue(OpenSsl.isAvailable()); -- testCloseNotify(SslProvider.OPENSSL, 0, false); -- testCloseNotify(SslProvider.OPENSSL_REFCNT, 0, false); -- } -- - private static void testCloseNotify(SslProvider provider, final long closeNotifyReadTimeout, final boolean timeout) - throws Exception { - SelfSignedCertificate ssc = new SelfSignedCertificate(); -@@ -720,7 +664,7 @@ public class SslHandlerTest { +diff --git a/handler/src/main/java/io/netty/handler/ssl/SslProvider.java b/handler/src/main/java/io/netty/handler/ssl/SslProvider.java +index 952b9e8..44dc842 100644 +--- a/handler/src/main/java/io/netty/handler/ssl/SslProvider.java ++++ b/handler/src/main/java/io/netty/handler/ssl/SslProvider.java +@@ -28,16 +28,7 @@ public enum SslProvider { + /** + * JDK's default implementation. + */ +- JDK, +- /** +- * OpenSSL-based implementation. +- */ +- OPENSSL, +- /** +- * OpenSSL-based implementation which does not have finalizers and instead implements {@link ReferenceCounted}. +- */ +- @UnstableApi +- OPENSSL_REFCNT; ++ JDK; + + /** + * Returns {@code true} if the specified {@link SslProvider} supports +@@ -48,9 +39,6 @@ public enum SslProvider { switch (provider) { - case OPENSSL: - case OPENSSL_REFCNT: -- return OpenSsl.isAvailable(); -+ return false; + case JDK: + return JdkAlpnApplicationProtocolNegotiator.isAlpnSupported(); +- case OPENSSL: +- case OPENSSL_REFCNT: +- return OpenSsl.isAlpnSupported(); + default: + throw new Error("Unknown SslProvider: " + provider); + } +@@ -72,9 +60,6 @@ public enum SslProvider { + switch (sslProvider) { + case JDK: + return SslUtils.isTLSv13SupportedByJDK(provider); +- case OPENSSL: +- case OPENSSL_REFCNT: +- return OpenSsl.isTlsv13Supported(); + default: + throw new Error("Unknown SslProvider: " + sslProvider); + } +@@ -89,9 +74,6 @@ public enum SslProvider { + case JDK: + // We currently don't support any SslContextOptions when using the JDK implementation + return false; +- case OPENSSL: +- case OPENSSL_REFCNT: +- return OpenSsl.isOptionSupported(option); + default: + throw new Error("Unknown SslProvider: " + sslProvider); + } +@@ -105,9 +87,6 @@ public enum SslProvider { + switch (sslProvider) { + case JDK: + return SslUtils.isTLSv13EnabledByJDK(provider); +- case OPENSSL: +- case OPENSSL_REFCNT: +- return OpenSsl.isTlsv13Supported(); default: - return true; + throw new Error("Unknown SslProvider: " + sslProvider); } -diff --git a/handler/src/test/java/io/netty/handler/ssl/ocsp/OcspTest.java b/handler/src/test/java/io/netty/handler/ssl/ocsp/OcspTest.java +diff --git a/handler/src/main/java/io/netty/handler/ssl/ocsp/OcspClientHandler.java b/handler/src/main/java/io/netty/handler/ssl/ocsp/OcspClientHandler.java deleted file mode 100644 -index 4aecc74..0000000 ---- a/handler/src/test/java/io/netty/handler/ssl/ocsp/OcspTest.java +index 7f9cdf6..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/ocsp/OcspClientHandler.java +++ /dev/null -@@ -1,501 +0,0 @@ +@@ -1,61 +0,0 @@ -/* - * Copyright 2017 The Netty Project - * @@ -8486,7 +10208,7 @@ index 4aecc74..0000000 - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * -- * http://www.apache.org/licenses/LICENSE-2.0 +- * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT @@ -8494,492 +10216,81 @@ index 4aecc74..0000000 - * License for the specific language governing permissions and limitations - * under the License. - */ -- -package io.netty.handler.ssl.ocsp; - --import io.netty.bootstrap.Bootstrap; --import io.netty.bootstrap.ServerBootstrap; --import io.netty.buffer.ByteBufAllocator; --import io.netty.buffer.Unpooled; --import io.netty.channel.Channel; --import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; --import io.netty.channel.ChannelInitializer; --import io.netty.channel.ChannelPipeline; --import io.netty.channel.DefaultEventLoopGroup; --import io.netty.channel.EventLoopGroup; --import io.netty.channel.local.LocalAddress; --import io.netty.channel.local.LocalChannel; --import io.netty.channel.local.LocalServerChannel; --import io.netty.handler.ssl.OpenSsl; +-import io.netty.handler.ssl.ReferenceCountedOpenSslContext; -import io.netty.handler.ssl.ReferenceCountedOpenSslEngine; --import io.netty.handler.ssl.SslContext; --import io.netty.handler.ssl.SslContextBuilder; --import io.netty.handler.ssl.SslHandler; --import io.netty.handler.ssl.SslProvider; --import io.netty.handler.ssl.util.InsecureTrustManagerFactory; --import io.netty.handler.ssl.util.SelfSignedCertificate; --import io.netty.util.CharsetUtil; --import io.netty.util.ReferenceCountUtil; -- --import java.net.SocketAddress; --import java.util.concurrent.CountDownLatch; --import java.util.concurrent.TimeUnit; --import java.util.concurrent.TimeoutException; --import java.util.concurrent.atomic.AtomicReference; +-import io.netty.handler.ssl.SslHandshakeCompletionEvent; +-import io.netty.util.internal.ObjectUtil; +-import io.netty.util.internal.UnstableApi; - -import javax.net.ssl.SSLHandshakeException; - --import org.junit.BeforeClass; --import org.junit.Test; -- --import static org.junit.Assert.assertArrayEquals; --import static org.junit.Assert.assertNotNull; --import static org.junit.Assert.assertNotSame; --import static org.junit.Assert.assertNull; --import static org.junit.Assert.assertSame; --import static org.junit.Assert.assertTrue; --import static org.junit.Assume.assumeTrue; -- --public class OcspTest { -- -- @BeforeClass -- public static void checkOcspSupported() { -- assumeTrue(OpenSsl.isOcspSupported()); -- } -- -- @Test(expected = IllegalArgumentException.class) -- public void testJdkClientEnableOcsp() throws Exception { -- SslContextBuilder.forClient() -- .sslProvider(SslProvider.JDK) -- .enableOcsp(true) -- .build(); -- } -- -- @Test(expected = IllegalArgumentException.class) -- public void testJdkServerEnableOcsp() throws Exception { -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- try { -- SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(SslProvider.JDK) -- .enableOcsp(true) -- .build(); -- } finally { -- ssc.delete(); -- } -- } -- -- @Test(expected = IllegalStateException.class) -- public void testClientOcspNotEnabledOpenSsl() throws Exception { -- testClientOcspNotEnabled(SslProvider.OPENSSL); -- } -- -- @Test(expected = IllegalStateException.class) -- public void testClientOcspNotEnabledOpenSslRefCnt() throws Exception { -- testClientOcspNotEnabled(SslProvider.OPENSSL_REFCNT); -- } -- -- private void testClientOcspNotEnabled(SslProvider sslProvider) throws Exception { -- SslContext context = SslContextBuilder.forClient() -- .sslProvider(sslProvider) -- .build(); -- try { -- SslHandler sslHandler = context.newHandler(ByteBufAllocator.DEFAULT); -- ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine(); -- try { -- engine.getOcspResponse(); -- } finally { -- engine.release(); -- } -- } finally { -- ReferenceCountUtil.release(context); -- } -- } -- -- @Test(expected = IllegalStateException.class) -- public void testServerOcspNotEnabledOpenSsl() throws Exception { -- testServerOcspNotEnabled(SslProvider.OPENSSL); -- } -- -- @Test(expected = IllegalStateException.class) -- public void testServerOcspNotEnabledOpenSslRefCnt() throws Exception { -- testServerOcspNotEnabled(SslProvider.OPENSSL_REFCNT); -- } -- -- private void testServerOcspNotEnabled(SslProvider sslProvider) throws Exception { -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- try { -- SslContext context = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslProvider) -- .build(); -- try { -- SslHandler sslHandler = context.newHandler(ByteBufAllocator.DEFAULT); -- ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine(); -- try { -- engine.setOcspResponse(new byte[] { 1, 2, 3 }); -- } finally { -- engine.release(); -- } -- } finally { -- ReferenceCountUtil.release(context); -- } -- } finally { -- ssc.delete(); -- } -- } -- -- @Test(timeout = 10000L) -- public void testClientAcceptingOcspStapleOpenSsl() throws Exception { -- testClientAcceptingOcspStaple(SslProvider.OPENSSL); -- } -- -- @Test(timeout = 10000L) -- public void testClientAcceptingOcspStapleOpenSslRefCnt() throws Exception { -- testClientAcceptingOcspStaple(SslProvider.OPENSSL_REFCNT); -- } -- -- /** -- * The Server provides an OCSP staple and the Client accepts it. -- */ -- private void testClientAcceptingOcspStaple(SslProvider sslProvider) throws Exception { -- final CountDownLatch latch = new CountDownLatch(1); -- ChannelInboundHandlerAdapter serverHandler = new ChannelInboundHandlerAdapter() { -- @Override -- public void channelActive(ChannelHandlerContext ctx) throws Exception { -- ctx.writeAndFlush(Unpooled.wrappedBuffer("Hello, World!".getBytes())); -- ctx.fireChannelActive(); -- } -- }; -- -- ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() { -- @Override -- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { -- try { -- ReferenceCountUtil.release(msg); -- } finally { -- latch.countDown(); -- } -- } -- }; -- -- byte[] response = newOcspResponse(); -- TestClientOcspContext callback = new TestClientOcspContext(true); -- -- handshake(sslProvider, latch, serverHandler, response, clientHandler, callback); -- -- byte[] actual = callback.response(); -- -- assertNotNull(actual); -- assertNotSame(response, actual); -- assertArrayEquals(response, actual); -- } -- -- @Test(timeout = 10000L) -- public void testClientRejectingOcspStapleOpenSsl() throws Exception { -- testClientRejectingOcspStaple(SslProvider.OPENSSL); -- } -- -- @Test(timeout = 10000L) -- public void testClientRejectingOcspStapleOpenSslRefCnt() throws Exception { -- testClientRejectingOcspStaple(SslProvider.OPENSSL_REFCNT); -- } -- -- /** -- * The Server provides an OCSP staple and the Client rejects it. -- */ -- private void testClientRejectingOcspStaple(SslProvider sslProvider) throws Exception { -- final AtomicReference causeRef = new AtomicReference(); -- final CountDownLatch latch = new CountDownLatch(1); -- -- ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() { -- @Override -- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { -- try { -- causeRef.set(cause); -- } finally { -- latch.countDown(); -- } -- } -- }; -- -- byte[] response = newOcspResponse(); -- TestClientOcspContext callback = new TestClientOcspContext(false); -- -- handshake(sslProvider, latch, null, response, clientHandler, callback); -- -- byte[] actual = callback.response(); -- -- assertNotNull(actual); -- assertNotSame(response, actual); -- assertArrayEquals(response, actual); -- -- Throwable cause = causeRef.get(); -- assertTrue("" + cause, cause instanceof SSLHandshakeException); -- } -- -- @Test(timeout = 10000L) -- public void testServerHasNoStapleOpenSsl() throws Exception { -- testServerHasNoStaple(SslProvider.OPENSSL); -- } -- -- @Test(timeout = 10000L) -- public void testServerHasNoStapleOpenSslRefCnt() throws Exception { -- testServerHasNoStaple(SslProvider.OPENSSL_REFCNT); -- } -- -- /** -- * The server has OCSP stapling enabled but doesn't provide a staple. -- */ -- private void testServerHasNoStaple(SslProvider sslProvider) throws Exception { -- final CountDownLatch latch = new CountDownLatch(1); -- ChannelInboundHandlerAdapter serverHandler = new ChannelInboundHandlerAdapter() { -- @Override -- public void channelActive(ChannelHandlerContext ctx) throws Exception { -- ctx.writeAndFlush(Unpooled.wrappedBuffer("Hello, World!".getBytes())); -- ctx.fireChannelActive(); -- } -- }; -- -- ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() { -- @Override -- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { -- try { -- ReferenceCountUtil.release(msg); -- } finally { -- latch.countDown(); -- } -- } -- }; -- -- byte[] response = null; -- TestClientOcspContext callback = new TestClientOcspContext(true); -- -- handshake(sslProvider, latch, serverHandler, response, clientHandler, callback); -- -- byte[] actual = callback.response(); -- -- assertNull(response); -- assertNull(actual); -- } +-/** +- * A handler for SSL clients to handle and act upon stapled OCSP responses. +- * +- * @see ReferenceCountedOpenSslContext#enableOcsp() +- * @see ReferenceCountedOpenSslEngine#getOcspResponse() +- */ +-@UnstableApi +-public abstract class OcspClientHandler extends ChannelInboundHandlerAdapter { - -- @Test(timeout = 10000L) -- public void testClientExceptionOpenSsl() throws Exception { -- testClientException(SslProvider.OPENSSL); -- } +- private final ReferenceCountedOpenSslEngine engine; - -- @Test(timeout = 10000L) -- public void testClientExceptionOpenSslRefCnt() throws Exception { -- testClientException(SslProvider.OPENSSL_REFCNT); +- protected OcspClientHandler(ReferenceCountedOpenSslEngine engine) { +- this.engine = ObjectUtil.checkNotNull(engine, "engine"); - } - - /** -- * Testing what happens if the {@link OcspClientCallback} throws an {@link Exception}. -- * -- * The exception should bubble up on the client side and the connection should get closed. +- * @see ReferenceCountedOpenSslEngine#getOcspResponse() - */ -- private void testClientException(SslProvider sslProvider) throws Exception { -- final AtomicReference causeRef = new AtomicReference(); -- final CountDownLatch latch = new CountDownLatch(1); -- -- ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() { -- @Override -- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { -- try { -- causeRef.set(cause); -- } finally { -- latch.countDown(); -- } -- } -- }; -- -- final OcspTestException clientException = new OcspTestException("testClientException"); -- byte[] response = newOcspResponse(); -- OcspClientCallback callback = new OcspClientCallback() { -- @Override -- public boolean verify(byte[] response) throws Exception { -- throw clientException; -- } -- }; -- -- handshake(sslProvider, latch, null, response, clientHandler, callback); -- -- assertSame(clientException, causeRef.get()); -- } -- -- private static void handshake(SslProvider sslProvider, CountDownLatch latch, ChannelHandler serverHandler, -- byte[] response, ChannelHandler clientHandler, OcspClientCallback callback) throws Exception { -- -- SelfSignedCertificate ssc = new SelfSignedCertificate(); -- try { -- SslContext serverSslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()) -- .sslProvider(sslProvider) -- .enableOcsp(true) -- .build(); -- -- try { -- SslContext clientSslContext = SslContextBuilder.forClient() -- .sslProvider(sslProvider) -- .enableOcsp(true) -- .trustManager(InsecureTrustManagerFactory.INSTANCE) -- .build(); -- -- try { -- EventLoopGroup group = new DefaultEventLoopGroup(); -- try { -- LocalAddress address = new LocalAddress("handshake-" + Math.random()); -- Channel server = newServer(group, address, serverSslContext, response, serverHandler); -- Channel client = newClient(group, address, clientSslContext, callback, clientHandler); -- try { -- assertTrue("Something went wrong.", latch.await(10L, TimeUnit.SECONDS)); -- } finally { -- client.close().syncUninterruptibly(); -- server.close().syncUninterruptibly(); -- } -- } finally { -- group.shutdownGracefully(1L, 1L, TimeUnit.SECONDS); -- } -- } finally { -- ReferenceCountUtil.release(clientSslContext); -- } -- } finally { -- ReferenceCountUtil.release(serverSslContext); -- } -- } finally { -- ssc.delete(); -- } -- } -- -- private static Channel newServer(EventLoopGroup group, SocketAddress address, -- SslContext context, byte[] response, ChannelHandler handler) { -- -- ServerBootstrap bootstrap = new ServerBootstrap() -- .channel(LocalServerChannel.class) -- .group(group) -- .childHandler(newServerHandler(context, response, handler)); -- -- return bootstrap.bind(address) -- .syncUninterruptibly() -- .channel(); -- } -- -- private static Channel newClient(EventLoopGroup group, SocketAddress address, -- SslContext context, OcspClientCallback callback, ChannelHandler handler) { -- -- Bootstrap bootstrap = new Bootstrap() -- .channel(LocalChannel.class) -- .group(group) -- .handler(newClientHandler(context, callback, handler)); -- -- return bootstrap.connect(address) -- .syncUninterruptibly() -- .channel(); -- } -- -- private static ChannelHandler newServerHandler(final SslContext context, -- final byte[] response, final ChannelHandler handler) { -- return new ChannelInitializer() { -- @Override -- protected void initChannel(Channel ch) throws Exception { -- ChannelPipeline pipeline = ch.pipeline(); -- SslHandler sslHandler = context.newHandler(ch.alloc()); -- -- if (response != null) { -- ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine(); -- engine.setOcspResponse(response); -- } -- -- pipeline.addLast(sslHandler); -- -- if (handler != null) { -- pipeline.addLast(handler); -- } -- } -- }; -- } -- -- private static ChannelHandler newClientHandler(final SslContext context, -- final OcspClientCallback callback, final ChannelHandler handler) { -- return new ChannelInitializer() { -- @Override -- protected void initChannel(Channel ch) throws Exception { -- ChannelPipeline pipeline = ch.pipeline(); -- -- SslHandler sslHandler = context.newHandler(ch.alloc()); -- ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine(); +- protected abstract boolean verify(ChannelHandlerContext ctx, ReferenceCountedOpenSslEngine engine) throws Exception; - -- pipeline.addLast(sslHandler); -- pipeline.addLast(new OcspClientCallbackHandler(engine, callback)); +- @Override +- public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { +- if (evt instanceof SslHandshakeCompletionEvent) { +- ctx.pipeline().remove(this); - -- if (handler != null) { -- pipeline.addLast(handler); -- } +- SslHandshakeCompletionEvent event = (SslHandshakeCompletionEvent) evt; +- if (event.isSuccess() && !verify(ctx, engine)) { +- throw new SSLHandshakeException("Bad OCSP response"); - } -- }; -- } -- -- private static byte[] newOcspResponse() { -- // Assume we got the OCSP staple from somewhere. Using a bogus byte[] -- // in the test because getting a true staple from the CA is quite involved. -- // It requires HttpCodec and Bouncycastle and the test may be very unreliable -- // because the OCSP responder servers are basically being DDoS'd by the -- // Internet. -- -- return "I am a bogus OCSP staple. OpenSSL does not care about the format of the byte[]!" -- .getBytes(CharsetUtil.US_ASCII); -- } -- -- private interface OcspClientCallback { -- boolean verify(byte[] staple) throws Exception; -- } -- -- private static final class TestClientOcspContext implements OcspClientCallback { -- -- private final CountDownLatch latch = new CountDownLatch(1); -- private final boolean valid; -- -- private volatile byte[] response; -- -- public TestClientOcspContext(boolean valid) { -- this.valid = valid; -- } -- -- public byte[] response() throws InterruptedException, TimeoutException { -- assertTrue(latch.await(10L, TimeUnit.SECONDS)); -- return response; -- } -- -- @Override -- public boolean verify(byte[] response) throws Exception { -- this.response = response; -- latch.countDown(); -- -- return valid; -- } -- } -- -- private static final class OcspClientCallbackHandler extends OcspClientHandler { -- -- private final OcspClientCallback callback; -- -- public OcspClientCallbackHandler(ReferenceCountedOpenSslEngine engine, OcspClientCallback callback) { -- super(engine); -- this.callback = callback; -- } -- -- @Override -- protected boolean verify(ChannelHandlerContext ctx, ReferenceCountedOpenSslEngine engine) throws Exception { -- byte[] response = engine.getOcspResponse(); -- return callback.verify(response); - } -- } - -- private static final class OcspTestException extends IllegalStateException { -- public OcspTestException(String message) { -- super(message); -- } +- ctx.fireUserEventTriggered(evt); - } -} +diff --git a/handler/src/main/java/io/netty/handler/ssl/ocsp/package-info.java b/handler/src/main/java/io/netty/handler/ssl/ocsp/package-info.java +deleted file mode 100644 +index 7e81ae6..0000000 +--- a/handler/src/main/java/io/netty/handler/ssl/ocsp/package-info.java ++++ /dev/null +@@ -1,23 +0,0 @@ +-/* +- * Copyright 2017 The Netty Project +- * +- * The Netty Project licenses this file to you under the Apache License, +- * version 2.0 (the "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at: +- * +- * https://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +- * License for the specific language governing permissions and limitations +- * under the License. +- */ +- +-/** +- * OCSP stapling, +- * formally known as the TLS Certificate Status Request extension, is an +- * alternative approach to the Online Certificate Status Protocol (OCSP) +- * for checking the revocation status of X.509 digital certificates. +- */ +-package io.netty.handler.ssl.ocsp; -- -2.9.4 +2.47.0 diff --git a/0005-Disable-Brotli-and-ZStd-compression.patch b/0005-Disable-Brotli-and-ZStd-compression.patch new file mode 100644 index 0000000000000000000000000000000000000000..fefbec13603ee4f3c9b77c160248fd3c80f9a294 --- /dev/null +++ b/0005-Disable-Brotli-and-ZStd-compression.patch @@ -0,0 +1,496 @@ +From ce73ba58d8c6c733fdc060d89ac6d229a55fdd28 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Fridrich=20=C5=A0trba?= +Date: Thu, 30 Mar 2023 13:19:04 +0200 +Subject: [PATCH 4/7] Disable Brotli and ZStd compression + +--- + .../codec/http/HttpContentCompressor.java | 105 +----------------- + .../codec/http/HttpContentDecompressor.java | 22 ---- + .../CompressorHttp2ConnectionEncoder.java | 42 +------ + .../DelegatingDecompressorFrameListener.java | 20 ---- + .../StandardCompressionOptions.java | 53 +-------- + 5 files changed, 5 insertions(+), 237 deletions(-) + +diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java +index 0effd37779..51e6c10934 100644 +--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java ++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java +@@ -22,9 +22,6 @@ import io.netty.buffer.ByteBuf; + import io.netty.channel.ChannelHandlerContext; + import io.netty.channel.embedded.EmbeddedChannel; + import io.netty.handler.codec.MessageToByteEncoder; +-import io.netty.handler.codec.compression.Brotli; +-import io.netty.handler.codec.compression.BrotliEncoder; +-import io.netty.handler.codec.compression.BrotliOptions; + import io.netty.handler.codec.compression.CompressionOptions; + import io.netty.handler.codec.compression.DeflateOptions; + import io.netty.handler.codec.compression.GzipOptions; +@@ -32,11 +29,6 @@ import io.netty.handler.codec.compression.StandardCompressionOptions; + import io.netty.handler.codec.compression.ZlibCodecFactory; + import io.netty.handler.codec.compression.ZlibEncoder; + import io.netty.handler.codec.compression.ZlibWrapper; +-import io.netty.handler.codec.compression.Zstd; +-import io.netty.handler.codec.compression.ZstdEncoder; +-import io.netty.handler.codec.compression.ZstdOptions; +-import io.netty.handler.codec.compression.SnappyFrameEncoder; +-import io.netty.handler.codec.compression.SnappyOptions; + import io.netty.util.internal.ObjectUtil; + + /** +@@ -49,11 +41,8 @@ import io.netty.util.internal.ObjectUtil; + public class HttpContentCompressor extends HttpContentEncoder { + + private final boolean supportsCompressionOptions; +- private final BrotliOptions brotliOptions; + private final GzipOptions gzipOptions; + private final DeflateOptions deflateOptions; +- private final ZstdOptions zstdOptions; +- private final SnappyOptions snappyOptions; + + private final int compressionLevel; + private final int windowBits; +@@ -137,11 +126,8 @@ public class HttpContentCompressor extends HttpContentEncoder { + this.windowBits = ObjectUtil.checkInRange(windowBits, 9, 15, "windowBits"); + this.memLevel = ObjectUtil.checkInRange(memLevel, 1, 9, "memLevel"); + this.contentSizeThreshold = ObjectUtil.checkPositiveOrZero(contentSizeThreshold, "contentSizeThreshold"); +- this.brotliOptions = null; + this.gzipOptions = null; + this.deflateOptions = null; +- this.zstdOptions = null; +- this.snappyOptions = null; + this.factories = null; + this.supportsCompressionOptions = false; + } +@@ -170,17 +156,11 @@ public class HttpContentCompressor extends HttpContentEncoder { + */ + public HttpContentCompressor(int contentSizeThreshold, CompressionOptions... compressionOptions) { + this.contentSizeThreshold = ObjectUtil.checkPositiveOrZero(contentSizeThreshold, "contentSizeThreshold"); +- BrotliOptions brotliOptions = null; + GzipOptions gzipOptions = null; + DeflateOptions deflateOptions = null; +- ZstdOptions zstdOptions = null; +- SnappyOptions snappyOptions = null; + if (compressionOptions == null || compressionOptions.length == 0) { +- brotliOptions = Brotli.isAvailable() ? StandardCompressionOptions.brotli() : null; + gzipOptions = StandardCompressionOptions.gzip(); + deflateOptions = StandardCompressionOptions.deflate(); +- zstdOptions = Zstd.isAvailable() ? StandardCompressionOptions.zstd() : null; +- snappyOptions = StandardCompressionOptions.snappy(); + } else { + ObjectUtil.deepCheckNotNull("compressionOptions", compressionOptions); + for (CompressionOptions compressionOption : compressionOptions) { +@@ -190,16 +170,10 @@ public class HttpContentCompressor extends HttpContentEncoder { + // This results in the static analysis of native-image identifying the instanceof BrotliOptions check + // and thus BrotliOptions itself as unreachable, enabling native-image to link all classes + // at build time and not complain about the missing Brotli classes. +- if (Brotli.isAvailable() && compressionOption instanceof BrotliOptions) { +- brotliOptions = (BrotliOptions) compressionOption; +- } else if (compressionOption instanceof GzipOptions) { ++ if (compressionOption instanceof GzipOptions) { + gzipOptions = (GzipOptions) compressionOption; + } else if (compressionOption instanceof DeflateOptions) { + deflateOptions = (DeflateOptions) compressionOption; +- } else if (compressionOption instanceof ZstdOptions) { +- zstdOptions = (ZstdOptions) compressionOption; +- } else if (compressionOption instanceof SnappyOptions) { +- snappyOptions = (SnappyOptions) compressionOption; + } else { + throw new IllegalArgumentException("Unsupported " + CompressionOptions.class.getSimpleName() + + ": " + compressionOption); +@@ -209,9 +183,6 @@ public class HttpContentCompressor extends HttpContentEncoder { + + this.gzipOptions = gzipOptions; + this.deflateOptions = deflateOptions; +- this.brotliOptions = brotliOptions; +- this.zstdOptions = zstdOptions; +- this.snappyOptions = snappyOptions; + + this.factories = new HashMap(); + +@@ -221,15 +192,6 @@ public class HttpContentCompressor extends HttpContentEncoder { + if (this.deflateOptions != null) { + this.factories.put("deflate", new DeflateEncoderFactory()); + } +- if (Brotli.isAvailable() && this.brotliOptions != null) { +- this.factories.put("br", new BrEncoderFactory()); +- } +- if (this.zstdOptions != null) { +- this.factories.put("zstd", new ZstdEncoderFactory()); +- } +- if (this.snappyOptions != null) { +- this.factories.put("snappy", new SnappyEncoderFactory()); +- } + + this.compressionLevel = -1; + this.windowBits = -1; +@@ -302,9 +264,6 @@ public class HttpContentCompressor extends HttpContentEncoder { + @SuppressWarnings("FloatingPointEquality") + protected String determineEncoding(String acceptEncoding) { + float starQ = -1.0f; +- float brQ = -1.0f; +- float zstdQ = -1.0f; +- float snappyQ = -1.0f; + float gzipQ = -1.0f; + float deflateQ = -1.0f; + for (String encoding : acceptEncoding.split(",")) { +@@ -320,41 +279,20 @@ public class HttpContentCompressor extends HttpContentEncoder { + } + if (encoding.contains("*")) { + starQ = q; +- } else if (encoding.contains("br") && q > brQ) { +- brQ = q; +- } else if (encoding.contains("zstd") && q > zstdQ) { +- zstdQ = q; +- } else if (encoding.contains("snappy") && q > snappyQ) { +- snappyQ = q; + } else if (encoding.contains("gzip") && q > gzipQ) { + gzipQ = q; + } else if (encoding.contains("deflate") && q > deflateQ) { + deflateQ = q; + } + } +- if (brQ > 0.0f || zstdQ > 0.0f || snappyQ > 0.0f || gzipQ > 0.0f || deflateQ > 0.0f) { +- if (brQ != -1.0f && brQ >= zstdQ && this.brotliOptions != null) { +- return "br"; +- } else if (zstdQ != -1.0f && zstdQ >= snappyQ && this.zstdOptions != null) { +- return "zstd"; +- } else if (snappyQ != -1.0f && snappyQ >= gzipQ && this.snappyOptions != null) { +- return "snappy"; +- } else if (gzipQ != -1.0f && gzipQ >= deflateQ && this.gzipOptions != null) { ++ if (gzipQ > 0.0f || deflateQ > 0.0f) { ++ if (gzipQ != -1.0f && gzipQ >= deflateQ && this.gzipOptions != null) { + return "gzip"; + } else if (deflateQ != -1.0f && this.deflateOptions != null) { + return "deflate"; + } + } + if (starQ > 0.0f) { +- if (brQ == -1.0f && this.brotliOptions != null) { +- return "br"; +- } +- if (zstdQ == -1.0f && this.zstdOptions != null) { +- return "zstd"; +- } +- if (snappyQ == -1.0f && this.snappyOptions != null) { +- return "snappy"; +- } + if (gzipQ == -1.0f && this.gzipOptions != null) { + return "gzip"; + } +@@ -435,41 +373,4 @@ public class HttpContentCompressor extends HttpContentEncoder { + deflateOptions.windowBits(), deflateOptions.memLevel()); + } + } +- +- /** +- * Compression Encoder Factory that creates {@link BrotliEncoder}s +- * used to compress http content for br content encoding +- */ +- private final class BrEncoderFactory implements CompressionEncoderFactory { +- +- @Override +- public MessageToByteEncoder createEncoder() { +- return new BrotliEncoder(brotliOptions.parameters()); +- } +- } +- +- /** +- * Compression Encoder Factory for create {@link ZstdEncoder} +- * used to compress http content for zstd content encoding +- */ +- private final class ZstdEncoderFactory implements CompressionEncoderFactory { +- +- @Override +- public MessageToByteEncoder createEncoder() { +- return new ZstdEncoder(zstdOptions.compressionLevel(), +- zstdOptions.blockSize(), zstdOptions.maxEncodeSize()); +- } +- } +- +- /** +- * Compression Encoder Factory for create {@link SnappyFrameEncoder} +- * used to compress http content for snappy content encoding +- */ +- private static final class SnappyEncoderFactory implements CompressionEncoderFactory { +- +- @Override +- public MessageToByteEncoder createEncoder() { +- return new SnappyFrameEncoder(); +- } +- } + } +diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java +index 3b1134b038..c2f3150bfd 100644 +--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java ++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java +@@ -15,22 +15,14 @@ + */ + package io.netty.handler.codec.http; + +-import static io.netty.handler.codec.http.HttpHeaderValues.BR; + import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE; + import static io.netty.handler.codec.http.HttpHeaderValues.GZIP; + import static io.netty.handler.codec.http.HttpHeaderValues.X_DEFLATE; + import static io.netty.handler.codec.http.HttpHeaderValues.X_GZIP; +-import static io.netty.handler.codec.http.HttpHeaderValues.SNAPPY; +-import static io.netty.handler.codec.http.HttpHeaderValues.ZSTD; + + import io.netty.channel.embedded.EmbeddedChannel; +-import io.netty.handler.codec.compression.Brotli; +-import io.netty.handler.codec.compression.BrotliDecoder; +-import io.netty.handler.codec.compression.SnappyFrameDecoder; + import io.netty.handler.codec.compression.ZlibCodecFactory; + import io.netty.handler.codec.compression.ZlibWrapper; +-import io.netty.handler.codec.compression.Zstd; +-import io.netty.handler.codec.compression.ZstdDecoder; + + /** + * Decompresses an {@link HttpMessage} and an {@link HttpContent} compressed in +@@ -72,20 +64,6 @@ public class HttpContentDecompressor extends HttpContentDecoder { + return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), + ctx.channel().config(), ZlibCodecFactory.newZlibDecoder(wrapper)); + } +- if (Brotli.isAvailable() && BR.contentEqualsIgnoreCase(contentEncoding)) { +- return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), +- ctx.channel().config(), new BrotliDecoder()); +- } +- +- if (SNAPPY.contentEqualsIgnoreCase(contentEncoding)) { +- return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), +- ctx.channel().config(), new SnappyFrameDecoder()); +- } +- +- if (Zstd.isAvailable() && ZSTD.contentEqualsIgnoreCase(contentEncoding)) { +- return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), +- ctx.channel().config(), new ZstdDecoder()); +- } + + // 'identity' or unsupported + return null; +diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/CompressorHttp2ConnectionEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/CompressorHttp2ConnectionEncoder.java +index b12213dff6..fdeadaebbe 100644 +--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/CompressorHttp2ConnectionEncoder.java ++++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/CompressorHttp2ConnectionEncoder.java +@@ -21,20 +21,12 @@ import io.netty.channel.ChannelHandlerContext; + import io.netty.channel.ChannelPromise; + import io.netty.channel.embedded.EmbeddedChannel; + import io.netty.handler.codec.ByteToMessageDecoder; +-import io.netty.handler.codec.compression.BrotliEncoder; + import io.netty.handler.codec.compression.ZlibCodecFactory; + import io.netty.handler.codec.compression.ZlibWrapper; +-import io.netty.handler.codec.compression.Brotli; +-import io.netty.handler.codec.compression.BrotliOptions; + import io.netty.handler.codec.compression.CompressionOptions; + import io.netty.handler.codec.compression.DeflateOptions; + import io.netty.handler.codec.compression.GzipOptions; + import io.netty.handler.codec.compression.StandardCompressionOptions; +-import io.netty.handler.codec.compression.Zstd; +-import io.netty.handler.codec.compression.ZstdEncoder; +-import io.netty.handler.codec.compression.ZstdOptions; +-import io.netty.handler.codec.compression.SnappyFrameEncoder; +-import io.netty.handler.codec.compression.SnappyOptions; + import io.netty.util.concurrent.PromiseCombiner; + import io.netty.util.internal.ObjectUtil; + +@@ -43,14 +35,11 @@ import java.util.List; + + import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_ENCODING; + import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +-import static io.netty.handler.codec.http.HttpHeaderValues.BR; + import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE; + import static io.netty.handler.codec.http.HttpHeaderValues.GZIP; + import static io.netty.handler.codec.http.HttpHeaderValues.IDENTITY; + import static io.netty.handler.codec.http.HttpHeaderValues.X_DEFLATE; + import static io.netty.handler.codec.http.HttpHeaderValues.X_GZIP; +-import static io.netty.handler.codec.http.HttpHeaderValues.ZSTD; +-import static io.netty.handler.codec.http.HttpHeaderValues.SNAPPY; + + /** + * A decorating HTTP2 encoder that will compress data frames according to the {@code content-encoding} header for each +@@ -69,11 +58,8 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE + + private final boolean supportsCompressionOptions; + +- private BrotliOptions brotliOptions; + private GzipOptions gzipCompressionOptions; + private DeflateOptions deflateOptions; +- private ZstdOptions zstdOptions; +- private SnappyOptions snappyOptions; + + /** + * Create a new {@link CompressorHttp2ConnectionEncoder} instance +@@ -87,13 +73,6 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE + List compressionOptions = new ArrayList(); + compressionOptions.add(StandardCompressionOptions.gzip()); + compressionOptions.add(StandardCompressionOptions.deflate()); +- compressionOptions.add(StandardCompressionOptions.snappy()); +- if (Brotli.isAvailable()) { +- compressionOptions.add(StandardCompressionOptions.brotli()); +- } +- if (Zstd.isAvailable()) { +- compressionOptions.add(StandardCompressionOptions.zstd()); +- } + return compressionOptions.toArray(new CompressionOptions[0]); + } + +@@ -139,16 +118,10 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE + // This results in the static analysis of native-image identifying the instanceof BrotliOptions check + // and thus BrotliOptions itself as unreachable, enabling native-image to link all classes at build time + // and not complain about the missing Brotli classes. +- if (Brotli.isAvailable() && compressionOptions instanceof BrotliOptions) { +- brotliOptions = (BrotliOptions) compressionOptions; +- } else if (compressionOptions instanceof GzipOptions) { ++ if (compressionOptions instanceof GzipOptions) { + gzipCompressionOptions = (GzipOptions) compressionOptions; + } else if (compressionOptions instanceof DeflateOptions) { + deflateOptions = (DeflateOptions) compressionOptions; +- } else if (compressionOptions instanceof ZstdOptions) { +- zstdOptions = (ZstdOptions) compressionOptions; +- } else if (compressionOptions instanceof SnappyOptions) { +- snappyOptions = (SnappyOptions) compressionOptions; + } else { + throw new IllegalArgumentException("Unsupported " + CompressionOptions.class.getSimpleName() + + ": " + compressionOptions); +@@ -286,19 +259,6 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE + if (DEFLATE.contentEqualsIgnoreCase(contentEncoding) || X_DEFLATE.contentEqualsIgnoreCase(contentEncoding)) { + return newCompressionChannel(ctx, ZlibWrapper.ZLIB); + } +- if (Brotli.isAvailable() && brotliOptions != null && BR.contentEqualsIgnoreCase(contentEncoding)) { +- return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), +- ctx.channel().config(), new BrotliEncoder(brotliOptions.parameters())); +- } +- if (zstdOptions != null && ZSTD.contentEqualsIgnoreCase(contentEncoding)) { +- return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), +- ctx.channel().config(), new ZstdEncoder(zstdOptions.compressionLevel(), +- zstdOptions.blockSize(), zstdOptions.maxEncodeSize())); +- } +- if (snappyOptions != null && SNAPPY.contentEqualsIgnoreCase(contentEncoding)) { +- return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), +- ctx.channel().config(), new SnappyFrameEncoder()); +- } + // 'identity' or unsupported + return null; + } +diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingDecompressorFrameListener.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingDecompressorFrameListener.java +index 88245d1116..de44d6013b 100644 +--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingDecompressorFrameListener.java ++++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingDecompressorFrameListener.java +@@ -19,24 +19,16 @@ import io.netty.buffer.Unpooled; + import io.netty.channel.ChannelHandlerContext; + import io.netty.channel.embedded.EmbeddedChannel; + import io.netty.handler.codec.ByteToMessageDecoder; +-import io.netty.handler.codec.compression.Brotli; +-import io.netty.handler.codec.compression.BrotliDecoder; +-import io.netty.handler.codec.compression.Zstd; +-import io.netty.handler.codec.compression.ZstdDecoder; + import io.netty.handler.codec.compression.ZlibCodecFactory; + import io.netty.handler.codec.compression.ZlibWrapper; +-import io.netty.handler.codec.compression.SnappyFrameDecoder; + + import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_ENCODING; + import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +-import static io.netty.handler.codec.http.HttpHeaderValues.BR; + import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE; + import static io.netty.handler.codec.http.HttpHeaderValues.GZIP; + import static io.netty.handler.codec.http.HttpHeaderValues.IDENTITY; + import static io.netty.handler.codec.http.HttpHeaderValues.X_DEFLATE; + import static io.netty.handler.codec.http.HttpHeaderValues.X_GZIP; +-import static io.netty.handler.codec.http.HttpHeaderValues.SNAPPY; +-import static io.netty.handler.codec.http.HttpHeaderValues.ZSTD; + import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; + import static io.netty.handler.codec.http2.Http2Exception.streamError; + import static io.netty.util.internal.ObjectUtil.checkNotNull; +@@ -181,18 +173,6 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor + return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), + ctx.channel().config(), ZlibCodecFactory.newZlibDecoder(wrapper)); + } +- if (Brotli.isAvailable() && BR.contentEqualsIgnoreCase(contentEncoding)) { +- return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), +- ctx.channel().config(), new BrotliDecoder()); +- } +- if (SNAPPY.contentEqualsIgnoreCase(contentEncoding)) { +- return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), +- ctx.channel().config(), new SnappyFrameDecoder()); +- } +- if (Zstd.isAvailable() && ZSTD.contentEqualsIgnoreCase(contentEncoding)) { +- return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), +- ctx.channel().config(), new ZstdDecoder()); +- } + // 'identity' or unsupported + return null; + } +diff --git a/codec/src/main/java/io/netty/handler/codec/compression/StandardCompressionOptions.java b/codec/src/main/java/io/netty/handler/codec/compression/StandardCompressionOptions.java +index ac2582418c..698c4b510a 100644 +--- a/codec/src/main/java/io/netty/handler/codec/compression/StandardCompressionOptions.java ++++ b/codec/src/main/java/io/netty/handler/codec/compression/StandardCompressionOptions.java +@@ -15,10 +15,8 @@ + */ + package io.netty.handler.codec.compression; + +-import com.aayushatharva.brotli4j.encoder.Encoder; +- + /** +- * Standard Compression Options for {@link BrotliOptions}, ++ * Standard Compression Options for + * {@link GzipOptions} and {@link DeflateOptions} + */ + public final class StandardCompressionOptions { +@@ -28,55 +26,6 @@ public final class StandardCompressionOptions { + } + + /** +- * Default implementation of {@link BrotliOptions} with {@link Encoder.Parameters#setQuality(int)} set to 4 +- * and {@link Encoder.Parameters#setMode(Encoder.Mode)} set to {@link Encoder.Mode#TEXT} +- */ +- public static BrotliOptions brotli() { +- return BrotliOptions.DEFAULT; +- } +- +- /** +- * Create a new {@link BrotliOptions} +- * +- * @param parameters {@link Encoder.Parameters} Instance +- * @throws NullPointerException If {@link Encoder.Parameters} is {@code null} +- */ +- public static BrotliOptions brotli(Encoder.Parameters parameters) { +- return new BrotliOptions(parameters); +- } +- +- /** +- * Default implementation of {@link ZstdOptions} with{compressionLevel(int)} set to +- * {@link ZstdConstants#DEFAULT_COMPRESSION_LEVEL},{@link ZstdConstants#DEFAULT_BLOCK_SIZE}, +- * {@link ZstdConstants#MAX_BLOCK_SIZE} +- */ +- public static ZstdOptions zstd() { +- return ZstdOptions.DEFAULT; +- } +- +- /** +- * Create a new {@link ZstdOptions} +- * +- * @param blockSize +- * is used to calculate the compressionLevel +- * @param maxEncodeSize +- * specifies the size of the largest compressed object +- * @param compressionLevel +- * specifies the level of the compression +- */ +- public static ZstdOptions zstd(int compressionLevel, int blockSize, int maxEncodeSize) { +- return new ZstdOptions(compressionLevel, blockSize, maxEncodeSize); +- } +- +- /** +- * Create a new {@link SnappyOptions} +- * +- */ +- public static SnappyOptions snappy() { +- return new SnappyOptions(); +- } +- +- /** + * Default implementation of {@link GzipOptions} with + * {@code compressionLevel()} set to 6, {@code windowBits()} set to 15 and {@code memLevel()} set to 8. + */ +-- +2.46.1 + diff --git a/0006-Do-not-use-the-Graal-annotations.patch b/0006-Do-not-use-the-Graal-annotations.patch new file mode 100644 index 0000000000000000000000000000000000000000..afc2220c0d7d273b7376dfb66356528f93bd2aae --- /dev/null +++ b/0006-Do-not-use-the-Graal-annotations.patch @@ -0,0 +1,153 @@ +From 6cc11ea389160c067b103bd4d0c356bb13442c77 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Fridrich=20=C5=A0trba?= +Date: Thu, 30 Mar 2023 13:19:45 +0200 +Subject: [PATCH 5/7] Do not use the Graal annotations + +--- + .../java/io/netty/util/NetUtilSubstitutions.java | 13 ------------- + .../util/internal/svm/CleanerJava6Substitution.java | 10 ---------- + .../svm/PlatformDependent0Substitution.java | 10 ---------- + .../internal/svm/PlatformDependentSubstitution.java | 9 --------- + .../svm/UnsafeRefArrayAccessSubstitution.java | 9 --------- + 5 files changed, 51 deletions(-) + +diff --git a/common/src/main/java/io/netty/util/NetUtilSubstitutions.java b/common/src/main/java/io/netty/util/NetUtilSubstitutions.java +index 65c7bb94a5..18bfc1664f 100644 +--- a/common/src/main/java/io/netty/util/NetUtilSubstitutions.java ++++ b/common/src/main/java/io/netty/util/NetUtilSubstitutions.java +@@ -15,35 +15,22 @@ + */ + package io.netty.util; + +-import com.oracle.svm.core.annotate.Alias; +-import com.oracle.svm.core.annotate.InjectAccessors; +-import com.oracle.svm.core.annotate.TargetClass; +- + import java.net.Inet4Address; + import java.net.Inet6Address; + import java.net.InetAddress; + import java.net.NetworkInterface; + import java.util.Collection; + +-@TargetClass(NetUtil.class) + final class NetUtilSubstitutions { + private NetUtilSubstitutions() { + } + +- @Alias +- @InjectAccessors(NetUtilLocalhost4Accessor.class) + public static Inet4Address LOCALHOST4; + +- @Alias +- @InjectAccessors(NetUtilLocalhost6Accessor.class) + public static Inet6Address LOCALHOST6; + +- @Alias +- @InjectAccessors(NetUtilLocalhostAccessor.class) + public static InetAddress LOCALHOST; + +- @Alias +- @InjectAccessors(NetUtilNetworkInterfacesAccessor.class) + public static Collection NETWORK_INTERFACES; + + private static final class NetUtilLocalhost4Accessor { +diff --git a/common/src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java b/common/src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java +index aed4777a0d..0fa5a858ab 100644 +--- a/common/src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java ++++ b/common/src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java +@@ -15,19 +15,9 @@ + */ + package io.netty.util.internal.svm; + +-import com.oracle.svm.core.annotate.Alias; +-import com.oracle.svm.core.annotate.RecomputeFieldValue; +-import com.oracle.svm.core.annotate.TargetClass; +- +-@TargetClass(className = "io.netty.util.internal.CleanerJava6") + final class CleanerJava6Substitution { + private CleanerJava6Substitution() { + } + +- @Alias +- @RecomputeFieldValue( +- kind = RecomputeFieldValue.Kind.FieldOffset, +- declClassName = "java.nio.DirectByteBuffer", +- name = "cleaner") + private static long CLEANER_FIELD_OFFSET; + } +diff --git a/common/src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java b/common/src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java +index 1a06a5518d..3bd61f467a 100644 +--- a/common/src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java ++++ b/common/src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java +@@ -15,19 +15,9 @@ + */ + package io.netty.util.internal.svm; + +-import com.oracle.svm.core.annotate.Alias; +-import com.oracle.svm.core.annotate.RecomputeFieldValue; +-import com.oracle.svm.core.annotate.TargetClass; +- +-@TargetClass(className = "io.netty.util.internal.PlatformDependent0") + final class PlatformDependent0Substitution { + private PlatformDependent0Substitution() { + } + +- @Alias +- @RecomputeFieldValue( +- kind = RecomputeFieldValue.Kind.FieldOffset, +- declClassName = "java.nio.Buffer", +- name = "address") + private static long ADDRESS_FIELD_OFFSET; + } +diff --git a/common/src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java b/common/src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java +index 08932da772..8df7f18218 100644 +--- a/common/src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java ++++ b/common/src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java +@@ -15,11 +15,6 @@ + */ + package io.netty.util.internal.svm; + +-import com.oracle.svm.core.annotate.Alias; +-import com.oracle.svm.core.annotate.RecomputeFieldValue; +-import com.oracle.svm.core.annotate.TargetClass; +- +-@TargetClass(className = "io.netty.util.internal.PlatformDependent") + final class PlatformDependentSubstitution { + private PlatformDependentSubstitution() { + } +@@ -31,9 +26,5 @@ final class PlatformDependentSubstitution { + * in PlatformDependent happens during image building, the non-recomputed value + * is cached. + */ +- @Alias +- @RecomputeFieldValue( +- kind = RecomputeFieldValue.Kind.ArrayBaseOffset, +- declClass = byte[].class) + private static long BYTE_ARRAY_BASE_OFFSET; + } +diff --git a/common/src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java b/common/src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java +index 08f492f132..f0b10d7cde 100644 +--- a/common/src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java ++++ b/common/src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java +@@ -15,18 +15,9 @@ + */ + package io.netty.util.internal.svm; + +-import com.oracle.svm.core.annotate.Alias; +-import com.oracle.svm.core.annotate.RecomputeFieldValue; +-import com.oracle.svm.core.annotate.TargetClass; +- +-@TargetClass(className = "io.netty.util.internal.shaded.org.jctools.util.UnsafeRefArrayAccess") + final class UnsafeRefArrayAccessSubstitution { + private UnsafeRefArrayAccessSubstitution() { + } + +- @Alias +- @RecomputeFieldValue( +- kind = RecomputeFieldValue.Kind.ArrayIndexShift, +- declClass = Object[].class) + public static int REF_ELEMENT_SHIFT; + } +-- +2.46.1 + diff --git a/0007-Do-not-use-the-Jetbrains-annotations.patch b/0007-Do-not-use-the-Jetbrains-annotations.patch new file mode 100644 index 0000000000000000000000000000000000000000..82fb0887b0631e1278b31f1a0587fd1db464b2b9 --- /dev/null +++ b/0007-Do-not-use-the-Jetbrains-annotations.patch @@ -0,0 +1,157 @@ +From 3d2d165de5adfaf0ab6cf79d48bc27d0546238bc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Fridrich=20=C5=A0trba?= +Date: Thu, 30 Mar 2023 13:20:35 +0200 +Subject: [PATCH 6/7] Do not use the Jetbrains annotations + +--- + common/src/main/java/io/netty/util/Recycler.java | 2 -- + .../java/io/netty/util/concurrent/AbstractEventExecutor.java | 4 +--- + .../java/io/netty/util/concurrent/GlobalEventExecutor.java | 4 +--- + .../io/netty/util/concurrent/SingleThreadEventExecutor.java | 5 ++--- + .../test/java/io/netty/util/RecyclerFastThreadLocalTest.java | 2 -- + common/src/test/java/io/netty/util/RecyclerTest.java | 4 +--- + 6 files changed, 5 insertions(+), 16 deletions(-) + +diff --git a/common/src/main/java/io/netty/util/Recycler.java b/common/src/main/java/io/netty/util/Recycler.java +index c434874bff..3df4f1d772 100644 +--- a/common/src/main/java/io/netty/util/Recycler.java ++++ b/common/src/main/java/io/netty/util/Recycler.java +@@ -24,7 +24,6 @@ import io.netty.util.internal.UnstableApi; + import io.netty.util.internal.logging.InternalLogger; + import io.netty.util.internal.logging.InternalLoggerFactory; + import org.jctools.queues.MessagePassingQueue; +-import org.jetbrains.annotations.VisibleForTesting; + + import java.util.ArrayDeque; + import java.util.Queue; +@@ -208,7 +207,6 @@ public abstract class Recycler { + return true; + } + +- @VisibleForTesting + final int threadLocalSize() { + LocalPool localPool = threadLocal.getIfExists(); + return localPool == null ? 0 : localPool.pooledHandles.size() + localPool.batch.size(); +diff --git a/common/src/main/java/io/netty/util/concurrent/AbstractEventExecutor.java b/common/src/main/java/io/netty/util/concurrent/AbstractEventExecutor.java +index 6409578637..e9463935ba 100644 +--- a/common/src/main/java/io/netty/util/concurrent/AbstractEventExecutor.java ++++ b/common/src/main/java/io/netty/util/concurrent/AbstractEventExecutor.java +@@ -19,8 +19,6 @@ import io.netty.util.internal.UnstableApi; + import io.netty.util.internal.logging.InternalLogger; + import io.netty.util.internal.logging.InternalLoggerFactory; + +-import org.jetbrains.annotations.Async.Execute; +- + import java.util.Collection; + import java.util.Collections; + import java.util.Iterator; +@@ -169,7 +167,7 @@ public abstract class AbstractEventExecutor extends AbstractExecutorService impl + } + } + +- protected static void runTask(@Execute Runnable task) { ++ protected static void runTask(Runnable task) { + task.run(); + } + +diff --git a/common/src/main/java/io/netty/util/concurrent/GlobalEventExecutor.java b/common/src/main/java/io/netty/util/concurrent/GlobalEventExecutor.java +index 4514c023a3..542eecee3f 100644 +--- a/common/src/main/java/io/netty/util/concurrent/GlobalEventExecutor.java ++++ b/common/src/main/java/io/netty/util/concurrent/GlobalEventExecutor.java +@@ -21,8 +21,6 @@ import io.netty.util.internal.ThreadExecutorMap; + import io.netty.util.internal.logging.InternalLogger; + import io.netty.util.internal.logging.InternalLoggerFactory; + +-import org.jetbrains.annotations.Async.Schedule; +- + import java.security.AccessController; + import java.security.PrivilegedAction; + import java.util.Queue; +@@ -221,7 +219,7 @@ public final class GlobalEventExecutor extends AbstractScheduledEventExecutor im + execute0(task); + } + +- private void execute0(@Schedule Runnable task) { ++ private void execute0(Runnable task) { + addTask(ObjectUtil.checkNotNull(task, "task")); + if (!inEventLoop()) { + startThread(); +diff --git a/common/src/main/java/io/netty/util/concurrent/SingleThreadEventExecutor.java b/common/src/main/java/io/netty/util/concurrent/SingleThreadEventExecutor.java +index b93b7cd377..7838cb7787 100644 +--- a/common/src/main/java/io/netty/util/concurrent/SingleThreadEventExecutor.java ++++ b/common/src/main/java/io/netty/util/concurrent/SingleThreadEventExecutor.java +@@ -21,7 +21,6 @@ import io.netty.util.internal.SystemPropertyUtil; + import io.netty.util.internal.ThreadExecutorMap; + import io.netty.util.internal.logging.InternalLogger; + import io.netty.util.internal.logging.InternalLoggerFactory; +-import org.jetbrains.annotations.Async.Schedule; + + import java.lang.Thread.State; + import java.util.ArrayList; +@@ -822,12 +821,12 @@ public abstract class SingleThreadEventExecutor extends AbstractScheduledEventEx + lazyExecute0(task); + } + +- private void execute0(@Schedule Runnable task) { ++ private void execute0(Runnable task) { + ObjectUtil.checkNotNull(task, "task"); + execute(task, wakesUpForTask(task)); + } + +- private void lazyExecute0(@Schedule Runnable task) { ++ private void lazyExecute0(Runnable task) { + execute(ObjectUtil.checkNotNull(task, "task"), false); + } + +diff --git a/common/src/test/java/io/netty/util/RecyclerFastThreadLocalTest.java b/common/src/test/java/io/netty/util/RecyclerFastThreadLocalTest.java +index 7c8d4da113..1dfaffc7da 100644 +--- a/common/src/test/java/io/netty/util/RecyclerFastThreadLocalTest.java ++++ b/common/src/test/java/io/netty/util/RecyclerFastThreadLocalTest.java +@@ -16,7 +16,6 @@ + package io.netty.util; + + import io.netty.util.concurrent.FastThreadLocalThread; +-import org.jetbrains.annotations.NotNull; + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.Timeout; + import org.junit.jupiter.api.extension.ExtendWith; +@@ -29,7 +28,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; + + @ExtendWith(RunInFastThreadLocalThreadExtension.class) + public class RecyclerFastThreadLocalTest extends RecyclerTest { +- @NotNull + @Override + protected Thread newThread(Runnable runnable) { + return new FastThreadLocalThread(runnable); +diff --git a/common/src/test/java/io/netty/util/RecyclerTest.java b/common/src/test/java/io/netty/util/RecyclerTest.java +index 49359ff962..1ee4c6bd5c 100644 +--- a/common/src/test/java/io/netty/util/RecyclerTest.java ++++ b/common/src/test/java/io/netty/util/RecyclerTest.java +@@ -15,7 +15,6 @@ + */ + package io.netty.util; + +-import org.jetbrains.annotations.NotNull; + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.Timeout; + import org.junit.jupiter.api.function.Executable; +@@ -55,7 +54,6 @@ public class RecyclerTest { + }; + } + +- @NotNull + protected Thread newThread(Runnable runnable) { + return new Thread(runnable); + } +@@ -341,7 +339,7 @@ public class RecyclerTest { + + ExecutorService single = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override +- public Thread newThread(@NotNull Runnable r) { ++ public Thread newThread(Runnable r) { + return RecyclerTest.this.newThread(r); + } + }); +-- +2.46.1 + diff --git a/CVE-2019-16869.patch b/CVE-2019-16869.patch deleted file mode 100644 index b3f2133dccfdce462b70d5dd6a317794fd5e610c..0000000000000000000000000000000000000000 --- a/CVE-2019-16869.patch +++ /dev/null @@ -1,98 +0,0 @@ -From: Norman Maurer -Date: Fri, 20 Sep 2019 21:02:11 +0200 -Subject: Correctly handle whitespaces in HTTP header names as defined by - RFC7230#section-3.2.4 (#9585) -Origin: https://github.com/netty/netty/commit/39cafcb05c99f2aa9fce7e6597664c9ed6a63a95 -Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2019-16869 -Bug-Debian: https://bugs.debian.org/941266 -Bug: https://github.com/netty/netty/issues/9571 - -Motivation: - -When parsing HTTP headers special care needs to be taken when a whitespace is detected in the header name. - -Modifications: - -- Ignore whitespace when decoding response (just like before) -- Throw exception when whitespace is detected during parsing -- Add unit tests - -Result: - -Fixes https://github.com/netty/netty/issues/9571 -[Salvatore Bonaccorso: Backport to 4.1.7 for context changes in -HttpObjectDecoder.java] ---- - .../handler/codec/http/HttpObjectDecoder.java | 16 +++++++++++++++- - .../codec/http/HttpRequestDecoderTest.java | 14 ++++++++++++++ - .../codec/http/HttpResponseDecoderTest.java | 15 +++++++++++++++ - 3 files changed, 44 insertions(+), 1 deletion(-) - ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -@@ -727,7 +727,21 @@ public abstract class HttpObjectDecoder - nameStart = findNonWhitespace(sb, 0); - for (nameEnd = nameStart; nameEnd < length; nameEnd ++) { - char ch = sb.charAt(nameEnd); -- if (ch == ':' || Character.isWhitespace(ch)) { -+ // https://tools.ietf.org/html/rfc7230#section-3.2.4 -+ // -+ // No whitespace is allowed between the header field-name and colon. In -+ // the past, differences in the handling of such whitespace have led to -+ // security vulnerabilities in request routing and response handling. A -+ // server MUST reject any received request message that contains -+ // whitespace between a header field-name and colon with a response code -+ // of 400 (Bad Request). A proxy MUST remove any such whitespace from a -+ // response message before forwarding the message downstream. -+ if (ch == ':' || -+ // In case of decoding a request we will just continue processing and header validation -+ // is done in the DefaultHttpHeaders implementation. -+ // -+ // In the case of decoding a response we will "skip" the whitespace. -+ (!isDecodingRequest() && Character.isWhitespace(ch))) { - break; - } - } ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -@@ -270,4 +270,18 @@ public class HttpRequestDecoderTest { - cnt.release(); - assertFalse(channel.finishAndReleaseAll()); - } -+ -+ @Test -+ public void testWhitespace() { -+ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); -+ String requestStr = "GET /some/path HTTP/1.1\r\n" + -+ "Transfer-Encoding : chunked\r\n" + -+ "Host: netty.io\n\r\n"; -+ -+ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); -+ HttpRequest request = channel.readInbound(); -+ assertTrue(request.decoderResult().isFailure()); -+ assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException); -+ assertFalse(channel.finish()); -+ } - } ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java -@@ -661,4 +661,19 @@ public class HttpResponseDecoderTest { - assertThat(message.decoderResult().cause(), instanceOf(PrematureChannelClosureException.class)); - assertNull(channel.readInbound()); - } -+ -+ @Test -+ public void testWhitespace() { -+ EmbeddedChannel channel = new EmbeddedChannel(new HttpResponseDecoder()); -+ String requestStr = "HTTP/1.1 200 OK\r\n" + -+ "Transfer-Encoding : chunked\r\n" + -+ "Host: netty.io\n\r\n"; -+ -+ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); -+ HttpResponse response = channel.readInbound(); -+ assertFalse(response.decoderResult().isFailure()); -+ assertEquals(HttpHeaderValues.CHUNKED.toString(), response.headers().get(HttpHeaderNames.TRANSFER_ENCODING)); -+ assertEquals("netty.io", response.headers().get(HttpHeaderNames.HOST)); -+ assertFalse(channel.finish()); -+ } - } diff --git a/CVE-2019-20444.patch b/CVE-2019-20444.patch deleted file mode 100644 index 369e3db2d9cd53b8aaabd83869d655ad6aa53dea..0000000000000000000000000000000000000000 --- a/CVE-2019-20444.patch +++ /dev/null @@ -1,60 +0,0 @@ -From a7c18d44b46e02dadfe3da225a06e5091f5f328e Mon Sep 17 00:00:00 2001 -From: Norman Maurer -Date: Wed, 11 Dec 2019 15:49:07 +0100 -Subject: [PATCH] Detect missing colon when parsing http headers with no value - (#9871) - -Motivation: - -Technical speaking its valid to have http headers with no values so we should support it. That said we need to detect if these are "generated" because of an "invalid" fold. - -Modifications: - -- Detect if a colon is missing when parsing headers. -- Add unit test - -Result: - -Fixes https://github.com/netty/netty/issues/9866 ---- - .../handler/codec/http/HttpObjectDecoder.java | 5 +++++ - .../codec/http/HttpRequestDecoderTest.java | 16 ++++++++++++++++ - 2 files changed, 21 insertions(+) - ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -@@ -746,6 +746,11 @@ - } - } - -+ if (nameEnd == length) { -+ // There was no colon present at all. -+ throw new IllegalArgumentException("No colon found"); -+ } -+ - for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) { - if (sb.charAt(colonEnd) == ':') { - colonEnd ++; ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -@@ -284,4 +284,20 @@ - assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException); - assertFalse(channel.finish()); - } -+ -+ @Test -+ public void testHeaderWithNoValueAndMissingColon() { -+ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); -+ String requestStr = "GET /some/path HTTP/1.1\r\n" + -+ "Content-Length: 0\r\n" + -+ "Host:\r\n" + -+ "netty.io\r\n\r\n"; -+ -+ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); -+ HttpRequest request = channel.readInbound(); -+ System.err.println(request.headers().names().toString()); -+ assertTrue(request.decoderResult().isFailure()); -+ assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException); -+ assertFalse(channel.finish()); -+ } - } diff --git a/CVE-2019-20445-1.patch b/CVE-2019-20445-1.patch deleted file mode 100644 index e6d2584a745b61b9e74af22a5a4626fe07bad84d..0000000000000000000000000000000000000000 --- a/CVE-2019-20445-1.patch +++ /dev/null @@ -1,186 +0,0 @@ -From 8494b046ec7e4f28dbd44bc699cc4c4c92251729 Mon Sep 17 00:00:00 2001 -From: Norman Maurer -Date: Fri, 13 Dec 2019 08:53:19 +0100 -Subject: [PATCH] Verify we do not receive multiple content-length headers or a - content-length and transfer-encoding: chunked header when using HTTP/1.1 - (#9865) - -Motivation: - -RFC7230 states that we should not accept multiple content-length headers and also should not accept a content-length header in combination with transfer-encoding: chunked - -Modifications: - -- Check for multiple content-length headers and if found mark message as invalid -- Check if we found a content-length header and also a transfer-encoding: chunked and if so mark the message as invalid -- Add unit test - -Result: - -Fixes https://github.com/netty/netty/issues/9861 ---- - .../handler/codec/http/HttpObjectDecoder.java | 50 +++++++++++++-- - .../codec/http/HttpRequestDecoderTest.java | 64 ++++++++++++++++--- - 2 files changed, 99 insertions(+), 15 deletions(-) - -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -index 28f048252fe..768bd3b26f5 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -@@ -600,23 +600,61 @@ private State readHeaders(ByteBuf buffer) { - if (name != null) { - headers.add(name, value); - } -+ - // reset name and value fields - name = null; - value = null; - -- State nextState; -+ List values = headers.getAll(HttpHeaderNames.CONTENT_LENGTH); -+ int contentLengthValuesCount = values.size(); -+ -+ if (contentLengthValuesCount > 0) { -+ // Guard against multiple Content-Length headers as stated in -+ // https://tools.ietf.org/html/rfc7230#section-3.3.2: -+ // -+ // If a message is received that has multiple Content-Length header -+ // fields with field-values consisting of the same decimal value, or a -+ // single Content-Length header field with a field value containing a -+ // list of identical decimal values (e.g., "Content-Length: 42, 42"), -+ // indicating that duplicate Content-Length header fields have been -+ // generated or combined by an upstream message processor, then the -+ // recipient MUST either reject the message as invalid or replace the -+ // duplicated field-values with a single valid Content-Length field -+ // containing that decimal value prior to determining the message body -+ // length or forwarding the message. -+ if (contentLengthValuesCount > 1 && message.protocolVersion() == HttpVersion.HTTP_1_1) { -+ throw new IllegalArgumentException("Multiple Content-Length headers found"); -+ } -+ contentLength = Long.parseLong(values.get(0)); -+ } - - if (isContentAlwaysEmpty(message)) { - HttpUtil.setTransferEncodingChunked(message, false); -- nextState = State.SKIP_CONTROL_CHARS; -+ return State.SKIP_CONTROL_CHARS; - } else if (HttpUtil.isTransferEncodingChunked(message)) { -- nextState = State.READ_CHUNK_SIZE; -+ // See https://tools.ietf.org/html/rfc7230#section-3.3.3 -+ // -+ // If a message is received with both a Transfer-Encoding and a -+ // Content-Length header field, the Transfer-Encoding overrides the -+ // Content-Length. Such a message might indicate an attempt to -+ // perform request smuggling (Section 9.5) or response splitting -+ // (Section 9.4) and ought to be handled as an error. A sender MUST -+ // remove the received Content-Length field prior to forwarding such -+ // a message downstream. -+ // -+ // This is also what http_parser does: -+ // https://github.com/nodejs/http-parser/blob/v2.9.2/http_parser.c#L1769 -+ if (contentLengthValuesCount > 0 && message.protocolVersion() == HttpVersion.HTTP_1_1) { -+ throw new IllegalArgumentException( -+ "Both 'Content-Length: " + contentLength + "' and 'Transfer-Encoding: chunked' found"); -+ } -+ -+ return State.READ_CHUNK_SIZE; - } else if (contentLength() >= 0) { -- nextState = State.READ_FIXED_LENGTH_CONTENT; -+ return State.READ_FIXED_LENGTH_CONTENT; - } else { -- nextState = State.READ_VARIABLE_LENGTH_CONTENT; -+ return State.READ_VARIABLE_LENGTH_CONTENT; - } -- return nextState; - } - - private long contentLength() { -diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -index 8a2345837fe..1e780b7959f 100644 ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -@@ -323,29 +323,75 @@ public void testTooLargeHeaders() { - - @Test - public void testWhitespace() { -- EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); - String requestStr = "GET /some/path HTTP/1.1\r\n" + - "Transfer-Encoding : chunked\r\n" + - "Host: netty.io\n\r\n"; -- -- assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); -- HttpRequest request = channel.readInbound(); -- assertTrue(request.decoderResult().isFailure()); -- assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException); -- assertFalse(channel.finish()); -+ testInvalidHeaders0(requestStr); - } - - @Test - public void testHeaderWithNoValueAndMissingColon() { -- EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); - String requestStr = "GET /some/path HTTP/1.1\r\n" + - "Content-Length: 0\r\n" + - "Host:\r\n" + - "netty.io\r\n\r\n"; -+ testInvalidHeaders0(requestStr); -+ } -+ -+ @Test -+ public void testMultipleContentLengthHeaders() { -+ String requestStr = "GET /some/path HTTP/1.1\r\n" + -+ "Content-Length: 1\r\n" + -+ "Content-Length: 0\r\n\r\n" + -+ "b"; -+ testInvalidHeaders0(requestStr); -+ } -+ -+ @Test -+ public void testMultipleContentLengthHeaders2() { -+ String requestStr = "GET /some/path HTTP/1.1\r\n" + -+ "Content-Length: 1\r\n" + -+ "Connection: close\r\n" + -+ "Content-Length: 0\r\n\r\n" + -+ "b"; -+ testInvalidHeaders0(requestStr); -+ } -+ -+ @Test -+ public void testContentLengthHeaderWithCommaValue() { -+ String requestStr = "GET /some/path HTTP/1.1\r\n" + -+ "Content-Length: 1,1\r\n\r\n" + -+ "b"; -+ testInvalidHeaders0(requestStr); -+ } - -+ @Test -+ public void testMultipleContentLengthHeadersWithFolding() { -+ String requestStr = "POST / HTTP/1.1\r\n" + -+ "Host: example.com\r\n" + -+ "Connection: close\r\n" + -+ "Content-Length: 5\r\n" + -+ "Content-Length:\r\n" + -+ "\t6\r\n\r\n" + -+ "123456"; -+ testInvalidHeaders0(requestStr); -+ } -+ -+ @Test -+ public void testContentLengthHeaderAndChunked() { -+ String requestStr = "POST / HTTP/1.1\r\n" + -+ "Host: example.com\r\n" + -+ "Connection: close\r\n" + -+ "Content-Length: 5\r\n" + -+ "Transfer-Encoding: chunked\r\n\r\n" + -+ "0\r\n\r\n"; -+ testInvalidHeaders0(requestStr); -+ } -+ -+ private static void testInvalidHeaders0(String requestStr) { -+ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); - assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); - HttpRequest request = channel.readInbound(); -- System.err.println(request.headers().names().toString()); - assertTrue(request.decoderResult().isFailure()); - assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException); - assertFalse(channel.finish()); diff --git a/CVE-2019-20445-2.patch b/CVE-2019-20445-2.patch deleted file mode 100644 index b593d9e74010dc9cb67e07b3fff39905cbc2cce4..0000000000000000000000000000000000000000 --- a/CVE-2019-20445-2.patch +++ /dev/null @@ -1,127 +0,0 @@ -From 629034624626b722128e0fcc6b3ec9d406cb3706 Mon Sep 17 00:00:00 2001 -From: Bennett Lynch -Date: Mon, 10 Feb 2020 01:41:57 -0800 -Subject: [PATCH] =?UTF-8?q?Remove=20"Content-Length"=20when=20decoding=20H?= - =?UTF-8?q?TTP/1.1=20message=20with=20both=20"Tra=E2=80=A6=20(#10003)?= -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Motivation - -As part of a recent commit for issue -https://github.com/netty/netty/issues/9861 the HttpObjectDecoder was -changed to throw an IllegalArgumentException (and produce a failed -decoder result) when decoding a message with both "Transfer-Encoding: -chunked" and "Content-Length". - -While it seems correct for Netty to try to sanitize these types of -messages, the spec explicitly mentions that the Content-Length header -should be *removed* in this scenario. - -Both Nginx 1.15.9 and Tomcat 9.0.31 also opt to remove the header: -https://github.com/apache/tomcat/blob/b693d7c1981fa7f51e58bc8c8e72e3fe80b7b773/java/org/apache/coyote/http11/Http11Processor.java#L747-L755 -https://github.com/nginx/nginx/blob/0ad4393e30c119d250415cb769e3d8bc8dce5186/src/http/ngx_http_request.c#L1946-L1953 - -Modifications - -* Change the default behavior from throwing an IllegalArgumentException -to removing the "Content-Length" header -* Extract the behavior to a new protected method, -handleChunkedEncodingWithContentLength(), that can be overridden to -change this behavior (or capture metrics) - -Result - -Messages of this nature will now be successfully decoded and have their -"Content-Length" header removed, rather than creating invalid messages -(decoder result failures). Users will be allowed to override and -configure this behavior. ---- - .../handler/codec/http/HttpObjectDecoder.java | 42 ++++++++++++------- - .../codec/http/HttpRequestDecoderTest.java | 10 ++++- - 2 files changed, 36 insertions(+), 16 deletions(-) - -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -index 768bd3b26f5..04861043590 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -@@ -632,23 +632,9 @@ private State readHeaders(ByteBuf buffer) { - HttpUtil.setTransferEncodingChunked(message, false); - return State.SKIP_CONTROL_CHARS; - } else if (HttpUtil.isTransferEncodingChunked(message)) { -- // See https://tools.ietf.org/html/rfc7230#section-3.3.3 -- // -- // If a message is received with both a Transfer-Encoding and a -- // Content-Length header field, the Transfer-Encoding overrides the -- // Content-Length. Such a message might indicate an attempt to -- // perform request smuggling (Section 9.5) or response splitting -- // (Section 9.4) and ought to be handled as an error. A sender MUST -- // remove the received Content-Length field prior to forwarding such -- // a message downstream. -- // -- // This is also what http_parser does: -- // https://github.com/nodejs/http-parser/blob/v2.9.2/http_parser.c#L1769 - if (contentLengthValuesCount > 0 && message.protocolVersion() == HttpVersion.HTTP_1_1) { -- throw new IllegalArgumentException( -- "Both 'Content-Length: " + contentLength + "' and 'Transfer-Encoding: chunked' found"); -+ handleTransferEncodingChunkedWithContentLength(message); - } -- - return State.READ_CHUNK_SIZE; - } else if (contentLength() >= 0) { - return State.READ_FIXED_LENGTH_CONTENT; -@@ -657,6 +643,32 @@ private State readHeaders(ByteBuf buffer) { - } - } - -+ /** -+ * Invoked when a message with both a "Transfer-Encoding: chunked" and a "Content-Length" header field is detected. -+ * The default behavior is to remove the Content-Length field, but this method could be overridden -+ * to change the behavior (to, e.g., throw an exception and produce an invalid message). -+ *

-+ * See: https://tools.ietf.org/html/rfc7230#section-3.3.3 -+ *

-+     *     If a message is received with both a Transfer-Encoding and a
-+     *     Content-Length header field, the Transfer-Encoding overrides the
-+     *     Content-Length.  Such a message might indicate an attempt to
-+     *     perform request smuggling (Section 9.5) or response splitting
-+     *     (Section 9.4) and ought to be handled as an error.  A sender MUST
-+     *     remove the received Content-Length field prior to forwarding such
-+     *     a message downstream.
-+     * 
-+ * Also see: -+ * https://github.com/apache/tomcat/blob/b693d7c1981fa7f51e58bc8c8e72e3fe80b7b773/ -+ * java/org/apache/coyote/http11/Http11Processor.java#L747-L755 -+ * https://github.com/nginx/nginx/blob/0ad4393e30c119d250415cb769e3d8bc8dce5186/ -+ * src/http/ngx_http_request.c#L1946-L1953 -+ */ -+ protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) { -+ message.headers().remove(HttpHeaderNames.CONTENT_LENGTH); -+ contentLength = Long.MIN_VALUE; -+ } -+ - private long contentLength() { - if (contentLength == Long.MIN_VALUE) { - contentLength = HttpUtil.getContentLength(message, -1L); -diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -index 2548af0e2af..f92a32d0ad9 100644 ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -@@ -408,7 +408,15 @@ public void testContentLengthHeaderAndChunked() { - "Content-Length: 5\r\n" + - "Transfer-Encoding: chunked\r\n\r\n" + - "0\r\n\r\n"; -- testInvalidHeaders0(requestStr); -+ -+ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); -+ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); -+ HttpRequest request = channel.readInbound(); -+ assertFalse(request.decoderResult().isFailure()); -+ assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false)); -+ assertFalse(request.headers().contains("Content-Length")); -+ LastHttpContent c = channel.readInbound(); -+ assertFalse(channel.finish()); - } - - private static void testInvalidHeaders0(String requestStr) { diff --git a/CVE-2019-20445-3.patch b/CVE-2019-20445-3.patch deleted file mode 100644 index 8527efa1618e5c7a432764ec11ed1f1531eb6dc2..0000000000000000000000000000000000000000 --- a/CVE-2019-20445-3.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 5f68897880467c00f29495b0aa46ed19bf7a873c Mon Sep 17 00:00:00 2001 -From: Artem Smotrakov -Date: Wed, 5 Feb 2020 14:33:28 +0100 -Subject: [PATCH] Added tests for Transfer-Encoding header with whitespace - (#9997) - -Motivation: - -Need tests to ensure that CVE-2020-7238 is fixed. - -Modifications: - -Added two test cases into HttpRequestDecoderTest which check that -no whitespace is allowed before the Transfer-Encoding header. - -Result: - -Improved test coverage for #9861 ---- - .../codec/http/HttpRequestDecoderTest.java | 25 ++++++++++++++++++- - 1 file changed, 24 insertions(+), 1 deletion(-) - -diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -index 1e780b7959f..2548af0e2af 100644 ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -@@ -325,7 +325,30 @@ public void testTooLargeHeaders() { - public void testWhitespace() { - String requestStr = "GET /some/path HTTP/1.1\r\n" + - "Transfer-Encoding : chunked\r\n" + -- "Host: netty.io\n\r\n"; -+ "Host: netty.io\r\n\r\n"; -+ testInvalidHeaders0(requestStr); -+ } -+ -+ @Test -+ public void testWhitespaceBeforeTransferEncoding01() { -+ String requestStr = "GET /some/path HTTP/1.1\r\n" + -+ " Transfer-Encoding : chunked\r\n" + -+ "Content-Length: 1\r\n" + -+ "Host: netty.io\r\n\r\n" + -+ "a"; -+ testInvalidHeaders0(requestStr); -+ } -+ -+ @Test -+ public void testWhitespaceBeforeTransferEncoding02() { -+ String requestStr = "POST / HTTP/1.1" + -+ " Transfer-Encoding : chunked\r\n" + -+ "Host: target.com" + -+ "Content-Length: 65\r\n\r\n" + -+ "0\r\n\r\n" + -+ "GET /maliciousRequest HTTP/1.1\r\n" + -+ "Host: evilServer.com\r\n" + -+ "Foo: x"; - testInvalidHeaders0(requestStr); - } - diff --git a/CVE-2020-11612.patch b/CVE-2020-11612.patch deleted file mode 100644 index 5777ea8f575c46856ce80a0baf87faa7235d89dd..0000000000000000000000000000000000000000 --- a/CVE-2020-11612.patch +++ /dev/null @@ -1,488 +0,0 @@ -From ad6830f1e90735407ae153a0ee9bf8e223e40d14 Mon Sep 17 00:00:00 2001 -From: Rich DiCroce -Date: Fri, 31 Jan 2020 06:11:06 -0500 -Subject: [PATCH] Allow a limit to be set on the decompressed buffer size -for ZlibDecoders (#9924) - -Motivation: -It is impossible to know in advance how much memory will be needed to -decompress a stream of bytes that was compressed using the DEFLATE -algorithm. In theory, up to 1032 times the compressed size could be -needed. For untrusted input, an attacker could exploit this to exhaust -the memory pool. - -Modifications: -ZlibDecoder and its subclasses now support an optional limit on the size -of the decompressed buffer. By default, if the limit is reached, -decompression stops and a DecompressionException is thrown. Behavior -upon reaching the limit is modifiable by subclasses in case they desire -something else. - -Result: -The decompressed buffer can now be limited to a configurable size, thus -mitigating the possibility of memory pool exhaustion. - ---- - .../codec/compression/JZlibDecoder.java | 57 +++++++++++++++- - .../codec/compression/JdkZlibDecoder.java | 58 +++++++++++++++-- - .../codec/compression/ZlibDecoder.java | 65 +++++++++++++++++++ - .../handler/codec/compression/JZlibTest.java | 4 +- - .../codec/compression/JdkZlibTest.java | 4 +- - .../codec/compression/ZlibCrossTest1.java | 4 +- - .../codec/compression/ZlibCrossTest2.java | 4 +- - .../handler/codec/compression/ZlibTest.java | 57 +++++++++++++++- - 8 files changed, 235 insertions(+), 18 deletions(-) - -diff --git a/codec/src/main/java/io/netty/handler/codec/compression/JZlibDecoder.java b/codec/src/main/java/io/netty/handler/codec/compression/JZlibDecoder.java -index 5d23bb8..cfb104e 100644 ---- a/codec/src/main/java/io/netty/handler/codec/compression/JZlibDecoder.java -+++ b/codec/src/main/java/io/netty/handler/codec/compression/JZlibDecoder.java -@@ -18,6 +18,7 @@ package io.netty.handler.codec.compression; - import com.jcraft.jzlib.Inflater; - import com.jcraft.jzlib.JZlib; - import io.netty.buffer.ByteBuf; -+import io.netty.buffer.ByteBufAllocator; - import io.netty.channel.ChannelHandlerContext; - - import java.util.List; -@@ -34,7 +35,21 @@ public class JZlibDecoder extends ZlibDecoder { - * @throws DecompressionException if failed to initialize zlib - */ - public JZlibDecoder() { -- this(ZlibWrapper.ZLIB); -+ this(ZlibWrapper.ZLIB, 0); -+ } -+ -+ /** -+ * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}) -+ * and specified maximum buffer allocation. -+ * -+ * @param maxAllocation -+ * Maximum size of the decompression buffer. Must be >= 0. -+ * If zero, maximum size is decided by the {@link ByteBufAllocator}. -+ * -+ * @throws DecompressionException if failed to initialize zlib -+ */ -+ public JZlibDecoder(int maxAllocation) { -+ this(ZlibWrapper.ZLIB, maxAllocation); - } - - /** -@@ -43,6 +58,21 @@ public class JZlibDecoder extends ZlibDecoder { - * @throws DecompressionException if failed to initialize zlib - */ - public JZlibDecoder(ZlibWrapper wrapper) { -+ this(wrapper, 0); -+ } -+ -+ /** -+ * Creates a new instance with the specified wrapper and maximum buffer allocation. -+ * -+ * @param maxAllocation -+ * Maximum size of the decompression buffer. Must be >= 0. -+ * If zero, maximum size is decided by the {@link ByteBufAllocator}. -+ * -+ * @throws DecompressionException if failed to initialize zlib -+ */ -+ public JZlibDecoder(ZlibWrapper wrapper, int maxAllocation) { -+ super(maxAllocation); -+ - if (wrapper == null) { - throw new NullPointerException("wrapper"); - } -@@ -61,6 +91,22 @@ public class JZlibDecoder extends ZlibDecoder { - * @throws DecompressionException if failed to initialize zlib - */ - public JZlibDecoder(byte[] dictionary) { -+ this(dictionary, 0); -+ } -+ -+ /** -+ * Creates a new instance with the specified preset dictionary and maximum buffer allocation. -+ * The wrapper is always {@link ZlibWrapper#ZLIB} because it is the only format that -+ * supports the preset dictionary. -+ * -+ * @param maxAllocation -+ * Maximum size of the decompression buffer. Must be >= 0. -+ * If zero, maximum size is decided by the {@link ByteBufAllocator}. -+ * -+ * @throws DecompressionException if failed to initialize zlib -+ */ -+ public JZlibDecoder(byte[] dictionary, int maxAllocation) { -+ super(maxAllocation); - if (dictionary == null) { - throw new NullPointerException("dictionary"); - } -@@ -110,11 +156,11 @@ public class JZlibDecoder extends ZlibDecoder { - final int oldNextInIndex = z.next_in_index; - - // Configure output. -- ByteBuf decompressed = ctx.alloc().heapBuffer(inputLength << 1); -+ ByteBuf decompressed = prepareDecompressBuffer(ctx, null, inputLength << 1); - - try { - loop: for (;;) { -- decompressed.ensureWritable(z.avail_in << 1); -+ decompressed = prepareDecompressBuffer(ctx, decompressed, z.avail_in << 1); - z.avail_out = decompressed.writableBytes(); - z.next_out = decompressed.array(); - z.next_out_index = decompressed.arrayOffset() + decompressed.writerIndex(); -@@ -170,4 +216,9 @@ public class JZlibDecoder extends ZlibDecoder { - z.next_out = null; - } - } -+ -+ @Override -+ protected void decompressionBufferExhausted(ByteBuf buffer) { -+ finished = true; -+ } - } -diff --git a/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibDecoder.java b/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibDecoder.java -index d9b4b91..74e6d5f 100644 ---- a/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibDecoder.java -+++ b/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibDecoder.java -@@ -16,6 +16,7 @@ - package io.netty.handler.codec.compression; - - import io.netty.buffer.ByteBuf; -+import io.netty.buffer.ByteBufAllocator; - import io.netty.channel.ChannelHandlerContext; - - import java.util.List; -@@ -63,7 +64,19 @@ public class JdkZlibDecoder extends ZlibDecoder { - * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}). - */ - public JdkZlibDecoder() { -- this(ZlibWrapper.ZLIB, null); -+ this(ZlibWrapper.ZLIB, null, 0); -+ } -+ -+ /** -+ * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}) -+ * and the specified maximum buffer allocation. -+ * -+ * @param maxAllocation -+ * Maximum size of the decompression buffer. Must be >= 0. -+ * If zero, maximum size is decided by the {@link ByteBufAllocator}. -+ */ -+ public JdkZlibDecoder(int maxAllocation) { -+ this(ZlibWrapper.ZLIB, null, maxAllocation); - } - - /** -@@ -72,7 +85,20 @@ public class JdkZlibDecoder extends ZlibDecoder { - * supports the preset dictionary. - */ - public JdkZlibDecoder(byte[] dictionary) { -- this(ZlibWrapper.ZLIB, dictionary); -+ this(ZlibWrapper.ZLIB, dictionary, 0); -+ } -+ -+ /** -+ * Creates a new instance with the specified preset dictionary and maximum buffer allocation. -+ * The wrapper is always {@link ZlibWrapper#ZLIB} because it is the only format that -+ * supports the preset dictionary. -+ * -+ * @param maxAllocation -+ * Maximum size of the decompression buffer. Must be >= 0. -+ * If zero, maximum size is decided by the {@link ByteBufAllocator}. -+ */ -+ public JdkZlibDecoder(byte[] dictionary, int maxAllocation) { -+ this(ZlibWrapper.ZLIB, dictionary, maxAllocation); - } - - /** -@@ -81,10 +107,25 @@ public class JdkZlibDecoder extends ZlibDecoder { - * supported atm. - */ - public JdkZlibDecoder(ZlibWrapper wrapper) { -- this(wrapper, null); -+ this(wrapper, null, 0); - } - -- private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary) { -+ /** -+ * Creates a new instance with the specified wrapper and maximum buffer allocation. -+ * Be aware that only {@link ZlibWrapper#GZIP}, {@link ZlibWrapper#ZLIB} and {@link ZlibWrapper#NONE} are -+ * supported atm. -+ * -+ * @param maxAllocation -+ * Maximum size of the decompression buffer. Must be >= 0. -+ * If zero, maximum size is decided by the {@link ByteBufAllocator}. -+ */ -+ public JdkZlibDecoder(ZlibWrapper wrapper, int maxAllocation) { -+ this(wrapper, null, maxAllocation); -+ } -+ -+ private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary, int maxAllocation) { -+ super(maxAllocation); -+ - if (wrapper == null) { - throw new NullPointerException("wrapper"); - } -@@ -167,7 +208,7 @@ public class JdkZlibDecoder extends ZlibDecoder { - inflater.setInput(array); - } - -- ByteBuf decompressed = ctx.alloc().heapBuffer(inflater.getRemaining() << 1); -+ ByteBuf decompressed = prepareDecompressBuffer(ctx, null,inflater.getRemaining() << 1); - try { - boolean readFooter = false; - while (!inflater.needsInput()) { -@@ -198,7 +239,7 @@ public class JdkZlibDecoder extends ZlibDecoder { - } - break; - } else { -- decompressed.ensureWritable(inflater.getRemaining() << 1); -+ decompressed = prepareDecompressBuffer(ctx, decompressed,inflater.getRemaining() << 1); - } - } - -@@ -222,6 +263,11 @@ public class JdkZlibDecoder extends ZlibDecoder { - } - } - -+ @Override -+ protected void decompressionBufferExhausted(ByteBuf buffer) { -+ finished = true; -+ } -+ - @Override - protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { - super.handlerRemoved0(ctx); -diff --git a/codec/src/main/java/io/netty/handler/codec/compression/ZlibDecoder.java b/codec/src/main/java/io/netty/handler/codec/compression/ZlibDecoder.java -index d01bc6b..26fd3e7 100644 ---- a/codec/src/main/java/io/netty/handler/codec/compression/ZlibDecoder.java -+++ b/codec/src/main/java/io/netty/handler/codec/compression/ZlibDecoder.java -@@ -16,6 +16,8 @@ - package io.netty.handler.codec.compression; - - import io.netty.buffer.ByteBuf; -+import io.netty.buffer.ByteBufAllocator; -+import io.netty.channel.ChannelHandlerContext; - import io.netty.handler.codec.ByteToMessageDecoder; - - /** -@@ -23,9 +25,72 @@ import io.netty.handler.codec.ByteToMessageDecoder; - */ - public abstract class ZlibDecoder extends ByteToMessageDecoder { - -+ /** -+ * Maximum allowed size of the decompression buffer. -+ */ -+ protected final int maxAllocation; -+ -+ /** -+ * Same as {@link #ZlibDecoder(int)} with maxAllocation = 0. -+ */ -+ public ZlibDecoder() { -+ this(0); -+ } -+ -+ /** -+ * Construct a new ZlibDecoder. -+ * @param maxAllocation -+ * Maximum size of the decompression buffer. Must be >= 0. -+ * If zero, maximum size is decided by the {@link ByteBufAllocator}. -+ */ -+ public ZlibDecoder(int maxAllocation) { -+ if (maxAllocation < 0) { -+ throw new IllegalArgumentException("maxAllocation must be >= 0"); -+ } -+ this.maxAllocation = maxAllocation; -+ } -+ - /** - * Returns {@code true} if and only if the end of the compressed stream - * has been reached. - */ - public abstract boolean isClosed(); -+ -+ /** -+ * Allocate or expand the decompression buffer, without exceeding the maximum allocation. -+ * Calls {@link #decompressionBufferExhausted(ByteBuf)} if the buffer is full and cannot be expanded further. -+ */ -+ protected ByteBuf prepareDecompressBuffer(ChannelHandlerContext ctx, ByteBuf buffer, int preferredSize) { -+ if (buffer == null) { -+ if (maxAllocation == 0) { -+ return ctx.alloc().heapBuffer(preferredSize); -+ } -+ -+ return ctx.alloc().heapBuffer(Math.min(preferredSize, maxAllocation), maxAllocation); -+ } -+ -+ // this always expands the buffer if possible, even if the expansion is less than preferredSize -+ // we throw the exception only if the buffer could not be expanded at all -+ // this means that one final attempt to deserialize will always be made with the buffer at maxAllocation -+ if (buffer.ensureWritable(preferredSize, true) == 1) { -+ // buffer must be consumed so subclasses don't add it to output -+ // we therefore duplicate it when calling decompressionBufferExhausted() to guarantee non-interference -+ // but wait until after to consume it so the subclass can tell how much output is really in the buffer -+ decompressionBufferExhausted(buffer.duplicate()); -+ buffer.skipBytes(buffer.readableBytes()); -+ throw new DecompressionException("Decompression buffer has reached maximum size: " + buffer.maxCapacity()); -+ } -+ -+ return buffer; -+ } -+ -+ /** -+ * Called when the decompression buffer cannot be expanded further. -+ * Default implementation is a no-op, but subclasses can override in case they want to -+ * do something before the {@link DecompressionException} is thrown, such as log the -+ * data that was decompressed so far. -+ */ -+ protected void decompressionBufferExhausted(ByteBuf buffer) { -+ } -+ - } -diff --git a/codec/src/test/java/io/netty/handler/codec/compression/JZlibTest.java b/codec/src/test/java/io/netty/handler/codec/compression/JZlibTest.java -index 28f3919..015559e 100644 ---- a/codec/src/test/java/io/netty/handler/codec/compression/JZlibTest.java -+++ b/codec/src/test/java/io/netty/handler/codec/compression/JZlibTest.java -@@ -23,7 +23,7 @@ public class JZlibTest extends ZlibTest { - } - - @Override -- protected ZlibDecoder createDecoder(ZlibWrapper wrapper) { -- return new JZlibDecoder(wrapper); -+ protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) { -+ return new JZlibDecoder(wrapper, maxAllocation); - } - } -diff --git a/codec/src/test/java/io/netty/handler/codec/compression/JdkZlibTest.java b/codec/src/test/java/io/netty/handler/codec/compression/JdkZlibTest.java -index 23f178d..fc53282 100644 ---- a/codec/src/test/java/io/netty/handler/codec/compression/JdkZlibTest.java -+++ b/codec/src/test/java/io/netty/handler/codec/compression/JdkZlibTest.java -@@ -26,8 +26,8 @@ public class JdkZlibTest extends ZlibTest { - } - - @Override -- protected ZlibDecoder createDecoder(ZlibWrapper wrapper) { -- return new JdkZlibDecoder(wrapper); -+ protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) { -+ return new JdkZlibDecoder(wrapper, maxAllocation); - } - - @Test(expected = DecompressionException.class) -diff --git a/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest1.java b/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest1.java -index 9e16e1a..3c31274 100644 ---- a/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest1.java -+++ b/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest1.java -@@ -23,7 +23,7 @@ public class ZlibCrossTest1 extends ZlibTest { - } - - @Override -- protected ZlibDecoder createDecoder(ZlibWrapper wrapper) { -- return new JZlibDecoder(wrapper); -+ protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) { -+ return new JZlibDecoder(wrapper, maxAllocation); - } - } -diff --git a/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest2.java b/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest2.java -index 8717019..00c6e18 100644 ---- a/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest2.java -+++ b/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest2.java -@@ -25,8 +25,8 @@ public class ZlibCrossTest2 extends ZlibTest { - } - - @Override -- protected ZlibDecoder createDecoder(ZlibWrapper wrapper) { -- return new JdkZlibDecoder(wrapper); -+ protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) { -+ return new JdkZlibDecoder(wrapper, maxAllocation); - } - - @Test(expected = DecompressionException.class) -diff --git a/codec/src/test/java/io/netty/handler/codec/compression/ZlibTest.java b/codec/src/test/java/io/netty/handler/codec/compression/ZlibTest.java -index 7c25ec4..9d79c81 100644 ---- a/codec/src/test/java/io/netty/handler/codec/compression/ZlibTest.java -+++ b/codec/src/test/java/io/netty/handler/codec/compression/ZlibTest.java -@@ -15,7 +15,9 @@ - */ - package io.netty.handler.codec.compression; - -+import io.netty.buffer.AbstractByteBufAllocator; - import io.netty.buffer.ByteBuf; -+import io.netty.buffer.ByteBufAllocator; - import io.netty.buffer.ByteBufInputStream; - import io.netty.buffer.Unpooled; - import io.netty.channel.embedded.EmbeddedChannel; -@@ -88,8 +90,12 @@ public abstract class ZlibTest { - rand.nextBytes(BYTES_LARGE); - } - -+ protected ZlibDecoder createDecoder(ZlibWrapper wrapper) { -+ return createDecoder(wrapper, 0); -+ } -+ - protected abstract ZlibEncoder createEncoder(ZlibWrapper wrapper); -- protected abstract ZlibDecoder createDecoder(ZlibWrapper wrapper); -+ protected abstract ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation); - - @Test - public void testGZIP2() throws Exception { -@@ -345,6 +351,25 @@ public abstract class ZlibTest { - testCompressLarge(ZlibWrapper.GZIP, ZlibWrapper.ZLIB_OR_NONE); - } - -+ @Test -+ public void testMaxAllocation() throws Exception { -+ int maxAllocation = 1024; -+ ZlibDecoder decoder = createDecoder(ZlibWrapper.ZLIB, maxAllocation); -+ EmbeddedChannel chDecoder = new EmbeddedChannel(decoder); -+ TestByteBufAllocator alloc = new TestByteBufAllocator(chDecoder.alloc()); -+ chDecoder.config().setAllocator(alloc); -+ -+ try { -+ chDecoder.writeInbound(Unpooled.wrappedBuffer(deflate(BYTES_LARGE))); -+ fail("decompressed size > maxAllocation, so should have thrown exception"); -+ } catch (DecompressionException e) { -+ assertTrue(e.getMessage().startsWith("Decompression buffer has reached maximum size")); -+ assertEquals(maxAllocation, alloc.getMaxAllocation()); -+ assertTrue(decoder.isClosed()); -+ assertFalse(chDecoder.finish()); -+ } -+ } -+ - private static byte[] gzip(byte[] bytes) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - GZIPOutputStream stream = new GZIPOutputStream(out); -@@ -360,4 +385,34 @@ public abstract class ZlibTest { - stream.close(); - return out.toByteArray(); - } -+ -+ private static final class TestByteBufAllocator extends AbstractByteBufAllocator { -+ private ByteBufAllocator wrapped; -+ private int maxAllocation; -+ -+ TestByteBufAllocator(ByteBufAllocator wrapped) { -+ this.wrapped = wrapped; -+ } -+ -+ public int getMaxAllocation() { -+ return maxAllocation; -+ } -+ -+ @Override -+ public boolean isDirectBufferPooled() { -+ return wrapped.isDirectBufferPooled(); -+ } -+ -+ @Override -+ protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) { -+ maxAllocation = Math.max(maxAllocation, maxCapacity); -+ return wrapped.heapBuffer(initialCapacity, maxCapacity); -+ } -+ -+ @Override -+ protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) { -+ maxAllocation = Math.max(maxAllocation, maxCapacity); -+ return wrapped.directBuffer(initialCapacity, maxCapacity); -+ } -+ } - } --- -2.23.0 - diff --git a/CVE-2021-21290.patch b/CVE-2021-21290.patch deleted file mode 100644 index 52f1a67f5d4895462d858d7d31e8a9b6cebd07d3..0000000000000000000000000000000000000000 --- a/CVE-2021-21290.patch +++ /dev/null @@ -1,292 +0,0 @@ -From f1f550f682ea781cda3d86e5114b7b7cab4513c8 Mon Sep 17 00:00:00 2001 -From: wang_yue111 <648774160@qq.com> -Date: Tue, 9 Mar 2021 14:57:32 +0800 -Subject: [PATCH] Use Files.createTempFile(...) to ensure the file is created - with proper permissions - -Motivation: - -File.createTempFile(String, String)` will create a temporary file in the system temporary directory if the 'java.io.tmpdir'. The permissions on that file utilize the umask. In a majority of cases, this means that the file that java creates has the permissions: `-rw-r--r--`, thus, any other local user on that system can read the contents of that file. -This can be a security concern if any sensitive data is stored in this file. - -This was reported by Jonathan Leitschuh as a security problem. - -Modifications: - -Use Files.createTempFile(...) which will use safe-defaults when running on java 7 and later. If running on java 6 there isnt much we can do, which is fair enough as java 6 shouldnt be considered "safe" anyway. - -Result: - -Create temporary files with sane permissions by default. - ---- - .../io/netty/buffer/AbstractByteBufTest.java | 4 ++-- - .../http/multipart/AbstractDiskHttpData.java | 5 +++-- - .../codec/http/HttpChunkedInputTest.java | 3 ++- - .../util/internal/NativeLibraryLoader.java | 3 ++- - .../util/internal/PlatformDependent.java | 20 +++++++++++++++++++ - .../ssl/util/SelfSignedCertificate.java | 5 +++-- - .../stream/ChunkedWriteHandlerTest.java | 3 ++- - .../socket/SocketFileRegionTest.java | 2 +- - .../epoll/EpollSocketTestPermutation.java | 1 + - .../netty/channel/epoll/EpollSpliceTest.java | 3 ++- - .../channel/unix/tests/UnixTestUtils.java | 3 ++- - 11 files changed, 40 insertions(+), 12 deletions(-) - -diff --git a/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java b/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java -index ef9d729..0d4d3e6 100644 ---- a/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java -+++ b/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java -@@ -3962,7 +3962,7 @@ public abstract class AbstractByteBufTest { - - @Test - public void testReadBytesAndWriteBytesWithFileChannel() throws IOException { -- File file = File.createTempFile("file-channel", ".tmp"); -+ File file = PlatformDependent.createTempFile("file-channel", ".tmp", null); - RandomAccessFile randomAccessFile = null; - try { - randomAccessFile = new RandomAccessFile(file, "rw"); -@@ -4005,7 +4005,7 @@ public abstract class AbstractByteBufTest { - - @Test - public void testGetBytesAndSetBytesWithFileChannel() throws IOException { -- File file = File.createTempFile("file-channel", ".tmp"); -+ File file = PlatformDependent.createTempFile("file-channel", ".tmp", null); - RandomAccessFile randomAccessFile = null; - try { - randomAccessFile = new RandomAccessFile(file, "rw"); -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java -index a21e72f..9251673 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java -@@ -18,6 +18,7 @@ package io.netty.handler.codec.http.multipart; - import io.netty.buffer.ByteBuf; - import io.netty.handler.codec.http.HttpConstants; - import io.netty.util.internal.EmptyArrays; -+import io.netty.util.internal.PlatformDependent; - import io.netty.util.internal.logging.InternalLogger; - import io.netty.util.internal.logging.InternalLoggerFactory; - -@@ -87,9 +88,9 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData { - File tmpFile; - if (getBaseDirectory() == null) { - // create a temporary file -- tmpFile = File.createTempFile(getPrefix(), newpostfix); -+ tmpFile = PlatformDependent.createTempFile(getPrefix(), newpostfix, null); - } else { -- tmpFile = File.createTempFile(getPrefix(), newpostfix, new File( -+ tmpFile = PlatformDependent.createTempFile(getPrefix(), newpostfix, new File( - getBaseDirectory())); - } - if (deleteOnExit()) { -diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpChunkedInputTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpChunkedInputTest.java -index 002c8d0..8e75eb9 100644 ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpChunkedInputTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpChunkedInputTest.java -@@ -25,6 +25,7 @@ import io.netty.handler.stream.ChunkedNioFile; - import io.netty.handler.stream.ChunkedNioStream; - import io.netty.handler.stream.ChunkedStream; - import io.netty.handler.stream.ChunkedWriteHandler; -+import io.netty.util.internal.PlatformDependent; - import org.junit.Test; - - import java.io.ByteArrayInputStream; -@@ -46,7 +47,7 @@ public class HttpChunkedInputTest { - - FileOutputStream out = null; - try { -- TMP = File.createTempFile("netty-chunk-", ".tmp"); -+ TMP = PlatformDependent.createTempFile("netty-chunk-", ".tmp", null); - TMP.deleteOnExit(); - out = new FileOutputStream(TMP); - out.write(BYTES); -diff --git a/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java b/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java -index 1b0a71d..cb4d4f2 100644 ---- a/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java -+++ b/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java -@@ -15,6 +15,7 @@ - */ - package io.netty.util.internal; - -+import io.netty.util.internal.PlatformDependent; - import io.netty.util.internal.logging.InternalLogger; - import io.netty.util.internal.logging.InternalLoggerFactory; - -@@ -212,7 +213,7 @@ public final class NativeLibraryLoader { - OutputStream out = null; - File tmpFile = null; - try { -- tmpFile = File.createTempFile(prefix, suffix, WORKDIR); -+ tmpFile = PlatformDependent.createTempFile(prefix, suffix, WORKDIR); - in = url.openStream(); - out = new FileOutputStream(tmpFile); - -diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent.java b/common/src/main/java/io/netty/util/internal/PlatformDependent.java -index 2fd3c98..6cc4532 100644 ---- a/common/src/main/java/io/netty/util/internal/PlatformDependent.java -+++ b/common/src/main/java/io/netty/util/internal/PlatformDependent.java -@@ -17,6 +17,7 @@ package io.netty.util.internal; - - import io.netty.util.internal.logging.InternalLogger; - import io.netty.util.internal.logging.InternalLoggerFactory; -+import io.netty.util.internal.PlatformDependent; - import org.jctools.queues.MpscArrayQueue; - import org.jctools.queues.MpscChunkedArrayQueue; - import org.jctools.queues.SpscLinkedQueue; -@@ -27,9 +28,11 @@ import org.jctools.util.Pow2; - import org.jctools.util.UnsafeAccess; - - import java.io.File; -+import java.io.IOException; - import java.lang.reflect.Method; - import java.nio.ByteBuffer; - import java.nio.ByteOrder; -+import java.nio.file.Files; - import java.security.AccessController; - import java.security.PrivilegedAction; - import java.util.Deque; -@@ -1166,6 +1169,23 @@ public final class PlatformDependent { - return true; - } - -+ public static File createTempFile(String prefix, String suffix, File directory) throws IOException { -+ if (javaVersion() >= 7) { -+ if (directory == null) { -+ return Files.createTempFile(prefix, suffix).toFile(); -+ } -+ return Files.createTempFile(directory.toPath(), prefix, suffix).toFile(); -+ } -+ if (directory == null) { -+ return File.createTempFile(prefix, suffix); -+ } -+ File file = File.createTempFile(prefix, suffix, directory); -+ // Try to adjust the perms, if this fails there is not much else we can do... -+ file.setReadable(false, false); -+ file.setReadable(true, true); -+ return file; -+ } -+ - /** - * Package private for testing purposes only! - */ -diff --git a/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java b/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java -index 112e1a8..4fa3d53 100644 ---- a/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java -+++ b/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java -@@ -20,6 +20,7 @@ import io.netty.buffer.ByteBuf; - import io.netty.buffer.Unpooled; - import io.netty.handler.codec.base64.Base64; - import io.netty.util.CharsetUtil; -+import io.netty.util.internal.PlatformDependent; - import io.netty.util.internal.SystemPropertyUtil; - import io.netty.util.internal.logging.InternalLogger; - import io.netty.util.internal.logging.InternalLoggerFactory; -@@ -236,7 +237,7 @@ public final class SelfSignedCertificate { - wrappedBuf.release(); - } - -- File keyFile = File.createTempFile("keyutil_" + fqdn + '_', ".key"); -+ File keyFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".key", null); - keyFile.deleteOnExit(); - - OutputStream keyOut = new FileOutputStream(keyFile); -@@ -267,7 +268,7 @@ public final class SelfSignedCertificate { - wrappedBuf.release(); - } - -- File certFile = File.createTempFile("keyutil_" + fqdn + '_', ".crt"); -+ File certFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".crt", null); - certFile.deleteOnExit(); - - OutputStream certOut = new FileOutputStream(certFile); -diff --git a/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java b/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java -index 66b6951..f556a5f 100644 ---- a/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java -+++ b/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java -@@ -23,6 +23,7 @@ import io.netty.channel.ChannelFutureListener; - import io.netty.channel.ChannelHandlerContext; - import io.netty.channel.embedded.EmbeddedChannel; - import io.netty.util.CharsetUtil; -+import io.netty.util.internal.PlatformDependent; - import org.junit.Test; - - import java.io.ByteArrayInputStream; -@@ -47,7 +48,7 @@ public class ChunkedWriteHandlerTest { - - FileOutputStream out = null; - try { -- TMP = File.createTempFile("netty-chunk-", ".tmp"); -+ TMP = PlatformDependent.createTempFile("netty-chunk-", ".tmp", null); - TMP.deleteOnExit(); - out = new FileOutputStream(TMP); - out.write(BYTES); -diff --git a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java -index f08a714..608beed 100644 ---- a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java -+++ b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java -@@ -100,7 +100,7 @@ public class SocketFileRegionTest extends AbstractSocketTest { - cb.option(ChannelOption.AUTO_READ, autoRead); - - final int bufferSize = 1024; -- final File file = File.createTempFile("netty-", ".tmp"); -+ final File file = PlatformDependent.createTempFile("netty-", ".tmp", null); - file.deleteOnExit(); - - final FileOutputStream out = new FileOutputStream(file); -diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java -index 2da13e1..f8d42cb 100644 ---- a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java -+++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java -@@ -32,6 +32,7 @@ import io.netty.testsuite.transport.socket.SocketTestPermutation; - import io.netty.util.concurrent.DefaultThreadFactory; - import io.netty.util.internal.logging.InternalLogger; - import io.netty.util.internal.logging.InternalLoggerFactory; -+import io.netty.util.internal.PlatformDependent; - - import java.io.BufferedReader; - import java.io.File; -diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java -index 98dcec9..6b975e6 100644 ---- a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java -+++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java -@@ -27,6 +27,7 @@ import io.netty.channel.ChannelInboundHandlerAdapter; - import io.netty.channel.EventLoopGroup; - import io.netty.channel.SimpleChannelInboundHandler; - import io.netty.channel.unix.FileDescriptor; -+import io.netty.util.internal.PlatformDependent; - import org.junit.Assert; - import org.junit.Test; - -@@ -192,7 +193,7 @@ public class EpollSpliceTest { - @Test - public void spliceToFile() throws Throwable { - EventLoopGroup group = new EpollEventLoopGroup(1); -- File file = File.createTempFile("netty-splice", null); -+ File file = PlatformDependent.createTempFile("netty-splice", null, null); - file.deleteOnExit(); - - SpliceHandler sh = new SpliceHandler(file); -diff --git a/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java b/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java -index e4ebcb4..2fa4187 100644 ---- a/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java -+++ b/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java -@@ -17,6 +17,7 @@ package io.netty.channel.unix.tests; - - import io.netty.channel.unix.DomainSocketAddress; - import io.netty.channel.unix.Socket; -+import io.netty.util.internal.PlatformDependent; - - import java.io.File; - import java.io.IOException; -@@ -26,7 +27,7 @@ public final class UnixTestUtils { - try { - File file; - do { -- file = File.createTempFile("NETTY", "UDS"); -+ file = PlatformDependent.createTempFile("NETTY", "UDS", null); - if (!file.delete()) { - throw new IOException("failed to delete: " + file); - } --- -2.23.0 - diff --git a/CVE-2021-21295-pre1.patch b/CVE-2021-21295-pre1.patch deleted file mode 100644 index 6f5a17bc21a71860480a9a2d6b7d4eb5a5824141..0000000000000000000000000000000000000000 --- a/CVE-2021-21295-pre1.patch +++ /dev/null @@ -1,240 +0,0 @@ -From bcb62be62bd989c0292e0f8e22a51127907cefdc Mon Sep 17 00:00:00 2001 -From: Bennett Lynch -Date: Thu, 11 Jun 2020 22:39:10 -0700 -Subject: [PATCH] Consolidate HttpObjectDecoder default values into - constants (#10344) - -Motivation - -HttpObjectDecoder and its associated classes make frequent use of -default values for maxInitialLineLength, maxHeaderSize, maxChunkSize, -etc. Today, these defaults are defined in-line in constructors and -duplicated across many classes. This repetition is more prone to error -and inconsistencies. - -Furthermore, due to the current lack of builder support, if a user wants -to change just one of these values (e.g., maxHeaderSize), they are also -required to know and repeat the other default values (e.g., -maxInitialLineLength and maxChunkSize). - -The primary motivation for this change is as we are considering adding -another constructor parameter (for multiple content length behavior), -appending this parameter may require some users to have prior knowledge -of the default initialBufferSize, and it would be cleaner to allow them -to reference the default constant. - -Modifications - -* Consolidate the HttpObjectDecoder default values into public constants -* Reference these constants where possible - -Result - -No functional change. Additional telescoping constructors will be easier -and safer to write. Users may have an easier experience changing single -parameters. ---- - .../netty/handler/codec/http/HttpClientCodec.java | 6 +++++- - .../handler/codec/http/HttpObjectDecoder.java | 15 ++++++++++++--- - .../handler/codec/http/HttpRequestDecoder.java | 7 ++++--- - .../handler/codec/http/HttpResponseDecoder.java | 7 ++++--- - .../netty/handler/codec/http/HttpServerCodec.java | 6 +++++- - .../io/netty/handler/codec/rtsp/RtspDecoder.java | 10 ---------- - .../handler/codec/rtsp/RtspObjectDecoder.java | 4 +++- - 7 files changed, 33 insertions(+), 22 deletions(-) - -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java -index da4c440466..a832bfdff3 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java -@@ -28,6 +28,10 @@ import java.util.List; - import java.util.Queue; - import java.util.concurrent.atomic.AtomicLong; - -+import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_CHUNK_SIZE; -+import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_HEADER_SIZE; -+import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_INITIAL_LINE_LENGTH; -+ - /** - * A combination of {@link HttpRequestEncoder} and {@link HttpResponseDecoder} - * which enables easier client side HTTP implementation. {@link HttpClientCodec} -@@ -61,7 +65,7 @@ public final class HttpClientCodec extends CombinedChannelDuplexHandler -Date: Mon, 4 Feb 2019 22:55:07 +0800 -Subject: [PATCH] use checkPositive/checkPositiveOrZero (#8835) - -Motivation: - -We can replace some "hand-rolled" integer checks with our own static utility method to simplify the code. - -Modifications: - -Use methods provided by `ObjectUtil`. - -Result: - -Cleaner code and less duplication ---- - .../handler/codec/dns/AbstractDnsRecord.java | 5 ++--- - .../codec/http/DefaultHttpHeaders.java | 3 +-- - .../handler/codec/http/HttpObjectDecoder.java | 21 ++++++------------- - .../codec/http/HttpResponseStatus.java | 7 +++---- - .../netty/handler/codec/http/HttpVersion.java | 10 ++++----- - .../multipart/AbstractMemoryHttpData.java | 3 +-- - .../codec/spdy/DefaultSpdyGoAwayFrame.java | 7 +++---- - .../codec/spdy/DefaultSpdyStreamFrame.java | 7 +++---- - .../codec/spdy/DefaultSpdySynReplyFrame.java | 3 +-- - .../codec/spdy/DefaultSpdySynStreamFrame.java | 8 +++---- - .../spdy/DefaultSpdyWindowUpdateFrame.java | 14 +++++-------- - .../handler/codec/spdy/SpdyFrameDecoder.java | 7 +++---- - .../handler/codec/spdy/SpdyHttpDecoder.java | 6 ++---- - .../codec/spdy/SpdySessionHandler.java | 19 ++++++++--------- - .../http2/DefaultHttp2ConnectionEncoder.java | 5 ++--- - .../codec/http2/DefaultHttp2FrameWriter.java | 14 +++++-------- - .../codec/http2/DefaultHttp2GoAwayFrame.java | 6 +++--- - .../DefaultHttp2LocalFlowController.java | 5 ++--- - .../DefaultHttp2RemoteFlowController.java | 5 ++--- - .../DelegatingDecompressorFrameListener.java | 5 ++--- - .../http2/UniformStreamByteDistributor.java | 5 ++--- - .../WeightedFairQueueByteDistributor.java | 11 +++++----- - .../binary/AbstractBinaryMemcacheDecoder.java | 6 +++--- - .../codec/stomp/StompSubframeDecoder.java | 13 +++--------- - 24 files changed, 75 insertions(+), 120 deletions(-) - -diff --git a/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsRecord.java b/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsRecord.java -index 28b92c27f9..2ba6e573a7 100644 ---- a/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsRecord.java -+++ b/codec-dns/src/main/java/io/netty/handler/codec/dns/AbstractDnsRecord.java -@@ -21,6 +21,7 @@ import io.netty.util.internal.UnstableApi; - import java.net.IDN; - - import static io.netty.util.internal.ObjectUtil.checkNotNull; -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; - - /** - * A skeletal implementation of {@link DnsRecord}. -@@ -62,9 +63,7 @@ public abstract class AbstractDnsRecord implements DnsRecord { - * @param timeToLive the TTL value of the record - */ - protected AbstractDnsRecord(String name, DnsRecordType type, int dnsClass, long timeToLive) { -- if (timeToLive < 0) { -- throw new IllegalArgumentException("timeToLive: " + timeToLive + " (expected: >= 0)"); -- } -+ checkPositiveOrZero(timeToLive, "timeToLive"); - // Convert to ASCII which will also check that the length is not too big. - // See: - // - https://github.com/netty/netty/issues/4937 -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java -index 6204f3ea7f..d18f196e8f 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java -@@ -341,8 +341,7 @@ public class DefaultHttpHeaders extends HttpHeaders { - default: - // Check to see if the character is not an ASCII character, or invalid - if (value < 0) { -- throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " + -- value); -+ throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " + value); - } - } - } -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -index d4caf29c6d..ed7caa7801 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -@@ -15,6 +15,8 @@ - */ - package io.netty.handler.codec.http; - -+import static io.netty.util.internal.ObjectUtil.checkPositive; -+ - import io.netty.buffer.ByteBuf; - import io.netty.buffer.Unpooled; - import io.netty.channel.ChannelHandlerContext; -@@ -177,21 +179,10 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - protected HttpObjectDecoder( - int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, - boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) { -- if (maxInitialLineLength <= 0) { -- throw new IllegalArgumentException( -- "maxInitialLineLength must be a positive integer: " + -- maxInitialLineLength); -- } -- if (maxHeaderSize <= 0) { -- throw new IllegalArgumentException( -- "maxHeaderSize must be a positive integer: " + -- maxHeaderSize); -- } -- if (maxChunkSize <= 0) { -- throw new IllegalArgumentException( -- "maxChunkSize must be a positive integer: " + -- maxChunkSize); -- } -+ checkPositive(maxInitialLineLength, "maxInitialLineLength"); -+ checkPositive(maxHeaderSize, "maxHeaderSize"); -+ checkPositive(maxChunkSize, "maxChunkSize"); -+ - AppendableCharSequence seq = new AppendableCharSequence(initialBufferSize); - lineParser = new LineParser(seq, maxInitialLineLength); - headerParser = new HeaderParser(seq, maxHeaderSize); -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java -index 026866ebcc..9f24e0d3cc 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java -@@ -22,6 +22,8 @@ import io.netty.util.AsciiString; - import io.netty.util.ByteProcessor; - import io.netty.util.CharsetUtil; - -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; -+ - /** - * The response code and its description of HTTP or its derived protocols, such as - * RTSP and -@@ -577,10 +579,7 @@ public class HttpResponseStatus implements Comparable { - } - - private HttpResponseStatus(int code, String reasonPhrase, boolean bytes) { -- if (code < 0) { -- throw new IllegalArgumentException( -- "code: " + code + " (expected: 0+)"); -- } -+ checkPositiveOrZero(code, "code"); - - if (reasonPhrase == null) { - throw new NullPointerException("reasonPhrase"); -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java -index a643f42458..7ba40eed90 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java -@@ -15,6 +15,8 @@ - */ - package io.netty.handler.codec.http; - -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; -+ - import io.netty.buffer.ByteBuf; - import io.netty.util.CharsetUtil; - -@@ -165,12 +167,8 @@ public class HttpVersion implements Comparable { - } - } - -- if (majorVersion < 0) { -- throw new IllegalArgumentException("negative majorVersion"); -- } -- if (minorVersion < 0) { -- throw new IllegalArgumentException("negative minorVersion"); -- } -+ checkPositiveOrZero(majorVersion, "majorVersion"); -+ checkPositiveOrZero(minorVersion, "minorVersion"); - - this.protocolName = protocolName; - this.majorVersion = majorVersion; -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java -index 31aa9ce64b..4cb7e567b2 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java -@@ -128,8 +128,7 @@ public abstract class AbstractMemoryHttpData extends AbstractHttpData { - } - long newsize = file.length(); - if (newsize > Integer.MAX_VALUE) { -- throw new IllegalArgumentException( -- "File too big to be loaded in memory"); -+ throw new IllegalArgumentException("File too big to be loaded in memory"); - } - checkSize(newsize); - FileInputStream inputStream = new FileInputStream(file); -diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java -index 4d88875a6e..79c21f2404 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyGoAwayFrame.java -@@ -15,6 +15,8 @@ - */ - package io.netty.handler.codec.spdy; - -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; -+ - import io.netty.util.internal.StringUtil; - - /** -@@ -62,10 +64,7 @@ public class DefaultSpdyGoAwayFrame implements SpdyGoAwayFrame { - - @Override - public SpdyGoAwayFrame setLastGoodStreamId(int lastGoodStreamId) { -- if (lastGoodStreamId < 0) { -- throw new IllegalArgumentException("Last-good-stream-ID" -- + " cannot be negative: " + lastGoodStreamId); -- } -+ checkPositiveOrZero(lastGoodStreamId, "lastGoodStreamId"); - this.lastGoodStreamId = lastGoodStreamId; - return this; - } -diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyStreamFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyStreamFrame.java -index 4618d4d4a9..487844ecd9 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyStreamFrame.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyStreamFrame.java -@@ -15,6 +15,8 @@ - */ - package io.netty.handler.codec.spdy; - -+import static io.netty.util.internal.ObjectUtil.checkPositive; -+ - /** - * The default {@link SpdyStreamFrame} implementation. - */ -@@ -39,10 +41,7 @@ public abstract class DefaultSpdyStreamFrame implements SpdyStreamFrame { - - @Override - public SpdyStreamFrame setStreamId(int streamId) { -- if (streamId <= 0) { -- throw new IllegalArgumentException( -- "Stream-ID must be positive: " + streamId); -- } -+ checkPositive(streamId, "streamId"); - this.streamId = streamId; - return this; - } -diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java -index 7efc905641..f757d1dbd6 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynReplyFrame.java -@@ -20,8 +20,7 @@ import io.netty.util.internal.StringUtil; - /** - * The default {@link SpdySynReplyFrame} implementation. - */ --public class DefaultSpdySynReplyFrame extends DefaultSpdyHeadersFrame -- implements SpdySynReplyFrame { -+public class DefaultSpdySynReplyFrame extends DefaultSpdyHeadersFrame implements SpdySynReplyFrame { - - /** - * Creates a new instance. -diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java -index f8adc1c5f1..46fe301636 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySynStreamFrame.java -@@ -15,6 +15,8 @@ - */ - package io.netty.handler.codec.spdy; - -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; -+ - import io.netty.util.internal.StringUtil; - - /** -@@ -77,11 +79,7 @@ public class DefaultSpdySynStreamFrame extends DefaultSpdyHeadersFrame - - @Override - public SpdySynStreamFrame setAssociatedStreamId(int associatedStreamId) { -- if (associatedStreamId < 0) { -- throw new IllegalArgumentException( -- "Associated-To-Stream-ID cannot be negative: " + -- associatedStreamId); -- } -+ checkPositiveOrZero(associatedStreamId, "associatedStreamId"); - this.associatedStreamId = associatedStreamId; - return this; - } -diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java -index f14611bac6..22b0406c80 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java -@@ -15,6 +15,9 @@ - */ - package io.netty.handler.codec.spdy; - -+import static io.netty.util.internal.ObjectUtil.checkPositive; -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; -+ - import io.netty.util.internal.StringUtil; - - /** -@@ -43,10 +46,7 @@ public class DefaultSpdyWindowUpdateFrame implements SpdyWindowUpdateFrame { - - @Override - public SpdyWindowUpdateFrame setStreamId(int streamId) { -- if (streamId < 0) { -- throw new IllegalArgumentException( -- "Stream-ID cannot be negative: " + streamId); -- } -+ checkPositiveOrZero(streamId, "streamId"); - this.streamId = streamId; - return this; - } -@@ -58,11 +58,7 @@ public class DefaultSpdyWindowUpdateFrame implements SpdyWindowUpdateFrame { - - @Override - public SpdyWindowUpdateFrame setDeltaWindowSize(int deltaWindowSize) { -- if (deltaWindowSize <= 0) { -- throw new IllegalArgumentException( -- "Delta-Window-Size must be positive: " + -- deltaWindowSize); -- } -+ checkPositive(deltaWindowSize, "deltaWindowSize"); - this.deltaWindowSize = deltaWindowSize; - return this; - } -diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java -index e0d1112813..fc432b6830 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java -@@ -38,6 +38,8 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.getSignedInt; - import static io.netty.handler.codec.spdy.SpdyCodecUtil.getUnsignedInt; - import static io.netty.handler.codec.spdy.SpdyCodecUtil.getUnsignedMedium; - import static io.netty.handler.codec.spdy.SpdyCodecUtil.getUnsignedShort; -+import static io.netty.util.internal.ObjectUtil.checkPositive; -+ - import io.netty.buffer.ByteBuf; - import io.netty.buffer.Unpooled; - -@@ -95,10 +97,7 @@ public class SpdyFrameDecoder { - if (delegate == null) { - throw new NullPointerException("delegate"); - } -- if (maxChunkSize <= 0) { -- throw new IllegalArgumentException( -- "maxChunkSize must be a positive integer: " + maxChunkSize); -- } -+ checkPositive(maxChunkSize, "maxChunkSize"); - this.spdyVersion = spdyVersion.getVersion(); - this.delegate = delegate; - this.maxChunkSize = maxChunkSize; -diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java -index 366ad15b66..5e16a6f4f2 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java -@@ -38,6 +38,7 @@ import java.util.List; - import java.util.Map; - - import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*; -+import static io.netty.util.internal.ObjectUtil.checkPositive; - - /** - * Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s, -@@ -103,10 +104,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { - if (version == null) { - throw new NullPointerException("version"); - } -- if (maxContentLength <= 0) { -- throw new IllegalArgumentException( -- "maxContentLength must be a positive integer: " + maxContentLength); -- } -+ checkPositive(maxContentLength, "maxContentLength"); - spdyVersion = version.getVersion(); - this.maxContentLength = maxContentLength; - this.messageMap = messageMap; -diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java -index 394f6c2e9a..8f90864151 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySessionHandler.java -@@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicInteger; - - import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_SESSION_STREAM_ID; - import static io.netty.handler.codec.spdy.SpdyCodecUtil.isServerId; -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; - - /** - * Manages streams within a SPDY session. -@@ -77,16 +78,14 @@ public class SpdySessionHandler extends ChannelDuplexHandler { - } - - public void setSessionReceiveWindowSize(int sessionReceiveWindowSize) { -- if (sessionReceiveWindowSize < 0) { -- throw new IllegalArgumentException("sessionReceiveWindowSize"); -- } -- // This will not send a window update frame immediately. -- // If this value increases the allowed receive window size, -- // a WINDOW_UPDATE frame will be sent when only half of the -- // session window size remains during data frame processing. -- // If this value decreases the allowed receive window size, -- // the window will be reduced as data frames are processed. -- initialSessionReceiveWindowSize = sessionReceiveWindowSize; -+ checkPositiveOrZero(sessionReceiveWindowSize, "sessionReceiveWindowSize"); -+ // This will not send a window update frame immediately. -+ // If this value increases the allowed receive window size, -+ // a WINDOW_UPDATE frame will be sent when only half of the -+ // session window size remains during data frame processing. -+ // If this value decreases the allowed receive window size, -+ // the window will be reduced as data frames are processed. -+ initialSessionReceiveWindowSize = sessionReceiveWindowSize; - } - - @Override -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java -index f0af13b394..18375db76a 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java -@@ -29,6 +29,7 @@ import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGH - import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; - import static io.netty.handler.codec.http2.Http2Exception.connectionError; - import static io.netty.util.internal.ObjectUtil.checkNotNull; -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; - import static java.lang.Integer.MAX_VALUE; - import static java.lang.Math.min; - -@@ -485,9 +486,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder { - - FlowControlledBase(final Http2Stream stream, int padding, boolean endOfStream, - final ChannelPromise promise) { -- if (padding < 0) { -- throw new IllegalArgumentException("padding must be >= 0"); -- } -+ checkPositiveOrZero(padding, "padding"); - this.padding = padding; - this.endOfStream = endOfStream; - this.stream = stream; -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java -index c7277561d6..77270f8343 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java -@@ -61,6 +61,8 @@ import static io.netty.handler.codec.http2.Http2FrameTypes.RST_STREAM; - import static io.netty.handler.codec.http2.Http2FrameTypes.SETTINGS; - import static io.netty.handler.codec.http2.Http2FrameTypes.WINDOW_UPDATE; - import static io.netty.util.internal.ObjectUtil.checkNotNull; -+import static io.netty.util.internal.ObjectUtil.checkPositive; -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; - import static java.lang.Math.max; - import static java.lang.Math.min; - -@@ -547,15 +549,11 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize - } - - private static void verifyStreamId(int streamId, String argumentName) { -- if (streamId <= 0) { -- throw new IllegalArgumentException(argumentName + " must be > 0"); -- } -+ checkPositive(streamId, "streamId"); - } - - private static void verifyStreamOrConnectionId(int streamId, String argumentName) { -- if (streamId < 0) { -- throw new IllegalArgumentException(argumentName + " must be >= 0"); -- } -+ checkPositiveOrZero(streamId, "streamId"); - } - - private static void verifyWeight(short weight) { -@@ -571,9 +569,7 @@ public class DefaultHttp2FrameWriter implements Http2FrameWriter, Http2FrameSize - } - - private static void verifyWindowSizeIncrement(int windowSizeIncrement) { -- if (windowSizeIncrement < 0) { -- throw new IllegalArgumentException("WindowSizeIncrement must be >= 0"); -- } -+ checkPositiveOrZero(windowSizeIncrement, "windowSizeIncrement"); - } - - private static void verifyPingPayload(ByteBuf data) { -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2GoAwayFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2GoAwayFrame.java -index 8f54b8e329..dc01f37482 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2GoAwayFrame.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2GoAwayFrame.java -@@ -15,6 +15,8 @@ - */ - package io.netty.handler.codec.http2; - -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; -+ - import io.netty.buffer.ByteBuf; - import io.netty.buffer.DefaultByteBufHolder; - import io.netty.buffer.Unpooled; -@@ -97,9 +99,7 @@ public final class DefaultHttp2GoAwayFrame extends DefaultByteBufHolder implemen - - @Override - public Http2GoAwayFrame setExtraStreamIds(int extraStreamIds) { -- if (extraStreamIds < 0) { -- throw new IllegalArgumentException("extraStreamIds must be non-negative"); -- } -+ checkPositiveOrZero(extraStreamIds, "extraStreamIds"); - this.extraStreamIds = extraStreamIds; - return this; - } -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2LocalFlowController.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2LocalFlowController.java -index 74dc3ae31c..cac715614d 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2LocalFlowController.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2LocalFlowController.java -@@ -24,6 +24,7 @@ import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; - import static io.netty.handler.codec.http2.Http2Exception.connectionError; - import static io.netty.handler.codec.http2.Http2Exception.streamError; - import static io.netty.util.internal.ObjectUtil.checkNotNull; -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; - import static java.lang.Math.max; - import static java.lang.Math.min; - import io.netty.buffer.ByteBuf; -@@ -173,9 +174,7 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController - @Override - public boolean consumeBytes(Http2Stream stream, int numBytes) throws Http2Exception { - assert ctx != null && ctx.executor().inEventLoop(); -- if (numBytes < 0) { -- throw new IllegalArgumentException("numBytes must not be negative"); -- } -+ checkPositiveOrZero(numBytes, "numBytes"); - if (numBytes == 0) { - return false; - } -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowController.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowController.java -index 034140c81f..125a394cae 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowController.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2RemoteFlowController.java -@@ -32,6 +32,7 @@ import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; - import static io.netty.handler.codec.http2.Http2Exception.streamError; - import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_LOCAL; - import static io.netty.util.internal.ObjectUtil.checkNotNull; -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; - import static java.lang.Math.max; - import static java.lang.Math.min; - -@@ -652,9 +653,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll - } - - void initialWindowSize(int newWindowSize) throws Http2Exception { -- if (newWindowSize < 0) { -- throw new IllegalArgumentException("Invalid initial window size: " + newWindowSize); -- } -+ checkPositiveOrZero(newWindowSize, "newWindowSize"); - - final int delta = newWindowSize - initialWindowSize; - initialWindowSize = newWindowSize; -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingDecompressorFrameListener.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingDecompressorFrameListener.java -index 78ef230c62..3e73bd68dd 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingDecompressorFrameListener.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingDecompressorFrameListener.java -@@ -33,6 +33,7 @@ import static io.netty.handler.codec.http.HttpHeaderValues.X_GZIP; - import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; - import static io.netty.handler.codec.http2.Http2Exception.streamError; - import static io.netty.util.internal.ObjectUtil.checkNotNull; -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; - - /** - * A HTTP2 frame listener that will decompress data frames according to the {@code content-encoding} header for each -@@ -398,9 +399,7 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor - * @return The number of pre-decompressed bytes that have been consumed. - */ - int consumeBytes(int streamId, int decompressedBytes) throws Http2Exception { -- if (decompressedBytes < 0) { -- throw new IllegalArgumentException("decompressedBytes must not be negative: " + decompressedBytes); -- } -+ checkPositiveOrZero(decompressedBytes, "decompressedBytes"); - if (decompressed - decompressedBytes < 0) { - throw streamError(streamId, INTERNAL_ERROR, - "Attempting to return too many bytes for stream %d. decompressed: %d " + -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/UniformStreamByteDistributor.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/UniformStreamByteDistributor.java -index c3e5e2faaa..6204c7bb9c 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/UniformStreamByteDistributor.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/UniformStreamByteDistributor.java -@@ -24,6 +24,7 @@ import static io.netty.handler.codec.http2.Http2CodecUtil.streamableBytes; - import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; - import static io.netty.handler.codec.http2.Http2Exception.connectionError; - import static io.netty.util.internal.ObjectUtil.checkNotNull; -+import static io.netty.util.internal.ObjectUtil.checkPositive; - import static java.lang.Math.max; - import static java.lang.Math.min; - -@@ -72,9 +73,7 @@ public final class UniformStreamByteDistributor implements StreamByteDistributor - * Must be > 0. - */ - public void minAllocationChunk(int minAllocationChunk) { -- if (minAllocationChunk <= 0) { -- throw new IllegalArgumentException("minAllocationChunk must be > 0"); -- } -+ checkPositive(minAllocationChunk, "minAllocationChunk"); - this.minAllocationChunk = minAllocationChunk; - } - -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/WeightedFairQueueByteDistributor.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/WeightedFairQueueByteDistributor.java -index c215376c72..d26c088c62 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/WeightedFairQueueByteDistributor.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/WeightedFairQueueByteDistributor.java -@@ -36,6 +36,8 @@ import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGH - import static io.netty.handler.codec.http2.Http2CodecUtil.streamableBytes; - import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; - import static io.netty.handler.codec.http2.Http2Exception.connectionError; -+import static io.netty.util.internal.ObjectUtil.checkPositive; -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; - import static java.lang.Integer.MAX_VALUE; - import static java.lang.Math.max; - import static java.lang.Math.min; -@@ -95,9 +97,8 @@ public final class WeightedFairQueueByteDistributor implements StreamByteDistrib - } - - public WeightedFairQueueByteDistributor(Http2Connection connection, int maxStateOnlySize) { -- if (maxStateOnlySize < 0) { -- throw new IllegalArgumentException("maxStateOnlySize: " + maxStateOnlySize + " (expected: >0)"); -- } else if (maxStateOnlySize == 0) { -+ checkPositiveOrZero(maxStateOnlySize, "maxStateOnlySize"); -+ if (maxStateOnlySize == 0) { - stateOnlyMap = IntCollections.emptyMap(); - stateOnlyRemovalQueue = EmptyPriorityQueue.instance(); - } else { -@@ -280,9 +281,7 @@ public final class WeightedFairQueueByteDistributor implements StreamByteDistrib - * @param allocationQuantum the amount of bytes that will be allocated to each stream. Must be > 0. - */ - public void allocationQuantum(int allocationQuantum) { -- if (allocationQuantum <= 0) { -- throw new IllegalArgumentException("allocationQuantum must be > 0"); -- } -+ checkPositive(allocationQuantum, "allocationQuantum"); - this.allocationQuantum = allocationQuantum; - } - -diff --git a/codec-memcache/src/main/java/io/netty/handler/codec/memcache/binary/AbstractBinaryMemcacheDecoder.java b/codec-memcache/src/main/java/io/netty/handler/codec/memcache/binary/AbstractBinaryMemcacheDecoder.java -index 2c90382829..bec754afbd 100644 ---- a/codec-memcache/src/main/java/io/netty/handler/codec/memcache/binary/AbstractBinaryMemcacheDecoder.java -+++ b/codec-memcache/src/main/java/io/netty/handler/codec/memcache/binary/AbstractBinaryMemcacheDecoder.java -@@ -15,6 +15,8 @@ - */ - package io.netty.handler.codec.memcache.binary; - -+import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; -+ - import io.netty.buffer.ByteBuf; - import io.netty.buffer.Unpooled; - import io.netty.channel.ChannelHandlerContext; -@@ -59,9 +61,7 @@ public abstract class AbstractBinaryMemcacheDecoder { - - public StompSubframeDecoder(int maxLineLength, int maxChunkSize) { - super(State.SKIP_CONTROL_CHARACTERS); -- if (maxLineLength <= 0) { -- throw new IllegalArgumentException( -- "maxLineLength must be a positive integer: " + -- maxLineLength); -- } -- if (maxChunkSize <= 0) { -- throw new IllegalArgumentException( -- "maxChunkSize must be a positive integer: " + -- maxChunkSize); -- } -+ checkPositive(maxLineLength, "maxLineLength"); -+ checkPositive(maxChunkSize, "maxChunkSize"); - this.maxChunkSize = maxChunkSize; - this.maxLineLength = maxLineLength; - } --- -2.23.0 - diff --git a/CVE-2021-21295-pre3.patch b/CVE-2021-21295-pre3.patch deleted file mode 100644 index 2c002e8bd261630a41e0a0e01c1b880c2c69cebd..0000000000000000000000000000000000000000 --- a/CVE-2021-21295-pre3.patch +++ /dev/null @@ -1,566 +0,0 @@ -From 9557c88da2aaa49b3c3fd7525462dc0694681c19 Mon Sep 17 00:00:00 2001 -From: Bennett Lynch -Date: Mon, 6 Jul 2020 01:25:13 -0700 -Subject: [PATCH] Add option to HttpObjectDecoder to allow duplicate - Content-Lengths (#10349) - -Motivation: - -Since https://github.com/netty/netty/pull/9865 (Netty 4.1.44) the -default behavior of the HttpObjectDecoder has been to reject any HTTP -message that is found to have multiple Content-Length headers when -decoding. This behavior is well-justified as per the risks outlined in -https://github.com/netty/netty/issues/9861, however, we can see from the -cited RFC section that there are multiple possible options offered for -responding to this scenario: - -> If a message is received that has multiple Content-Length header -> fields with field-values consisting of the same decimal value, or a -> single Content-Length header field with a field value containing a -> list of identical decimal values (e.g., "Content-Length: 42, 42"), -> indicating that duplicate Content-Length header fields have been -> generated or combined by an upstream message processor, then the -> recipient MUST either reject the message as invalid or replace the -> duplicated field-values with a single valid Content-Length field -> containing that decimal value prior to determining the message body -> length or forwarding the message. - -https://tools.ietf.org/html/rfc7230#section-3.3.2 - -Netty opted for the first option (rejecting as invalid), which seems -like the safest, but the second option (replacing duplicate values with -a single value) is also valid behavior. - -Modifications: - -* Introduce "allowDuplicateContentLengths" parameter to -HttpObjectDecoder (defaulting to false). -* When set to true, will allow multiple Content-Length headers only if -they are all the same value. The duplicated field-values will be -replaced with a single valid Content-Length field. -* Add new parameterized test class for testing different variations of -multiple Content-Length headers. - -Result: - -This is a backwards-compatible change with no functional change to the -existing behavior. - -Note that the existing logic would result in NumberFormatExceptions -for header values like "Content-Length: 42, 42". The new logic correctly -reports these as IllegalArgumentException with the proper error message. - -Additionally note that this behavior is only applied to HTTP/1.1, but I -suspect that we may want to expand that to include HTTP/1.0 as well... -That behavior is not modified here to minimize the scope of this change. ---- - .../handler/codec/http/HttpClientCodec.java | 35 ++++- - .../handler/codec/http/HttpObjectDecoder.java | 75 ++++++++- - .../codec/http/HttpRequestDecoder.java | 7 + - .../codec/http/HttpResponseDecoder.java | 7 + - .../handler/codec/http/HttpServerCodec.java | 16 ++ - .../MultipleContentLengthHeadersTest.java | 144 ++++++++++++++++++ - 6 files changed, 268 insertions(+), 16 deletions(-) - create mode 100644 codec-http/src/test/java/io/netty/handler/codec/http/MultipleContentLengthHeadersTest.java - -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java -index a832bfdff3..9a99fff97f 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientCodec.java -@@ -28,9 +28,11 @@ import java.util.List; - import java.util.Queue; - import java.util.concurrent.atomic.AtomicLong; - -+import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS; - import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_CHUNK_SIZE; - import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_HEADER_SIZE; - import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_INITIAL_LINE_LENGTH; -+import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_VALIDATE_HEADERS; - - /** - * A combination of {@link HttpRequestEncoder} and {@link HttpResponseDecoder} -@@ -48,6 +50,8 @@ import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_INITIAL_ - */ - public final class HttpClientCodec extends CombinedChannelDuplexHandler - implements HttpClientUpgradeHandler.SourceCodec { -+ public static final boolean DEFAULT_FAIL_ON_MISSING_RESPONSE = false; -+ public static final boolean DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST = false; - - /** A queue that is used for correlating a request and a response. */ - private final Queue queue = new ArrayDeque(); -@@ -65,14 +69,15 @@ public final class HttpClientCodec extends CombinedChannelDuplexHandlerParameters that prevents excessive memory consumption - * - * -- * -+ * - * - * - * -+ * - * - * - * -+ * - * - * - * - * -+ * - * - *
NameMeaningNameDefault valueMeaning
{@code maxInitialLineLength}{@value #DEFAULT_MAX_INITIAL_LINE_LENGTH}The maximum length of the initial line - * (e.g. {@code "GET / HTTP/1.0"} or {@code "HTTP/1.0 200 OK"}) - * If the length of the initial line exceeds this value, a -@@ -48,11 +51,13 @@ import java.util.List; - *
{@code maxHeaderSize}{@value #DEFAULT_MAX_HEADER_SIZE}The maximum length of all headers. If the sum of the length of each - * header exceeds this value, a {@link TooLongFrameException} will be raised.
{@code maxChunkSize}{@value #DEFAULT_MAX_CHUNK_SIZE}The maximum length of the content or each chunk. If the content length - * (or the length of each chunk) exceeds this value, the content or chunk - * will be split into multiple {@link HttpContent}s whose length is -@@ -60,6 +65,21 @@ import java.util.List; - *
- * -+ *

Parameters that control parsing behavior

-+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ *
NameDefault valueMeaning
{@code allowDuplicateContentLengths}{@value #DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS}When set to {@code false}, will reject any messages that contain multiple Content-Length header fields. -+ * When set to {@code true}, will allow multiple Content-Length headers only if they are all the same decimal value. -+ * The duplicated field-values will be replaced with a single valid Content-Length field. -+ * See RFC 7230, Section 3.3.2.
-+ * - *

Chunked Content

- * - * If the content of an HTTP message is greater than {@code maxChunkSize} or -@@ -108,12 +128,15 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - public static final int DEFAULT_MAX_CHUNK_SIZE = 8192; - public static final boolean DEFAULT_VALIDATE_HEADERS = true; - public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128; -+ public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false; - - private static final String EMPTY_VALUE = ""; -+ private static final Pattern COMMA_PATTERN = Pattern.compile(","); - - private final int maxChunkSize; - private final boolean chunkedSupported; - protected final boolean validateHeaders; -+ private final boolean allowDuplicateContentLengths; - private final HeaderParser headerParser; - private final LineParser lineParser; - -@@ -176,9 +199,20 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - DEFAULT_INITIAL_BUFFER_SIZE); - } - -+ /** -+ * Creates a new instance with the specified parameters. -+ */ - protected HttpObjectDecoder( - int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, - boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) { -+ this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, initialBufferSize, -+ DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS); -+ } -+ -+ protected HttpObjectDecoder( -+ int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, -+ boolean chunkedSupported, boolean validateHeaders, int initialBufferSize, -+ boolean allowDuplicateContentLengths) { - checkPositive(maxInitialLineLength, "maxInitialLineLength"); - checkPositive(maxHeaderSize, "maxHeaderSize"); - checkPositive(maxChunkSize, "maxChunkSize"); -@@ -189,6 +223,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - this.maxChunkSize = maxChunkSize; - this.chunkedSupported = chunkedSupported; - this.validateHeaders = validateHeaders; -+ this.allowDuplicateContentLengths = allowDuplicateContentLengths; - } - - @Override -@@ -602,10 +637,9 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - name = null; - value = null; - -- List values = headers.getAll(HttpHeaderNames.CONTENT_LENGTH); -- int contentLengthValuesCount = values.size(); -+ List contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH); - -- if (contentLengthValuesCount > 0) { -+ if (!contentLengthFields.isEmpty()) { - // Guard against multiple Content-Length headers as stated in - // https://tools.ietf.org/html/rfc7230#section-3.3.2: - // -@@ -619,17 +653,42 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - // duplicated field-values with a single valid Content-Length field - // containing that decimal value prior to determining the message body - // length or forwarding the message. -- if (contentLengthValuesCount > 1 && message.protocolVersion() == HttpVersion.HTTP_1_1) { -- throw new IllegalArgumentException("Multiple Content-Length headers found"); -+ boolean multipleContentLengths = -+ contentLengthFields.size() > 1 || contentLengthFields.get(0).indexOf(COMMA) >= 0; -+ if (multipleContentLengths && message.protocolVersion() == HttpVersion.HTTP_1_1) { -+ if (allowDuplicateContentLengths) { -+ // Find and enforce that all Content-Length values are the same -+ String firstValue = null; -+ for (String field : contentLengthFields) { -+ String[] tokens = COMMA_PATTERN.split(field, -1); -+ for (String token : tokens) { -+ String trimmed = token.trim(); -+ if (firstValue == null) { -+ firstValue = trimmed; -+ } else if (!trimmed.equals(firstValue)) { -+ throw new IllegalArgumentException( -+ "Multiple Content-Length values found: " + contentLengthFields); -+ } -+ } -+ } -+ // Replace the duplicated field-values with a single valid Content-Length field -+ headers.set(HttpHeaderNames.CONTENT_LENGTH, firstValue); -+ contentLength = Long.parseLong(firstValue); -+ } else { -+ // Reject the message as invalid -+ throw new IllegalArgumentException( -+ "Multiple Content-Length values found: " + contentLengthFields); -+ } -+ } else { -+ contentLength = Long.parseLong(contentLengthFields.get(0)); - } -- contentLength = Long.parseLong(values.get(0)); - } - - if (isContentAlwaysEmpty(message)) { - HttpUtil.setTransferEncodingChunked(message, false); - return State.SKIP_CONTROL_CHARS; - } else if (HttpUtil.isTransferEncodingChunked(message)) { -- if (contentLengthValuesCount > 0 && message.protocolVersion() == HttpVersion.HTTP_1_1) { -+ if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) { - handleTransferEncodingChunkedWithContentLength(message); - } - return State.READ_CHUNK_SIZE; -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestDecoder.java -index 70c1db5540..ba2d79ecb4 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestDecoder.java -@@ -82,6 +82,13 @@ public class HttpRequestDecoder extends HttpObjectDecoder { - initialBufferSize); - } - -+ public HttpRequestDecoder( -+ int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders, -+ int initialBufferSize, boolean allowDuplicateContentLengths) { -+ super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders, -+ initialBufferSize, allowDuplicateContentLengths); -+ } -+ - @Override - protected HttpMessage createMessage(String[] initialLine) throws Exception { - return new DefaultHttpRequest( -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseDecoder.java -index 39d4d6a5ad..62f6dd3554 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseDecoder.java -@@ -113,6 +113,13 @@ public class HttpResponseDecoder extends HttpObjectDecoder { - initialBufferSize); - } - -+ public HttpResponseDecoder( -+ int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders, -+ int initialBufferSize, boolean allowDuplicateContentLengths) { -+ super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders, -+ initialBufferSize, allowDuplicateContentLengths); -+ } -+ - @Override - protected HttpMessage createMessage(String[] initialLine) { - return new DefaultHttpResponse( -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpServerCodec.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpServerCodec.java -index 8ae6295cf7..b2b905e083 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpServerCodec.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpServerCodec.java -@@ -75,6 +75,16 @@ public final class HttpServerCodec extends CombinedChannelDuplexHandler out) throws Exception { - int oldSize = out.size(); -diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/MultipleContentLengthHeadersTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/MultipleContentLengthHeadersTest.java -new file mode 100644 -index 0000000000..29c7d84b71 ---- /dev/null -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/MultipleContentLengthHeadersTest.java -@@ -0,0 +1,144 @@ -+/* -+ * Copyright 2020 The Netty Project -+ * -+ * The Netty Project licenses this file to you under the Apache License, -+ * version 2.0 (the "License"); you may not use this file except in compliance -+ * with the License. You may obtain a copy of the License at: -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -+ * License for the specific language governing permissions and limitations -+ * under the License. -+ */ -+package io.netty.handler.codec.http; -+ -+import io.netty.buffer.Unpooled; -+import io.netty.channel.embedded.EmbeddedChannel; -+import io.netty.util.CharsetUtil; -+import org.junit.Before; -+import org.junit.Test; -+import org.junit.runner.RunWith; -+import org.junit.runners.Parameterized; -+import org.junit.runners.Parameterized.Parameters; -+ -+import java.util.Arrays; -+import java.util.Collection; -+import java.util.List; -+ -+import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_INITIAL_BUFFER_SIZE; -+import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_CHUNK_SIZE; -+import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_HEADER_SIZE; -+import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_INITIAL_LINE_LENGTH; -+import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_VALIDATE_HEADERS; -+import static org.hamcrest.MatcherAssert.assertThat; -+import static org.hamcrest.Matchers.contains; -+import static org.hamcrest.Matchers.containsString; -+import static org.hamcrest.Matchers.is; -+import static org.hamcrest.core.IsInstanceOf.instanceOf; -+ -+@RunWith(Parameterized.class) -+public class MultipleContentLengthHeadersTest { -+ -+ private final boolean allowDuplicateContentLengths; -+ private final boolean sameValue; -+ private final boolean singleField; -+ -+ private EmbeddedChannel channel; -+ -+ @Parameters -+ public static Collection parameters() { -+ return Arrays.asList(new Object[][] { -+ { false, false, false }, -+ { false, false, true }, -+ { false, true, false }, -+ { false, true, true }, -+ { true, false, false }, -+ { true, false, true }, -+ { true, true, false }, -+ { true, true, true } -+ }); -+ } -+ -+ public MultipleContentLengthHeadersTest( -+ boolean allowDuplicateContentLengths, boolean sameValue, boolean singleField) { -+ this.allowDuplicateContentLengths = allowDuplicateContentLengths; -+ this.sameValue = sameValue; -+ this.singleField = singleField; -+ } -+ -+ @Before -+ public void setUp() { -+ HttpRequestDecoder decoder = new HttpRequestDecoder( -+ DEFAULT_MAX_INITIAL_LINE_LENGTH, -+ DEFAULT_MAX_HEADER_SIZE, -+ DEFAULT_MAX_CHUNK_SIZE, -+ DEFAULT_VALIDATE_HEADERS, -+ DEFAULT_INITIAL_BUFFER_SIZE, -+ allowDuplicateContentLengths); -+ channel = new EmbeddedChannel(decoder); -+ } -+ -+ @Test -+ public void testMultipleContentLengthHeadersBehavior() { -+ String requestStr = setupRequestString(); -+ assertThat(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)), is(true)); -+ HttpRequest request = channel.readInbound(); -+ -+ if (allowDuplicateContentLengths) { -+ if (sameValue) { -+ assertValid(request); -+ List contentLengths = request.headers().getAll(HttpHeaderNames.CONTENT_LENGTH); -+ assertThat(contentLengths, contains("1")); -+ LastHttpContent body = channel.readInbound(); -+ assertThat(body.content().readableBytes(), is(1)); -+ assertThat(body.content().readCharSequence(1, CharsetUtil.US_ASCII).toString(), is("a")); -+ } else { -+ assertInvalid(request); -+ } -+ } else { -+ assertInvalid(request); -+ } -+ assertThat(channel.finish(), is(false)); -+ } -+ -+ private String setupRequestString() { -+ String firstValue = "1"; -+ String secondValue = sameValue ? firstValue : "2"; -+ String contentLength; -+ if (singleField) { -+ contentLength = "Content-Length: " + firstValue + ", " + secondValue + "\r\n\r\n"; -+ } else { -+ contentLength = "Content-Length: " + firstValue + "\r\n" + -+ "Content-Length: " + secondValue + "\r\n\r\n"; -+ } -+ return "PUT /some/path HTTP/1.1\r\n" + -+ contentLength + -+ "ab"; -+ } -+ -+ @Test -+ public void testDanglingComma() { -+ String requestStr = "GET /some/path HTTP/1.1\r\n" + -+ "Content-Length: 1,\r\n" + -+ "Connection: close\n\n" + -+ "ab"; -+ assertThat(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)), is(true)); -+ HttpRequest request = channel.readInbound(); -+ assertInvalid(request); -+ assertThat(channel.finish(), is(false)); -+ } -+ -+ private static void assertValid(HttpRequest request) { -+ assertThat(request.decoderResult().isFailure(), is(false)); -+ } -+ -+ private static void assertInvalid(HttpRequest request) { -+ assertThat(request.decoderResult().isFailure(), is(true)); -+ assertThat(request.decoderResult().cause(), instanceOf(IllegalArgumentException.class)); -+ assertThat(request.decoderResult().cause().getMessage(), -+ containsString("Multiple Content-Length values found")); -+ } -+} --- -2.23.0 - diff --git a/CVE-2021-21295-pre4.patch b/CVE-2021-21295-pre4.patch deleted file mode 100644 index fb65aea7fa13e64874de14df963b9f53ce1213bd..0000000000000000000000000000000000000000 --- a/CVE-2021-21295-pre4.patch +++ /dev/null @@ -1,771 +0,0 @@ -From a91df58ca17d5b30c57c46dde5b1d60bb659b029 Mon Sep 17 00:00:00 2001 -From: Scott Mitchell -Date: Tue, 11 Jul 2017 14:53:49 -0700 -Subject: [PATCH] HTTP/2 enforce HTTP message flow - -Motivation: -codec-http2 currently does not strictly enforce the HTTP/1.x semantics with respect to the number of headers defined in RFC 7540 Section 8.1 [1]. We currently don't validate the number of headers nor do we validate that the trailing headers should indicate EOS. - -[1] https://tools.ietf.org/html/rfc7540#section-8.1 - -Modifications: -- DefaultHttp2ConnectionDecoder should only allow decoding of a single headers and a single trailers -- DefaultHttp2ConnectionEncoder should only allow encoding of a single headers and optionally a single trailers - -Result: -Constraints of RFC 7540 restricting the number of headers/trailers is enforced. ---- - .../handler/codec/http/HttpStatusClass.java | 21 ++ - .../codec/http2/DefaultHttp2Connection.java | 52 +++-- - .../http2/DefaultHttp2ConnectionDecoder.java | 11 + - .../http2/DefaultHttp2ConnectionEncoder.java | 18 +- - .../handler/codec/http2/Http2Stream.java | 32 ++- - .../DefaultHttp2ConnectionDecoderTest.java | 103 ++++++++- - .../DefaultHttp2ConnectionEncoderTest.java | 203 +++++++++++++++++- - .../http2/InboundHttp2ToHttpAdapterTest.java | 43 ---- - 8 files changed, 419 insertions(+), 64 deletions(-) - -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpStatusClass.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpStatusClass.java -index 9f57e18984..0a4f4c11ab 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpStatusClass.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpStatusClass.java -@@ -74,6 +74,27 @@ public enum HttpStatusClass { - return UNKNOWN; - } - -+ /** -+ * Returns the class of the specified HTTP status code. -+ * @param code Just the numeric portion of the http status code. -+ */ -+ public static HttpStatusClass valueOf(CharSequence code) { -+ if (code != null && code.length() == 3) { -+ char c0 = code.charAt(0); -+ return isDigit(c0) && isDigit(code.charAt(1)) && isDigit(code.charAt(2)) ? valueOf(digit(c0) * 100) -+ : UNKNOWN; -+ } -+ return UNKNOWN; -+ } -+ -+ private static int digit(char c) { -+ return c - '0'; -+ } -+ -+ private static boolean isDigit(char c) { -+ return c >= '0' && c <= '9'; -+ } -+ - private final int min; - private final int max; - private final AsciiString defaultReasonPhrase; -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java -index 2789423bc7..12815c225c 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java -@@ -373,13 +373,16 @@ public class DefaultHttp2Connection implements Http2Connection { - * Simple stream implementation. Streams can be compared to each other by priority. - */ - private class DefaultStream implements Http2Stream { -- private static final byte SENT_STATE_RST = 0x1; -- private static final byte SENT_STATE_HEADERS = 0x2; -- private static final byte SENT_STATE_PUSHPROMISE = 0x4; -+ private static final byte META_STATE_SENT_RST = 1; -+ private static final byte META_STATE_SENT_HEADERS = 1 << 1; -+ private static final byte META_STATE_SENT_TRAILERS = 1 << 2; -+ private static final byte META_STATE_SENT_PUSHPROMISE = 1 << 3; -+ private static final byte META_STATE_RECV_HEADERS = 1 << 4; -+ private static final byte META_STATE_RECV_TRAILERS = 1 << 5; - private final int id; - private final PropertyMap properties = new PropertyMap(); - private State state; -- private byte sentState; -+ private byte metaState; - - DefaultStream(int id, State state) { - this.id = id; -@@ -398,35 +401,60 @@ public class DefaultHttp2Connection implements Http2Connection { - - @Override - public boolean isResetSent() { -- return (sentState & SENT_STATE_RST) != 0; -+ return (metaState & META_STATE_SENT_RST) != 0; - } - - @Override - public Http2Stream resetSent() { -- sentState |= SENT_STATE_RST; -+ metaState |= META_STATE_SENT_RST; - return this; - } - - @Override -- public Http2Stream headersSent() { -- sentState |= SENT_STATE_HEADERS; -+ public Http2Stream headersSent(boolean isInformational) { -+ if (!isInformational) { -+ metaState |= isHeadersSent() ? META_STATE_SENT_TRAILERS : META_STATE_SENT_HEADERS; -+ } - return this; - } - - @Override - public boolean isHeadersSent() { -- return (sentState & SENT_STATE_HEADERS) != 0; -+ return (metaState & META_STATE_SENT_HEADERS) != 0; -+ } -+ -+ @Override -+ public boolean isTrailersSent() { -+ return (metaState & META_STATE_SENT_TRAILERS) != 0; -+ } -+ -+ @Override -+ public Http2Stream headersReceived(boolean isInformational) { -+ if (!isInformational) { -+ metaState |= isHeadersReceived() ? META_STATE_RECV_TRAILERS : META_STATE_RECV_HEADERS; -+ } -+ return this; -+ } -+ -+ @Override -+ public boolean isHeadersReceived() { -+ return (metaState & META_STATE_RECV_HEADERS) != 0; -+ } -+ -+ @Override -+ public boolean isTrailersReceived() { -+ return (metaState & META_STATE_RECV_TRAILERS) != 0; - } - - @Override - public Http2Stream pushPromiseSent() { -- sentState |= SENT_STATE_PUSHPROMISE; -+ metaState |= META_STATE_SENT_PUSHPROMISE; - return this; - } - - @Override - public boolean isPushPromiseSent() { -- return (sentState & SENT_STATE_PUSHPROMISE) != 0; -+ return (metaState & META_STATE_SENT_PUSHPROMISE) != 0; - } - - @Override -@@ -599,7 +627,7 @@ public class DefaultHttp2Connection implements Http2Connection { - } - - @Override -- public Http2Stream headersSent() { -+ public Http2Stream headersSent(boolean isInformational) { - throw new UnsupportedOperationException(); - } - -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java -index ef643fafad..4027978651 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java -@@ -16,6 +16,7 @@ package io.netty.handler.codec.http2; - - import io.netty.buffer.ByteBuf; - import io.netty.channel.ChannelHandlerContext; -+import io.netty.handler.codec.http.HttpStatusClass; - import io.netty.handler.codec.http2.Http2Connection.Endpoint; - import io.netty.util.internal.UnstableApi; - import io.netty.util.internal.logging.InternalLogger; -@@ -23,6 +24,7 @@ import io.netty.util.internal.logging.InternalLoggerFactory; - - import java.util.List; - -+import static io.netty.handler.codec.http.HttpStatusClass.INFORMATIONAL; - import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT; - import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR; - import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; -@@ -282,6 +284,14 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - return; - } - -+ boolean isInformational = !connection.isServer() && -+ HttpStatusClass.valueOf(headers.status()) == INFORMATIONAL; -+ if ((isInformational || !endOfStream) && stream.isHeadersReceived() || stream.isTrailersReceived()) { -+ throw streamError(streamId, PROTOCOL_ERROR, -+ "Stream %d received too many headers EOS: %s state: %s", -+ streamId, endOfStream, stream.state()); -+ } -+ - switch (stream.state()) { - case RESERVED_REMOTE: - stream.open(endOfStream); -@@ -305,6 +315,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - stream.state()); - } - -+ stream.headersReceived(isInformational); - encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive); - - listener.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream); -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java -index 18375db76a..f129f63fd8 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoder.java -@@ -21,10 +21,12 @@ import io.netty.channel.ChannelFutureListener; - import io.netty.channel.ChannelHandlerContext; - import io.netty.channel.ChannelPromise; - import io.netty.channel.CoalescingBufferQueue; -+import io.netty.handler.codec.http.HttpStatusClass; - import io.netty.util.internal.UnstableApi; - - import java.util.ArrayDeque; - -+import static io.netty.handler.codec.http.HttpStatusClass.INFORMATIONAL; - import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT; - import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; - import static io.netty.handler.codec.http2.Http2Exception.connectionError; -@@ -146,6 +148,15 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder { - return writeHeaders(ctx, streamId, headers, 0, DEFAULT_PRIORITY_WEIGHT, false, padding, endStream, promise); - } - -+ private static boolean validateHeadersSentState(Http2Stream stream, Http2Headers headers, boolean isServer, -+ boolean endOfStream) { -+ boolean isInformational = isServer && HttpStatusClass.valueOf(headers.status()) == INFORMATIONAL; -+ if ((isInformational || !endOfStream) && stream.isHeadersSent() || stream.isTrailersSent()) { -+ throw new IllegalStateException("Stream " + stream.id() + " sent too many headers EOS: " + endOfStream); -+ } -+ return isInformational; -+ } -+ - @Override - public ChannelFuture writeHeaders(final ChannelHandlerContext ctx, final int streamId, - final Http2Headers headers, final int streamDependency, final short weight, -@@ -181,6 +192,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder { - // for this stream. - Http2RemoteFlowController flowController = flowController(); - if (!endOfStream || !flowController.hasFlowControlled(stream)) { -+ boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream); - if (endOfStream) { - final Http2Stream finalStream = stream; - final ChannelFutureListener closeStreamLocalListener = new ChannelFutureListener() { -@@ -191,6 +203,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder { - }; - promise = promise.unvoid().addListener(closeStreamLocalListener); - } -+ - ChannelFuture future = frameWriter.writeHeaders(ctx, streamId, headers, streamDependency, - weight, exclusive, padding, endOfStream, promise); - // Writing headers may fail during the encode state if they violate HPACK limits. -@@ -198,7 +211,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder { - if (failureCause == null) { - // Synchronously set the headersSent flag to ensure that we do not subsequently write - // other headers containing pseudo-header fields. -- stream.headersSent(); -+ stream.headersSent(isInformational); - } else { - lifecycleManager.onError(ctx, failureCause); - } -@@ -452,6 +465,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder { - - @Override - public void write(ChannelHandlerContext ctx, int allowedBytes) { -+ boolean isInformational = validateHeadersSentState(stream, headers, connection.isServer(), endOfStream); - if (promise.isVoid()) { - promise = ctx.newPromise(); - } -@@ -462,7 +476,7 @@ public class DefaultHttp2ConnectionEncoder implements Http2ConnectionEncoder { - // Writing headers may fail during the encode state if they violate HPACK limits. - Throwable failureCause = f.cause(); - if (failureCause == null) { -- stream.headersSent(); -+ stream.headersSent(isInformational); - } else { - lifecycleManager.onError(ctx, failureCause); - } -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Stream.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Stream.java -index 167087551b..3b654425cf 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Stream.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Stream.java -@@ -130,15 +130,41 @@ public interface Http2Stream { - V removeProperty(Http2Connection.PropertyKey key); - - /** -- * Indicates that headers has been sent to the remote on this stream. -+ * Indicates that headers have been sent to the remote endpoint on this stream. The first call to this method would -+ * be for the initial headers (see {@link #isHeadersSent()}} and the second call would indicate the trailers -+ * (see {@link #isTrailersReceived()}). -+ * @param isInformational {@code true} if the headers contain an informational status code (for responses only). - */ -- Http2Stream headersSent(); -+ Http2Stream headersSent(boolean isInformational); - - /** -- * Indicates whether or not headers was sent to the remote endpoint. -+ * Indicates whether or not headers were sent to the remote endpoint. - */ - boolean isHeadersSent(); - -+ /** -+ * Indicates whether or not trailers were sent to the remote endpoint. -+ */ -+ boolean isTrailersSent(); -+ -+ /** -+ * Indicates that headers have been received. The first call to this method would be for the initial headers -+ * (see {@link #isHeadersReceived()}} and the second call would indicate the trailers -+ * (see {@link #isTrailersReceived()}). -+ * @param isInformational {@code true} if the headers contain an informational status code (for responses only). -+ */ -+ Http2Stream headersReceived(boolean isInformational); -+ -+ /** -+ * Indicates whether or not the initial headers have been received. -+ */ -+ boolean isHeadersReceived(); -+ -+ /** -+ * Indicates whether or not the trailers have been received. -+ */ -+ boolean isTrailersReceived(); -+ - /** - * Indicates that a push promise was sent to the remote endpoint. - */ -diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java -index 6dc4266799..3fcf560eff 100644 ---- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java -+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java -@@ -21,6 +21,7 @@ import io.netty.channel.ChannelFuture; - import io.netty.channel.ChannelHandlerContext; - import io.netty.channel.ChannelPromise; - import io.netty.channel.DefaultChannelPromise; -+import io.netty.handler.codec.http.HttpResponseStatus; - import junit.framework.AssertionFailedError; - import org.junit.Before; - import org.junit.Test; -@@ -50,10 +51,10 @@ import static org.mockito.Mockito.anyBoolean; - import static org.mockito.Mockito.anyInt; - import static org.mockito.Mockito.anyLong; - import static org.mockito.Mockito.anyShort; --import static org.mockito.Mockito.eq; --import static org.mockito.Mockito.isNull; - import static org.mockito.Mockito.doAnswer; - import static org.mockito.Mockito.doNothing; -+import static org.mockito.Mockito.eq; -+import static org.mockito.Mockito.isNull; - import static org.mockito.Mockito.never; - import static org.mockito.Mockito.times; - import static org.mockito.Mockito.verify; -@@ -67,6 +68,8 @@ public class DefaultHttp2ConnectionDecoderTest { - private static final int STREAM_ID = 3; - private static final int PUSH_STREAM_ID = 2; - private static final int STREAM_DEPENDENCY_ID = 5; -+ private static final int STATE_RECV_HEADERS = 1; -+ private static final int STATE_RECV_TRAILERS = 1 << 1; - - private Http2ConnectionDecoder decoder; - private ChannelPromise promise; -@@ -122,11 +125,49 @@ public class DefaultHttp2ConnectionDecoderTest { - - promise = new DefaultChannelPromise(channel); - -+ final AtomicInteger headersReceivedState = new AtomicInteger(); - when(channel.isActive()).thenReturn(true); - when(stream.id()).thenReturn(STREAM_ID); - when(stream.state()).thenReturn(OPEN); - when(stream.open(anyBoolean())).thenReturn(stream); - when(pushStream.id()).thenReturn(PUSH_STREAM_ID); -+ doAnswer(new Answer() { -+ @Override -+ public Boolean answer(InvocationOnMock in) throws Throwable { -+ return (headersReceivedState.get() & STATE_RECV_HEADERS) != 0; -+ } -+ }).when(stream).isHeadersReceived(); -+ doAnswer(new Answer() { -+ @Override -+ public Boolean answer(InvocationOnMock in) throws Throwable { -+ return (headersReceivedState.get() & STATE_RECV_TRAILERS) != 0; -+ } -+ }).when(stream).isTrailersReceived(); -+ doAnswer(new Answer() { -+ @Override -+ public Http2Stream answer(InvocationOnMock in) throws Throwable { -+ boolean isInformational = in.getArgument(0); -+ if (isInformational) { -+ return stream; -+ } -+ for (;;) { -+ int current = headersReceivedState.get(); -+ int next = current; -+ if ((current & STATE_RECV_HEADERS) != 0) { -+ if ((current & STATE_RECV_TRAILERS) != 0) { -+ throw new IllegalStateException("already sent headers!"); -+ } -+ next |= STATE_RECV_TRAILERS; -+ } else { -+ next |= STATE_RECV_HEADERS; -+ } -+ if (headersReceivedState.compareAndSet(current, next)) { -+ break; -+ } -+ } -+ return stream; -+ } -+ }).when(stream).headersReceived(anyBoolean()); - doAnswer(new Answer() { - @Override - public Http2Stream answer(InvocationOnMock in) throws Throwable { -@@ -452,7 +493,65 @@ public class DefaultHttp2ConnectionDecoderTest { - eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false)); - } - -+ @Test(expected = Http2Exception.class) -+ public void trailersDoNotEndStreamThrows() throws Exception { -+ decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false); -+ // Trailers must end the stream! -+ decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false); -+ } -+ -+ @Test(expected = Http2Exception.class) -+ public void tooManyHeadersEOSThrows() throws Exception { -+ tooManyHeaderThrows(true); -+ } -+ -+ @Test(expected = Http2Exception.class) -+ public void tooManyHeadersNoEOSThrows() throws Exception { -+ tooManyHeaderThrows(false); -+ } -+ -+ private void tooManyHeaderThrows(boolean eos) throws Exception { -+ decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false); -+ decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true); -+ // We already received the trailers! -+ decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, eos); -+ } -+ -+ private static Http2Headers informationalHeaders() { -+ Http2Headers headers = new DefaultHttp2Headers(); -+ headers.status(HttpResponseStatus.CONTINUE.codeAsText()); -+ return headers; -+ } -+ -+ @Test -+ public void infoHeadersAndTrailersAllowed() throws Exception { -+ infoHeadersAndTrailersAllowed(true, 1); -+ } -+ - @Test -+ public void multipleInfoHeadersAndTrailersAllowed() throws Exception { -+ infoHeadersAndTrailersAllowed(true, 10); -+ } -+ -+ @Test(expected = Http2Exception.class) -+ public void infoHeadersAndTrailersNoEOSThrows() throws Exception { -+ infoHeadersAndTrailersAllowed(false, 1); -+ } -+ -+ @Test(expected = Http2Exception.class) -+ public void multipleInfoHeadersAndTrailersNoEOSThrows() throws Exception { -+ infoHeadersAndTrailersAllowed(false, 10); -+ } -+ -+ private void infoHeadersAndTrailersAllowed(boolean eos, int infoHeaderCount) throws Exception { -+ for (int i = 0; i < infoHeaderCount; ++i) { -+ decode().onHeadersRead(ctx, STREAM_ID, informationalHeaders(), 0, false); -+ } -+ decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false); -+ decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, eos); -+ } -+ -+ @Test() - public void headersReadForPromisedStreamShouldCloseStream() throws Exception { - when(stream.state()).thenReturn(RESERVED_REMOTE); - decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, true); -diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoderTest.java -index ac63f6dce9..4c5482ba9e 100644 ---- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoderTest.java -+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionEncoderTest.java -@@ -24,6 +24,7 @@ import io.netty.channel.ChannelHandlerContext; - import io.netty.channel.ChannelPipeline; - import io.netty.channel.ChannelPromise; - import io.netty.channel.DefaultChannelPromise; -+import io.netty.handler.codec.http.HttpResponseStatus; - import io.netty.handler.codec.http2.Http2RemoteFlowController.FlowControlled; - import io.netty.util.concurrent.ImmediateEventExecutor; - import junit.framework.AssertionFailedError; -@@ -59,11 +60,12 @@ import static org.mockito.Mockito.anyBoolean; - import static org.mockito.Mockito.anyInt; - import static org.mockito.Mockito.anyLong; - import static org.mockito.Mockito.anyShort; --import static org.mockito.Mockito.eq; - import static org.mockito.Mockito.doAnswer; - import static org.mockito.Mockito.doNothing; -+import static org.mockito.Mockito.eq; - import static org.mockito.Mockito.mock; - import static org.mockito.Mockito.never; -+import static org.mockito.Mockito.times; - import static org.mockito.Mockito.verify; - import static org.mockito.Mockito.verifyNoMoreInteractions; - import static org.mockito.Mockito.when; -@@ -342,11 +344,208 @@ public class DefaultHttp2ConnectionEncoderTest { - eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(promise)); - } - -+ @Test -+ public void trailersDoNotEndStreamThrows() { -+ writeAllFlowControlledFrames(); -+ final int streamId = 6; -+ ChannelPromise promise = newPromise(); -+ encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise); -+ -+ ChannelPromise promise2 = newPromise(); -+ ChannelFuture future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2); -+ assertTrue(future.isDone()); -+ assertFalse(future.isSuccess()); -+ -+ verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(promise)); -+ } -+ -+ @Test -+ public void trailersDoNotEndStreamWithDataThrows() { -+ writeAllFlowControlledFrames(); -+ final int streamId = 6; -+ ChannelPromise promise = newPromise(); -+ encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise); -+ -+ Http2Stream stream = connection.stream(streamId); -+ when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true); -+ -+ ChannelPromise promise2 = newPromise(); -+ ChannelFuture future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2); -+ assertTrue(future.isDone()); -+ assertFalse(future.isSuccess()); -+ -+ verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(promise)); -+ } -+ -+ @Test -+ public void tooManyHeadersNoEOSThrows() { -+ tooManyHeadersThrows(false); -+ } -+ -+ @Test -+ public void tooManyHeadersEOSThrows() { -+ tooManyHeadersThrows(true); -+ } -+ -+ private void tooManyHeadersThrows(boolean eos) { -+ writeAllFlowControlledFrames(); -+ final int streamId = 6; -+ ChannelPromise promise = newPromise(); -+ encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise); -+ ChannelPromise promise2 = newPromise(); -+ encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true, promise2); -+ -+ ChannelPromise promise3 = newPromise(); -+ ChannelFuture future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3); -+ assertTrue(future.isDone()); -+ assertFalse(future.isSuccess()); -+ -+ verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(promise)); -+ verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true), eq(promise2)); -+ } -+ -+ @Test -+ public void infoHeadersAndTrailersAllowed() throws Exception { -+ infoHeadersAndTrailers(true, 1); -+ } -+ -+ @Test -+ public void multipleInfoHeadersAndTrailersAllowed() throws Exception { -+ infoHeadersAndTrailers(true, 10); -+ } -+ -+ @Test -+ public void infoHeadersAndTrailersNoEOSThrows() throws Exception { -+ infoHeadersAndTrailers(false, 1); -+ } -+ -+ @Test -+ public void multipleInfoHeadersAndTrailersNoEOSThrows() throws Exception { -+ infoHeadersAndTrailers(false, 10); -+ } -+ -+ private void infoHeadersAndTrailers(boolean eos, int infoHeaderCount) { -+ writeAllFlowControlledFrames(); -+ final int streamId = 6; -+ Http2Headers infoHeaders = informationalHeaders(); -+ for (int i = 0; i < infoHeaderCount; ++i) { -+ encoder.writeHeaders(ctx, streamId, infoHeaders, 0, false, newPromise()); -+ } -+ ChannelPromise promise2 = newPromise(); -+ encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2); -+ -+ ChannelPromise promise3 = newPromise(); -+ ChannelFuture future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3); -+ assertTrue(future.isDone()); -+ assertEquals(eos, future.isSuccess()); -+ -+ verify(writer, times(infoHeaderCount)).writeHeaders(eq(ctx), eq(streamId), eq(infoHeaders), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class)); -+ verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(promise2)); -+ if (eos) { -+ verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true), eq(promise3)); -+ } -+ } -+ -+ private static Http2Headers informationalHeaders() { -+ Http2Headers headers = new DefaultHttp2Headers(); -+ headers.status(HttpResponseStatus.CONTINUE.codeAsText()); -+ return headers; -+ } -+ -+ @Test -+ public void tooManyHeadersWithDataNoEOSThrows() { -+ tooManyHeadersWithDataThrows(false); -+ } -+ -+ @Test -+ public void tooManyHeadersWithDataEOSThrows() { -+ tooManyHeadersWithDataThrows(true); -+ } -+ -+ private void tooManyHeadersWithDataThrows(boolean eos) { -+ writeAllFlowControlledFrames(); -+ final int streamId = 6; -+ ChannelPromise promise = newPromise(); -+ encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise); -+ -+ Http2Stream stream = connection.stream(streamId); -+ when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true); -+ -+ ChannelPromise promise2 = newPromise(); -+ encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, true, promise2); -+ -+ ChannelPromise promise3 = newPromise(); -+ ChannelFuture future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3); -+ assertTrue(future.isDone()); -+ assertFalse(future.isSuccess()); -+ -+ verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(promise)); -+ verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true), eq(promise2)); -+ } -+ -+ @Test -+ public void infoHeadersAndTrailersWithDataAllowed() { -+ infoHeadersAndTrailersWithData(true, 1); -+ } -+ -+ @Test -+ public void multipleInfoHeadersAndTrailersWithDataAllowed() { -+ infoHeadersAndTrailersWithData(true, 10); -+ } -+ -+ @Test -+ public void infoHeadersAndTrailersWithDataNoEOSThrows() { -+ infoHeadersAndTrailersWithData(false, 1); -+ } -+ -+ @Test -+ public void multipleInfoHeadersAndTrailersWithDataNoEOSThrows() { -+ infoHeadersAndTrailersWithData(false, 10); -+ } -+ -+ private void infoHeadersAndTrailersWithData(boolean eos, int infoHeaderCount) { -+ writeAllFlowControlledFrames(); -+ final int streamId = 6; -+ Http2Headers infoHeaders = informationalHeaders(); -+ for (int i = 0; i < infoHeaderCount; ++i) { -+ encoder.writeHeaders(ctx, streamId, infoHeaders, 0, false, newPromise()); -+ } -+ -+ Http2Stream stream = connection.stream(streamId); -+ when(remoteFlow.hasFlowControlled(eq(stream))).thenReturn(true); -+ -+ ChannelPromise promise2 = newPromise(); -+ encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, false, promise2); -+ -+ ChannelPromise promise3 = newPromise(); -+ ChannelFuture future = encoder.writeHeaders(ctx, streamId, EmptyHttp2Headers.INSTANCE, 0, eos, promise3); -+ assertTrue(future.isDone()); -+ assertEquals(eos, future.isSuccess()); -+ -+ verify(writer, times(infoHeaderCount)).writeHeaders(eq(ctx), eq(streamId), eq(infoHeaders), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class)); -+ verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), eq(promise2)); -+ if (eos) { -+ verify(writer, times(1)).writeHeaders(eq(ctx), eq(streamId), eq(EmptyHttp2Headers.INSTANCE), eq(0), -+ eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(true), eq(promise3)); -+ } -+ } -+ - @Test - public void pushPromiseWriteAfterGoAwayReceivedShouldFail() throws Exception { - createStream(STREAM_ID, false); - goAwayReceived(0); -- ChannelFuture future = encoder.writePushPromise(ctx, STREAM_ID, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, -+ ChannelFuture future = encoder.writePushPromise(ctx, STREAM_ID, PUSH_STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, - newPromise()); - assertTrue(future.isDone()); - assertFalse(future.isSuccess()); -diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java -index 28a5c5b44b..33393afc0a 100644 ---- a/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java -+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java -@@ -360,49 +360,6 @@ public class InboundHttp2ToHttpAdapterTest { - } - } - -- @Test -- public void clientRequestMultipleHeaders() throws Exception { -- boostrapEnv(1, 1, 1); -- // writeHeaders will implicitly add an END_HEADERS tag each time and so this test does not follow the HTTP -- // message flow. We currently accept this message flow and just add the second headers to the trailing headers. -- final String text = ""; -- final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); -- final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, -- "/some/path/resource2", content, true); -- try { -- HttpHeaders httpHeaders = request.headers(); -- httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); -- httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); -- httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16); -- HttpHeaders trailingHeaders = request.trailingHeaders(); -- trailingHeaders.set(of("FoO"), of("goo")); -- trailingHeaders.set(of("foO2"), of("goo2")); -- trailingHeaders.add(of("fOo2"), of("goo3")); -- final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( -- new AsciiString("/some/path/resource2")); -- final Http2Headers http2Headers2 = new DefaultHttp2Headers() -- .set(new AsciiString("foo"), new AsciiString("goo")) -- .set(new AsciiString("foo2"), new AsciiString("goo2")) -- .add(new AsciiString("foo2"), new AsciiString("goo3")); -- runInChannel(clientChannel, new Http2Runnable() { -- @Override -- public void run() throws Http2Exception { -- clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); -- clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers2, 0, false, newPromiseClient()); -- clientHandler.encoder().writeData(ctxClient(), 3, content.retain(), 0, true, newPromiseClient()); -- clientChannel.flush(); -- } -- }); -- awaitRequests(); -- ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(FullHttpMessage.class); -- verify(serverListener).messageReceived(requestCaptor.capture()); -- capturedRequests = requestCaptor.getAllValues(); -- assertEquals(request, capturedRequests.get(0)); -- } finally { -- request.release(); -- } -- } -- - @Test - public void clientRequestTrailingHeaders() throws Exception { - boostrapEnv(1, 1, 1); --- -2.23.0 - diff --git a/CVE-2021-21295.patch b/CVE-2021-21295.patch deleted file mode 100644 index 9a28be4afc9abffda3eb8a7536669a129b715e36..0000000000000000000000000000000000000000 --- a/CVE-2021-21295.patch +++ /dev/null @@ -1,547 +0,0 @@ -From 89c241e3b1795ff257af4ad6eadc616cb2fb3dc4 Mon Sep 17 00:00:00 2001 -From: Norman Maurer -Date: Tue, 9 Mar 2021 08:20:09 +0100 -Subject: [PATCH] Merge pull request from GHSA-wm47-8v5p-wjpj - -Motivation: - -As stated by https://tools.ietf.org/html/rfc7540#section-8.1.2.6 we should report a stream error if the content-length does not match the sum of all data frames. - -Modifications: - -- Verify that the sum of data frames match if a content-length header was send. -- Handle multiple content-length headers and also handle negative values -- Add io.netty.http2.validateContentLength system property which allows to disable the more strict validation -- Add unit tests - -Result: - -Correctly handle the case when the content-length header was included but not match what is send and also when content-length header is invalid ---- - .../handler/codec/http/HttpObjectDecoder.java | 48 +------ - .../io/netty/handler/codec/http/HttpUtil.java | 87 ++++++++++++ - .../http2/DefaultHttp2ConnectionDecoder.java | 100 ++++++++++++-- - .../DefaultHttp2ConnectionDecoderTest.java | 128 ++++++++++++++++++ - 4 files changed, 313 insertions(+), 50 deletions(-) - -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -index b52e36ac92..82b0c365c1 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -@@ -16,7 +16,6 @@ - package io.netty.handler.codec.http; - - import static io.netty.util.internal.ObjectUtil.checkPositive; --import static io.netty.util.internal.StringUtil.COMMA; - - import io.netty.buffer.ByteBuf; - import io.netty.buffer.Unpooled; -@@ -638,49 +637,16 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - value = null; - - List contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH); -- - if (!contentLengthFields.isEmpty()) { -+ HttpVersion version = message.protocolVersion(); -+ boolean isHttp10OrEarlier = version.majorVersion() < 1 || (version.majorVersion() == 1 -+ && version.minorVersion() == 0); - // Guard against multiple Content-Length headers as stated in - // https://tools.ietf.org/html/rfc7230#section-3.3.2: -- // -- // If a message is received that has multiple Content-Length header -- // fields with field-values consisting of the same decimal value, or a -- // single Content-Length header field with a field value containing a -- // list of identical decimal values (e.g., "Content-Length: 42, 42"), -- // indicating that duplicate Content-Length header fields have been -- // generated or combined by an upstream message processor, then the -- // recipient MUST either reject the message as invalid or replace the -- // duplicated field-values with a single valid Content-Length field -- // containing that decimal value prior to determining the message body -- // length or forwarding the message. -- boolean multipleContentLengths = -- contentLengthFields.size() > 1 || contentLengthFields.get(0).indexOf(COMMA) >= 0; -- if (multipleContentLengths && message.protocolVersion() == HttpVersion.HTTP_1_1) { -- if (allowDuplicateContentLengths) { -- // Find and enforce that all Content-Length values are the same -- String firstValue = null; -- for (String field : contentLengthFields) { -- String[] tokens = COMMA_PATTERN.split(field, -1); -- for (String token : tokens) { -- String trimmed = token.trim(); -- if (firstValue == null) { -- firstValue = trimmed; -- } else if (!trimmed.equals(firstValue)) { -- throw new IllegalArgumentException( -- "Multiple Content-Length values found: " + contentLengthFields); -- } -- } -- } -- // Replace the duplicated field-values with a single valid Content-Length field -- headers.set(HttpHeaderNames.CONTENT_LENGTH, firstValue); -- contentLength = Long.parseLong(firstValue); -- } else { -- // Reject the message as invalid -- throw new IllegalArgumentException( -- "Multiple Content-Length values found: " + contentLengthFields); -- } -- } else { -- contentLength = Long.parseLong(contentLengthFields.get(0)); -+ contentLength = HttpUtil.normalizeAndGetContentLength(contentLengthFields, -+ isHttp10OrEarlier, allowDuplicateContentLengths); -+ if (contentLength != -1) { -+ headers.set(HttpHeaderNames.CONTENT_LENGTH, contentLength); - } - } - -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java -index 035cd754cf..3d1c57f5ac 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java -@@ -25,6 +25,11 @@ import java.nio.charset.UnsupportedCharsetException; - import java.util.Iterator; - import java.util.List; - -+import io.netty.handler.codec.Headers; -+import io.netty.util.internal.UnstableApi; -+ -+import static io.netty.util.internal.StringUtil.COMMA; -+ - /** - * Utility methods useful in the HTTP context. - */ -@@ -40,6 +45,7 @@ public final class HttpUtil { - static final EmptyHttpHeaders EMPTY_HEADERS = new EmptyHttpHeaders(); - private static final AsciiString CHARSET_EQUALS = AsciiString.of(HttpHeaderValues.CHARSET + "="); - private static final AsciiString SEMICOLON = AsciiString.of(";"); -+ private static final String COMMA_STRING = String.valueOf(COMMA); - - private HttpUtil() { } - -@@ -519,4 +525,85 @@ public final class HttpUtil { - return contentTypeValue.length() > 0 ? contentTypeValue : null; - } - } -+ -+ /** -+ * Validates, and optionally extracts the content length from headers. This method is not intended for -+ * general use, but is here to be shared between HTTP/1 and HTTP/2 parsing. -+ * -+ * @param contentLengthFields the content-length header fields. -+ * @param isHttp10OrEarlier {@code true} if we are handling HTTP/1.0 or earlier -+ * @param allowDuplicateContentLengths {@code true} if multiple, identical-value content lengths should be allowed. -+ * @return the normalized content length from the headers or {@code -1} if the fields were empty. -+ * @throws IllegalArgumentException if the content-length fields are not valid -+ */ -+ @UnstableApi -+ public static long normalizeAndGetContentLength( -+ List contentLengthFields, boolean isHttp10OrEarlier, -+ boolean allowDuplicateContentLengths) { -+ if (contentLengthFields.isEmpty()) { -+ return -1; -+ } -+ -+ // Guard against multiple Content-Length headers as stated in -+ // https://tools.ietf.org/html/rfc7230#section-3.3.2: -+ // -+ // If a message is received that has multiple Content-Length header -+ // fields with field-values consisting of the same decimal value, or a -+ // single Content-Length header field with a field value containing a -+ // list of identical decimal values (e.g., "Content-Length: 42, 42"), -+ // indicating that duplicate Content-Length header fields have been -+ // generated or combined by an upstream message processor, then the -+ // recipient MUST either reject the message as invalid or replace the -+ // duplicated field-values with a single valid Content-Length field -+ // containing that decimal value prior to determining the message body -+ // length or forwarding the message. -+ String firstField = contentLengthFields.get(0).toString(); -+ boolean multipleContentLengths = -+ contentLengthFields.size() > 1 || firstField.indexOf(COMMA) >= 0; -+ -+ if (multipleContentLengths && !isHttp10OrEarlier) { -+ if (allowDuplicateContentLengths) { -+ // Find and enforce that all Content-Length values are the same -+ String firstValue = null; -+ for (CharSequence field : contentLengthFields) { -+ String[] tokens = field.toString().split(COMMA_STRING, -1); -+ for (String token : tokens) { -+ String trimmed = token.trim(); -+ if (firstValue == null) { -+ firstValue = trimmed; -+ } else if (!trimmed.equals(firstValue)) { -+ throw new IllegalArgumentException( -+ "Multiple Content-Length values found: " + contentLengthFields); -+ } -+ } -+ } -+ // Replace the duplicated field-values with a single valid Content-Length field -+ firstField = firstValue; -+ } else { -+ // Reject the message as invalid -+ throw new IllegalArgumentException( -+ "Multiple Content-Length values found: " + contentLengthFields); -+ } -+ } -+ // Ensure we not allow sign as part of the content-length: -+ // See https://github.com/squid-cache/squid/security/advisories/GHSA-qf3v-rc95-96j5 -+ if (!Character.isDigit(firstField.charAt(0))) { -+ // Reject the message as invalid -+ throw new IllegalArgumentException( -+ "Content-Length value is not a number: " + firstField); -+ } -+ try { -+ final long value = Long.parseLong(firstField); -+ if (value < 0) { -+ // Reject the message as invalid -+ throw new IllegalArgumentException( -+ "Content-Length value must be >=0: " + value); -+ } -+ return value; -+ } catch (NumberFormatException e) { -+ // Reject the message as invalid -+ throw new IllegalArgumentException( -+ "Content-Length value is not a number: " + firstField, e); -+ } -+ } - } -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java -index 4027978651..f04a0b5a69 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java -@@ -16,8 +16,11 @@ package io.netty.handler.codec.http2; - - import io.netty.buffer.ByteBuf; - import io.netty.channel.ChannelHandlerContext; -+import io.netty.handler.codec.http.HttpHeaderNames; - import io.netty.handler.codec.http.HttpStatusClass; -+import io.netty.handler.codec.http.HttpUtil; - import io.netty.handler.codec.http2.Http2Connection.Endpoint; -+import io.netty.util.internal.SystemPropertyUtil; - import io.netty.util.internal.UnstableApi; - import io.netty.util.internal.logging.InternalLogger; - import io.netty.util.internal.logging.InternalLoggerFactory; -@@ -49,6 +52,8 @@ import static java.lang.Math.min; - */ - @UnstableApi - public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { -+ private static final boolean VALIDATE_CONTENT_LENGTH = -+ SystemPropertyUtil.getBoolean("io.netty.http2.validateContentLength", true); - private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultHttp2ConnectionDecoder.class); - private Http2FrameListener internalFrameListener = new PrefaceFrameListener(); - private final Http2Connection connection; -@@ -57,6 +62,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - private final Http2FrameReader frameReader; - private Http2FrameListener listener; - private final Http2PromisedRequestVerifier requestVerifier; -+ private final Http2Connection.PropertyKey contentLengthKey; - - public DefaultHttp2ConnectionDecoder(Http2Connection connection, - Http2ConnectionEncoder encoder, -@@ -69,6 +75,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - Http2FrameReader frameReader, - Http2PromisedRequestVerifier requestVerifier) { - this.connection = checkNotNull(connection, "connection"); -+ contentLengthKey = this.connection.newKey(); - this.frameReader = checkNotNull(frameReader, "frameReader"); - this.encoder = checkNotNull(encoder, "encoder"); - this.requestVerifier = checkNotNull(requestVerifier, "requestVerifier"); -@@ -171,6 +178,23 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - listener.onUnknownFrame(ctx, frameType, streamId, flags, payload); - } - -+ // See https://tools.ietf.org/html/rfc7540#section-8.1.2.6 -+ private void verifyContentLength(Http2Stream stream, int data, boolean isEnd) throws Http2Exception { -+ if (!VALIDATE_CONTENT_LENGTH) { -+ return; -+ } -+ ContentLength contentLength = stream.getProperty(contentLengthKey); -+ if (contentLength != null) { -+ try { -+ contentLength.increaseReceivedBytes(connection.isServer(), stream.id(), data, isEnd); -+ } finally { -+ if (isEnd) { -+ stream.removeProperty(contentLengthKey); -+ } -+ } -+ } -+ } -+ - /** - * Handles all inbound frames from the network. - */ -@@ -180,7 +204,8 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - boolean endOfStream) throws Http2Exception { - Http2Stream stream = connection.stream(streamId); - Http2LocalFlowController flowController = flowController(); -- int bytesToReturn = data.readableBytes() + padding; -+ int readable = data.readableBytes(); -+ int bytesToReturn = readable + padding; - - final boolean shouldIgnore; - try { -@@ -207,7 +232,6 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - // All bytes have been consumed. - return bytesToReturn; - } -- - Http2Exception error = null; - switch (stream.state()) { - case OPEN: -@@ -235,6 +259,8 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - throw error; - } - -+ verifyContentLength(stream, readable, endOfStream); -+ - // Call back the application and retrieve the number of bytes that have been - // immediately processed. - bytesToReturn = listener.onDataRead(ctx, streamId, data, padding, endOfStream); -@@ -315,14 +341,34 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - stream.state()); - } - -- stream.headersReceived(isInformational); -- encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive); -- -- listener.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream); -+ if (!stream.isHeadersReceived()) { -+ // extract the content-length header -+ List contentLength = headers.getAll(HttpHeaderNames.CONTENT_LENGTH); -+ if (contentLength != null && !contentLength.isEmpty()) { -+ try { -+ long cLength = HttpUtil.normalizeAndGetContentLength(contentLength, false, true); -+ if (cLength != -1) { -+ headers.setLong(HttpHeaderNames.CONTENT_LENGTH, cLength); -+ stream.setProperty(contentLengthKey, new ContentLength(cLength)); -+ } -+ } catch (IllegalArgumentException e) { -+ throw streamError(stream.id(), PROTOCOL_ERROR, -+ "Multiple content-length headers received", e); -+ } -+ } -+ } - -- // If the headers completes this stream, close it. -- if (endOfStream) { -- lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture()); -+ stream.headersReceived(isInformational); -+ try { -+ verifyContentLength(stream, 0, endOfStream); -+ encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive); -+ listener.onHeadersRead(ctx, streamId, headers, streamDependency, -+ weight, exclusive, padding, endOfStream); -+ } finally { -+ // If the headers completes this stream, close it. -+ if (endOfStream) { -+ lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture()); -+ } - } - } - -@@ -673,4 +719,40 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - onUnknownFrame0(ctx, frameType, streamId, flags, payload); - } - } -+ -+ private static final class ContentLength { -+ private final long expected; -+ private long seen; -+ -+ ContentLength(long expected) { -+ this.expected = expected; -+ } -+ -+ void increaseReceivedBytes(boolean server, int streamId, int bytes, boolean isEnd) throws Http2Exception { -+ seen += bytes; -+ // Check for overflow -+ if (seen < 0) { -+ throw streamError(streamId, PROTOCOL_ERROR, -+ "Received amount of data did overflow and so not match content-length header %d", expected); -+ } -+ // Check if we received more data then what was advertised via the content-length header. -+ if (seen > expected) { -+ throw streamError(streamId, PROTOCOL_ERROR, -+ "Received amount of data %d does not match content-length header %d", seen, expected); -+ } -+ -+ if (isEnd) { -+ if (seen == 0 && !server) { -+ // This may be a response to a HEAD request, let's just allow it. -+ return; -+ } -+ -+ // Check that we really saw what was told via the content-length header. -+ if (expected > seen) { -+ throw streamError(streamId, PROTOCOL_ERROR, -+ "Received amount of data %d does not match content-length header %d", seen, expected); -+ } -+ } -+ } -+ } - } -diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java -index 3fcf560eff..467c6a9904 100644 ---- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java -+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java -@@ -21,17 +21,21 @@ import io.netty.channel.ChannelFuture; - import io.netty.channel.ChannelHandlerContext; - import io.netty.channel.ChannelPromise; - import io.netty.channel.DefaultChannelPromise; -+import io.netty.handler.codec.http.HttpHeaderNames; - import io.netty.handler.codec.http.HttpResponseStatus; - import junit.framework.AssertionFailedError; - import org.junit.Before; - import org.junit.Test; - import org.mockito.ArgumentCaptor; -+import org.mockito.ArgumentMatchers; - import org.mockito.Mock; - import org.mockito.MockitoAnnotations; - import org.mockito.invocation.InvocationOnMock; - import org.mockito.stubbing.Answer; - - import java.util.Collections; -+import java.util.IdentityHashMap; -+import java.util.Map; - import java.util.concurrent.atomic.AtomicInteger; - - import static io.netty.buffer.Unpooled.EMPTY_BUFFER; -@@ -130,6 +134,21 @@ public class DefaultHttp2ConnectionDecoderTest { - when(stream.id()).thenReturn(STREAM_ID); - when(stream.state()).thenReturn(OPEN); - when(stream.open(anyBoolean())).thenReturn(stream); -+ -+ final Map properties = new IdentityHashMap(); -+ when(stream.getProperty(ArgumentMatchers.any())).thenAnswer(new Answer() { -+ @Override -+ public Object answer(InvocationOnMock invocationOnMock) { -+ return properties.get(invocationOnMock.getArgument(0)); -+ } -+ }); -+ when(stream.setProperty(ArgumentMatchers.any(), any())).then(new Answer() { -+ @Override -+ public Object answer(InvocationOnMock invocationOnMock) { -+ return properties.put(invocationOnMock.getArgument(0), invocationOnMock.getArgument(1)); -+ } -+ }); -+ - when(pushStream.id()).thenReturn(PUSH_STREAM_ID); - doAnswer(new Answer() { - @Override -@@ -751,6 +770,115 @@ public class DefaultHttp2ConnectionDecoderTest { - verify(listener).onGoAwayRead(eq(ctx), eq(1), eq(2L), eq(EMPTY_BUFFER)); - } - -+ @Test(expected = Http2Exception.StreamException.class) -+ public void dataContentLengthMissmatch() throws Exception { -+ dataContentLengthInvalid(false); -+ } -+ -+ @Test(expected = Http2Exception.StreamException.class) -+ public void dataContentLengthInvalid() throws Exception { -+ dataContentLengthInvalid(true); -+ } -+ -+ private void dataContentLengthInvalid(boolean negative) throws Exception { -+ final ByteBuf data = dummyData(); -+ int padding = 10; -+ int processedBytes = data.readableBytes() + padding; -+ mockFlowControl(processedBytes); -+ try { -+ decode().onHeadersRead(ctx, STREAM_ID, new DefaultHttp2Headers() -+ .setLong(HttpHeaderNames.CONTENT_LENGTH, negative ? -1L : 1L), padding, false); -+ decode().onDataRead(ctx, STREAM_ID, data, padding, true); -+ verify(localFlow).receiveFlowControlledFrame(eq(stream), eq(data), eq(padding), eq(true)); -+ verify(localFlow).consumeBytes(eq(stream), eq(processedBytes)); -+ -+ verify(listener, times(1)).onHeadersRead(eq(ctx), anyInt(), -+ any(Http2Headers.class), eq(0), eq(DEFAULT_PRIORITY_WEIGHT), eq(false), -+ eq(padding), eq(false)); -+ // Verify that the event was absorbed and not propagated to the observer. -+ verify(listener, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean()); -+ } finally { -+ data.release(); -+ } -+ } -+ -+ @Test(expected = Http2Exception.StreamException.class) -+ public void headersContentLengthPositiveSign() throws Exception { -+ headersContentLengthSign("+1"); -+ } -+ -+ @Test(expected = Http2Exception.StreamException.class) -+ public void headersContentLengthNegativeSign() throws Exception { -+ headersContentLengthSign("-1"); -+ } -+ -+ private void headersContentLengthSign(String length) throws Exception { -+ int padding = 10; -+ when(connection.isServer()).thenReturn(true); -+ decode().onHeadersRead(ctx, STREAM_ID, new DefaultHttp2Headers() -+ .set(HttpHeaderNames.CONTENT_LENGTH, length), padding, false); -+ -+ // Verify that the event was absorbed and not propagated to the observer. -+ verify(listener, never()).onHeadersRead(eq(ctx), anyInt(), -+ any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean()); -+ } -+ -+ @Test(expected = Http2Exception.StreamException.class) -+ public void headersContentLengthMissmatch() throws Exception { -+ headersContentLength(false); -+ } -+ -+ @Test(expected = Http2Exception.StreamException.class) -+ public void headersContentLengthInvalid() throws Exception { -+ headersContentLength(true); -+ } -+ -+ private void headersContentLength(boolean negative) throws Exception { -+ int padding = 10; -+ when(connection.isServer()).thenReturn(true); -+ decode().onHeadersRead(ctx, STREAM_ID, new DefaultHttp2Headers() -+ .setLong(HttpHeaderNames.CONTENT_LENGTH, negative ? -1L : 1L), padding, true); -+ -+ // Verify that the event was absorbed and not propagated to the observer. -+ verify(listener, never()).onHeadersRead(eq(ctx), anyInt(), -+ any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean()); -+ } -+ -+ @Test -+ public void multipleHeadersContentLengthSame() throws Exception { -+ multipleHeadersContentLength(true); -+ } -+ -+ @Test(expected = Http2Exception.StreamException.class) -+ public void multipleHeadersContentLengthDifferent() throws Exception { -+ multipleHeadersContentLength(false); -+ } -+ -+ private void multipleHeadersContentLength(boolean same) throws Exception { -+ int padding = 10; -+ when(connection.isServer()).thenReturn(true); -+ Http2Headers headers = new DefaultHttp2Headers(); -+ if (same) { -+ headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 0); -+ headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 0); -+ } else { -+ headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 0); -+ headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 1); -+ } -+ -+ decode().onHeadersRead(ctx, STREAM_ID, headers, padding, true); -+ -+ if (same) { -+ verify(listener, times(1)).onHeadersRead(eq(ctx), anyInt(), -+ any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean()); -+ assertEquals(1, headers.getAll(HttpHeaderNames.CONTENT_LENGTH).size()); -+ } else { -+ // Verify that the event was absorbed and not propagated to the observer. -+ verify(listener, never()).onHeadersRead(eq(ctx), anyInt(), -+ any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean()); -+ } -+ } -+ - private static ByteBuf dummyData() { - // The buffer is purposely 8 bytes so it will even work for a ping frame. - return wrappedBuffer("abcdefgh".getBytes(UTF_8)); --- -2.23.0 - diff --git a/CVE-2021-21409.patch b/CVE-2021-21409.patch deleted file mode 100644 index 889de61705be11dc93f1c04e0370c8cfeefe9153..0000000000000000000000000000000000000000 --- a/CVE-2021-21409.patch +++ /dev/null @@ -1,51 +0,0 @@ -From b0fa4d5aab4215f3c22ce6123dd8dd5f38dc0432 Mon Sep 17 00:00:00 2001 -From: Norman Maurer -Date: Tue, 30 Mar 2021 09:40:47 +0200 -Subject: [PATCH] Merge pull request from GHSA-f256-j965-7f32 - -Motivation: - -We also need to ensure that all the header validation is done when a single header with the endStream flag is received - -Modifications: - -- Adjust code to always enforce the validation -- Add more unit tests - -Result: - -Always correctly validate ---- - .../handler/codec/http2/DefaultHttp2ConnectionDecoder.java | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java -index f04a0b5a69..097ac8cdad 100644 ---- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java -+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java -@@ -300,10 +300,13 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { - Http2Stream stream = connection.stream(streamId); - boolean allowHalfClosedRemote = false; -+ boolean isTrailers = false; - if (stream == null && !connection.streamMayHaveExisted(streamId)) { - stream = connection.remote().createStream(streamId, endOfStream); - // Allow the state to be HALF_CLOSE_REMOTE if we're creating it in that state. - allowHalfClosedRemote = stream.state() == HALF_CLOSED_REMOTE; -+ } else if (stream != null) { -+ isTrailers = stream.isHeadersReceived(); - } - - if (shouldIgnoreHeadersOrDataFrame(ctx, streamId, stream, "HEADERS")) { -@@ -341,7 +344,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { - stream.state()); - } - -- if (!stream.isHeadersReceived()) { -+ if (!isTrailers) { - // extract the content-length header - List contentLength = headers.getAll(HttpHeaderNames.CONTENT_LENGTH); - if (contentLength != null && !contentLength.isEmpty()) { --- -2.23.0 - diff --git a/CVE-2021-37136.patch b/CVE-2021-37136.patch deleted file mode 100644 index 7f7e7958c6fa7917bae4f67732ae87b32b727915..0000000000000000000000000000000000000000 --- a/CVE-2021-37136.patch +++ /dev/null @@ -1,95 +0,0 @@ -From 41d3d61a61608f2223bb364955ab2045dd5e4020 Mon Sep 17 00:00:00 2001 -From: Norman Maurer -Date: Thu, 9 Sep 2021 14:53:58 +0200 -Subject: [PATCH] Merge pull request from GHSA-grg4-wf29-r9vv - -Motivation: - -We should do the Bzip2 decoding in a streaming fashion and so ensure we propagate the buffer as soon as possible through the pipeline. This allows the users to release these buffers as fast as possible. - -Modification: - -- Change the Bzip2Decoder to do the decompression of data in a streaming fashion. -- Add some safety check to ensure the block length never execeeds the maximum (as defined in the spec) - -Result: - -No more risk of an OOME by decompress some large data via bzip2. - -Thanks to Ori Hollander of JFrog Security for reporting the issue. - -(we got acquired during the process and now Vdoo is part of JFrog company) ---- - .../codec/compression/Bzip2BlockDecompressor.java | 5 +++++ - .../handler/codec/compression/Bzip2Constants.java | 2 ++ - .../handler/codec/compression/Bzip2Decoder.java | 15 ++++++++------- - 3 files changed, 15 insertions(+), 7 deletions(-) - -diff --git a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2BlockDecompressor.java b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2BlockDecompressor.java -index 9b8ff3f04c9..801900c4873 100644 ---- a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2BlockDecompressor.java -+++ b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2BlockDecompressor.java -@@ -228,6 +228,11 @@ boolean decodeHuffmanData(final Bzip2HuffmanStageDecoder huffmanDecoder) { - bwtBlock[bwtBlockLength++] = nextByte; - } - } -+ if (bwtBlockLength > MAX_BLOCK_LENGTH) { -+ throw new DecompressionException("block length exceeds max block length: " -+ + bwtBlockLength + " > " + MAX_BLOCK_LENGTH); -+ } -+ - this.bwtBlockLength = bwtBlockLength; - initialiseInverseBWT(); - return true; -diff --git a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Constants.java b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Constants.java -index ba8fee54d39..087f45faa0b 100644 ---- a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Constants.java -+++ b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Constants.java -@@ -49,6 +49,8 @@ - static final int MIN_BLOCK_SIZE = 1; - static final int MAX_BLOCK_SIZE = 9; - -+ static final int MAX_BLOCK_LENGTH = MAX_BLOCK_SIZE * BASE_BLOCK_SIZE; -+ - /** - * Maximum possible Huffman alphabet size. - */ -diff --git a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Decoder.java b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Decoder.java -index 5434b41d199..61c14f62ab0 100644 ---- a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Decoder.java -+++ b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Decoder.java -@@ -291,26 +291,27 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t - } - - final int blockLength = blockDecompressor.blockLength(); -- final ByteBuf uncompressed = ctx.alloc().buffer(blockLength); -- boolean success = false; -+ ByteBuf uncompressed = ctx.alloc().buffer(blockLength); - try { - int uncByte; - while ((uncByte = blockDecompressor.read()) >= 0) { - uncompressed.writeByte(uncByte); - } -- -+ // We did read all the data, lets reset the state and do the CRC check. -+ currentState = State.INIT_BLOCK; - int currentBlockCRC = blockDecompressor.checkCRC(); - streamCRC = (streamCRC << 1 | streamCRC >>> 31) ^ currentBlockCRC; - - out.add(uncompressed); -- success = true; -+ uncompressed = null; - } finally { -- if (!success) { -+ if (uncompressed != null) { - uncompressed.release(); - } - } -- currentState = State.INIT_BLOCK; -- break; -+ // Return here so the ByteBuf that was put in the List will be forwarded to the user and so can be -+ // released as soon as possible. -+ return; - case EOF: - in.skipBytes(in.readableBytes()); - return; diff --git a/CVE-2021-37137.patch b/CVE-2021-37137.patch deleted file mode 100644 index 1c7617440a18302f6d6004b56be83f818b99f065..0000000000000000000000000000000000000000 --- a/CVE-2021-37137.patch +++ /dev/null @@ -1,193 +0,0 @@ -From 6da4956b31023ae967451e1d94ff51a746a9194f Mon Sep 17 00:00:00 2001 -From: Norman Maurer -Date: Thu, 9 Sep 2021 14:55:08 +0200 -Subject: [PATCH] Merge pull request from GHSA-9vjp-v76f-g363 - -Motivation: - -e Snappy frame decoder function doesn't restrict the size of the compressed data (and the uncompressed data) which may lead to excessive memory usage. Beside this it also may buffer reserved skippable chunks until the whole chunk was received which may lead to excessive memory usage as well. - -Modifications: - -- Add various validations for the max allowed size of a chunk -- Skip bytes on the fly when an skippable chunk is handled - -Result: - -No more risk of OOME. Thanks to Ori Hollander of JFrog Security for reporting the issue. ---- - .../handler/codec/compression/Snappy.java | 30 +++++++++--- - .../codec/compression/SnappyFrameDecoder.java | 46 ++++++++++++++++--- - 2 files changed, 62 insertions(+), 14 deletions(-) - -diff --git a/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java b/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java -index 0a2d1c09b32..c851e4d8d6c 100644 ---- a/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java -+++ b/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java -@@ -38,12 +38,11 @@ - private static final int COPY_2_BYTE_OFFSET = 2; - private static final int COPY_4_BYTE_OFFSET = 3; - -- private State state = State.READY; -+ private State state = State.READING_PREAMBLE; - private byte tag; - private int written; - - private enum State { -- READY, - READING_PREAMBLE, - READING_TAG, - READING_LITERAL, -@@ -51,7 +50,7 @@ - } - - public void reset() { -- state = State.READY; -+ state = State.READING_PREAMBLE; - tag = 0; - written = 0; - } -@@ -270,8 +269,6 @@ private static void encodeCopy(ByteBuf out, int offset, int length) { - public void decode(ByteBuf in, ByteBuf out) { - while (in.isReadable()) { - switch (state) { -- case READY: -- state = State.READING_PREAMBLE; - case READING_PREAMBLE: - int uncompressedLength = readPreamble(in); - if (uncompressedLength == PREAMBLE_NOT_FULL) { -@@ -281,7 +277,6 @@ public void decode(ByteBuf in, ByteBuf out) { - } - if (uncompressedLength == 0) { - // Should never happen, but it does mean we have nothing further to do -- state = State.READY; - return; - } - out.ensureWritable(uncompressedLength); -@@ -378,6 +373,27 @@ private static int readPreamble(ByteBuf in) { - return 0; - } - -+ /** -+ * Get the length varint (a series of bytes, where the lower 7 bits -+ * are data and the upper bit is a flag to indicate more bytes to be -+ * read). -+ * -+ * @param in The input buffer to get the preamble from -+ * @return The calculated length based on the input buffer, or 0 if -+ * no preamble is able to be calculated -+ */ -+ int getPreamble(ByteBuf in) { -+ if (state == State.READING_PREAMBLE) { -+ int readerIndex = in.readerIndex(); -+ try { -+ return readPreamble(in); -+ } finally { -+ in.readerIndex(readerIndex); -+ } -+ } -+ return 0; -+ } -+ - /** - * Reads a literal from the input buffer directly to the output buffer. - * A "literal" is an uncompressed segment of data stored directly in the -diff --git a/codec/src/main/java/io/netty/handler/codec/compression/SnappyFrameDecoder.java b/codec/src/main/java/io/netty/handler/codec/compression/SnappyFrameDecoder.java -index 74a12895946..51997596eb6 100644 ---- a/codec/src/main/java/io/netty/handler/codec/compression/SnappyFrameDecoder.java -+++ b/codec/src/main/java/io/netty/handler/codec/compression/SnappyFrameDecoder.java -@@ -45,13 +45,19 @@ - } - - private static final int SNAPPY_IDENTIFIER_LEN = 6; -+ // See https://github.com/google/snappy/blob/1.1.9/framing_format.txt#L95 - private static final int MAX_UNCOMPRESSED_DATA_SIZE = 65536 + 4; -+ // See https://github.com/google/snappy/blob/1.1.9/framing_format.txt#L82 -+ private static final int MAX_DECOMPRESSED_DATA_SIZE = 65536; -+ // See https://github.com/google/snappy/blob/1.1.9/framing_format.txt#L82 -+ private static final int MAX_COMPRESSED_CHUNK_SIZE = 16777216 - 1; - - private final Snappy snappy = new Snappy(); - private final boolean validateChecksums; - - private boolean started; - private boolean corrupted; -+ private int numBytesToSkip; - - /** - * Creates a new snappy-framed decoder with validation of checksums -@@ -82,6 +88,16 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t - return; - } - -+ if (numBytesToSkip != 0) { -+ // The last chunkType we detected was RESERVED_SKIPPABLE and we still have some bytes to skip. -+ int skipBytes = Math.min(numBytesToSkip, in.readableBytes()); -+ in.skipBytes(skipBytes); -+ numBytesToSkip -= skipBytes; -+ -+ // Let's return and try again. -+ return; -+ } -+ - try { - int idx = in.readerIndex(); - final int inSize = in.readableBytes(); -@@ -123,12 +139,15 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t - throw new DecompressionException("Received RESERVED_SKIPPABLE tag before STREAM_IDENTIFIER"); - } - -- if (inSize < 4 + chunkLength) { -- // TODO: Don't keep skippable bytes -- return; -- } -+ in.skipBytes(4); - -- in.skipBytes(4 + chunkLength); -+ int skipBytes = Math.min(chunkLength, in.readableBytes()); -+ in.skipBytes(skipBytes); -+ if (skipBytes != chunkLength) { -+ // We could skip all bytes, let's store the remaining so we can do so once we receive more -+ // data. -+ numBytesToSkip = chunkLength - skipBytes; -+ } - break; - case RESERVED_UNSKIPPABLE: - // The spec mandates that reserved unskippable chunks must immediately -@@ -141,7 +160,8 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t - throw new DecompressionException("Received UNCOMPRESSED_DATA tag before STREAM_IDENTIFIER"); - } - if (chunkLength > MAX_UNCOMPRESSED_DATA_SIZE) { -- throw new DecompressionException("Received UNCOMPRESSED_DATA larger than 65540 bytes"); -+ throw new DecompressionException("Received UNCOMPRESSED_DATA larger than " + -+ MAX_UNCOMPRESSED_DATA_SIZE + " bytes"); - } - - if (inSize < 4 + chunkLength) { -@@ -162,13 +182,25 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t - throw new DecompressionException("Received COMPRESSED_DATA tag before STREAM_IDENTIFIER"); - } - -+ if (chunkLength > MAX_COMPRESSED_CHUNK_SIZE) { -+ throw new DecompressionException("Received COMPRESSED_DATA that contains" + -+ " chunk that exceeds " + MAX_COMPRESSED_CHUNK_SIZE + " bytes"); -+ } -+ - if (inSize < 4 + chunkLength) { - return; - } - - in.skipBytes(4); - int checksum = in.readIntLE(); -- ByteBuf uncompressed = ctx.alloc().buffer(); -+ -+ int uncompressedSize = snappy.getPreamble(in); -+ if (uncompressedSize > MAX_DECOMPRESSED_DATA_SIZE) { -+ throw new DecompressionException("Received COMPRESSED_DATA that contains" + -+ " uncompressed data that exceeds " + MAX_DECOMPRESSED_DATA_SIZE + " bytes"); -+ } -+ -+ ByteBuf uncompressed = ctx.alloc().buffer(uncompressedSize, MAX_DECOMPRESSED_DATA_SIZE); - try { - if (validateChecksums) { - int oldWriterIndex = in.writerIndex(); diff --git a/CVE-2021-43797-pre.patch b/CVE-2021-43797-pre.patch deleted file mode 100644 index d18e213d3092085bd84795e05de0532b5e3948a8..0000000000000000000000000000000000000000 --- a/CVE-2021-43797-pre.patch +++ /dev/null @@ -1,231 +0,0 @@ -From 74187ebf123de466cb31270213b2464267a1cfd4 Mon Sep 17 00:00:00 2001 -From: Norman Maurer -Date: Wed, 26 Feb 2020 09:49:39 +0100 -Subject: [PATCH 1/1] More strict parsing of initial line / http headers - (#10058) - -Motivation: - -Our parsing of the initial line / http headers did treat some characters as separators which should better trigger an exception during parsing. - -Modifications: - -- Tighten up parsing of the inital line by follow recommentation of RFC7230 -- Restrict separators to OWS for http headers -- Add unit test - -Result: - -Stricter parsing of HTTP1 ---- - .../handler/codec/http/HttpObjectDecoder.java | 63 ++++++++++++++----- - .../codec/http/HttpRequestDecoderTest.java | 25 +++++++- - .../codec/http/HttpResponseDecoderTest.java | 2 +- - .../util/internal/AppendableCharSequence.java | 7 +++ - 4 files changed, 80 insertions(+), 17 deletions(-) - -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -index 82b0c36..b4de681 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -@@ -773,13 +773,13 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - int cStart; - int cEnd; - -- aStart = findNonWhitespace(sb, 0); -- aEnd = findWhitespace(sb, aStart); -+ aStart = findNonSPLenient(sb, 0); -+ aEnd = findSPLenient(sb, aStart); - -- bStart = findNonWhitespace(sb, aEnd); -- bEnd = findWhitespace(sb, bStart); -+ bStart = findNonSPLenient(sb, aEnd); -+ bEnd = findSPLenient(sb, bStart); - -- cStart = findNonWhitespace(sb, bEnd); -+ cStart = findNonSPLenient(sb, bEnd); - cEnd = findEndOfString(sb); - - return new String[] { -@@ -796,7 +796,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - int valueStart; - int valueEnd; - -- nameStart = findNonWhitespace(sb, 0); -+ nameStart = findNonWhitespace(sb, 0, false); - for (nameEnd = nameStart; nameEnd < length; nameEnd ++) { - char ch = sb.charAt(nameEnd); - // https://tools.ietf.org/html/rfc7230#section-3.2.4 -@@ -813,7 +813,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - // is done in the DefaultHttpHeaders implementation. - // - // In the case of decoding a response we will "skip" the whitespace. -- (!isDecodingRequest() && Character.isWhitespace(ch))) { -+ (!isDecodingRequest() && isOWS(ch))) { - break; - } - } -@@ -831,7 +831,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - } - - name = sb.subStringUnsafe(nameStart, nameEnd); -- valueStart = findNonWhitespace(sb, colonEnd); -+ valueStart = findNonWhitespace(sb, colonEnd, true); - if (valueStart == length) { - value = EMPTY_VALUE; - } else { -@@ -840,19 +840,45 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - } - } - -- private static int findNonWhitespace(AppendableCharSequence sb, int offset) { -+ private static int findNonSPLenient(AppendableCharSequence sb, int offset) { - for (int result = offset; result < sb.length(); ++result) { -- if (!Character.isWhitespace(sb.charAtUnsafe(result))) { -+ char c = sb.charAtUnsafe(result); -+ // See https://tools.ietf.org/html/rfc7230#section-3.5 -+ if (isSPLenient(c)) { -+ continue; -+ } -+ if (Character.isWhitespace(c)) { -+ // Any other whitespace delimiter is invalid -+ throw new IllegalArgumentException("Invalid separator"); -+ } -+ return result; -+ } -+ return sb.length(); -+ } -+ -+ private static int findSPLenient(AppendableCharSequence sb, int offset) { -+ for (int result = offset; result < sb.length(); ++result) { -+ if (isSPLenient(sb.charAtUnsafe(result))) { - return result; - } - } - return sb.length(); - } - -- private static int findWhitespace(AppendableCharSequence sb, int offset) { -+ private static boolean isSPLenient(char c) { -+ // See https://tools.ietf.org/html/rfc7230#section-3.5 -+ return c == ' ' || c == (char) 0x09 || c == (char) 0x0B || c == (char) 0x0C || c == (char) 0x0D; -+ } -+ -+ private static int findNonWhitespace(AppendableCharSequence sb, int offset, boolean validateOWS) { - for (int result = offset; result < sb.length(); ++result) { -- if (Character.isWhitespace(sb.charAtUnsafe(result))) { -+ char c = sb.charAtUnsafe(result); -+ if (!Character.isWhitespace(c)) { - return result; -+ } else if (validateOWS && !isOWS(c)) { -+ // Only OWS is supported for whitespace -+ throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," + -+ " but received a '" + c + "'"); - } - } - return sb.length(); -@@ -867,6 +893,10 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - return 0; - } - -+ private static boolean isOWS(char ch) { -+ return ch == ' ' || ch == (char) 0x09; -+ } -+ - private static class HeaderParser implements ByteProcessor { - private final AppendableCharSequence seq; - private final int maxLength; -@@ -896,10 +926,13 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - @Override - public boolean process(byte value) throws Exception { - char nextByte = (char) (value & 0xFF); -- if (nextByte == HttpConstants.CR) { -- return true; -- } - if (nextByte == HttpConstants.LF) { -+ int len = seq.length(); -+ // Drop CR if we had a CRLF pair -+ if (len >= 1 && seq.charAtUnsafe(len - 1) == HttpConstants.CR) { -+ -- size; -+ seq.setLength(len - 1); -+ } - return false; - } - -diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -index 000bd0c..902b379 100644 ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -@@ -373,6 +373,30 @@ public class HttpRequestDecoderTest { - testInvalidHeaders0(requestStr); - } - -+ @Test -+ public void testContentLengthAndTransferEncodingHeadersWithVerticalTab() { -+ testContentLengthAndTransferEncodingHeadersWithInvalidSeparator((char) 0x0b, false); -+ testContentLengthAndTransferEncodingHeadersWithInvalidSeparator((char) 0x0b, true); -+ } -+ -+ @Test -+ public void testContentLengthAndTransferEncodingHeadersWithCR() { -+ testContentLengthAndTransferEncodingHeadersWithInvalidSeparator((char) 0x0d, false); -+ testContentLengthAndTransferEncodingHeadersWithInvalidSeparator((char) 0x0d, true); -+ } -+ -+ private static void testContentLengthAndTransferEncodingHeadersWithInvalidSeparator( -+ char separator, boolean extraLine) { -+ String requestStr = "POST / HTTP/1.1\r\n" + -+ "Host: example.com\r\n" + -+ "Connection: close\r\n" + -+ "Content-Length: 9\r\n" + -+ "Transfer-Encoding:" + separator + "chunked\r\n\r\n" + -+ (extraLine ? "0\r\n\r\n" : "") + -+ "something\r\n\r\n"; -+ testInvalidHeaders0(requestStr); -+ } -+ - @Test - public void testContentLengthHeaderAndChunked() { - String requestStr = "POST / HTTP/1.1\r\n" + -@@ -381,7 +405,6 @@ public class HttpRequestDecoderTest { - "Content-Length: 5\r\n" + - "Transfer-Encoding: chunked\r\n\r\n" + - "0\r\n\r\n"; -- - EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); - assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); - HttpRequest request = channel.readInbound(); -diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java -index d67b3ad..66cefc9 100644 ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java -@@ -50,7 +50,7 @@ public class HttpResponseDecoderTest { - final int maxHeaderSize = 8192; - - final EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder(4096, maxHeaderSize, 8192)); -- final char[] bytes = new char[maxHeaderSize / 2 - 2]; -+ final char[] bytes = new char[maxHeaderSize / 2 - 4]; - Arrays.fill(bytes, 'a'); - - ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\n", CharsetUtil.US_ASCII)); -diff --git a/common/src/main/java/io/netty/util/internal/AppendableCharSequence.java b/common/src/main/java/io/netty/util/internal/AppendableCharSequence.java -index 408c32f..bf1eee5 100644 ---- a/common/src/main/java/io/netty/util/internal/AppendableCharSequence.java -+++ b/common/src/main/java/io/netty/util/internal/AppendableCharSequence.java -@@ -37,6 +37,13 @@ public final class AppendableCharSequence implements CharSequence, Appendable { - pos = chars.length; - } - -+ public void setLength(int length) { -+ if (length < 0 || length > pos) { -+ throw new IllegalArgumentException("length: " + length + " (length: >= 0, <= " + pos + ')'); -+ } -+ this.pos = length; -+ } -+ - @Override - public int length() { - return pos; --- -2.27.0 - diff --git a/CVE-2021-43797.patch b/CVE-2021-43797.patch deleted file mode 100644 index 1fbaf5e359dc6a35f2089f6abddf9eeddebe4250..0000000000000000000000000000000000000000 --- a/CVE-2021-43797.patch +++ /dev/null @@ -1,312 +0,0 @@ -From 2d941652f20135719b9ae5f4a373328ce5379970 Mon Sep 17 00:00:00 2001 -From: Norman Maurer -Date: Thu, 9 Dec 2021 14:49:43 +0100 -Subject: [PATCH] Merge pull request from GHSA-wx5j-54mm-rqqq - -Motivation: - -We should validate that only OWS is allowed before / after a header name and otherwise throw. At the moment we just "strip" everything except OWS. - -Modifications: - -- Adjust code to correctly validate -- Add unit tests - -Result: - -More strict and correct behaviour ---- - .../codec/http/DefaultHttpHeaders.java | 8 ++ - .../handler/codec/http/HttpObjectDecoder.java | 8 +- - .../codec/http/HttpRequestDecoderTest.java | 87 +++++++++++++++++-- - .../codec/http/HttpResponseDecoderTest.java | 78 +++++++++++++++++ - 4 files changed, 171 insertions(+), 10 deletions(-) - -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java -index d18f196..35dd88e 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java -@@ -324,6 +324,10 @@ public class DefaultHttpHeaders extends HttpHeaders { - - private static void validateHeaderNameElement(byte value) { - switch (value) { -+ case 0x1c: -+ case 0x1d: -+ case 0x1e: -+ case 0x1f: - case 0x00: - case '\t': - case '\n': -@@ -348,6 +352,10 @@ public class DefaultHttpHeaders extends HttpHeaders { - - private static void validateHeaderNameElement(char value) { - switch (value) { -+ case 0x1c: -+ case 0x1d: -+ case 0x1e: -+ case 0x1f: - case 0x00: - case '\t': - case '\n': -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -index b4de681..15e86a5 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java -@@ -796,7 +796,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - int valueStart; - int valueEnd; - -- nameStart = findNonWhitespace(sb, 0, false); -+ nameStart = findNonWhitespace(sb, 0); - for (nameEnd = nameStart; nameEnd < length; nameEnd ++) { - char ch = sb.charAt(nameEnd); - // https://tools.ietf.org/html/rfc7230#section-3.2.4 -@@ -831,7 +831,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - } - - name = sb.subStringUnsafe(nameStart, nameEnd); -- valueStart = findNonWhitespace(sb, colonEnd, true); -+ valueStart = findNonWhitespace(sb, colonEnd); - if (valueStart == length) { - value = EMPTY_VALUE; - } else { -@@ -870,12 +870,12 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { - return c == ' ' || c == (char) 0x09 || c == (char) 0x0B || c == (char) 0x0C || c == (char) 0x0D; - } - -- private static int findNonWhitespace(AppendableCharSequence sb, int offset, boolean validateOWS) { -+ private static int findNonWhitespace(AppendableCharSequence sb, int offset) { - for (int result = offset; result < sb.length(); ++result) { - char c = sb.charAtUnsafe(result); - if (!Character.isWhitespace(c)) { - return result; -- } else if (validateOWS && !isOWS(c)) { -+ } else if (!isOWS(c)) { - // Only OWS is supported for whitespace - throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," + - " but received a '" + c + "'"); -diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -index 902b379..fc7cfb4 100644 ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java -@@ -15,6 +15,7 @@ - */ - package io.netty.handler.codec.http; - -+import io.netty.buffer.ByteBuf; - import io.netty.buffer.Unpooled; - import io.netty.channel.embedded.EmbeddedChannel; - import io.netty.util.AsciiString; -@@ -294,6 +295,75 @@ public class HttpRequestDecoderTest { - assertFalse(channel.finishAndReleaseAll()); - } - -+ @Test -+ public void testHeaderNameStartsWithControlChar1c() { -+ testHeaderNameStartsWithControlChar(0x1c); -+ } -+ -+ @Test -+ public void testHeaderNameStartsWithControlChar1d() { -+ testHeaderNameStartsWithControlChar(0x1d); -+ } -+ -+ @Test -+ public void testHeaderNameStartsWithControlChar1e() { -+ testHeaderNameStartsWithControlChar(0x1e); -+ } -+ -+ @Test -+ public void testHeaderNameStartsWithControlChar1f() { -+ testHeaderNameStartsWithControlChar(0x1f); -+ } -+ -+ @Test -+ public void testHeaderNameStartsWithControlChar0c() { -+ testHeaderNameStartsWithControlChar(0x0c); -+ } -+ -+ private void testHeaderNameStartsWithControlChar(int controlChar) { -+ ByteBuf requestBuffer = Unpooled.buffer(); -+ requestBuffer.writeCharSequence("GET /some/path HTTP/1.1\r\n" + -+ "Host: netty.io\r\n", CharsetUtil.US_ASCII); -+ requestBuffer.writeByte(controlChar); -+ requestBuffer.writeCharSequence("Transfer-Encoding: chunked\r\n\r\n", CharsetUtil.US_ASCII); -+ testInvalidHeaders0(requestBuffer); -+ } -+ -+ @Test -+ public void testHeaderNameEndsWithControlChar1c() { -+ testHeaderNameEndsWithControlChar(0x1c); -+ } -+ -+ @Test -+ public void testHeaderNameEndsWithControlChar1d() { -+ testHeaderNameEndsWithControlChar(0x1d); -+ } -+ -+ @Test -+ public void testHeaderNameEndsWithControlChar1e() { -+ testHeaderNameEndsWithControlChar(0x1e); -+ } -+ -+ @Test -+ public void testHeaderNameEndsWithControlChar1f() { -+ testHeaderNameEndsWithControlChar(0x1f); -+ } -+ -+ @Test -+ public void testHeaderNameEndsWithControlChar0c() { -+ testHeaderNameEndsWithControlChar(0x0c); -+ } -+ -+ private void testHeaderNameEndsWithControlChar(int controlChar) { -+ ByteBuf requestBuffer = Unpooled.buffer(); -+ requestBuffer.writeCharSequence("GET /some/path HTTP/1.1\r\n" + -+ "Host: netty.io\r\n", CharsetUtil.US_ASCII); -+ requestBuffer.writeCharSequence("Transfer-Encoding", CharsetUtil.US_ASCII); -+ requestBuffer.writeByte(controlChar); -+ requestBuffer.writeCharSequence(": chunked\r\n\r\n", CharsetUtil.US_ASCII); -+ testInvalidHeaders0(requestBuffer); -+ } -+ - @Test - public void testWhitespace() { - String requestStr = "GET /some/path HTTP/1.1\r\n" + -@@ -303,9 +373,9 @@ public class HttpRequestDecoderTest { - } - - @Test -- public void testWhitespaceBeforeTransferEncoding01() { -+ public void testWhitespaceInTransferEncoding01() { - String requestStr = "GET /some/path HTTP/1.1\r\n" + -- " Transfer-Encoding : chunked\r\n" + -+ "Transfer-Encoding : chunked\r\n" + - "Content-Length: 1\r\n" + - "Host: netty.io\r\n\r\n" + - "a"; -@@ -313,9 +383,9 @@ public class HttpRequestDecoderTest { - } - - @Test -- public void testWhitespaceBeforeTransferEncoding02() { -+ public void testWhitespaceInTransferEncoding02() { - String requestStr = "POST / HTTP/1.1" + -- " Transfer-Encoding : chunked\r\n" + -+ "Transfer-Encoding : chunked\r\n" + - "Host: target.com" + - "Content-Length: 65\r\n\r\n" + - "0\r\n\r\n" + -@@ -412,15 +482,20 @@ public class HttpRequestDecoderTest { - assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false)); - assertFalse(request.headers().contains("Content-Length")); - LastHttpContent c = channel.readInbound(); -+ c.release(); - assertFalse(channel.finish()); - } - - private static void testInvalidHeaders0(String requestStr) { -+ testInvalidHeaders0(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)); -+ } -+ -+ private static void testInvalidHeaders0(ByteBuf requestBuffer) { - EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder()); -- assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII))); -+ assertTrue(channel.writeInbound(requestBuffer)); - HttpRequest request = channel.readInbound(); -+ assertThat(request.decoderResult().cause(), instanceOf(IllegalArgumentException.class)); - assertTrue(request.decoderResult().isFailure()); -- assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException); - assertFalse(channel.finish()); - } - } -diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java -index 66cefc9..df44293 100644 ---- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java -@@ -676,4 +676,82 @@ public class HttpResponseDecoderTest { - assertEquals("netty.io", response.headers().get(HttpHeaderNames.HOST)); - assertFalse(channel.finish()); - } -+ -+ @Test -+ public void testHeaderNameStartsWithControlChar1c() { -+ testHeaderNameStartsWithControlChar(0x1c); -+ } -+ -+ @Test -+ public void testHeaderNameStartsWithControlChar1d() { -+ testHeaderNameStartsWithControlChar(0x1d); -+ } -+ -+ @Test -+ public void testHeaderNameStartsWithControlChar1e() { -+ testHeaderNameStartsWithControlChar(0x1e); -+ } -+ -+ @Test -+ public void testHeaderNameStartsWithControlChar1f() { -+ testHeaderNameStartsWithControlChar(0x1f); -+ } -+ -+ @Test -+ public void testHeaderNameStartsWithControlChar0c() { -+ testHeaderNameStartsWithControlChar(0x0c); -+ } -+ -+ private void testHeaderNameStartsWithControlChar(int controlChar) { -+ ByteBuf responseBuffer = Unpooled.buffer(); -+ responseBuffer.writeCharSequence("HTTP/1.1 200 OK\r\n" + -+ "Host: netty.io\r\n", CharsetUtil.US_ASCII); -+ responseBuffer.writeByte(controlChar); -+ responseBuffer.writeCharSequence("Transfer-Encoding: chunked\r\n\r\n", CharsetUtil.US_ASCII); -+ testInvalidHeaders0(responseBuffer); -+ } -+ -+ @Test -+ public void testHeaderNameEndsWithControlChar1c() { -+ testHeaderNameEndsWithControlChar(0x1c); -+ } -+ -+ @Test -+ public void testHeaderNameEndsWithControlChar1d() { -+ testHeaderNameEndsWithControlChar(0x1d); -+ } -+ -+ @Test -+ public void testHeaderNameEndsWithControlChar1e() { -+ testHeaderNameEndsWithControlChar(0x1e); -+ } -+ -+ @Test -+ public void testHeaderNameEndsWithControlChar1f() { -+ testHeaderNameEndsWithControlChar(0x1f); -+ } -+ -+ @Test -+ public void testHeaderNameEndsWithControlChar0c() { -+ testHeaderNameEndsWithControlChar(0x0c); -+ } -+ -+ private void testHeaderNameEndsWithControlChar(int controlChar) { -+ ByteBuf responseBuffer = Unpooled.buffer(); -+ responseBuffer.writeCharSequence("HTTP/1.1 200 OK\r\n" + -+ "Host: netty.io\r\n", CharsetUtil.US_ASCII); -+ responseBuffer.writeCharSequence("Transfer-Encoding", CharsetUtil.US_ASCII); -+ responseBuffer.writeByte(controlChar); -+ responseBuffer.writeCharSequence(": chunked\r\n\r\n", CharsetUtil.US_ASCII); -+ testInvalidHeaders0(responseBuffer); -+ } -+ -+ private static void testInvalidHeaders0(ByteBuf responseBuffer) { -+ EmbeddedChannel channel = new EmbeddedChannel(new HttpResponseDecoder()); -+ assertTrue(channel.writeInbound(responseBuffer)); -+ HttpResponse response = channel.readInbound(); -+ assertThat(response.decoderResult().cause(), instanceOf(IllegalArgumentException.class)); -+ assertTrue(response.decoderResult().isFailure()); -+ assertFalse(channel.finish()); -+ } - } --- -2.27.0 - diff --git a/CVE-2022-41881.patch b/CVE-2022-41881.patch deleted file mode 100644 index cf50717dd40b9f509b49182dd077ad3218b65597..0000000000000000000000000000000000000000 --- a/CVE-2022-41881.patch +++ /dev/null @@ -1,166 +0,0 @@ -From cd91cf3c99123bd1e53fd6a1de0e3d1922f05bb2 Mon Sep 17 00:00:00 2001 -From: Norman Maurer -Date: Mon, 12 Dec 2022 14:04:31 +0100 -Subject: [PATCH] Merge pull request from GHSA-fx2c-96vj-985v - -Motivation: - -A StackOverflowError can be raised when parsing a malformed crafted message due to an -infinite recursion. We should bail out early - -Modifications: - -- Add a limit to the maximum nesting of TLV. -- Add unit test - -Result: - -Not possible anymore to trigger a StackOverflowError with a special crafted packet - ---- - .../handler/codec/haproxy/HAProxyMessage.java | 16 +++-- - .../haproxy/HAProxyMessageDecoderTest.java | 65 +++++++++++++++++++ - 2 files changed, 76 insertions(+), 5 deletions(-) - -diff --git a/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessage.java b/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessage.java -index b40bf42..3f30c4a 100644 ---- a/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessage.java -+++ b/codec-haproxy/src/main/java/io/netty/handler/codec/haproxy/HAProxyMessage.java -@@ -30,6 +30,9 @@ import java.util.List; - */ - public final class HAProxyMessage { - -+ // Let's pick some conservative limit here. -+ private static final int MAX_NESTING_LEVEL = 128; -+ - /** - * Version 1 proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is - * 'UNKNOWN' we must discard all other header values. -@@ -238,7 +241,7 @@ public final class HAProxyMessage { - } - - private static List readTlvs(final ByteBuf header) { -- HAProxyTLV haProxyTLV = readNextTLV(header); -+ HAProxyTLV haProxyTLV = readNextTLV(header, 0); - if (haProxyTLV == null) { - return Collections.emptyList(); - } -@@ -250,12 +253,15 @@ public final class HAProxyMessage { - if (haProxyTLV instanceof HAProxySSLTLV) { - haProxyTLVs.addAll(((HAProxySSLTLV) haProxyTLV).encapsulatedTLVs()); - } -- } while ((haProxyTLV = readNextTLV(header)) != null); -+ } while ((haProxyTLV = readNextTLV(header, 0)) != null); - return haProxyTLVs; - } - -- private static HAProxyTLV readNextTLV(final ByteBuf header) { -- -+ private static HAProxyTLV readNextTLV(final ByteBuf header, int nestingLevel) { -+ if (nestingLevel > MAX_NESTING_LEVEL) { -+ throw new HAProxyProtocolException( -+ "Maximum TLV nesting level reached: " + nestingLevel + " (expected: < " + MAX_NESTING_LEVEL + ')'); -+ } - // We need at least 4 bytes for a TLV - if (header.readableBytes() < 4) { - return null; -@@ -276,7 +282,7 @@ public final class HAProxyMessage { - - final List encapsulatedTlvs = new ArrayList(4); - do { -- final HAProxyTLV haProxyTLV = readNextTLV(byteBuf); -+ final HAProxyTLV haProxyTLV = readNextTLV(byteBuf, nestingLevel + 1); - if (haProxyTLV == null) { - break; - } -diff --git a/codec-haproxy/src/test/java/io/netty/handler/codec/haproxy/HAProxyMessageDecoderTest.java b/codec-haproxy/src/test/java/io/netty/handler/codec/haproxy/HAProxyMessageDecoderTest.java -index 2d4039d..8d27e4a 100644 ---- a/codec-haproxy/src/test/java/io/netty/handler/codec/haproxy/HAProxyMessageDecoderTest.java -+++ b/codec-haproxy/src/test/java/io/netty/handler/codec/haproxy/HAProxyMessageDecoderTest.java -@@ -16,6 +16,7 @@ - package io.netty.handler.codec.haproxy; - - import io.netty.buffer.ByteBuf; -+import io.netty.buffer.Unpooled; - import io.netty.channel.ChannelFuture; - import io.netty.channel.embedded.EmbeddedChannel; - import io.netty.handler.codec.ProtocolDetectionResult; -@@ -26,6 +27,9 @@ import io.netty.util.CharsetUtil; - import org.junit.Before; - import org.junit.Test; - -+import java.io.ByteArrayOutputStream; -+import java.nio.ByteBuffer; -+import java.nio.ByteOrder; - import java.util.List; - - import static io.netty.buffer.Unpooled.*; -@@ -1013,4 +1017,65 @@ public class HAProxyMessageDecoderTest { - assertNull(result.detectedProtocol()); - incompleteHeader.release(); - } -+ -+ @Test -+ public void testNestedTLV() throws Exception { -+ ByteArrayOutputStream headerWriter = new ByteArrayOutputStream(); -+ //src_ip = "AAAA", dst_ip = "BBBB", src_port = "CC", dst_port = "DD" -+ headerWriter.write(new byte[] {'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'C', 'D', 'D'}); -+ //write TLVs -+ int countOfTLVs = 8100; -+ ByteBuffer tlvLengthBuf = ByteBuffer.allocate(2); -+ tlvLengthBuf.order(ByteOrder.BIG_ENDIAN); -+ short totalLength = (short) (countOfTLVs * (1 + 2 + 1 + 4)); -+ for (int i = 0; i < countOfTLVs; i++) { -+ //write PP2_TYPE_SSL TLV -+ headerWriter.write(0x20); //PP2_TYPE_SSL -+ //notice that the TLV length cannot be bigger than 0xffff -+ totalLength -= 1 + 2; //exclude type and length themselves -+ tlvLengthBuf.clear(); -+ tlvLengthBuf.putShort(totalLength); -+ //add to the header -+ headerWriter.write(tlvLengthBuf.array()); -+ //write client field -+ headerWriter.write(1); -+ //write verify field -+ headerWriter.write(new byte[] {'V', 'V', 'V', 'V'}); -+ //subtract the client and verify fields -+ totalLength -= 1 + 4; -+ } -+ byte[] header = headerWriter.toByteArray(); -+ ByteBuffer numsWrite = ByteBuffer.allocate(2); -+ numsWrite.order(ByteOrder.BIG_ENDIAN); -+ numsWrite.putShort((short) header.length); -+ -+ final ByteBuf data = Unpooled.buffer(); -+ data.writeBytes(new byte[] { -+ (byte) 0x0D, -+ (byte) 0x0A, -+ (byte) 0x0D, -+ (byte) 0x0A, -+ (byte) 0x00, -+ (byte) 0x0D, -+ (byte) 0x0A, -+ (byte) 0x51, -+ (byte) 0x55, -+ (byte) 0x49, -+ (byte) 0x54, -+ (byte) 0x0A -+ }); -+ //verCmd = 32 -+ byte versionCmd = 0x20 | 1; //V2 | ProxyCmd -+ data.writeByte(versionCmd); -+ data.writeByte(17); //TPAF_TCP4_BYTE -+ data.writeBytes(numsWrite.array()); -+ data.writeBytes(header); -+ -+ assertThrows(HAProxyProtocolException.class, new Executable() { -+ @Override -+ public void execute() { -+ ch.writeInbound(data); -+ } -+ }); -+ } - } --- -2.30.0 - diff --git a/CVE-2024-29025.patch b/CVE-2024-29025.patch deleted file mode 100644 index d5512467ba7159c40cc9cfcaa0a83a0f0a4d29fb..0000000000000000000000000000000000000000 --- a/CVE-2024-29025.patch +++ /dev/null @@ -1,411 +0,0 @@ -From: Markus Koschany -Date: Sat, 11 May 2024 21:52:15 +0200 -Subject: CVE-2024-29025 - -Bug-Debian: https://bugs.debian.org/1068110 -Origin: https://github.com/netty/netty/commit/0d0c6ed782d13d423586ad0c71737b2c7d02058c ---- - .../HttpPostMultipartRequestDecoder.java | 41 +++++++ - .../multipart/HttpPostRequestDecoder.java | 70 ++++++++++++ - .../HttpPostStandardRequestDecoder.java | 44 ++++++++ - .../multipart/HttpPostRequestDecoderTest.java | 103 ++++++++++++++++++ - 4 files changed, 258 insertions(+) - -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java -index 17c3e64..7cac6a0 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java -@@ -61,6 +61,16 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest - */ - private final HttpRequest request; - -+ /** -+ * The maximum number of fields allows by the form -+ */ -+ private final int maxFields; -+ -+ /** -+ * The maximum number of accumulated bytes when decoding a field -+ */ -+ private final int maxBufferedBytes; -+ - /** - * Default charset to use - */ -@@ -172,9 +182,34 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest - * errors - */ - public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) { -+ this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS, HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES); -+ } -+ -+ /** -+ * -+ * @param factory -+ * the factory used to create InterfaceHttpData -+ * @param request -+ * the request to decode -+ * @param charset -+ * the charset to use as default -+ * @param maxFields -+ * the maximum number of fields the form can have, {@code -1} to disable -+ * @param maxBufferedBytes -+ * the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable -+ * @throws NullPointerException -+ * for request or charset or factory -+ * @throws ErrorDataDecoderException -+ * if the default charset was wrong when decoding or other -+ * errors -+ */ -+ public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset, -+ int maxFields, int maxBufferedBytes) { - this.request = checkNotNull(request, "request"); - this.charset = checkNotNull(charset, "charset"); - this.factory = checkNotNull(factory, "factory"); -+ this.maxFields = maxFields; -+ this.maxBufferedBytes = maxBufferedBytes; - // Fill default values - - setMultipart(this.request.headers().get(HttpHeaderNames.CONTENT_TYPE)); -@@ -333,6 +368,9 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest - isLastChunk = true; - } - parseBody(); -+ if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) { -+ throw new HttpPostRequestDecoder.TooLongFormFieldException(); -+ } - if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) { - undecodedChunk.discardReadBytes(); - } -@@ -417,6 +455,9 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest - if (data == null) { - return; - } -+ if (maxFields > 0 && bodyListHttpData.size() >= maxFields) { -+ throw new HttpPostRequestDecoder.TooManyFormFieldsException(); -+ } - List datas = bodyMapHttpData.get(data.getName()); - if (datas == null) { - datas = new ArrayList(1); -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java -index 0c10626..d57b63e 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java -@@ -25,6 +25,7 @@ import io.netty.util.internal.StringUtil; - - import java.nio.charset.Charset; - import java.util.List; -+import io.netty.util.internal.ObjectUtil; - - /** - * This decoder will decode Body and can handle POST BODY. -@@ -36,6 +37,10 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder { - - static final int DEFAULT_DISCARD_THRESHOLD = 10 * 1024 * 1024; - -+ static final int DEFAULT_MAX_FIELDS = 128; -+ -+ static final int DEFAULT_MAX_BUFFERED_BYTES = 1024; -+ - private final InterfaceHttpPostRequestDecoder decoder; - - /** -@@ -52,6 +57,25 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder { - this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET); - } - -+ /** -+ * -+ * @param request -+ * the request to decode -+ * @param maxFields -+ * the maximum number of fields the form can have, {@code -1} to disable -+ * @param maxBufferedBytes -+ * the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable -+ * @throws NullPointerException -+ * for request -+ * @throws ErrorDataDecoderException -+ * if the default charset was wrong when decoding or other -+ * errors -+ */ -+ public HttpPostRequestDecoder(HttpRequest request, int maxFields, int maxBufferedBytes) { -+ this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET, -+ maxFields, maxBufferedBytes); -+ } -+ - /** - * - * @param factory -@@ -100,6 +124,38 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder { - } - } - -+ /** -+ * -+ * @param factory -+ * the factory used to create InterfaceHttpData -+ * @param request -+ * the request to decode -+ * @param charset -+ * the charset to use as default -+ * @param maxFields -+ * the maximum number of fields the form can have, {@code -1} to disable -+ * @param maxBufferedBytes -+ * the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable -+ * @throws NullPointerException -+ * for request or charset or factory -+ * @throws ErrorDataDecoderException -+ * if the default charset was wrong when decoding or other -+ * errors -+ */ -+ public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset, -+ int maxFields, int maxBufferedBytes) { -+ ObjectUtil.checkNotNull(factory, "factory"); -+ ObjectUtil.checkNotNull(request, "request"); -+ ObjectUtil.checkNotNull(charset, "charset"); -+ -+ // Fill default values -+ if (isMultipart(request)) { -+ decoder = new HttpPostMultipartRequestDecoder(factory, request, charset, maxFields, maxBufferedBytes); -+ } else { -+ decoder = new HttpPostStandardRequestDecoder(factory, request, charset, maxFields, maxBufferedBytes); -+ } -+ } -+ - /** - * states follow NOTSTARTED PREAMBLE ( (HEADERDELIMITER DISPOSITION (FIELD | - * FILEUPLOAD))* (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE (MIXEDDELIMITER -@@ -342,4 +398,18 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder { - super(msg, cause); - } - } -+ -+ /** -+ * Exception when the maximum number of fields for a given form is reached -+ */ -+ public static final class TooManyFormFieldsException extends DecoderException { -+ private static final long serialVersionUID = 1336267941020800769L; -+ } -+ -+ /** -+ * Exception when a field content is too long -+ */ -+ public static final class TooLongFormFieldException extends DecoderException { -+ private static final long serialVersionUID = 1336267941020800769L; -+ } - } -diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java -index ece64d8..65a9e16 100644 ---- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java -+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java -@@ -16,6 +16,7 @@ - package io.netty.handler.codec.http.multipart; - - import io.netty.buffer.ByteBuf; -+import io.netty.handler.codec.DecoderException; - import io.netty.handler.codec.http.HttpConstants; - import io.netty.handler.codec.http.HttpContent; - import io.netty.handler.codec.http.HttpRequest; -@@ -26,6 +27,8 @@ import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDec - import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException; - import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus; - import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException; -+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooManyFormFieldsException; -+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooLongFormFieldException; - - import java.io.IOException; - import java.nio.charset.Charset; -@@ -60,6 +63,16 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD - */ - private final Charset charset; - -+ /** -+ * The maximum number of fields allows by the form -+ */ -+ private final int maxFields; -+ -+ /** -+ * The maximum number of accumulated bytes when decoding a field -+ */ -+ private final int maxBufferedBytes; -+ - /** - * Does the last chunk already received - */ -@@ -145,9 +158,34 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD - * errors - */ - public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) { -+ this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS, HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES); -+ } -+ -+ /** -+ * -+ * @param factory -+ * the factory used to create InterfaceHttpData -+ * @param request -+ * the request to decode -+ * @param charset -+ * the charset to use as default -+ * @param maxFields -+ * the maximum number of fields the form can have, {@code -1} to disable -+ * @param maxBufferedBytes -+ * the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable -+ * @throws NullPointerException -+ * for request or charset or factory -+ * @throws ErrorDataDecoderException -+ * if the default charset was wrong when decoding or other -+ * errors -+ */ -+ public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset, -+ int maxFields, int maxBufferedBytes) { - this.request = checkNotNull(request, "request"); - this.charset = checkNotNull(charset, "charset"); - this.factory = checkNotNull(factory, "factory"); -+ this.maxFields = maxFields; -+ this.maxBufferedBytes = maxBufferedBytes; - if (request instanceof HttpContent) { - // Offer automatically if the given request is als type of HttpContent - // See #1089 -@@ -287,6 +325,9 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD - isLastChunk = true; - } - parseBody(); -+ if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) { -+ throw new TooLongFormFieldException(); -+ } - if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) { - undecodedChunk.discardReadBytes(); - } -@@ -367,6 +408,9 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD - if (data == null) { - return; - } -+ if (maxFields > 0 && bodyListHttpData.size() >= maxFields) { -+ throw new TooManyFormFieldsException(); -+ } - List datas = bodyMapHttpData.get(data.getName()); - if (datas == null) { - datas = new ArrayList(1); -diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java -index 1334107..bdc5fd7 100644 ---- a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java -+++ b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java -@@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf; - import io.netty.buffer.ByteBufAllocator; - import io.netty.buffer.Unpooled; - import io.netty.buffer.UnpooledByteBufAllocator; -+import io.netty.handler.codec.DecoderException; - import io.netty.handler.codec.DecoderResult; - import io.netty.handler.codec.http.DefaultFullHttpRequest; - import io.netty.handler.codec.http.DefaultHttpContent; -@@ -491,4 +492,106 @@ public class HttpPostRequestDecoderTest { - content.release(); - } - } -+ -+ @Test -+ public void testTooManyFormFieldsPostStandardDecoder() { -+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/"); -+ -+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, 1024, -1); -+ -+ int num = 0; -+ while (true) { -+ try { -+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("foo=bar&".getBytes()))); -+ } catch (DecoderException e) { -+ assertEquals(HttpPostRequestDecoder.TooManyFormFieldsException.class, e.getClass()); -+ break; -+ } -+ assertTrue(num++ < 1024); -+ } -+ assertEquals(1024, num); -+ } -+ -+ @Test -+ public void testTooManyFormFieldsPostMultipartDecoder() { -+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/"); -+ req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f"); -+ -+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, 1024, -1); -+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("--be38b42a9ad2713f\n".getBytes()))); -+ -+ int num = 0; -+ while (true) { -+ try { -+ byte[] bodyBytes = ("content-disposition: form-data; name=\"title\"\n" + -+ "content-length: 10\n" + -+ "content-type: text/plain; charset=UTF-8\n" + -+ "\n" + -+ "bar-stream\n" + -+ "--be38b42a9ad2713f\n").getBytes(); -+ ByteBuf content = Unpooled.wrappedBuffer(bodyBytes); -+ decoder.offer(new DefaultHttpContent(content)); -+ } catch (DecoderException e) { -+ assertEquals(HttpPostRequestDecoder.TooManyFormFieldsException.class, e.getClass()); -+ break; -+ } -+ assertTrue(num++ < 1024); -+ } -+ assertEquals(1024, num); -+ } -+ -+ @Test -+ public void testTooLongFormFieldStandardDecoder() { -+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/"); -+ -+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 16 * 1024); -+ -+ try { -+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(new byte[16 * 1024 + 1]))); -+ fail(); -+ } catch (DecoderException e) { -+ assertEquals(HttpPostRequestDecoder.TooLongFormFieldException.class, e.getClass()); -+ } -+ } -+ -+ @Test -+ public void testFieldGreaterThanMaxBufferedBytesStandardDecoder() { -+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/"); -+ -+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 6); -+ -+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("foo=bar".getBytes()))); -+ } -+ -+ @Test -+ public void testTooLongFormFieldMultipartDecoder() { -+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/"); -+ req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f"); -+ -+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 16 * 1024); -+ -+ try { -+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(new byte[16 * 1024 + 1]))); -+ fail(); -+ } catch (DecoderException e) { -+ assertEquals(HttpPostRequestDecoder.TooLongFormFieldException.class, e.getClass()); -+ } -+ } -+ -+ @Test -+ public void testFieldGreaterThanMaxBufferedBytesMultipartDecoder() { -+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/"); -+ req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f"); -+ -+ byte[] bodyBytes = ("content-disposition: form-data; name=\"title\"\n" + -+ "content-length: 10\n" + -+ "content-type: text/plain; charset=UTF-8\n" + -+ "\n" + -+ "bar-stream\n" + -+ "--be38b42a9ad2713f\n").getBytes(); -+ -+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, bodyBytes.length - 1); -+ -+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(bodyBytes))); -+ } - } --- -2.47.0 - diff --git a/fix-strip.patch b/fix-strip.patch index 5a86ae92805d1fc26803cceeb0390885630aba0e..ea696cb95d50c2a7d289738b2685099e2658bfed 100644 --- a/fix-strip.patch +++ b/fix-strip.patch @@ -1,30 +1,14 @@ diff -Nur a/transport-native-epoll/pom.xml b/transport-native-epoll/pom.xml ---- a/transport-native-epoll/pom.xml 2023-03-05 15:42:40.947323733 +0800 -+++ b/transport-native-epoll/pom.xml 2023-03-05 15:45:43.877975494 +0800 -@@ -32,7 +32,7 @@ +--- a/transport-native-epoll/pom.xml 2024-11-18 14:43:59.659694000 +0800 ++++ b/transport-native-epoll/pom.xml 2024-11-18 14:43:44.271694000 +0800 +@@ -37,8 +37,8 @@ ${project.build.directory}/unix-common-lib ${unix.common.lib.dir}/META-INF/native/lib ${unix.common.lib.dir}/META-INF/native/include -- LDFLAGS=-L${unix.common.lib.unpacked.dir} -Wl,--no-as-needed -lrt -Wl,--whole-archive -l${unix.common.lib.name} -Wl,--no-whole-archive -+ LDFLAGS=-L${unix.common.lib.unpacked.dir} -Wl,--no-as-needed -lrt -Wl,--whole-archive -Wl,-s -l${unix.common.lib.name} -Wl,--no-whole-archive +- CFLAGS=-O2 -pipe -Werror -fno-omit-frame-pointer -Wunused-variable -fvisibility=hidden -D_FORTIFY_SOURCE=2 -ffunction-sections -fdata-sections -I${unix.common.include.unpacked.dir} +- LDFLAGS=-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -Wl,--gc-sections -L${unix.common.lib.unpacked.dir} ++ CFLAGS=-O2 -pipe -Werror -fno-omit-frame-pointer -Wunused-variable -fvisibility=hidden -D_FORTIFY_SOURCE=2 -ffunction-sections -fdata-sections -Wl,-s -I${unix.common.include.unpacked.dir} ++ LDFLAGS=-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -Wl,--gc-sections -Wl,-s -L${unix.common.lib.unpacked.dir} + LIBS=-Wl,--whole-archive -l${unix.common.lib.name} -Wl,--no-whole-archive -ldl + ${project.basedir}/src/main/c true - - -@@ -221,7 +221,7 @@ - ${linux.sendmmsg.support}${glibc.sendmmsg.support} - - .*IO_NETTY_SENDMSSG_NOT_FOUND.* -- CFLAGS=-O3 -DIO_NETTY_SENDMMSG_NOT_FOUND -Werror -fno-omit-frame-pointer -Wunused-variable -I${unix.common.include.unpacked.dir} -+ CFLAGS=-O3 -DIO_NETTY_SENDMMSG_NOT_FOUND -Werror -fno-omit-frame-pointer -Wunused-variable -Wl,-s -I${unix.common.include.unpacked.dir} - false - - -@@ -237,7 +237,7 @@ - ${jni.compiler.args.cflags} - - ^((?!CFLAGS=).)*$ -- CFLAGS=-O3 -Werror -fno-omit-frame-pointer -Wunused-variable -I${unix.common.include.unpacked.dir} -+ CFLAGS=-O3 -Werror -fno-omit-frame-pointer -Wunused-variable -Wl,-s -I${unix.common.include.unpacked.dir} - false - - diff --git a/netty-4.1.114.Final.tar.gz b/netty-4.1.114.Final.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..da24fb41af930d460345a8e6eeb93b4c875fcdee Binary files /dev/null and b/netty-4.1.114.Final.tar.gz differ diff --git a/netty-4.1.13.Final.tar.gz b/netty-4.1.13.Final.tar.gz deleted file mode 100644 index 0659ded00089fc5140016d476b68c5092f74223c..0000000000000000000000000000000000000000 Binary files a/netty-4.1.13.Final.tar.gz and /dev/null differ diff --git a/netty-jni-util-0.0.9.Final-sources.jar b/netty-jni-util-0.0.9.Final-sources.jar new file mode 100644 index 0000000000000000000000000000000000000000..41e7f191877c710ded9c0d21dffcd228cd265e71 Binary files /dev/null and b/netty-jni-util-0.0.9.Final-sources.jar differ diff --git a/netty.spec b/netty.spec index 29167d5e609e075d77a292f1f2168eb5538a4c0a..3d1a4fc5d4fe72c13f066637d56e39427c840cc0 100644 --- a/netty.spec +++ b/netty.spec @@ -1,52 +1,45 @@ %global debug_package %{nil} Name: netty -Version: 4.1.13 -Release: 22 -Summary: Asynchronous event-driven network application Java framework -License: ASL 2.0 +Version: 4.1.114 +Release: 1 +Summary: An asynchronous event-driven network application framework and tools for Java +License: Apache-2.0 URL: https://netty.io/ Source0: https://github.com/netty/netty/archive/netty-%{version}.Final.tar.gz Source1: codegen.bash -Patch0000: 0001-Remove-OpenSSL-parts-depending-on-tcnative.patch -Patch0001: 0002-Remove-NPN.patch -Patch0002: 0003-Remove-conscrypt-ALPN.patch -Patch0003: CVE-2019-16869.patch -Patch0004: CVE-2019-20444.patch -Patch0005: CVE-2019-20445-1.patch -Patch0006: CVE-2019-20445-2.patch -Patch0007: CVE-2019-20445-3.patch -Patch0008: CVE-2020-11612.patch -Patch0009: CVE-2021-21290.patch -Patch0010: CVE-2021-21295-pre1.patch -Patch0011: CVE-2021-21295-pre2.patch -Patch0012: CVE-2021-21295-pre3.patch -Patch0013: CVE-2021-21295-pre4.patch -Patch0014: CVE-2021-21295.patch -Patch0015: CVE-2021-21409.patch -Patch0016: fix-build-error.patch -Patch0017: CVE-2021-37136.patch -Patch0018: CVE-2021-37137.patch -Patch0019: CVE-2021-43797-pre.patch -Patch0020: CVE-2021-43797.patch -Patch0021: fix-strip.patch -# https://github.com/netty/netty/commit/cd91cf3c99123bd1e53fd6a1de0e3d1922f05bb2 -Patch0022: CVE-2022-41881.patch -Patch0023: CVE-2024-29025.patch - -BuildRequires: maven-local mvn(ant-contrib:ant-contrib) -BuildRequires: mvn(com.jcraft:jzlib) mvn(commons-logging:commons-logging) -BuildRequires: mvn(kr.motd.maven:os-maven-plugin) mvn(log4j:log4j:1.2.17) -BuildRequires: mvn(org.apache.felix:maven-bundle-plugin) mvn(org.apache.maven.plugins:maven-antrun-plugin) -BuildRequires: mvn(org.apache.maven.plugins:maven-dependency-plugin) mvn(org.apache.maven.plugins:maven-remote-resources-plugin) -BuildRequires: mvn(org.codehaus.mojo:build-helper-maven-plugin) mvn(org.codehaus.mojo:exec-maven-plugin) -BuildRequires: mvn(org.fusesource.hawtjni:maven-hawtjni-plugin) mvn(org.javassist:javassist) -BuildRequires: mvn(org.jctools:jctools-core) mvn(org.slf4j:slf4j-api) -BuildRequires: mvn(org.sonatype.oss:oss-parent:pom:) +Source2: https://repo1.maven.org/maven2/io/netty/netty-jni-util/0.0.9.Final/netty-jni-util-0.0.9.Final-sources.jar +Patch0000: 0001-Remove-optional-dep-Blockhound.patch +Patch0001: 0002-Remove-optional-dep-conscrypt.patch +Patch0002: 0003-Remove-optional-deps-jetty-alpn-and-npn.patch +Patch0003: 0004-Remove-optional-dep-tcnative.patch +Patch0004: 0005-Disable-Brotli-and-ZStd-compression.patch +Patch0005: 0006-Do-not-use-the-Graal-annotations.patch +Patch0006: 0007-Do-not-use-the-Jetbrains-annotations.patch +Patch0007: no-werror.patch +Patch0008: reproducible.patch +Patch0009: fix-strip.patch + +BuildRequires: autoconf automake libtool gcc +BuildRequires: maven-local BuildRequires: mvn(com.fasterxml:aalto-xml) -BuildRequires: mvn(com.ning:compress-lzf) -BuildRequires: mvn(org.apache.logging.log4j:log4j-api) mvn(org.bouncycastle:bcpkix-jdk15on) -BuildRequires: mvn(org.jboss.marshalling:jboss-marshalling) mvn(org.eclipse.jetty.alpn:alpn-api) +BuildRequires: mvn(com.jcraft:jzlib) +BuildRequires: mvn(commons-logging:commons-logging) +BuildRequires: mvn(io.netty:netty-tcnative-classes) >= 2.0.60 +BuildRequires: mvn(kr.motd.maven:os-maven-plugin) +BuildRequires: mvn(org.apache.felix:maven-bundle-plugin) +BuildRequires: mvn(org.apache.logging.log4j:log4j-1.2-api) +BuildRequires: mvn(org.apache.logging.log4j:log4j-api) +BuildRequires: mvn(org.apache.maven.plugins:maven-antrun-plugin) +BuildRequires: mvn(org.apache.maven.plugins:maven-dependency-plugin) +BuildRequires: mvn(org.apache.maven.plugins:maven-remote-resources-plugin) +BuildRequires: mvn(org.bouncycastle:bcpkix-jdk15on) +BuildRequires: mvn(org.bouncycastle:bctls-jdk15on) +BuildRequires: mvn(org.codehaus.mojo:build-helper-maven-plugin) +BuildRequires: mvn(org.codehaus.mojo:exec-maven-plugin) +BuildRequires: mvn(org.fusesource.hawtjni:hawtjni-maven-plugin) +BuildRequires: mvn(org.jctools:jctools-core) +BuildRequires: mvn(org.slf4j:slf4j-api) %description Netty is an asynchronous event-driven network application framework @@ -63,17 +56,15 @@ Man pages and other related documents for %{name}. %prep %autosetup -p1 -n netty-netty-%{version}.Final -%pom_disable_module "transport-rxtx" -%pom_remove_dep ":netty-transport-rxtx" all -%pom_disable_module "transport-udt" -%pom_remove_dep ":netty-transport-udt" all -%pom_remove_dep ":netty-build" all +%pom_disable_module transport-rxtx +%pom_remove_dep -r :netty-transport-rxtx +%pom_disable_module transport-udt +%pom_remove_dep -r :netty-transport-udt +%pom_remove_dep -r :netty-tcnative-classes + +%pom_remove_parent . bom dev-tools + %pom_disable_module "example" -%pom_remove_dep ":netty-example" all -%pom_disable_module "testsuite" -%pom_disable_module "testsuite-autobahn" -%pom_disable_module "testsuite-osgi" -%pom_disable_module "tarball" %pom_disable_module "microbench" %pom_xpath_inject 'pom:plugin[pom:artifactId="maven-remote-resources-plugin"]' ' @@ -84,6 +75,7 @@ Man pages and other related documents for %{name}. ${project.version} ' + %pom_remove_plugin :maven-antrun-plugin %pom_remove_plugin :maven-dependency-plugin %pom_remove_plugin :xml-maven-plugin @@ -98,6 +90,11 @@ Man pages and other related documents for %{name}. %pom_remove_plugin -r :maven-jxr-plugin %pom_remove_plugin -r :maven-javadoc-plugin %pom_remove_plugin -r :forbiddenapis +%pom_remove_plugin -r :revapi-maven-plugin +%pom_remove_plugin -r :bom-helper-maven-plugin +%pom_remove_plugin :japicmp-maven-plugin +%pom_remove_plugin :duplicate-finder-maven-plugin all +%pom_remove_plugin :flatten-maven-plugin all cp %{SOURCE1} common/codegen.bash chmod a+x common/codegen.bash @@ -116,47 +113,92 @@ chmod a+x common/codegen.bash ' %pom_remove_plugin :groovy-maven-plugin common + +%pom_remove_dep "org.graalvm.nativeimage:" common +rm common/src/main/java/io/netty/util/internal/svm/* + %pom_remove_dep -r "com.google.protobuf:protobuf-java" %pom_remove_dep -r "com.google.protobuf.nano:protobuf-javanano" rm codec/src/main/java/io/netty/handler/codec/protobuf/* sed -i '/import.*protobuf/d' codec/src/main/java/io/netty/handler/codec/DatagramPacket*.java -sed -i 's|taskdef|taskdef classpathref="maven.plugin.classpath"|' all/pom.xml +%pom_remove_dep -r "org.jboss.marshalling:jboss-marshalling" +rm codec/src/main/java/io/netty/handler/codec/marshalling/* -%pom_xpath_inject "pom:plugins/pom:plugin[pom:artifactId = 'maven-antrun-plugin']" 'ant-contribant-contrib1.0b3' all/pom.xml -%pom_xpath_inject "pom:execution[pom:id = 'build-native-lib']/pom:configuration" 'true' transport-native-epoll/pom.xml +%pom_remove_dep -r com.github.jponge:lzma-java +rm codec/src/*/java/io/netty/handler/codec/compression/Lzma*.java +%pom_remove_dep -r com.ning:compress-lzf +rm codec/src/*/java/io/netty/handler/codec/compression/Lzf*.java +%pom_remove_dep -r net.jpountz.lz4:lz4 +rm codec/src/*/java/io/netty/handler/codec/compression/Lz4*.java +%pom_remove_dep -r com.aayushatharva.brotli4j: +rm codec/src/*/java/io/netty/handler/codec/compression/Brotli*.java +%pom_remove_dep -r com.github.luben:zstd-jni +rm codec/src/*/java/io/netty/handler/codec/compression/Zstd*.java + +%pom_disable_module resolver-dns-native-macos +%pom_remove_dep -r :netty-resolver-dns-native-macos + +%pom_disable_module testsuite +%pom_disable_module testsuite-autobahn +%pom_disable_module testsuite-http2 +%pom_disable_module testsuite-native +%pom_disable_module testsuite-native-image +%pom_disable_module testsuite-native-image-client +%pom_disable_module testsuite-native-image-client-runtime-init +%pom_disable_module testsuite-osgi +%pom_disable_module testsuite-shading +%pom_disable_module transport-native-unix-common-tests + +%pom_remove_dep io.netty:netty-jni-util transport-native-unix-common +%pom_remove_plugin :maven-dependency-plugin transport-native-unix-common +mkdir -p transport-native-unix-common/target/netty-jni-util +unzip %{SOURCE2} -d transport-native-unix-common/target/netty-jni-util %pom_xpath_remove "pom:build/pom:plugins/pom:plugin[pom:artifactId = 'maven-bundle-plugin']/pom:executions/pom:execution[pom:id = 'generate-manifest']/pom:configuration/pom:instructions/pom:Import-Package" common/pom.xml +%pom_remove_dep -r :annotations-java5 + %mvn_package ":::linux*:" %mvn_package ':*-tests' __noinstall -# remove the BuildRequires lzma-java that is deprecated -%pom_remove_dep -r "com.github.jponge:lzma-java" -rm -f codec/src/main/java/io/netty/handler/codec/compression/LzmaFrameEncoder.java -rm -f codec/src/test/java/io/netty/handler/codec/compression/LzmaFrameEncoderTest.java - -%pom_remove_dep -r net.jpountz.lz4:lz4 -rm -f codec/src/*/java/io/netty/handler/codec/compression/Lz4*.java - %build export CFLAGS="$RPM_OPT_FLAGS" LDFLAGS="$RPM_LD_FLAGS" %mvn_build -f - %install %mvn_install - %files -f .mfiles %doc LICENSE.txt NOTICE.txt - %files help -f .mfiles-javadoc - %changelog +* Thu Nov 14 2024 yaoxin - 4.1.114-1 +- Update to 4.1.114 + * Validate HTTP Method (#14280) + * Release AdaptiveByteBuf when ownership could not be transfered. (#14285) + * Make arenas reuse their last chunk more aggressively (#14291) + * Only add Magazine to Set if we can ensure its removed again (#14292) + * Ensure Chunk will not leak if init of AdaptiveByteBuf fails for whatever reason (#14295) + * Correctly release one-off allocated chunks (#14302) + * Ensure pooled memory is released when AdaptivePoolingAllocator is GC'ed (#14304) + * Slices / duplicates of AdaptiveByteBuf must not escape the rootParent (#14310) + * Fix sizeBucket bug in AdaptivePoolingAllocator (#14311) + * AdaptiveByteBufAllocator: More strict reference counting for chunks (#14322) + * Ensure we not store the DnsQueryContext for later removal when we couldnt obtain a query id (#14326) + * Reduce memory fragmentation (#14333) + * Properly free magazine chunks and avoid orphaned magazines (#14334) + * Magazines must be freed under the expand lock (#14336) + * Release message before failing promise when multiple requests are written while upgrade is in progress. (#14342) + * Allow to reuse more then one session per host / port mapping (#14356) + * Ensure writes will not fail when triggered after receiving UpgradeEvent.UPGRADE_SUCCESSFUL (#14362) + * Refactor DnsNameResolver to be able to use different strategies when it comes to creating Channels for queries. (#14374) + * DnsNameResolver: allow users to skip bind() during bootstrap (#14375) + * DnsResolverBuilder methods should make it clear that these are for DatagramChannel (#14379) + * Tue Nov 12 2024 yaoxin - 4.1.13-22 - Fix CVE-2024-29025 diff --git a/fix-build-error.patch b/no-werror.patch similarity index 40% rename from fix-build-error.patch rename to no-werror.patch index 5b2d8a365d4e6cb8a666e83e08c4492139b31be0..7e7a13fdf91829eea133dc60d2c3b316dd00fd1f 100644 --- a/fix-build-error.patch +++ b/no-werror.patch @@ -1,52 +1,56 @@ -From c4659b32ae737903c7e5a28c1a2fd706f43e6640 Mon Sep 17 00:00:00 2001 -From: wang_yue111 <648774160@qq.com> -Date: Mon, 16 Aug 2021 11:15:47 +0800 -Subject: [PATCH] fix build error - ---- - transport-native-unix-common/pom.xml | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/transport-native-unix-common/pom.xml b/transport-native-unix-common/pom.xml -index 72f02c0..04430e9 100644 ---- a/transport-native-unix-common/pom.xml -+++ b/transport-native-unix-common/pom.xml -@@ -99,7 +99,7 @@ +--- netty-netty-4.1.72.Final/transport-native-unix-common/pom.xml 2021-12-13 17:10:20.606957494 +0100 ++++ netty-netty-4.1.72.Final/transport-native-unix-common/pom.xml 2021-12-13 17:11:21.279319445 +0100 +@@ -153,7 +153,7 @@ -- -+ - +- ++ +