View Javadoc
1   package org.apache.mina.filter.ssl;
2   
3   import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
4   import org.apache.mina.core.filterchain.IoFilterChain;
5   import org.apache.mina.core.service.IoHandlerAdapter;
6   import org.apache.mina.core.session.IoSession;
7   import org.apache.mina.filter.codec.ProtocolCodecFilter;
8   import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
9   import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
10  import org.apache.mina.transport.socket.nio.NioSocketConnector;
11  import org.apache.mina.util.AvailablePortFinder;
12  import org.junit.Before;
13  import org.junit.Test;
14  
15  import javax.net.ssl.KeyManagerFactory;
16  import javax.net.ssl.SSLContext;
17  import javax.net.ssl.SSLException;
18  import javax.net.ssl.TrustManagerFactory;
19  import java.net.InetSocketAddress;
20  import java.security.KeyStore;
21  import java.security.Security;
22  import java.util.concurrent.CountDownLatch;
23  import java.util.concurrent.TimeUnit;
24  
25  import static org.junit.Assert.assertFalse;
26  import static org.junit.Assert.assertTrue;
27  
28  /**
29   * Test SNI matching scenarios. (tests for DIRMINA-1122)
30   *
31   * <pre>
32   * emptykeystore.sslTest        - empty keystore
33   * server-cn.keystore           - keystore with single certificate chain  (CN=mina)
34   * client-cn.truststore         - keystore with trusted certificate
35   * server-san-ext.keystore      - keystore with single certificate chain (CN=mina;SAN=*.bbb.ccc,xxx.yyy)
36   * client-san-ext.truststore    - keystore with trusted certificate
37   * </pre>
38   */
39  public class SslIdentificationAlgorithmTest {
40  
41      private static final String KEY_MANAGER_FACTORY_ALGORITHM;
42  
43      static {
44          String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
45          if (algorithm == null) {
46              algorithm = KeyManagerFactory.getDefaultAlgorithm();
47          }
48  
49          KEY_MANAGER_FACTORY_ALGORITHM = algorithm;
50      }
51  
52      private int port;
53      private CountDownLatch handshakeDone;
54  
55      @Before
56      public void setUp() {
57          port = AvailablePortFinder.getNextAvailable(5555);
58          handshakeDone = new CountDownLatch(2);
59      }
60  
61      @Test
62      public void shouldAuthenticateWhenServerCertificateCommonNameMatchesClientSNI() throws Exception {
63          SSLContext acceptorContext = createSSLContext("server-cn.keystore", "emptykeystore.sslTest");
64          SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-cn.truststore");
65  
66          startAcceptor(acceptorContext);
67          startConnector(connectorContext, "mina");
68  
69          assertTrue(handshakeDone.await(10, TimeUnit.SECONDS));
70      }
71  
72      @Test
73      public void shouldFailAuthenticationWhenServerCertificateCommonNameDoesNotMatchClientSNI() throws Exception {
74          SSLContext acceptorContext = createSSLContext("server-cn.keystore", "emptykeystore.sslTest");
75          SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-cn.truststore");
76  
77          startAcceptor(acceptorContext);
78          startConnector(connectorContext, "example.com");
79  
80          assertFalse(handshakeDone.await(10, TimeUnit.SECONDS));
81      }
82  
83      @Test
84      public void shouldFailAuthenticationWhenClientMissingSNIAndIdentificationAlgorithmProvided() throws Exception {
85          SSLContext acceptorContext = createSSLContext("server-cn.keystore", "emptykeystore.sslTest");
86          SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-cn.truststore");
87  
88          startAcceptor(acceptorContext);
89          startConnector(connectorContext, null);
90  
91          assertFalse(handshakeDone.await(10, TimeUnit.SECONDS));
92      }
93  
94      /**
95       * Subject Alternative Name (SAN) scenarios
96       * 
97       * @exception  Exception If the test fails
98       */
99      @Test
100     public void shouldAuthenticateWhenServerCertificateAlternativeNameMatchesClientSNIExactly() throws Exception {
101         SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest");
102         SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore");
103 
104         startAcceptor(acceptorContext);
105         startConnector(connectorContext, "xxx.yyy");
106 
107         assertTrue(handshakeDone.await(10, TimeUnit.SECONDS));
108     }
109 
110     @Test
111     public void shouldAuthenticateWhenServerCertificateAlternativeNameMatchesClientSNIViaWildcard() throws Exception {
112         SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest");
113         SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore");
114 
115         startAcceptor(acceptorContext);
116         startConnector(connectorContext, "aaa.bbb.ccc");
117 
118         assertTrue(handshakeDone.await(10, TimeUnit.SECONDS));
119     }
120 
121     @Test
122     public void shouldFailAuthenticationWhenServerCommonNameMatchesSNIAndSNINotInAlternativeName() throws Exception {
123         SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest");
124         SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore");
125 
126         startAcceptor(acceptorContext);
127         startConnector(connectorContext, "mina");
128 
129         assertFalse(handshakeDone.await(10, TimeUnit.SECONDS));
130     }
131 
132     @Test
133     public void shouldFailAuthenticationWhenMatchingAlternativeNameWildcardExactly() throws Exception {
134         SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest");
135         SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore");
136 
137         startAcceptor(acceptorContext);
138         startConnector(connectorContext, "*.bbb.ccc");
139 
140         assertFalse(handshakeDone.await(10, TimeUnit.SECONDS));
141     }
142 
143     @Test
144     public void shouldFailAuthenticationWhenMatchingAlternativeNameWithTooManyLabels() throws Exception {
145         SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest");
146         SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore");
147 
148         startAcceptor(acceptorContext);
149         startConnector(connectorContext, "mmm.nnn.bbb.ccc");
150 
151         assertFalse(handshakeDone.await(10, TimeUnit.SECONDS));
152     }
153 
154     private void startAcceptor(SSLContext sslContext) throws Exception {
155         NioSocketAcceptor acceptor = new NioSocketAcceptor();
156         acceptor.setReuseAddress(true);
157 
158         SslFilter sslFilter = new SslFilter(sslContext);
159         sslFilter.setEnabledProtocols(new String[] {"TLSv1.2"});
160 
161         DefaultIoFilterChainBuilder filters = acceptor.getFilterChain();
162         filters.addLast("ssl", sslFilter);
163         filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory()));
164 
165         acceptor.setHandler(new IoHandlerAdapter() {
166             @Override
167             public void sessionCreated(IoSession session) throws Exception {
168                 // Add the SSL notification in the session's attribute liste
169                 session.setAttribute(SslFilter.USE_NOTIFICATION, Boolean.TRUE);
170             }
171 
172             @Override
173             public void sessionOpened(IoSession session) {
174                 session.write("acceptor write");
175             }
176 
177             @Override
178             public void messageReceived(IoSession session, Object message) throws Exception {
179                 // Check if the 'fake' session secured message notification has been received
180                 if (message == SslFilter.SESSION_SECURED) {
181                     handshakeDone.countDown();
182                 }
183             }
184         });
185 
186         acceptor.bind(new InetSocketAddress(port));
187     }
188 
189     private void startConnector(SSLContext sslContext, final String sni) {
190         NioSocketConnector connector = new NioSocketConnector();
191 
192         SslFilter sslFilter = new SslFilter(sslContext) {
193 
194             @Override
195             public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws SSLException {
196                 if (sni != null) {
197                     IoSession session = parent.getSession();
198                     session.setAttribute(SslFilter.PEER_ADDRESS, new InetSocketAddress(sni, port));
199                 }
200 
201                 super.onPreAdd(parent, name, nextFilter);
202             }
203         };
204 
205         sslFilter.setUseClientMode(true);
206         sslFilter.setEndpointIdentificationAlgorithm("HTTPS");
207         sslFilter.setEnabledProtocols(new String[] {"TLSv1.2"});
208 
209         DefaultIoFilterChainBuilder filters = connector.getFilterChain();
210         filters.addLast("ssl", sslFilter);
211         filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory()));
212 
213         connector.setHandler(new IoHandlerAdapter() {
214             @Override
215             public void sessionCreated(IoSession session) throws Exception {
216                 // Add the SSL notification in the session's attribute liste
217                 session.setAttribute(SslFilter.USE_NOTIFICATION, Boolean.TRUE);
218             }
219             
220             @Override
221             public void sessionOpened(IoSession session) {
222                 session.write("connector write");
223             }
224             
225             @Override
226             public void messageReceived(IoSession session, Object message) throws Exception {
227                 // Check if the 'fake' session secured message notification has been received
228                 if (message == SslFilter.SESSION_SECURED) {
229                     handshakeDone.countDown();
230                 }
231             }
232         });
233 
234         connector.connect(new InetSocketAddress("localhost", port));
235     }
236 
237     private SSLContext createSSLContext(String keyStorePath, String trustStorePath) throws Exception {
238         char[] password = "password".toCharArray();
239 
240         KeyStore keyStore = KeyStore.getInstance("JKS");
241         keyStore.load(SslTest.class.getResourceAsStream(keyStorePath), password);
242 
243         KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
244         kmf.init(keyStore, password);
245 
246         KeyStore trustStore = KeyStore.getInstance("JKS");
247         trustStore.load(SslTest.class.getResourceAsStream(trustStorePath), password);
248 
249         TrustManagerFactory tmf = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
250         tmf.init(trustStore);
251 
252         SSLContext ctx = SSLContext.getInstance("TLSv1.2");
253         ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
254 
255         return ctx;
256     }
257 }