View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  
21  package org.apache.mina.core.service;
22  
23  import static org.junit.Assert.fail;
24  
25  import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
26  import org.apache.mina.core.future.ConnectFuture;
27  import org.apache.mina.core.service.AbstractIoService;
28  import org.apache.mina.core.service.IoHandlerAdapter;
29  import org.apache.mina.core.session.IoSession;
30  import org.apache.mina.filter.codec.ProtocolCodecFilter;
31  import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
32  import org.apache.mina.filter.ssl.SslFilter;
33  import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
34  import org.apache.mina.transport.socket.nio.NioSocketConnector;
35  import org.apache.mina.util.AvailablePortFinder;
36  import org.junit.Ignore;
37  import org.junit.Test;
38  
39  import javax.net.ssl.KeyManagerFactory;
40  import javax.net.ssl.SSLContext;
41  import javax.net.ssl.TrustManagerFactory;
42  
43  import java.io.IOException;
44  import java.net.InetAddress;
45  import java.net.InetSocketAddress;
46  import java.net.SocketAddress;
47  import java.security.GeneralSecurityException;
48  import java.security.KeyStore;
49  import java.security.Security;
50  import java.util.concurrent.CountDownLatch;
51  
52  /**
53   * Test a SSL session and provoke HandshakeException.
54   * This test should not hang or timeout when DIRMINA-1076/1077 is fixed.
55   * 
56   * @author chrjohn
57   */
58  public class SslTestHandshakeExceptionDIRMINA1077Test {
59      private int port = AvailablePortFinder.getNextAvailable();
60      private static InetAddress address;
61      private static NioSocketAcceptor acceptor;
62  
63      /** A JVM independant KEY_MANAGER_FACTORY algorithm */
64      private static final String KEY_MANAGER_FACTORY_ALGORITHM;
65  
66      static {
67          String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
68          if (algorithm == null) {
69              algorithm = KeyManagerFactory.getDefaultAlgorithm();
70          }
71  
72          KEY_MANAGER_FACTORY_ALGORITHM = algorithm;
73      }
74  
75  
76      private static class TestHandler extends IoHandlerAdapter {
77          public void messageReceived(IoSession session, Object message) throws Exception {}
78  
79          @Override
80          public void exceptionCaught( IoSession session, Throwable cause )
81              throws Exception {}
82      }
83  
84      /**
85       * Starts a Server with the SSL Filter and a simple text line 
86       * protocol codec filter
87       */
88      private void startServer(int port) throws Exception {
89          acceptor = new NioSocketAcceptor();
90  
91          acceptor.setReuseAddress(true);
92          DefaultIoFilterChainBuilder filters = acceptor.getFilterChain();
93  
94          // Inject the SSL filter
95          SslFilter sslFilter = new SslFilter(createSSLContext(true));
96          filters.addLast("sslFilter", sslFilter);
97          sslFilter.setNeedClientAuth(true);
98  
99          // Inject the TestLine codec filter
100         filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory()));
101 
102         acceptor.setHandler(new TestHandler());
103         acceptor.bind(new InetSocketAddress(port));
104     }
105     
106     private static void stopServer() {
107         acceptor.dispose(true);
108     }
109 
110     private void startAndStopClient( int port, CountDownLatch disposalLatch ) throws Exception {
111         NioSocketConnector nioSocketConnector = new NioSocketConnector();
112         nioSocketConnector.setHandler(new TestHandler());
113         DefaultIoFilterChainBuilder filters = nioSocketConnector.getFilterChain();
114 
115         // Inject the SSL filter
116         SslFilter sslFilter = new SslFilter(createSSLContext(false));
117         sslFilter.setUseClientMode( true );
118         filters.addLast("sslFilter", sslFilter);
119 
120         address = InetAddress.getByName("localhost");
121         SocketAddress remoteAddress = new InetSocketAddress( address, port );
122         ConnectFuture connect = nioSocketConnector.connect( remoteAddress );
123         connect.awaitUninterruptibly();
124 //        System.out.println( "Closing connection..." );
125         nioSocketConnector.dispose( true );
126         disposalLatch.countDown();
127 //        System.out.println( "Connection closed!" );
128     }
129 
130     private static SSLContext createSSLContext(boolean emptyKeystore) throws IOException, GeneralSecurityException {
131         char[] passphrase = "password".toCharArray();
132 
133         SSLContext ctx = SSLContext.getInstance("TLSv1.2");
134         KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
135         TrustManagerFactory tmf = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
136 
137         KeyStore ks = KeyStore.getInstance("JKS");
138         KeyStore ts = KeyStore.getInstance("JKS");
139 
140         // use empty keystore to provoke handshake exception
141         if (emptyKeystore) {
142             ks.load(SslTestHandshakeExceptionDIRMINA1077Test.class.getResourceAsStream("emptykeystore.sslTest"), passphrase);
143         } else {
144             ks.load(SslTestHandshakeExceptionDIRMINA1077Test.class.getResourceAsStream("keystore.sslTest"), passphrase);
145         }
146         ts.load(SslTestHandshakeExceptionDIRMINA1077Test.class.getResourceAsStream("truststore.sslTest"), passphrase);
147 
148         kmf.init(ks, passphrase);
149         tmf.init(ts);
150 
151         ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
152 
153         return ctx;
154     }
155 
156     @Test(timeout=15000)
157     @Ignore
158     public void testSSL() throws Exception {
159         long startTime = System.currentTimeMillis();
160         // without DIRMINA-1076/1077 fixed, the test will hang after short time
161         while (System.currentTimeMillis() < startTime + 10000) {
162             try {
163                 port = AvailablePortFinder.getNextAvailable();
164                 final CountDownLatch disposalLatch = new CountDownLatch( 1 );
165                 startServer(port);
166                 
167                 Thread t = new Thread() {
168                     public void run() {
169                         try {
170                             startAndStopClient(port, disposalLatch);
171                         } catch ( Exception e ) {}
172                     }
173                 };
174                 t.setDaemon( true );
175                 t.start();
176                 disposalLatch.await();
177                 t.join( 1000 );
178 
179                 if ( t.isAlive() ) {
180                     for ( StackTraceElement stackTraceElement : t.getStackTrace() ) {
181                         if ( "dispose".equals( stackTraceElement.getMethodName() )
182                              && AbstractIoService.class.getCanonicalName()
183                                                        .equals( stackTraceElement.getClassName() ) ) {
184                             System.err.println( "Detected hang in AbstractIoService.dispose()!" );
185                         }
186                     }
187                     fail( "Thread should have died by now, supposed hang in AbstractIoService.dispose()" );
188                 }
189             } finally {
190                 stopServer();
191             }
192         }
193     }
194 }