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.hc.core5.testing.classic;
29  
30  import java.io.IOException;
31  import java.util.concurrent.atomic.AtomicReference;
32  
33  import javax.net.ssl.SSLException;
34  import javax.net.ssl.SSLHandshakeException;
35  import javax.net.ssl.SSLParameters;
36  import javax.net.ssl.SSLSession;
37  
38  import org.apache.hc.core5.function.Callback;
39  import org.apache.hc.core5.http.ClassicHttpRequest;
40  import org.apache.hc.core5.http.ClassicHttpResponse;
41  import org.apache.hc.core5.http.ContentType;
42  import org.apache.hc.core5.http.HttpHost;
43  import org.apache.hc.core5.http.HttpStatus;
44  import org.apache.hc.core5.http.Method;
45  import org.apache.hc.core5.http.ProtocolVersion;
46  import org.apache.hc.core5.http.impl.bootstrap.HttpRequester;
47  import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
48  import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap;
49  import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
50  import org.apache.hc.core5.http.io.SocketConfig;
51  import org.apache.hc.core5.http.io.entity.EntityUtils;
52  import org.apache.hc.core5.http.io.entity.StringEntity;
53  import org.apache.hc.core5.http.io.ssl.SSLSessionVerifier;
54  import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
55  import org.apache.hc.core5.http.protocol.BasicHttpContext;
56  import org.apache.hc.core5.http.protocol.HttpContext;
57  import org.apache.hc.core5.http.ssl.TLS;
58  import org.apache.hc.core5.io.CloseMode;
59  import org.apache.hc.core5.ssl.SSLContexts;
60  import org.apache.hc.core5.testing.SSLTestContexts;
61  import org.apache.hc.core5.util.Timeout;
62  import org.hamcrest.CoreMatchers;
63  import org.hamcrest.MatcherAssert;
64  import org.junit.Assert;
65  import org.junit.Rule;
66  import org.junit.Test;
67  import org.junit.rules.ExternalResource;
68  
69  public class ClassicTLSIntegrationTest {
70  
71      private static final Timeout TIMEOUT = Timeout.ofSeconds(30);
72  
73      private HttpServer server;
74  
75      @Rule
76      public ExternalResource serverResource = new ExternalResource() {
77  
78          @Override
79          protected void after() {
80              if (server != null) {
81                  try {
82                      server.close(CloseMode.IMMEDIATE);
83                  } catch (final Exception ignore) {
84                  }
85              }
86          }
87  
88      };
89  
90      private HttpRequester requester;
91  
92      @Rule
93      public ExternalResource clientResource = new ExternalResource() {
94  
95          @Override
96          protected void after() {
97              if (requester != null) {
98                  try {
99                      requester.close(CloseMode.GRACEFUL);
100                 } catch (final Exception ignore) {
101                 }
102             }
103         }
104 
105     };
106 
107     @Test
108     public void testTLSSuccess() throws Exception {
109         server = ServerBootstrap.bootstrap()
110                 .setSocketConfig(SocketConfig.custom()
111                         .setSoTimeout(TIMEOUT)
112                         .build())
113                 .setSslContext(SSLTestContexts.createServerSSLContext())
114                 .setExceptionListener(LoggingExceptionListener.INSTANCE)
115                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
116                 .register("*", new EchoHandler())
117                 .create();
118         server.start();
119 
120         final AtomicReference<SSLSession> sslSessionRef = new AtomicReference<>(null);
121 
122         requester = RequesterBootstrap.bootstrap()
123                 .setSslContext(SSLTestContexts.createClientSSLContext())
124                 .setSslSessionVerifier(new SSLSessionVerifier() {
125 
126                     @Override
127                     public void verify(final HttpHost endpoint, final SSLSession sslSession) throws SSLException {
128                         sslSessionRef.set(sslSession);
129                     }
130 
131                 })
132                 .setSocketConfig(SocketConfig.custom()
133                         .setSoTimeout(TIMEOUT)
134                         .build())
135                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
136                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
137                 .create();
138 
139         final HttpContext context = new BasicHttpContext();
140         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
141         final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
142         request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
143         try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
144             MatcherAssert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
145             final String body1 = EntityUtils.toString(response1.getEntity());
146             MatcherAssert.assertThat(body1, CoreMatchers.equalTo("some stuff"));
147         }
148 
149         final SSLSession sslSession = sslSessionRef.getAndSet(null);
150         final ProtocolVersion tlsVersion = TLS.parse(sslSession.getProtocol());
151         MatcherAssert.assertThat(tlsVersion.greaterEquals(TLS.V_1_2.version), CoreMatchers.equalTo(true));
152         MatcherAssert.assertThat(sslSession.getPeerPrincipal().getName(),
153                 CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation"));
154     }
155 
156     @Test(expected = SSLHandshakeException.class)
157     public void testTLSTrustFailure() throws Exception {
158         server = ServerBootstrap.bootstrap()
159                 .setSocketConfig(SocketConfig.custom()
160                         .setSoTimeout(TIMEOUT)
161                         .build())
162                 .setSslContext(SSLTestContexts.createServerSSLContext())
163                 .setExceptionListener(LoggingExceptionListener.INSTANCE)
164                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
165                 .register("*", new EchoHandler())
166                 .create();
167         server.start();
168 
169         requester = RequesterBootstrap.bootstrap()
170                 .setSslContext(SSLContexts.createDefault())
171                 .setSocketConfig(SocketConfig.custom()
172                         .setSoTimeout(TIMEOUT)
173                         .build())
174                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
175                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
176                 .create();
177 
178         final HttpContext context = new BasicHttpContext();
179         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
180         final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
181         request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
182         try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
183             EntityUtils.consume(response1.getEntity());
184         }
185     }
186 
187     @Test(expected = IOException.class)
188     public void testTLSClientAuthFailure() throws Exception {
189         server = ServerBootstrap.bootstrap()
190                 .setSslContext(SSLTestContexts.createClientSSLContext())
191                 .setSocketConfig(SocketConfig.custom()
192                         .setSoTimeout(TIMEOUT)
193                         .build())
194                 .setSslContext(SSLTestContexts.createServerSSLContext())
195                 .setSslSetupHandler(new Callback<SSLParameters>() {
196 
197                     @Override
198                     public void execute(final SSLParameters sslParameters) {
199                         sslParameters.setNeedClientAuth(true);
200                     }
201 
202                 })
203                 .setExceptionListener(LoggingExceptionListener.INSTANCE)
204                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
205                 .register("*", new EchoHandler())
206                 .create();
207         server.start();
208 
209         requester = RequesterBootstrap.bootstrap()
210                 .setSslContext(SSLTestContexts.createClientSSLContext())
211                 .setSocketConfig(SocketConfig.custom()
212                         .setSoTimeout(TIMEOUT)
213                         .build())
214                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
215                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
216                 .create();
217 
218         final HttpContext context = new BasicHttpContext();
219         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
220         final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
221         request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
222         try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
223             EntityUtils.consume(response1.getEntity());
224         }
225     }
226 
227     @Test(expected = IOException.class)
228     public void testSSLDisabledByDefault() throws Exception {
229         server = ServerBootstrap.bootstrap()
230                 .setSslContext(SSLTestContexts.createServerSSLContext())
231                 .setSslSetupHandler(new Callback<SSLParameters>() {
232 
233                     @Override
234                     public void execute(final SSLParameters sslParameters) {
235                         sslParameters.setProtocols(new String[]{"SSLv3"});
236                     }
237 
238                 })
239                 .create();
240         server.start();
241 
242         requester = RequesterBootstrap.bootstrap()
243                 .setSslContext(SSLTestContexts.createClientSSLContext())
244                 .setSocketConfig(SocketConfig.custom()
245                         .setSoTimeout(TIMEOUT)
246                         .build())
247                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
248                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
249                 .create();
250 
251         final HttpContext context = new BasicHttpContext();
252         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
253         final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
254         request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
255         try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
256             EntityUtils.consume(response1.getEntity());
257         }
258     }
259 
260     @Test
261     public void testWeakCiphersDisabledByDefault() throws Exception {
262 
263         requester = RequesterBootstrap.bootstrap()
264                 .setSslContext(SSLTestContexts.createClientSSLContext())
265                 .setSocketConfig(SocketConfig.custom()
266                         .setSoTimeout(TIMEOUT)
267                         .build())
268                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
269                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
270                 .create();
271 
272         final String[] weakCiphersSuites = {
273                 "SSL_RSA_WITH_RC4_128_SHA",
274                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
275                 "TLS_DH_anon_WITH_AES_128_CBC_SHA",
276                 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
277                 "SSL_RSA_WITH_NULL_SHA",
278                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
279                 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
280                 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
281                 "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
282                 "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
283                 "TLS_RSA_WITH_NULL_SHA256",
284                 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
285                 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
286                 "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
287                 "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"
288         };
289 
290         for (final String cipherSuite : weakCiphersSuites) {
291             server = ServerBootstrap.bootstrap()
292                     .setSslContext(SSLTestContexts.createServerSSLContext())
293                     .setSslSetupHandler(new Callback<SSLParameters>() {
294 
295                         @Override
296                         public void execute(final SSLParameters sslParameters) {
297                             sslParameters.setProtocols(new String[]{cipherSuite});
298                         }
299 
300                     })
301                     .create();
302             try {
303                 server.start();
304 
305                 final HttpContext context = new BasicHttpContext();
306                 final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
307                 final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
308                 request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
309                 try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
310                     EntityUtils.consume(response1.getEntity());
311                 }
312 
313                 Assert.fail("IOException expected");
314             } catch (final IOException | IllegalArgumentException expected) {
315             } finally {
316                 server.close(CloseMode.IMMEDIATE);
317             }
318         }
319     }
320 
321 }