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.ByteBufAllocator;
22 import io.netty.channel.Channel;
23 import io.netty.channel.ChannelHandler.Sharable;
24 import io.netty.channel.ChannelHandlerContext;
25 import io.netty.channel.ChannelInitializer;
26 import io.netty.channel.SimpleChannelInboundHandler;
27 import io.netty.handler.codec.DecoderException;
28 import io.netty.handler.ssl.JdkSslClientContext;
29 import io.netty.handler.ssl.OpenSsl;
30 import io.netty.handler.ssl.OpenSslServerContext;
31 import io.netty.handler.ssl.SslContext;
32 import io.netty.handler.ssl.SslHandler;
33 import io.netty.handler.ssl.SslHandshakeCompletionEvent;
34 import io.netty.pkitesting.CertificateBuilder;
35 import io.netty.pkitesting.X509Bundle;
36 import io.netty.util.concurrent.Future;
37 import io.netty.util.internal.logging.InternalLogger;
38 import io.netty.util.internal.logging.InternalLoggerFactory;
39 import org.junit.jupiter.api.TestInfo;
40 import org.junit.jupiter.api.Timeout;
41 import org.junit.jupiter.api.condition.DisabledIf;
42 import org.junit.jupiter.params.ParameterizedTest;
43 import org.junit.jupiter.params.provider.MethodSource;
44
45 import java.io.File;
46 import java.nio.channels.ClosedChannelException;
47 import java.util.ArrayList;
48 import java.util.Collection;
49 import java.util.List;
50 import java.util.concurrent.Executor;
51 import java.util.concurrent.ExecutorService;
52 import java.util.concurrent.Executors;
53 import java.util.concurrent.TimeUnit;
54 import java.util.concurrent.atomic.AtomicReference;
55
56 import javax.net.ssl.SSLHandshakeException;
57
58 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
59 import static org.junit.jupiter.api.Assertions.assertSame;
60 import static org.junit.jupiter.api.Assertions.assertThrows;
61 import static org.junit.jupiter.api.Assertions.assertTrue;
62 import static org.junit.jupiter.api.Assumptions.assumeTrue;
63
64 public class SocketSslClientRenegotiateTest extends AbstractSocketTest {
65 private static final InternalLogger logger = InternalLoggerFactory.getInstance(
66 SocketSslClientRenegotiateTest.class);
67 private static final File CERT_FILE;
68 private static final File KEY_FILE;
69
70 static {
71 try {
72 X509Bundle cert = new CertificateBuilder()
73 .subject("cn=localhost")
74 .setIsCertificateAuthority(true)
75 .buildSelfSigned();
76 CERT_FILE = cert.toTempCertChainPem();
77 KEY_FILE = cert.toTempPrivateKeyPem();
78 } catch (Exception e) {
79 throw new ExceptionInInitializerError(e);
80 }
81 }
82
83 private static boolean openSslNotAvailable() {
84 return !OpenSsl.isAvailable();
85 }
86
87 public static Collection<Object[]> data() throws Exception {
88 List<SslContext> serverContexts = new ArrayList<SslContext>();
89 List<SslContext> clientContexts = new ArrayList<SslContext>();
90 clientContexts.add(new JdkSslClientContext(CERT_FILE));
91
92 boolean hasOpenSsl = OpenSsl.isAvailable();
93 if (hasOpenSsl) {
94 OpenSslServerContext context = new OpenSslServerContext(CERT_FILE, KEY_FILE);
95 serverContexts.add(context);
96 } else {
97 logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
98 }
99
100 List<Object[]> params = new ArrayList<Object[]>();
101 for (SslContext sc: serverContexts) {
102 for (SslContext cc: clientContexts) {
103 for (int i = 0; i < 32; i++) {
104 params.add(new Object[] { sc, cc, true});
105 params.add(new Object[] { sc, cc, false});
106 }
107 }
108 }
109
110 return params;
111 }
112
113 private final AtomicReference<Throwable> clientException = new AtomicReference<Throwable>();
114 private final AtomicReference<Throwable> serverException = new AtomicReference<Throwable>();
115
116 private volatile Channel clientChannel;
117 private volatile Channel serverChannel;
118
119 private volatile SslHandler clientSslHandler;
120 private volatile SslHandler serverSslHandler;
121
122 private final TestHandler clientHandler = new TestHandler(clientException);
123
124 private final TestHandler serverHandler = new TestHandler(serverException);
125
126 @DisabledIf("openSslNotAvailable")
127 @ParameterizedTest(name = "{index}: serverEngine = {0}, clientEngine = {1}, delegate = {2}")
128 @MethodSource("data")
129 @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
130 public void testSslRenegotiationRejected(final SslContext serverCtx, final SslContext clientCtx,
131 final boolean delegate, TestInfo testInfo) throws Throwable {
132 assumeTrue(OpenSsl.isRenegotiationSupported());
133 assumeTrue(OpenSsl.isAvailable());
134 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
135 @Override
136 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
137 testSslRenegotiationRejected(sb, cb, serverCtx, clientCtx, delegate);
138 }
139 });
140 }
141
142 private static SslHandler newSslHandler(SslContext sslCtx, ByteBufAllocator allocator, Executor executor) {
143 if (executor == null) {
144 return sslCtx.newHandler(allocator);
145 } else {
146 return sslCtx.newHandler(allocator, executor);
147 }
148 }
149
150 public void testSslRenegotiationRejected(ServerBootstrap sb, Bootstrap cb, final SslContext serverCtx,
151 final SslContext clientCtx, boolean delegate) throws Throwable {
152 reset();
153
154 final ExecutorService executorService = delegate ? Executors.newCachedThreadPool() : null;
155
156 try {
157 sb.childHandler(new ChannelInitializer<Channel>() {
158 @Override
159 @SuppressWarnings("deprecation")
160 public void initChannel(Channel sch) throws Exception {
161 serverChannel = sch;
162 serverSslHandler = newSslHandler(serverCtx, sch.alloc(), executorService);
163
164 serverSslHandler.engine().setEnabledProtocols(new String[]{"TLSv1.2"});
165 sch.pipeline().addLast("ssl", serverSslHandler);
166 sch.pipeline().addLast("handler", serverHandler);
167 }
168 });
169
170 cb.handler(new ChannelInitializer<Channel>() {
171 @Override
172 @SuppressWarnings("deprecation")
173 public void initChannel(Channel sch) throws Exception {
174 clientChannel = sch;
175 clientSslHandler = newSslHandler(clientCtx, sch.alloc(), executorService);
176
177 clientSslHandler.engine().setEnabledProtocols(new String[]{"TLSv1.2"});
178 sch.pipeline().addLast("ssl", clientSslHandler);
179 sch.pipeline().addLast("handler", clientHandler);
180 }
181 });
182
183 Channel sc = sb.bind().sync().channel();
184 cb.connect(sc.localAddress()).sync();
185
186 Future<Channel> clientHandshakeFuture = clientSslHandler.handshakeFuture();
187 clientHandshakeFuture.sync();
188
189 String renegotiation = clientSslHandler.engine().getEnabledCipherSuites()[0];
190
191 clientSslHandler.engine().setEnabledCipherSuites(new String[]{renegotiation});
192 clientSslHandler.renegotiate().await();
193 serverChannel.close().awaitUninterruptibly();
194 clientChannel.close().awaitUninterruptibly();
195 sc.close().awaitUninterruptibly();
196 DecoderException e = assertThrows(DecoderException.class, () -> {
197 if (serverException.get() != null) {
198 throw serverException.get();
199 }
200 });
201 assertInstanceOf(SSLHandshakeException.class, e.getCause());
202 if (clientException.get() != null) {
203 throw clientException.get();
204 }
205 } finally {
206 if (executorService != null) {
207 executorService.shutdown();
208 assertTrue(executorService.awaitTermination(5, TimeUnit.SECONDS));
209 }
210 }
211 }
212
213 private void reset() {
214 clientException.set(null);
215 serverException.set(null);
216 clientHandler.handshakeCounter = 0;
217 serverHandler.handshakeCounter = 0;
218 clientChannel = null;
219 serverChannel = null;
220
221 clientSslHandler = null;
222 serverSslHandler = null;
223 }
224
225 @Sharable
226 private static final class TestHandler extends SimpleChannelInboundHandler<ByteBuf> {
227
228 protected final AtomicReference<Throwable> exception;
229 private int handshakeCounter;
230
231 TestHandler(AtomicReference<Throwable> exception) {
232 this.exception = exception;
233 }
234
235 @Override
236 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
237 ctx.flush();
238 }
239
240 @Override
241 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
242 exception.compareAndSet(null, cause);
243 ctx.close();
244 }
245
246 @Override
247 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
248 if (evt instanceof SslHandshakeCompletionEvent) {
249 SslHandshakeCompletionEvent handshakeEvt = (SslHandshakeCompletionEvent) evt;
250 if (handshakeCounter == 0) {
251 handshakeCounter++;
252 if (handshakeEvt.cause() != null) {
253 logger.warn("Handshake failed:", handshakeEvt.cause());
254 }
255 assertSame(SslHandshakeCompletionEvent.SUCCESS, evt);
256 } else {
257 if (ctx.channel().parent() == null) {
258 assertInstanceOf(ClosedChannelException.class, handshakeEvt.cause());
259 }
260 }
261 }
262 }
263
264 @Override
265 public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { }
266 }
267 }