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
30
31
32
33
34
35
36
37
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
96
97
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
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
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
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
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 }