View Javadoc
1   /*
2   * Copyright 2014 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.ByteBuf;
21  import io.netty.buffer.ByteBufAllocator;
22  import io.netty.channel.Channel;
23  import io.netty.channel.ChannelHandlerContext;
24  import io.netty.channel.ChannelInitializer;
25  import io.netty.channel.ChannelPipeline;
26  import io.netty.channel.SimpleChannelInboundHandler;
27  import io.netty.handler.logging.LogLevel;
28  import io.netty.handler.logging.LoggingHandler;
29  import io.netty.handler.ssl.OpenSsl;
30  import io.netty.handler.ssl.SslContext;
31  import io.netty.handler.ssl.SslContextBuilder;
32  import io.netty.handler.ssl.SslHandler;
33  import io.netty.handler.ssl.SslHandshakeCompletionEvent;
34  import io.netty.handler.ssl.SslProvider;
35  import io.netty.pkitesting.CertificateBuilder;
36  import io.netty.pkitesting.X509Bundle;
37  import io.netty.util.internal.PlatformDependent;
38  import io.netty.util.internal.logging.InternalLogger;
39  import io.netty.util.internal.logging.InternalLoggerFactory;
40  import org.junit.jupiter.api.TestInfo;
41  import org.junit.jupiter.api.Timeout;
42  import org.junit.jupiter.params.ParameterizedTest;
43  import org.junit.jupiter.params.provider.MethodSource;
44  
45  import javax.net.ssl.SSLPeerUnverifiedException;
46  import javax.net.ssl.SSLSession;
47  import java.io.File;
48  import java.io.IOException;
49  import java.util.ArrayList;
50  import java.util.Collection;
51  import java.util.List;
52  import java.util.concurrent.CountDownLatch;
53  import java.util.concurrent.Executor;
54  import java.util.concurrent.ExecutorService;
55  import java.util.concurrent.Executors;
56  import java.util.concurrent.TimeUnit;
57  import java.util.concurrent.atomic.AtomicReference;
58  
59  import static org.junit.jupiter.api.Assertions.assertEquals;
60  import static org.junit.jupiter.api.Assertions.assertFalse;
61  import static org.junit.jupiter.api.Assertions.assertThrows;
62  import static org.junit.jupiter.api.Assertions.assertTrue;
63  import static org.junit.jupiter.api.Assertions.fail;
64  
65  public class SocketSslGreetingTest extends AbstractSocketTest {
66  
67      private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketSslGreetingTest.class);
68  
69      private static final LogLevel LOG_LEVEL = LogLevel.TRACE;
70      private static final File CERT_FILE;
71      private static final File KEY_FILE;
72  
73      static {
74          try {
75              X509Bundle cert = new CertificateBuilder()
76                      .subject("cn=localhost")
77                      .setIsCertificateAuthority(true)
78                      .buildSelfSigned();
79              CERT_FILE = cert.toTempCertChainPem();
80              KEY_FILE = cert.toTempPrivateKeyPem();
81          } catch (Exception e) {
82              throw new ExceptionInInitializerError(e);
83          }
84      }
85  
86      public static Collection<Object[]> data() throws Exception {
87          List<SslContext> serverContexts = new ArrayList<SslContext>();
88          serverContexts.add(SslContextBuilder.forServer(CERT_FILE, KEY_FILE).sslProvider(SslProvider.JDK).build());
89  
90          List<SslContext> clientContexts = new ArrayList<SslContext>();
91          clientContexts.add(SslContextBuilder.forClient().sslProvider(SslProvider.JDK)
92                  .endpointIdentificationAlgorithm(null).trustManager(CERT_FILE).build());
93  
94          boolean hasOpenSsl = OpenSsl.isAvailable();
95          if (hasOpenSsl) {
96              serverContexts.add(SslContextBuilder.forServer(CERT_FILE, KEY_FILE)
97                                                  .sslProvider(SslProvider.OPENSSL).build());
98              clientContexts.add(SslContextBuilder.forClient().sslProvider(SslProvider.OPENSSL)
99                                                  .endpointIdentificationAlgorithm(null)
100                                                 .trustManager(CERT_FILE).build());
101         } else {
102             logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause());
103         }
104 
105         List<Object[]> params = new ArrayList<Object[]>();
106         for (SslContext sc: serverContexts) {
107             for (SslContext cc: clientContexts) {
108                 params.add(new Object[] { sc, cc, true });
109                 params.add(new Object[] { sc, cc, false });
110             }
111         }
112         return params;
113     }
114 
115     private static SslHandler newSslHandler(SslContext sslCtx, ByteBufAllocator allocator, Executor executor) {
116         if (executor == null) {
117             return sslCtx.newHandler(allocator);
118         } else {
119             return sslCtx.newHandler(allocator, executor);
120         }
121     }
122 
123     // Test for https://github.com/netty/netty/pull/2437
124     @ParameterizedTest(name = "{index}: serverEngine = {0}, clientEngine = {1}, delegate = {2}")
125     @MethodSource("data")
126     @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
127     public void testSslGreeting(final SslContext serverCtx, final SslContext clientCtx, final boolean delegate,
128                                 TestInfo testInfo) throws Throwable {
129         run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
130             @Override
131             public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
132                 testSslGreeting(sb, cb, serverCtx, clientCtx, delegate);
133             }
134         });
135     }
136 
137     public void testSslGreeting(ServerBootstrap sb, Bootstrap cb, final SslContext serverCtx,
138                                 final SslContext clientCtx, boolean delegate) throws Throwable {
139         final ServerHandler sh = new ServerHandler();
140         final ClientHandler ch = new ClientHandler();
141 
142         final ExecutorService executorService = delegate ? Executors.newCachedThreadPool() : null;
143         try {
144             sb.childHandler(new ChannelInitializer<Channel>() {
145                 @Override
146                 public void initChannel(Channel sch) throws Exception {
147                     ChannelPipeline p = sch.pipeline();
148                     p.addLast(newSslHandler(serverCtx, sch.alloc(), executorService));
149                     p.addLast(new LoggingHandler(LOG_LEVEL));
150                     p.addLast(sh);
151                 }
152             });
153 
154             cb.handler(new ChannelInitializer<Channel>() {
155                 @Override
156                 public void initChannel(Channel sch) throws Exception {
157                     ChannelPipeline p = sch.pipeline();
158                     p.addLast(newSslHandler(clientCtx, sch.alloc(), executorService));
159                     p.addLast(new LoggingHandler(LOG_LEVEL));
160                     p.addLast(ch);
161                 }
162             });
163 
164             Channel sc = sb.bind().sync().channel();
165             Channel cc = cb.connect(sc.localAddress()).sync().channel();
166 
167             ch.latch.await();
168 
169             sh.channel.close().awaitUninterruptibly();
170             cc.close().awaitUninterruptibly();
171             sc.close().awaitUninterruptibly();
172 
173             if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
174                 throw sh.exception.get();
175             }
176             if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
177                 throw ch.exception.get();
178             }
179             if (sh.exception.get() != null) {
180                 throw sh.exception.get();
181             }
182             if (ch.exception.get() != null) {
183                 throw ch.exception.get();
184             }
185         } finally {
186             if (executorService != null) {
187                 executorService.shutdown();
188                 assertTrue(executorService.awaitTermination(5, TimeUnit.SECONDS));
189             }
190         }
191     }
192 
193     private static class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
194 
195         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
196         final CountDownLatch latch = new CountDownLatch(1);
197 
198         @Override
199         public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
200             assertEquals('a', buf.readByte());
201             assertFalse(buf.isReadable());
202             latch.countDown();
203             ctx.close();
204         }
205 
206         @Override
207         public void exceptionCaught(ChannelHandlerContext ctx,
208                                     Throwable cause) throws Exception {
209             if (logger.isWarnEnabled()) {
210                 logger.warn("Unexpected exception from the client side", cause);
211             }
212 
213             exception.compareAndSet(null, cause);
214             ctx.close();
215         }
216     }
217 
218     private static class ServerHandler extends SimpleChannelInboundHandler<String> {
219         volatile Channel channel;
220         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
221 
222         @Override
223         protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
224             // discard
225         }
226 
227         @Override
228         public void channelActive(ChannelHandlerContext ctx)
229                 throws Exception {
230             channel = ctx.channel();
231             channel.writeAndFlush(ctx.alloc().buffer().writeByte('a'));
232         }
233 
234         @Override
235         public void exceptionCaught(ChannelHandlerContext ctx,
236                                     Throwable cause) throws Exception {
237             if (logger.isWarnEnabled()) {
238                 logger.warn("Unexpected exception from the server side", cause);
239             }
240 
241             exception.compareAndSet(null, cause);
242             ctx.close();
243         }
244 
245         @Override
246         public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception {
247             if (evt instanceof SslHandshakeCompletionEvent) {
248                 final SslHandshakeCompletionEvent event = (SslHandshakeCompletionEvent) evt;
249                 if (event.isSuccess()) {
250                     SSLSession session = ctx.pipeline().get(SslHandler.class).engine().getSession();
251                     assertThrows(SSLPeerUnverifiedException.class, () -> session.getPeerCertificates());
252                     try {
253                         session.getPeerCertificateChain();
254                         fail();
255                     } catch (SSLPeerUnverifiedException e) {
256                         // expected
257                     } catch (UnsupportedOperationException e) {
258                         // Starting from Java15 this method throws UnsupportedOperationException as it was
259                         // deprecated before and getPeerCertificates() should be used
260                         if (PlatformDependent.javaVersion() < 15) {
261                             throw e;
262                         }
263                     }
264                     assertThrows(SSLPeerUnverifiedException.class, () -> session.getPeerPrincipal());
265                 }
266             }
267             ctx.fireUserEventTriggered(evt);
268         }
269     }
270 }