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.security.Provider;
57  import java.util.concurrent.ExecutionException;
58  import java.util.concurrent.ThreadLocalRandom;
59  import java.util.concurrent.TimeUnit;
60  import java.util.stream.IntStream;
61  import java.util.stream.Stream;
62  import javax.net.ssl.KeyManagerFactory;
63  import javax.net.ssl.SNIHostName;
64  import javax.net.ssl.TrustManagerFactory;
65  
66  @EnabledIf("supportKeyManagerAndTLS13")
67  @TestInstance(TestInstance.Lifecycle.PER_CLASS)
68  @Execution(ExecutionMode.SAME_THREAD)
69  public class SocketSslLargeCertificateTest {
70  
71      private CertificateBuilder base;
72      private X509Bundle rootCert;
73      private MultiThreadIoEventLoopGroup group;
74  
75      @BeforeAll
76      public void setUp() throws Exception {
77          base = new CertificateBuilder()
78                  .ecp256()
79                  .setKeyUsage(true, CertificateBuilder.KeyUsage.digitalSignature,
80                          CertificateBuilder.KeyUsage.keyCertSign);
81          rootCert = base.copy()
82                  .subject("cn=root.netty.io")
83                  .setIsCertificateAuthority(true)
84                  .buildSelfSigned();
85          group = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
86      }
87  
88      @AfterAll
89      public void tearDown() {
90          group.shutdownGracefully(100, 1000, TimeUnit.MILLISECONDS);
91      }
92  
93      public static boolean supportKeyManagerAndTLS13() {
94          return OpenSsl.isAvailable() &&
95                  OpenSsl.supportsKeyManagerFactory() &&
96                  SslProvider.isTlsv13Supported(SslProvider.OPENSSL);
97      }
98  
99      public static Stream<Arguments> certExtensionSizes() {
100         int defaultMaxHandshakeMessageLength = 16384;
101         return IntStream.rangeClosed(defaultMaxHandshakeMessageLength - 768, defaultMaxHandshakeMessageLength)
102                 .mapToObj(Arguments::of);
103     }
104 
105     @ParameterizedTest
106     @MethodSource("certExtensionSizes")
107     void resumptionWithLargeCertificates(int certExtensionSize) throws Exception {
108         X509Bundle serverCert = base.copy()
109                 .subject("cn=localhost")
110                 .addExtendedKeyUsageServerAuth()
111                 .buildIssuedBy(rootCert);
112         byte[] extension = new byte[certExtensionSize];
113         ThreadLocalRandom.current().nextBytes(extension);
114         X509Bundle clientCert = base.copy()
115                 .subject("cn=client")
116                 .addExtendedKeyUsageClientAuth()
117                 .addExtensionOctetString("1.2.840.113635.100.6.2.1", false, extension)
118                 .buildIssuedBy(rootCert);
119 
120         TrustManagerFactory tmf = rootCert.toTrustManagerFactory();
121         KeyManagerFactory serverKmf = serverCert.toKeyManagerFactory();
122         KeyManagerFactory clientKmf = clientCert.toKeyManagerFactory();
123 
124         SslContext serverSsl = SslContextBuilder.forServer(serverKmf)
125                 .sslProvider(SslProvider.OPENSSL)
126                 .trustManager(tmf)
127                 .protocols("TLSv1.3")
128                 .clientAuth(ClientAuth.REQUIRE)
129                 .option(OpenSslContextOption.MAX_CERTIFICATE_LIST_BYTES, 32768)
130                 .build();
131         SslContext clientSsl = SslContextBuilder.forClient()
132                 .sslProvider(SslProvider.OPENSSL)
133                 .keyManager(clientKmf)
134                 .trustManager(tmf)
135                 .protocols("TLSv1.3")
136                 .option(OpenSslContextOption.MAX_CERTIFICATE_LIST_BYTES, 32768)
137                 .serverName(new SNIHostName("localhost"))
138                 .endpointIdentificationAlgorithm(null)
139                 .build();
140 
141         final Promise<Void> completion = ImmediateEventExecutor.INSTANCE.newPromise();
142 
143         ChannelFuture bindFuture = new ServerBootstrap()
144                 .group(group)
145                 .channel(NioServerSocketChannel.class)
146                 .childHandler(new ChannelInitializer<Channel>() {
147                     @Override
148                     protected void initChannel(Channel ch) throws Exception {
149                         ch.pipeline().addLast(serverSsl.newHandler(ch.alloc()));
150                         ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
151                             @Override
152                             public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
153                                 if (evt instanceof SslHandshakeCompletionEvent) {
154                                     SslHandshakeCompletionEvent completionEvent = (SslHandshakeCompletionEvent) evt;
155                                     if (completionEvent.isSuccess()) {
156                                         ctx.writeAndFlush(Unpooled.buffer());
157                                     } else {
158                                         completion.tryFailure(new ExecutionException(completionEvent.cause()));
159                                         ctx.close();
160                                     }
161                                 }
162                             }
163 
164                             @Override
165                             public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
166                                 ctx.write(msg);
167                             }
168 
169                             @Override
170                             public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
171                                 ctx.flush();
172                             }
173                         });
174                     }
175                 })
176                 .bind(InetAddress.getLoopbackAddress(), 0);
177         Channel serverChannel = bindFuture.sync().channel();
178         InetSocketAddress serverAddress = (InetSocketAddress) serverChannel.localAddress();
179         ChannelFuture connectFuture = new Bootstrap()
180                 .group(group)
181                 .channel(NioSocketChannel.class)
182                 .handler(new ChannelInitializer<Channel>() {
183                     @Override
184                     protected void initChannel(Channel ch) throws Exception {
185                         ch.pipeline().addLast(clientSsl.newHandler(ch.alloc(), "localhost", serverAddress.getPort()));
186                         ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
187                             @Override
188                             public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
189                                 if (evt instanceof SslHandshakeCompletionEvent) {
190                                     SslHandshakeCompletionEvent completionEvent = (SslHandshakeCompletionEvent) evt;
191                                     if (completionEvent.isSuccess()) {
192                                         ctx.writeAndFlush(Unpooled.copiedBuffer("hello", StandardCharsets.UTF_8));
193                                     } else {
194                                         completion.tryFailure(new ExecutionException(completionEvent.cause()));
195                                         ctx.close();
196                                     }
197                                 }
198                             }
199 
200                             private boolean receivedRead;
201                             @Override
202                             public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
203                                 receivedRead = true;
204                                 ReferenceCountUtil.release(msg);
205                             }
206 
207                             @Override
208                             public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
209                                 ctx.fireChannelReadComplete();
210                                 if (receivedRead) {
211                                     receivedRead = false;
212                                     ctx.writeAndFlush(Unpooled.buffer()).addListener(ChannelFutureListener.CLOSE);
213                                     ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
214                                         @Override
215                                         public void operationComplete(ChannelFuture future) throws Exception {
216                                             completion.setSuccess(null);
217                                         }
218                                     });
219                                 }
220                             }
221 
222                             @Override
223                             public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
224                                 completion.tryFailure(cause);
225                                 super.exceptionCaught(ctx, cause);
226                             }
227                         });
228                     }
229                 })
230                 .connect(serverAddress);
231         Channel clientChannel = connectFuture.sync().channel();
232         completion.sync();
233         clientChannel.close().sync();
234         serverChannel.close().sync();
235     }
236 }