View Javadoc
1   /*
2    * Copyright 2025 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.testsuite.transport.socket;
17  
18  import io.netty.bootstrap.Bootstrap;
19  import io.netty.bootstrap.ServerBootstrap;
20  import io.netty.buffer.Unpooled;
21  import io.netty.channel.Channel;
22  import io.netty.channel.ChannelFuture;
23  import io.netty.channel.ChannelFutureListener;
24  import io.netty.channel.ChannelHandlerContext;
25  import io.netty.channel.ChannelInboundHandlerAdapter;
26  import io.netty.channel.ChannelInitializer;
27  import io.netty.channel.MultiThreadIoEventLoopGroup;
28  import io.netty.channel.nio.NioIoHandler;
29  import io.netty.channel.socket.nio.NioServerSocketChannel;
30  import io.netty.channel.socket.nio.NioSocketChannel;
31  import io.netty.handler.ssl.ClientAuth;
32  import io.netty.handler.ssl.OpenSsl;
33  import io.netty.handler.ssl.OpenSslContextOption;
34  import io.netty.handler.ssl.SslContext;
35  import io.netty.handler.ssl.SslContextBuilder;
36  import io.netty.handler.ssl.SslHandshakeCompletionEvent;
37  import io.netty.handler.ssl.SslProvider;
38  import io.netty.pkitesting.CertificateBuilder;
39  import io.netty.pkitesting.X509Bundle;
40  import io.netty.util.ReferenceCountUtil;
41  import io.netty.util.concurrent.ImmediateEventExecutor;
42  import io.netty.util.concurrent.Promise;
43  import org.junit.jupiter.api.AfterAll;
44  import org.junit.jupiter.api.BeforeAll;
45  import org.junit.jupiter.api.TestInstance;
46  import org.junit.jupiter.api.condition.EnabledIf;
47  import org.junit.jupiter.api.parallel.Execution;
48  import org.junit.jupiter.api.parallel.ExecutionMode;
49  import org.junit.jupiter.params.ParameterizedTest;
50  import org.junit.jupiter.params.provider.Arguments;
51  import org.junit.jupiter.params.provider.MethodSource;
52  
53  import java.net.InetAddress;
54  import java.net.InetSocketAddress;
55  import java.nio.charset.StandardCharsets;
56  import java.util.concurrent.ExecutionException;
57  import java.util.concurrent.ThreadLocalRandom;
58  import java.util.concurrent.TimeUnit;
59  import java.util.stream.IntStream;
60  import java.util.stream.Stream;
61  import javax.net.ssl.KeyManagerFactory;
62  import javax.net.ssl.SNIHostName;
63  import javax.net.ssl.TrustManagerFactory;
64  
65  @EnabledIf("supportKeyManagerAndTLS13")
66  @TestInstance(TestInstance.Lifecycle.PER_CLASS)
67  @Execution(ExecutionMode.SAME_THREAD)
68  public class SocketSslLargeCertificateTest {
69  
70      private CertificateBuilder base;
71      private X509Bundle rootCert;
72      private MultiThreadIoEventLoopGroup group;
73  
74      @BeforeAll
75      public void setUp() throws Exception {
76          base = new CertificateBuilder()
77                  .ecp256()
78                  .setKeyUsage(true, CertificateBuilder.KeyUsage.digitalSignature,
79                          CertificateBuilder.KeyUsage.keyCertSign);
80          rootCert = base.copy()
81                  .subject("cn=root.netty.io")
82                  .setIsCertificateAuthority(true)
83                  .buildSelfSigned();
84          group = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
85      }
86  
87      @AfterAll
88      public void tearDown() {
89          group.shutdownGracefully(100, 1000, TimeUnit.MILLISECONDS);
90      }
91  
92      public static boolean supportKeyManagerAndTLS13() {
93          return OpenSsl.isAvailable() &&
94                  OpenSsl.supportsKeyManagerFactory() &&
95                  SslProvider.isTlsv13Supported(SslProvider.OPENSSL);
96      }
97  
98      public static Stream<Arguments> certExtensionSizes() {
99          int defaultMaxHandshakeMessageLength = 16384;
100         return IntStream.rangeClosed(defaultMaxHandshakeMessageLength - 768, defaultMaxHandshakeMessageLength)
101                 .mapToObj(Arguments::of);
102     }
103 
104     @ParameterizedTest
105     @MethodSource("certExtensionSizes")
106     void resumptionWithLargeCertificates(int certExtensionSize) throws Exception {
107         X509Bundle serverCert = base.copy()
108                 .subject("cn=localhost")
109                 .addExtendedKeyUsageServerAuth()
110                 .buildIssuedBy(rootCert);
111         byte[] extension = new byte[certExtensionSize];
112         ThreadLocalRandom.current().nextBytes(extension);
113         X509Bundle clientCert = base.copy()
114                 .subject("cn=client")
115                 .addExtendedKeyUsageClientAuth()
116                 .addExtensionOctetString("1.2.840.113635.100.6.2.1", false, extension)
117                 .buildIssuedBy(rootCert);
118 
119         TrustManagerFactory tmf = rootCert.toTrustManagerFactory();
120         KeyManagerFactory serverKmf = serverCert.toKeyManagerFactory();
121         KeyManagerFactory clientKmf = clientCert.toKeyManagerFactory();
122 
123         SslContext serverSsl = SslContextBuilder.forServer(serverKmf)
124                 .sslProvider(SslProvider.OPENSSL)
125                 .trustManager(tmf)
126                 .protocols("TLSv1.3")
127                 .clientAuth(ClientAuth.REQUIRE)
128                 .option(OpenSslContextOption.MAX_CERTIFICATE_LIST_BYTES, 32768)
129                 .build();
130         SslContext clientSsl = SslContextBuilder.forClient()
131                 .sslProvider(SslProvider.OPENSSL)
132                 .keyManager(clientKmf)
133                 .trustManager(tmf)
134                 .protocols("TLSv1.3")
135                 .option(OpenSslContextOption.MAX_CERTIFICATE_LIST_BYTES, 32768)
136                 .serverName(new SNIHostName("localhost"))
137                 .endpointIdentificationAlgorithm(null)
138                 .build();
139 
140         final Promise<Void> completion = ImmediateEventExecutor.INSTANCE.newPromise();
141 
142         ChannelFuture bindFuture = new ServerBootstrap()
143                 .group(group)
144                 .channel(NioServerSocketChannel.class)
145                 .childHandler(new ChannelInitializer<Channel>() {
146                     @Override
147                     protected void initChannel(Channel ch) throws Exception {
148                         ch.pipeline().addLast(serverSsl.newHandler(ch.alloc()));
149                         ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
150                             @Override
151                             public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
152                                 if (evt instanceof SslHandshakeCompletionEvent) {
153                                     SslHandshakeCompletionEvent completionEvent = (SslHandshakeCompletionEvent) evt;
154                                     if (completionEvent.isSuccess()) {
155                                         ctx.writeAndFlush(Unpooled.buffer());
156                                     } else {
157                                         completion.tryFailure(new ExecutionException(completionEvent.cause()));
158                                         ctx.close();
159                                     }
160                                 }
161                             }
162 
163                             @Override
164                             public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
165                                 ctx.write(msg);
166                             }
167 
168                             @Override
169                             public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
170                                 ctx.flush();
171                             }
172                         });
173                     }
174                 })
175                 .bind(InetAddress.getLoopbackAddress(), 0);
176         Channel serverChannel = bindFuture.sync().channel();
177         InetSocketAddress serverAddress = (InetSocketAddress) serverChannel.localAddress();
178         ChannelFuture connectFuture = new Bootstrap()
179                 .group(group)
180                 .channel(NioSocketChannel.class)
181                 .handler(new ChannelInitializer<Channel>() {
182                     @Override
183                     protected void initChannel(Channel ch) throws Exception {
184                         ch.pipeline().addLast(clientSsl.newHandler(ch.alloc(), "localhost", serverAddress.getPort()));
185                         ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
186                             @Override
187                             public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
188                                 if (evt instanceof SslHandshakeCompletionEvent) {
189                                     SslHandshakeCompletionEvent completionEvent = (SslHandshakeCompletionEvent) evt;
190                                     if (completionEvent.isSuccess()) {
191                                         ctx.writeAndFlush(Unpooled.copiedBuffer("hello", StandardCharsets.UTF_8));
192                                     } else {
193                                         completion.tryFailure(new ExecutionException(completionEvent.cause()));
194                                         ctx.close();
195                                     }
196                                 }
197                             }
198 
199                             private boolean receivedRead;
200                             @Override
201                             public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
202                                 receivedRead = true;
203                                 ReferenceCountUtil.release(msg);
204                             }
205 
206                             @Override
207                             public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
208                                 ctx.fireChannelReadComplete();
209                                 if (receivedRead) {
210                                     receivedRead = false;
211                                     ctx.writeAndFlush(Unpooled.buffer()).addListener(ChannelFutureListener.CLOSE);
212                                     ctx.channel().closeFuture().addListener(future -> completion.setSuccess(null));
213                                 }
214                             }
215 
216                             @Override
217                             public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
218                                 completion.tryFailure(cause);
219                                 super.exceptionCaught(ctx, cause);
220                             }
221                         });
222                     }
223                 })
224                 .connect(serverAddress);
225         Channel clientChannel = connectFuture.sync().channel();
226         completion.sync();
227         clientChannel.close().sync();
228         serverChannel.close().sync();
229     }
230 }