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.nio;
29  
30  import java.net.InetSocketAddress;
31  import java.net.URI;
32  import java.net.URISyntaxException;
33  import java.net.URL;
34  import java.security.Provider;
35  import java.security.SecureRandom;
36  import java.security.Security;
37  import java.util.concurrent.Future;
38  
39  import org.apache.hc.core5.http.HttpHeaders;
40  import org.apache.hc.core5.http.HttpResponse;
41  import org.apache.hc.core5.http.Message;
42  import org.apache.hc.core5.http.Method;
43  import org.apache.hc.core5.http.config.Http1Config;
44  import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
45  import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder;
46  import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
47  import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
48  import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
49  import org.apache.hc.core5.http.protocol.HttpProcessor;
50  import org.apache.hc.core5.http.protocol.RequestValidateHost;
51  import org.apache.hc.core5.reactor.IOReactorConfig;
52  import org.apache.hc.core5.ssl.SSLContextBuilder;
53  import org.apache.hc.core5.util.TimeValue;
54  import org.apache.hc.core5.util.Timeout;
55  import org.conscrypt.Conscrypt;
56  import org.junit.jupiter.api.Assertions;
57  import org.junit.jupiter.api.Order;
58  import org.junit.jupiter.api.Test;
59  import org.junit.jupiter.api.extension.AfterEachCallback;
60  import org.junit.jupiter.api.extension.BeforeEachCallback;
61  import org.junit.jupiter.api.extension.ExtensionContext;
62  import org.junit.jupiter.api.extension.RegisterExtension;
63  
64  public abstract class JSSEProviderIntegrationTest {
65  
66      private final String securityProviderName;
67      private final String protocolVersion;
68  
69      public JSSEProviderIntegrationTest(final String securityProviderName, final String protocolVersion) {
70          super();
71          this.securityProviderName = securityProviderName;
72          this.protocolVersion = protocolVersion;
73      }
74  
75      private static final Timeout TIMEOUT = Timeout.ofMinutes(1);
76      private static final int REQ_NUM = 25;
77  
78      private Provider securityProvider;
79  
80      class SecurityProviderResource implements BeforeEachCallback, AfterEachCallback {
81  
82          @Override
83          public void beforeEach(final ExtensionContext context) throws Exception {
84              if ("Conscrypt".equalsIgnoreCase(securityProviderName)) {
85                  try {
86                      securityProvider = Conscrypt.newProviderBuilder().provideTrustManager(true).build();
87                  } catch (final UnsatisfiedLinkError e) {
88                      Assertions.fail("Conscrypt provider failed to be loaded: " + e.getMessage());
89                  }
90              } else {
91                  securityProvider = null;
92              }
93              if (securityProvider != null) {
94                  Security.insertProviderAt(securityProvider, 1);
95              }
96          }
97  
98          @Override
99          public void afterEach(final ExtensionContext context) throws Exception {
100             if (securityProvider != null) {
101                 Security.removeProvider(securityProvider.getName());
102                 securityProvider = null;
103             }
104         }
105 
106     }
107 
108     @RegisterExtension
109     @Order(1)
110     private final SecurityProviderResource securityProviderResource = new SecurityProviderResource();
111 
112     private Http1TestServer server;
113 
114     class ServerResource implements BeforeEachCallback, AfterEachCallback {
115 
116         @Override
117         public void beforeEach(final ExtensionContext context) throws Exception {
118             final URL keyStoreURL = getClass().getResource("/test-server.p12");
119             final String storePassword = "nopassword";
120 
121             server = new Http1TestServer(
122                     IOReactorConfig.custom()
123                             .setSoTimeout(TIMEOUT)
124                             .build(),
125                     SSLContextBuilder.create()
126                             .setProvider(securityProvider)
127                             .setKeyStoreType("pkcs12")
128                             .loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
129                             .loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
130                             .setSecureRandom(new SecureRandom())
131                             .build(),
132                     (endpoint, sslEngine) -> {
133                         if (protocolVersion != null) {
134                             sslEngine.setEnabledProtocols(new String[]{protocolVersion});
135                         }
136                     },
137                     null);
138         }
139 
140         @Override
141         public void afterEach(final ExtensionContext context) throws Exception {
142             if (server != null) {
143                 server.shutdown(TimeValue.ofSeconds(5));
144             }
145         }
146 
147     }
148 
149     @RegisterExtension
150     @Order(2)
151     private final ServerResource serverResource = new ServerResource();
152 
153     private Http1TestClient client;
154 
155     class ClientResource implements BeforeEachCallback, AfterEachCallback {
156 
157         @Override
158         public void beforeEach(final ExtensionContext context) throws Exception {
159             final URL keyStoreURL = getClass().getResource("/test-client.p12");
160             final String storePassword = "nopassword";
161 
162             client = new Http1TestClient(
163                     IOReactorConfig.custom()
164                             .setSoTimeout(TIMEOUT)
165                             .build(),
166                     SSLContextBuilder.create()
167                             .setProvider(securityProvider)
168                             .setKeyStoreType("pkcs12")
169                             .loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
170                             .setSecureRandom(new SecureRandom())
171                             .build(),
172                     (endpoint, sslEngine) -> {
173                         if (protocolVersion != null) {
174                             sslEngine.setEnabledProtocols(new String[]{protocolVersion});
175                         }
176                     },
177                     null);
178         }
179 
180         @Override
181         public void afterEach(final ExtensionContext context) throws Exception {
182             if (client != null) {
183                 client.shutdown(TimeValue.ofSeconds(5));
184             }
185         }
186 
187     }
188 
189     @RegisterExtension
190     @Order(3)
191     private final ClientResource clientResource = new ClientResource();
192 
193     private URI createRequestURI(final InetSocketAddress serverEndpoint, final String path) {
194         try {
195             return new URI("https", null, "localhost", serverEndpoint.getPort(), path, null, null);
196         } catch (final URISyntaxException e) {
197             throw new IllegalStateException();
198         }
199     }
200 
201     @Test
202     public void testSimpleGet() throws Exception {
203         server.register("/hello", () -> new SingleLineResponseHandler("Hi there"));
204         final InetSocketAddress serverEndpoint = server.start();
205 
206         client.start();
207         final Future<ClientSessionEndpoint> connectFuture = client.connect(
208                 "localhost", serverEndpoint.getPort(), TIMEOUT);
209         final ClientSessionEndpoint streamEndpoint = connectFuture.get();
210 
211         for (int i = 0; i < REQ_NUM; i++) {
212             final Future<Message<HttpResponse, String>> future = streamEndpoint.execute(
213                     new BasicRequestProducer(Method.GET, createRequestURI(serverEndpoint, "/hello")),
214                     new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
215             final Message<HttpResponse, String> result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
216             Assertions.assertNotNull(result);
217             final HttpResponse response1 = result.getHead();
218             final String entity1 = result.getBody();
219             Assertions.assertNotNull(response1);
220             Assertions.assertEquals(200, response1.getCode());
221             Assertions.assertEquals("Hi there", entity1);
222         }
223     }
224 
225     @Test
226     public void testSimpleGetConnectionClose() throws Exception {
227         server.register("/hello", () -> new SingleLineResponseHandler("Hi there"));
228         final InetSocketAddress serverEndpoint = server.start();
229 
230         client.start();
231         final URI requestURI = createRequestURI(serverEndpoint, "/hello");
232         for (int i = 0; i < REQ_NUM; i++) {
233             final Future<ClientSessionEndpoint> connectFuture = client.connect(
234                     "localhost", serverEndpoint.getPort(), TIMEOUT);
235             try (final ClientSessionEndpoint streamEndpoint = connectFuture.get()) {
236                 final Future<Message<HttpResponse, String>> future = streamEndpoint.execute(
237                         AsyncRequestBuilder.get(requestURI)
238                                 .addHeader(HttpHeaders.CONNECTION, "close")
239                                 .build(),
240                         new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
241                 final Message<HttpResponse, String> result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
242                 Assertions.assertNotNull(result);
243                 final HttpResponse response1 = result.getHead();
244                 final String entity1 = result.getBody();
245                 Assertions.assertNotNull(response1);
246                 Assertions.assertEquals(200, response1.getCode());
247                 Assertions.assertEquals("Hi there", entity1);
248             }
249         }
250     }
251 
252     @Test
253     public void testSimpleGetIdentityTransfer() throws Exception {
254         server.register("/hello", () -> new SingleLineResponseHandler("Hi there"));
255         final HttpProcessor httpProcessor = new DefaultHttpProcessor(new RequestValidateHost());
256         final InetSocketAddress serverEndpoint = server.start(httpProcessor, Http1Config.DEFAULT);
257 
258         client.start();
259 
260         for (int i = 0; i < REQ_NUM; i++) {
261             final Future<ClientSessionEndpoint> connectFuture = client.connect(
262                     "localhost", serverEndpoint.getPort(), TIMEOUT);
263             try (final ClientSessionEndpoint streamEndpoint = connectFuture.get()) {
264                 final Future<Message<HttpResponse, String>> future = streamEndpoint.execute(
265                         new BasicRequestProducer(Method.GET, createRequestURI(serverEndpoint, "/hello")),
266                         new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
267                 final Message<HttpResponse, String> result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
268                 Assertions.assertNotNull(result);
269                 final HttpResponse response = result.getHead();
270                 final String entity = result.getBody();
271                 Assertions.assertNotNull(response);
272                 Assertions.assertEquals(200, response.getCode());
273                 Assertions.assertEquals("Hi there", entity);
274             }
275         }
276     }
277 
278 }