View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.nio.integration;
29  
30  import java.io.IOException;
31  import java.math.BigInteger;
32  import java.net.InetSocketAddress;
33  import java.net.URL;
34  import java.util.concurrent.ExecutionException;
35  import java.util.concurrent.Future;
36  import java.util.concurrent.TimeUnit;
37  import java.util.concurrent.atomic.AtomicReference;
38  
39  import javax.net.ssl.SSLContext;
40  import javax.net.ssl.SSLEngine;
41  import javax.net.ssl.SSLException;
42  import javax.net.ssl.SSLHandshakeException;
43  import javax.net.ssl.SSLSession;
44  
45  import org.apache.http.HttpException;
46  import org.apache.http.HttpHost;
47  import org.apache.http.HttpRequest;
48  import org.apache.http.HttpResponse;
49  import org.apache.http.config.ConnectionConfig;
50  import org.apache.http.impl.nio.pool.BasicNIOConnFactory;
51  import org.apache.http.message.BasicHttpRequest;
52  import org.apache.http.nio.NHttpConnection;
53  import org.apache.http.nio.protocol.BasicAsyncRequestHandler;
54  import org.apache.http.nio.reactor.IOSession;
55  import org.apache.http.nio.reactor.ListenerEndpoint;
56  import org.apache.http.nio.reactor.ssl.SSLSetupHandler;
57  import org.apache.http.nio.testserver.ClientConnectionFactory;
58  import org.apache.http.nio.testserver.HttpClientNio;
59  import org.apache.http.nio.testserver.HttpServerNio;
60  import org.apache.http.nio.testserver.ServerConnectionFactory;
61  import org.apache.http.nio.util.TestingSupport;
62  import org.apache.http.protocol.HttpContext;
63  import org.apache.http.protocol.HttpCoreContext;
64  import org.apache.http.protocol.HttpRequestHandler;
65  import org.apache.http.ssl.SSLContextBuilder;
66  import org.apache.http.ssl.SSLContexts;
67  import org.hamcrest.CoreMatchers;
68  import org.junit.Assert;
69  import org.junit.Rule;
70  import org.junit.Test;
71  import org.junit.rules.ExternalResource;
72  
73  public class TestTLSIntegration {
74  
75      private final static long RESULT_TIMEOUT_SEC = 30;
76  
77      private static int JRE_LEVEL = TestingSupport.determineJRELevel();
78  
79      private HttpServerNio server;
80  
81      @Rule
82      public ExternalResource serverResource = new ExternalResource() {
83  
84          @Override
85          protected void after() {
86              if (server != null) {
87                  try {
88                      server.shutdown();
89                  } catch (final Exception ignore) {
90                  }
91              }
92          }
93  
94      };
95  
96      private HttpClientNio client;
97  
98      @Rule
99      public ExternalResource clientResource = new ExternalResource() {
100 
101         @Override
102         protected void after() {
103             if (client != null) {
104                 try {
105                     client.shutdown();
106                 } catch (final Exception ignore) {
107                 }
108             }
109         }
110 
111     };
112 
113     private static SSLContext createServerSSLContext() throws Exception {
114         if (JRE_LEVEL >= 8) {
115             final URL keyStoreURL = TestTLSIntegration.class.getResource("/test-server.p12");
116             final String storePassword = "nopassword";
117             return SSLContextBuilder.create()
118                     .setKeyStoreType("pkcs12")
119                     .loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
120                     .loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
121                     .build();
122         } else {
123             final URL keyStoreURL = TestTLSIntegration.class.getResource("/test.keystore");
124             final String storePassword = "nopassword";
125             return SSLContextBuilder.create()
126                     .loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
127                     .loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
128                     .build();
129         }
130     }
131 
132     private static SSLContext createClientSSLContext() throws Exception {
133         if (JRE_LEVEL >= 8) {
134             final URL keyStoreURL = TestTLSIntegration.class.getResource("/test-client.p12");
135             final String storePassword = "nopassword";
136             return SSLContextBuilder.create()
137                     .setKeyStoreType("pkcs12")
138                     .loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
139                     .build();
140         } else {
141             final URL keyStoreURL = TestTLSIntegration.class.getResource("/test.keystore");
142             final String storePassword = "nopassword";
143             return SSLContextBuilder.create()
144                     .loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
145                     .build();
146         }
147     }
148 
149     @Test
150     public void testTLSSuccess() throws Exception {
151         server = new HttpServerNio();
152         server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), null));
153         server.setTimeout(5000);
154         server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
155         server.start();
156 
157         final AtomicReference<SSLSession> sslSessionRef = new AtomicReference<SSLSession>(null);
158 
159         this.client = new HttpClientNio(new BasicNIOConnFactory(createClientSSLContext(), new SSLSetupHandler() {
160 
161             @Override
162             public void initalize(final SSLEngine sslEngine) throws SSLException {
163 
164             }
165 
166             @Override
167             public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
168                 sslSessionRef.set(sslSession);
169             }
170 
171         }, ConnectionConfig.DEFAULT));
172         client.setTimeout(5000);
173         client.start();
174 
175         final ListenerEndpoint endpoint = server.getListenerEndpoint();
176         endpoint.waitFor();
177 
178         final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
179 
180         final HttpHost target = new HttpHost("localhost", address.getPort(), "https");
181 
182         final BasicHttpRequest request = new BasicHttpRequest("GET", "BLAHx200");
183         final Future<HttpResponse> future = client.execute(target, request);
184         final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
185         Assert.assertThat(response, CoreMatchers.notNullValue());
186         Assert.assertThat(response.getStatusLine().getStatusCode(), CoreMatchers.equalTo(200));
187 
188         final SSLSession sslSession = sslSessionRef.getAndSet(null);
189         if (JRE_LEVEL >= 8) {
190             Assert.assertThat(sslSession.getPeerPrincipal().getName(),
191                     CoreMatchers.equalTo("CN=Test Server,OU=HttpComponents Project,O=Apache Software Foundation"));
192         } else {
193             Assert.assertThat(sslSession.getPeerPrincipal().getName(),
194                     CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation"));
195         }
196     }
197 
198     @Test
199     public void testTLSTrustFailure() throws Exception {
200         server = new HttpServerNio();
201         server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), null));
202         server.setTimeout(5000);
203         server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
204         server.start();
205 
206         client = new HttpClientNio(new BasicNIOConnFactory(SSLContexts.createDefault(), null, ConnectionConfig.DEFAULT));
207         client.setTimeout(5000);
208         client.start();
209 
210         final ListenerEndpoint endpoint = server.getListenerEndpoint();
211         endpoint.waitFor();
212 
213         final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
214 
215         final HttpHost target = new HttpHost("localhost", address.getPort(), "https");
216 
217         final BasicHttpRequest request = new BasicHttpRequest("GET", "BLAHx200");
218         final Future<HttpResponse> future = client.execute(target, request);
219         try {
220             future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
221             Assert.fail("ExecutionException expected");
222         } catch (final ExecutionException ex) {
223             final Throwable cause = ex.getCause();
224             Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(SSLHandshakeException.class));
225         }
226     }
227 
228     @Test
229     public void testTLSClientAuthFailure() throws Exception {
230         server = new HttpServerNio();
231         server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), new SSLSetupHandler() {
232 
233             @Override
234             public void initalize(final SSLEngine sslEngine) throws SSLException {
235                 sslEngine.setNeedClientAuth(true);
236             }
237 
238             @Override
239             public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
240             }
241 
242         }));
243         server.setTimeout(5000);
244         server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
245         server.start();
246 
247         this.client = new HttpClientNio(new BasicNIOConnFactory(createClientSSLContext(), null, ConnectionConfig.DEFAULT));
248         client.setTimeout(5000);
249         client.start();
250 
251         final ListenerEndpoint endpoint = server.getListenerEndpoint();
252         endpoint.waitFor();
253 
254         final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
255 
256         final HttpHost target = new HttpHost("localhost", address.getPort(), "https");
257 
258         final BasicHttpRequest request = new BasicHttpRequest("GET", "BLAHx200");
259         final Future<HttpResponse> future = client.execute(target, request);
260         try {
261             future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
262             Assert.fail("ExecutionException expected");
263         } catch (final ExecutionException ex) {
264             final Throwable cause = ex.getCause();
265             Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
266         }
267     }
268 
269     @Test
270     public void testTLSProtocolMismatch() throws Exception {
271         server = new HttpServerNio();
272         server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), new SSLSetupHandler() {
273 
274             @Override
275             public void initalize(final SSLEngine sslEngine) throws SSLException {
276                 sslEngine.setEnabledProtocols(new String[]{"TLSv1.2"});
277             }
278 
279             @Override
280             public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
281             }
282 
283         }));
284         server.setTimeout(5000);
285         server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
286         server.start();
287 
288         this.client = new HttpClientNio(new BasicNIOConnFactory(createClientSSLContext(), new SSLSetupHandler() {
289 
290             @Override
291             public void initalize(final SSLEngine sslEngine) throws SSLException {
292                 sslEngine.setEnabledProtocols(new String[]{"SSLv3"});
293             }
294 
295             @Override
296             public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
297             }
298 
299         }, ConnectionConfig.DEFAULT));
300         client.setTimeout(5000);
301         client.start();
302 
303         final ListenerEndpoint endpoint = server.getListenerEndpoint();
304         endpoint.waitFor();
305 
306         final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
307 
308         final HttpHost target = new HttpHost("localhost", address.getPort(), "https");
309 
310         final BasicHttpRequest request = new BasicHttpRequest("GET", "BLAHx200");
311         final Future<HttpResponse> future = client.execute(target, request);
312         try {
313             future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
314             Assert.fail("ExecutionException expected");
315         } catch (final ExecutionException ex) {
316             final Throwable cause = ex.getCause();
317             Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
318         }
319     }
320 
321     @Test
322     public void testTLSCipherMismatch() throws Exception {
323         server = new HttpServerNio();
324         server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), new SSLSetupHandler() {
325 
326             @Override
327             public void initalize(final SSLEngine sslEngine) throws SSLException {
328                 sslEngine.setEnabledCipherSuites(new String[]{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"});
329             }
330 
331             @Override
332             public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
333             }
334 
335         }));
336         server.setTimeout(5000);
337         server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
338         server.start();
339 
340         this.client = new HttpClientNio(new BasicNIOConnFactory(createClientSSLContext(), new SSLSetupHandler() {
341 
342             @Override
343             public void initalize(final SSLEngine sslEngine) throws SSLException {
344                 sslEngine.setEnabledCipherSuites(new String[]{"SSL_RSA_EXPORT_WITH_RC4_40_MD5"});
345             }
346 
347             @Override
348             public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
349             }
350 
351         }, ConnectionConfig.DEFAULT));
352         client.setTimeout(5000);
353         client.start();
354 
355         final ListenerEndpoint endpoint = server.getListenerEndpoint();
356         endpoint.waitFor();
357 
358         final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
359 
360         final HttpHost target = new HttpHost("localhost", address.getPort(), "https");
361 
362         final BasicHttpRequest request = new BasicHttpRequest("GET", "BLAHx200");
363         final Future<HttpResponse> future = client.execute(target, request);
364         try {
365             future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
366             Assert.fail("ExecutionException expected");
367         } catch (final ExecutionException ex) {
368             final Throwable cause = ex.getCause();
369             Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
370         }
371     }
372 
373     @Test
374     public void testCustomSSLContext() throws Exception {
375         final SSLSetupHandler sslSetupHandler = new SSLSetupHandler() {
376 
377             @Override
378             public void initalize(
379                     final SSLEngine sslengine) throws SSLException {
380             }
381 
382             @Override
383             public void verify(
384                     final IOSession ioSession, final SSLSession sslsession) throws SSLException {
385                 final BigInteger sslid = new BigInteger(sslsession.getId());
386                 ioSession.setAttribute("ssl-id", sslid);
387             }
388 
389         };
390 
391         final HttpRequestHandler requestHandler = new HttpRequestHandler() {
392 
393             @Override
394             public void handle(
395                     final HttpRequest request,
396                     final HttpResponse response,
397                     final HttpContext context) throws HttpException, IOException {
398                 final NHttpConnection conn = (NHttpConnection) context.getAttribute(
399                         HttpCoreContext.HTTP_CONNECTION);
400                 final BigInteger sslid = (BigInteger) conn.getContext().getAttribute(
401                         "ssl-id");
402                 Assert.assertNotNull(sslid);
403             }
404 
405         };
406 
407         this.server = new HttpServerNio();
408         this.server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), sslSetupHandler));
409         this.server.setTimeout(5000);
410         this.server.registerHandler("*", new BasicAsyncRequestHandler(requestHandler));
411         this.server.start();
412 
413         this.client = new HttpClientNio(new BasicNIOConnFactory(new ClientConnectionFactory(createClientSSLContext()), null));
414         this.client.setTimeout(5000);
415         this.client.start();
416 
417         final ListenerEndpoint endpoint = this.server.getListenerEndpoint();
418         endpoint.waitFor();
419 
420         final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
421 
422         final HttpHost target = new HttpHost("localhost", address.getPort());
423         final BasicHttpRequest request = new BasicHttpRequest("GET", "/");
424         final Future<HttpResponse> future = this.client.execute(target, request);
425         final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
426         Assert.assertNotNull(response);
427         Assert.assertEquals(200, response.getStatusLine().getStatusCode());
428     }
429 
430 }