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 static org.hamcrest.MatcherAssert.assertThat;
31  
32  import java.io.IOException;
33  import java.net.InetAddress;
34  import java.util.concurrent.atomic.AtomicReference;
35  
36  import javax.net.ssl.SSLHandshakeException;
37  import javax.net.ssl.SSLSession;
38  
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.message.BasicClassicHttpRequest;
54  import org.apache.hc.core5.http.protocol.BasicHttpContext;
55  import org.apache.hc.core5.http.protocol.HttpContext;
56  import org.apache.hc.core5.http.protocol.HttpCoreContext;
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.jupiter.api.Assertions;
64  import org.junit.jupiter.api.Test;
65  import org.junit.jupiter.api.extension.AfterEachCallback;
66  import org.junit.jupiter.api.extension.ExtensionContext;
67  import org.junit.jupiter.api.extension.RegisterExtension;
68  
69  public class ClassicTLSIntegrationTest {
70  
71      private static final Timeout TIMEOUT = Timeout.ofMinutes(1);
72  
73      private HttpServer server;
74  
75      @RegisterExtension
76      public final AfterEachCallback serverCleanup = new AfterEachCallback() {
77  
78          @Override
79          public void afterEach(final ExtensionContext context) throws Exception {
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      @RegisterExtension
93      public final AfterEachCallback clientCleanup = new AfterEachCallback() {
94  
95          @Override
96          public void afterEach(final ExtensionContext context) throws Exception {
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<>();
121 
122         requester = RequesterBootstrap.bootstrap()
123                 .setSslContext(SSLTestContexts.createClientSSLContext())
124                 .setSslSessionVerifier((endpoint, sslSession) -> sslSessionRef.set(sslSession))
125                 .setSocketConfig(SocketConfig.custom()
126                         .setSoTimeout(TIMEOUT)
127                         .build())
128                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
129                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
130                 .create();
131 
132         final HttpCoreContext context = HttpCoreContext.create() ;
133         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
134         final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
135         request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
136         try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
137             assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
138             final String body1 = EntityUtils.toString(response1.getEntity());
139             assertThat(body1, CoreMatchers.equalTo("some stuff"));
140         }
141 
142         final SSLSession sslSession = sslSessionRef.getAndSet(null);
143         final ProtocolVersion tlsVersion = TLS.parse(sslSession.getProtocol());
144         assertThat(tlsVersion.greaterEquals(TLS.V_1_2.getVersion()), CoreMatchers.equalTo(true));
145         assertThat(sslSession.getPeerPrincipal().getName(),
146                 CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation"));
147     }
148 
149     @Test
150     public void testTLSTrustFailure() throws Exception {
151         server = ServerBootstrap.bootstrap()
152                 .setSocketConfig(SocketConfig.custom()
153                         .setSoTimeout(TIMEOUT)
154                         .build())
155                 .setSslContext(SSLTestContexts.createServerSSLContext())
156                 .setExceptionListener(LoggingExceptionListener.INSTANCE)
157                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
158                 .register("*", new EchoHandler())
159                 .create();
160         server.start();
161 
162         requester = RequesterBootstrap.bootstrap()
163                 .setSslContext(SSLContexts.createDefault())
164                 .setSocketConfig(SocketConfig.custom()
165                         .setSoTimeout(TIMEOUT)
166                         .build())
167                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
168                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
169                 .create();
170 
171         final HttpCoreContext context = HttpCoreContext.create() ;
172         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
173         final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
174         request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
175         Assertions.assertThrows(IOException.class, () -> {
176             try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
177                 EntityUtils.consume(response1.getEntity());
178             }
179         });
180     }
181 
182     @Test
183     public void testTLSClientAuthFailure() throws Exception {
184         server = ServerBootstrap.bootstrap()
185                 .setSslContext(SSLTestContexts.createClientSSLContext())
186                 .setSocketConfig(SocketConfig.custom()
187                         .setSoTimeout(TIMEOUT)
188                         .build())
189                 .setSslContext(SSLTestContexts.createServerSSLContext())
190                 .setSslSetupHandler(sslParameters -> sslParameters.setNeedClientAuth(true))
191                 .setExceptionListener(LoggingExceptionListener.INSTANCE)
192                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
193                 .register("*", new EchoHandler())
194                 .create();
195         server.start();
196 
197         requester = RequesterBootstrap.bootstrap()
198                 .setSslContext(SSLTestContexts.createClientSSLContext())
199                 .setSocketConfig(SocketConfig.custom()
200                         .setSoTimeout(TIMEOUT)
201                         .build())
202                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
203                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
204                 .create();
205 
206         final HttpCoreContext context = HttpCoreContext.create() ;
207         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
208         final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
209         request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
210         Assertions.assertThrows(IOException.class, () -> {
211             try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
212                 EntityUtils.consume(response1.getEntity());
213             }
214         });
215     }
216 
217     @Test
218     public void testSSLDisabledByDefault() throws Exception {
219         server = ServerBootstrap.bootstrap()
220                 .setSslContext(SSLTestContexts.createServerSSLContext())
221                 .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[]{"SSLv3"}))
222                 .create();
223         server.start();
224 
225         requester = RequesterBootstrap.bootstrap()
226                 .setSslContext(SSLTestContexts.createClientSSLContext())
227                 .setSocketConfig(SocketConfig.custom()
228                         .setSoTimeout(TIMEOUT)
229                         .build())
230                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
231                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
232                 .create();
233 
234         final HttpCoreContext context = HttpCoreContext.create() ;
235         final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
236         final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
237         request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
238         Assertions.assertThrows(IOException.class, () -> {
239             try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
240                 EntityUtils.consume(response1.getEntity());
241             }
242         });
243     }
244 
245     @Test
246     public void testWeakCiphersDisabledByDefault() throws Exception {
247 
248         requester = RequesterBootstrap.bootstrap()
249                 .setSslContext(SSLTestContexts.createClientSSLContext())
250                 .setSocketConfig(SocketConfig.custom()
251                         .setSoTimeout(TIMEOUT)
252                         .build())
253                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
254                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
255                 .create();
256 
257         final String[] weakCiphersSuites = {
258                 "SSL_RSA_WITH_RC4_128_SHA",
259                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
260                 "TLS_DH_anon_WITH_AES_128_CBC_SHA",
261                 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
262                 "SSL_RSA_WITH_NULL_SHA",
263                 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
264                 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
265                 "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
266                 "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
267                 "TLS_RSA_WITH_NULL_SHA256",
268                 "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
269                 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
270                 "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
271                 "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"
272         };
273 
274         for (final String cipherSuite : weakCiphersSuites) {
275             server = ServerBootstrap.bootstrap()
276                     .setSslContext(SSLTestContexts.createServerSSLContext())
277                     .setSslSetupHandler(sslParameters -> sslParameters.setProtocols(new String[]{cipherSuite}))
278                     .create();
279             Assertions.assertThrows(Exception.class, () -> {
280                 try {
281                     server.start();
282 
283                     final HttpCoreContext context = HttpCoreContext.create() ;
284                     final HttpHost target = new HttpHost("https", "localhost", server.getLocalPort());
285                     final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
286                     request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
287                     try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT, context)) {
288                         EntityUtils.consume(response1.getEntity());
289                     }
290                 } finally {
291                     server.close(CloseMode.IMMEDIATE);
292                 }
293             });
294         }
295     }
296 
297     @Test
298     public void testHostNameVerification() throws Exception {
299         server = ServerBootstrap.bootstrap()
300                 .setSslContext(SSLTestContexts.createServerSSLContext())
301                 .create();
302         server.start();
303 
304         requester = RequesterBootstrap.bootstrap()
305                 .setSslContext(SSLTestContexts.createClientSSLContext())
306                 .setSocketConfig(SocketConfig.custom()
307                         .setSoTimeout(TIMEOUT)
308                         .build())
309                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
310                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
311                 .create();
312 
313         final HttpContext context = new BasicHttpContext();
314         final HttpHost target1 = new HttpHost("https", InetAddress.getLocalHost(), "localhost", server.getLocalPort());
315         final ClassicHttpRequest request1 = new BasicClassicHttpRequest(Method.POST, "/stuff");
316         request1.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
317         try (final ClassicHttpResponse response1 = requester.execute(target1, request1, TIMEOUT, context)) {
318             EntityUtils.consume(response1.getEntity());
319         }
320 
321         Assertions.assertThrows(SSLHandshakeException.class, () -> {
322             final HttpHost target2 = new HttpHost("https", InetAddress.getLocalHost(), "some-other-host", server.getLocalPort());
323             final ClassicHttpRequest request2 = new BasicClassicHttpRequest(Method.POST, "/stuff");
324             request2.setEntity(new StringEntity("some stuff", ContentType.TEXT_PLAIN));
325             try (final ClassicHttpResponse response2 = requester.execute(target2, request2, TIMEOUT, context)) {
326                 EntityUtils.consume(response2.getEntity());
327             }
328         });
329     }
330 
331 }