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