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