1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.ByteBuf;
21 import io.netty.buffer.ByteBufUtil;
22 import io.netty.buffer.Unpooled;
23 import io.netty.channel.Channel;
24 import io.netty.channel.ChannelFutureListener;
25 import io.netty.channel.ChannelHandlerContext;
26 import io.netty.channel.ChannelHandler.Sharable;
27 import io.netty.channel.ChannelInitializer;
28 import io.netty.channel.SimpleChannelInboundHandler;
29 import io.netty.channel.socket.SocketChannel;
30 import io.netty.handler.ssl.ResumableX509ExtendedTrustManager;
31 import io.netty.handler.ssl.SslContext;
32 import io.netty.handler.ssl.SslContextBuilder;
33 import io.netty.handler.ssl.SslHandler;
34 import io.netty.handler.ssl.SslHandshakeCompletionEvent;
35 import io.netty.handler.ssl.SslProvider;
36 import io.netty.pkitesting.CertificateBuilder;
37 import io.netty.pkitesting.X509Bundle;
38 import io.netty.util.internal.logging.InternalLogger;
39 import io.netty.util.internal.logging.InternalLoggerFactory;
40
41 import org.junit.jupiter.api.TestInfo;
42 import org.junit.jupiter.api.Timeout;
43 import org.junit.jupiter.params.ParameterizedTest;
44 import org.junit.jupiter.params.provider.MethodSource;
45
46 import javax.net.ssl.SSLEngine;
47 import javax.net.ssl.SSLSession;
48 import javax.net.ssl.SSLSessionContext;
49 import javax.net.ssl.TrustManager;
50 import javax.net.ssl.X509ExtendedTrustManager;
51
52 import java.io.File;
53 import java.io.IOException;
54 import java.net.InetSocketAddress;
55 import java.net.Socket;
56 import java.security.cert.CertificateException;
57 import java.security.cert.X509Certificate;
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.Collections;
61 import java.util.Enumeration;
62 import java.util.HashSet;
63 import java.util.Set;
64 import java.util.concurrent.BlockingQueue;
65 import java.util.concurrent.ExecutionException;
66 import java.util.concurrent.LinkedBlockingQueue;
67 import java.util.concurrent.TimeUnit;
68 import java.util.concurrent.atomic.AtomicReference;
69
70 import static io.netty.testsuite.transport.TestsuitePermutation.randomBufferType;
71 import static org.junit.jupiter.api.Assertions.assertEquals;
72 import static org.junit.jupiter.api.Assertions.assertTrue;
73
74 public class SocketSslSessionReuseTest extends AbstractSocketTest {
75
76 private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketSslSessionReuseTest.class);
77
78 private static final File CERT_FILE;
79 private static final File KEY_FILE;
80
81 static {
82 try {
83 X509Bundle cert = new CertificateBuilder()
84 .subject("cn=localhost")
85 .setIsCertificateAuthority(true)
86 .buildSelfSigned();
87 CERT_FILE = cert.toTempCertChainPem();
88 KEY_FILE = cert.toTempPrivateKeyPem();
89 } catch (Exception e) {
90 throw new ExceptionInInitializerError(e);
91 }
92 }
93
94 public static Collection<Object[]> jdkOnly() throws Exception {
95 return Collections.singleton(new Object[]{
96 SslContextBuilder.forServer(CERT_FILE, KEY_FILE).sslProvider(SslProvider.JDK),
97 SslContextBuilder.forClient().trustManager(CERT_FILE).sslProvider(SslProvider.JDK)
98 .endpointIdentificationAlgorithm(null)
99 });
100 }
101
102 public static Collection<Object[]> jdkAndOpenSSL() throws Exception {
103 return Arrays.asList(new Object[]{
104 SslContextBuilder.forServer(CERT_FILE, KEY_FILE).sslProvider(SslProvider.JDK),
105 SslContextBuilder.forClient().trustManager(CERT_FILE).sslProvider(SslProvider.JDK)
106 .endpointIdentificationAlgorithm(null)
107 },
108 new Object[]{
109 SslContextBuilder.forServer(CERT_FILE, KEY_FILE).sslProvider(SslProvider.OPENSSL),
110 SslContextBuilder.forClient().trustManager(CERT_FILE).sslProvider(SslProvider.OPENSSL)
111 .endpointIdentificationAlgorithm(null)
112 });
113 }
114
115 @ParameterizedTest(name = "{index}: serverEngine = {0}, clientEngine = {1}")
116 @MethodSource("jdkOnly")
117 @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
118 public void testSslSessionReuse(
119 final SslContextBuilder serverCtx, final SslContextBuilder clientCtx, TestInfo testInfo) throws Throwable {
120 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
121 @Override
122 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
123 testSslSessionReuse(sb, cb, serverCtx.build(), clientCtx.build());
124 }
125 });
126 }
127
128 public void testSslSessionReuse(ServerBootstrap sb, Bootstrap cb,
129 final SslContext serverCtx, final SslContext clientCtx) throws Throwable {
130 final ReadAndDiscardHandler sh = new ReadAndDiscardHandler(true, true);
131 final ReadAndDiscardHandler ch = new ReadAndDiscardHandler(false, true);
132 final String[] protocols = { "TLSv1", "TLSv1.1", "TLSv1.2" };
133
134 sb.childHandler(new ChannelInitializer<SocketChannel>() {
135 @Override
136 protected void initChannel(SocketChannel sch) throws Exception {
137 SSLEngine engine = serverCtx.newEngine(sch.alloc());
138 engine.setUseClientMode(false);
139 engine.setEnabledProtocols(protocols);
140
141 sch.pipeline().addLast(new SslHandler(engine));
142 sch.pipeline().addLast(sh);
143 }
144 });
145 final Channel sc = sb.bind().sync().channel();
146
147 cb.handler(new ChannelInitializer<SocketChannel>() {
148 @Override
149 protected void initChannel(SocketChannel sch) throws Exception {
150 InetSocketAddress serverAddr = (InetSocketAddress) sc.localAddress();
151 SSLEngine engine = clientCtx.newEngine(sch.alloc(), serverAddr.getHostString(), serverAddr.getPort());
152 engine.setUseClientMode(true);
153 engine.setEnabledProtocols(protocols);
154
155 sch.pipeline().addLast(new SslHandler(engine));
156 sch.pipeline().addLast(ch);
157 }
158 });
159
160 try {
161 SSLSessionContext clientSessionCtx = clientCtx.sessionContext();
162 Channel cc = cb.connect(sc.localAddress()).sync().channel();
163 ByteBuf msg = randomBufferType(cc.alloc(), new byte[] { 0xa, 0xb, 0xc, 0xd }, 0, 4);
164
165 cc.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE).sync();
166 cc.closeFuture().sync();
167 rethrowHandlerExceptions(sh, ch);
168 Set<String> sessions = sessionIdSet(clientSessionCtx.getIds());
169
170 cc = cb.connect(sc.localAddress()).sync().channel();
171 msg = randomBufferType(cc.alloc(), new byte[] { 0xa, 0xb, 0xc, 0xd }, 0, 4);
172 cc.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE).sync();
173 cc.closeFuture().sync();
174 assertEquals(sessions, sessionIdSet(clientSessionCtx.getIds()), "Expected no new sessions");
175 rethrowHandlerExceptions(sh, ch);
176 } finally {
177 sc.close().awaitUninterruptibly();
178 }
179 }
180
181 @ParameterizedTest(name = "{index}: serverEngine = {0}, clientEngine = {1}")
182 @MethodSource("jdkAndOpenSSL")
183 @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
184 public void testSslSessionTrustManagerResumption(
185 final SslContextBuilder serverCtx, final SslContextBuilder clientCtx, TestInfo testInfo) throws Throwable {
186 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
187 @Override
188 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
189 testSslSessionTrustManagerResumption(sb, cb, serverCtx, clientCtx);
190 }
191 });
192 }
193
194 public void testSslSessionTrustManagerResumption(
195 ServerBootstrap sb, Bootstrap cb,
196 SslContextBuilder serverCtxBldr, final SslContextBuilder clientCtxBldr) throws Throwable {
197 final String[] protocols = { "TLSv1", "TLSv1.1", "TLSv1.2" };
198 serverCtxBldr.protocols(protocols);
199 clientCtxBldr.protocols(protocols);
200 TrustManager clientTrustManager = new SessionSettingTrustManager();
201 clientCtxBldr.trustManager(clientTrustManager);
202 final SslContext serverContext = serverCtxBldr.build();
203 final SslContext clientContext = clientCtxBldr.build();
204
205 final BlockingQueue<String> sessionValue = new LinkedBlockingQueue<String>();
206 final ReadAndDiscardHandler sh = new ReadAndDiscardHandler(true, true);
207 final ReadAndDiscardHandler ch = new ReadAndDiscardHandler(false, true) {
208 @Override
209 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
210 if (evt instanceof SslHandshakeCompletionEvent) {
211 SslHandshakeCompletionEvent handshakeCompletionEvent = (SslHandshakeCompletionEvent) evt;
212 if (handshakeCompletionEvent.isSuccess()) {
213 SSLSession session = ctx.pipeline().get(SslHandler.class).engine().getSession();
214 assertTrue(sessionValue.offer(String.valueOf(session.getValue("key"))));
215 } else {
216 logger.error("SSL handshake failed", handshakeCompletionEvent.cause());
217 }
218 }
219 super.userEventTriggered(ctx, evt);
220 }
221 };
222
223 sb.childHandler(new ChannelInitializer<SocketChannel>() {
224 @Override
225 protected void initChannel(SocketChannel sch) throws Exception {
226 sch.pipeline().addLast(serverContext.newHandler(sch.alloc()));
227 sch.pipeline().addLast(sh);
228 }
229 });
230 final Channel sc = sb.bind().sync().channel();
231
232 cb.handler(new ChannelInitializer<SocketChannel>() {
233 @Override
234 protected void initChannel(SocketChannel sch) throws Exception {
235 InetSocketAddress serverAddr = (InetSocketAddress) sc.localAddress();
236 SslHandler sslHandler = clientContext.newHandler(
237 sch.alloc(), serverAddr.getHostString(), serverAddr.getPort());
238
239 sch.pipeline().addLast(sslHandler);
240 sch.pipeline().addLast(ch);
241 }
242 });
243
244 try {
245 Channel cc = cb.connect(sc.localAddress()).sync().channel();
246 ByteBuf msg = randomBufferType(cc.alloc(), new byte[] { 0xa, 0xb, 0xc, 0xd }, 0, 4);
247 cc.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE).sync();
248 cc.closeFuture().sync();
249 rethrowHandlerExceptions(sh, ch);
250 assertEquals("value", sessionValue.poll(10, TimeUnit.SECONDS));
251
252 cc = cb.connect(sc.localAddress()).sync().channel();
253 msg = randomBufferType(cc.alloc(), new byte[] { 0xa, 0xb, 0xc, 0xd }, 0, 4);
254 cc.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE).sync();
255 cc.closeFuture().sync();
256 rethrowHandlerExceptions(sh, ch);
257 assertEquals("value", sessionValue.poll(10, TimeUnit.SECONDS));
258 } finally {
259 sc.close().awaitUninterruptibly();
260 }
261 }
262
263 private static void rethrowHandlerExceptions(ReadAndDiscardHandler sh, ReadAndDiscardHandler ch) throws Throwable {
264 if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
265 throw new ExecutionException(sh.exception.get());
266 }
267 if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
268 throw new ExecutionException(ch.exception.get());
269 }
270 if (sh.exception.get() != null) {
271 throw new ExecutionException(sh.exception.get());
272 }
273 if (ch.exception.get() != null) {
274 throw new ExecutionException(ch.exception.get());
275 }
276 }
277
278 private static Set<String> sessionIdSet(Enumeration<byte[]> sessionIds) {
279 Set<String> idSet = new HashSet<String>();
280 byte[] id;
281 while (sessionIds.hasMoreElements()) {
282 id = sessionIds.nextElement();
283 idSet.add(ByteBufUtil.hexDump(Unpooled.wrappedBuffer(id)));
284 }
285 return idSet;
286 }
287
288 @Sharable
289 private static class ReadAndDiscardHandler extends SimpleChannelInboundHandler<ByteBuf> {
290 final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
291 private final boolean server;
292 private final boolean autoRead;
293
294 ReadAndDiscardHandler(boolean server, boolean autoRead) {
295 this.server = server;
296 this.autoRead = autoRead;
297 }
298
299 @Override
300 public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
301 in.skipBytes(in.readableBytes());
302 }
303
304 @Override
305 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
306 try {
307 ctx.flush();
308 } finally {
309 if (!autoRead) {
310 ctx.read();
311 }
312 }
313 }
314
315 @Override
316 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
317 if (logger.isWarnEnabled()) {
318 logger.warn(
319 "Unexpected exception from the " +
320 (server? "server" : "client") + " side", cause);
321 }
322
323 exception.compareAndSet(null, cause);
324 ctx.close();
325 }
326 }
327
328 private static final class SessionSettingTrustManager extends X509ExtendedTrustManager
329 implements ResumableX509ExtendedTrustManager {
330 @Override
331 public void resumeServerTrusted(X509Certificate[] chain, SSLEngine engine) throws CertificateException {
332 engine.getSession().putValue("key", "value");
333 }
334
335 @Override
336 public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
337 throws CertificateException {
338 engine.getHandshakeSession().putValue("key", "value");
339 }
340
341 @Override
342 public void resumeClientTrusted(X509Certificate[] chain, SSLEngine engine) throws CertificateException {
343 throw new CertificateException("Unsupported operation");
344 }
345
346 @Override
347 public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
348 throws CertificateException {
349 throw new CertificateException("Unsupported operation");
350 }
351
352 @Override
353 public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
354 throws CertificateException {
355 throw new CertificateException("Unsupported operation");
356 }
357
358 @Override
359 public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
360 throws CertificateException {
361 throw new CertificateException("Unsupported operation");
362 }
363
364 @Override
365 public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
366 throw new CertificateException("Unsupported operation");
367 }
368
369 @Override
370 public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
371 throw new CertificateException("Unsupported operation");
372 }
373
374 @Override
375 public X509Certificate[] getAcceptedIssuers() {
376 return new X509Certificate[0];
377 }
378 }
379 }