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