You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2025/09/25 09:20:26 UTC
(httpcomponents-core) branch master updated: HTTPCORE-785: Improved ALPN tests with the default (Oracle) and Conscrypt security providers
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git
The following commit(s) were added to refs/heads/master by this push:
new f9201670f HTTPCORE-785: Improved ALPN tests with the default (Oracle) and Conscrypt security providers
f9201670f is described below
commit f9201670ffc4f3e4f1b338a291dfa0f0c5bc89d7
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Wed Sep 24 16:52:48 2025 +0200
HTTPCORE-785: Improved ALPN tests with the default (Oracle) and Conscrypt security providers
---
.../extension/SecurityProviderResource.java | 88 +++++++
...2ProtocolNegotiationTest.java => AlpnTest.java} | 27 +-
...roviderIntegrationTests.java => AlpnTests.java} | 33 ++-
.../testing/nio/JSSEProviderIntegrationTest.java | 293 ---------------------
4 files changed, 130 insertions(+), 311 deletions(-)
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/SecurityProviderResource.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/SecurityProviderResource.java
new file mode 100644
index 000000000..34e7e84b9
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/extension/SecurityProviderResource.java
@@ -0,0 +1,88 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.core5.testing.extension;
+
+import java.security.Provider;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.conscrypt.Conscrypt;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class SecurityProviderResource implements BeforeEachCallback, AfterEachCallback {
+
+ private final String securityProviderName;
+
+ private Provider securityProvider;
+
+ public SecurityProviderResource(final String securityProviderName) {
+ super();
+ this.securityProviderName = securityProviderName;
+ }
+
+ @Override
+ public void beforeEach(final ExtensionContext context) throws Exception {
+ if ("Conscrypt".equalsIgnoreCase(securityProviderName)) {
+ final Set<String> supportedArchitectures = new HashSet<>(Arrays.asList("x86", "x86_64",
+ "x86-64", "amd64", "aarch64", "armeabi-v7a", "arm64-v8a"));
+ Assumptions.assumeTrue(supportedArchitectures.contains(System.getProperty("os.arch")));
+ try {
+ securityProvider = Conscrypt.newProviderBuilder().provideTrustManager(true).build();
+ } catch (final UnsatisfiedLinkError e) {
+ Assertions.fail("Conscrypt provider failed to be loaded: " + e.getMessage());
+ }
+ } else if ("Oracle".equalsIgnoreCase(securityProviderName)) {
+ securityProvider = null;
+ } else if ("SUN".equalsIgnoreCase(securityProviderName)) {
+ securityProvider = null;
+ } else {
+ throw new AssertionError("Unsupported security provider: " + securityProviderName);
+ }
+ if (securityProvider != null) {
+ Security.insertProviderAt(securityProvider, 1);
+ }
+ }
+
+ @Override
+ public void afterEach(final ExtensionContext context) throws Exception {
+ if (securityProvider != null) {
+ Security.removeProvider(securityProvider.getName());
+ securityProvider = null;
+ }
+ }
+
+ public Provider securityProvider() {
+ return securityProvider;
+ }
+
+}
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ProtocolNegotiationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AlpnTest.java
similarity index 87%
rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ProtocolNegotiationTest.java
rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AlpnTest.java
index 09d6ce3e1..045c4ee21 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/H2ProtocolNegotiationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AlpnTest.java
@@ -31,6 +31,9 @@
import java.net.InetSocketAddress;
import java.util.concurrent.Future;
+import java.util.function.Function;
+
+import javax.net.ssl.SSLContext;
import org.apache.hc.core5.function.Supplier;
import org.apache.hc.core5.http.ContentType;
@@ -48,35 +51,47 @@
import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
+import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
import org.apache.hc.core5.http2.HttpVersionPolicy;
-import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.core5.reactor.ListenerEndpoint;
import org.apache.hc.core5.testing.SSLTestContexts;
+import org.apache.hc.core5.testing.extension.SecurityProviderResource;
import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource;
import org.apache.hc.core5.testing.extension.nio.H2AsyncServerResource;
import org.apache.hc.core5.util.Timeout;
import org.hamcrest.CoreMatchers;
+import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-class H2ProtocolNegotiationTest {
+abstract class AlpnTest {
private static final Timeout TIMEOUT = Timeout.ofMinutes(1);
@RegisterExtension
+ @Order(1)
+ private final SecurityProviderResource securityProviderResource;
+ @RegisterExtension
+ @Order(2)
private final H2AsyncServerResource serverResource;
@RegisterExtension
+ @Order(3)
private final H2AsyncRequesterResource clientResource;
- public H2ProtocolNegotiationTest() {
+ public AlpnTest(final String securityProviderName,
+ final Function<SSLContext, TlsStrategy> serverTlsStrategyFactory,
+ final Function<SSLContext, TlsStrategy> clientTlsStrategyFactory) {
+ this.securityProviderResource = new SecurityProviderResource(securityProviderName);
this.serverResource = new H2AsyncServerResource();
this.serverResource.configure(bootstrap -> bootstrap
.setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
- .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
+ .setTlsStrategy(serverTlsStrategyFactory != null ?
+ serverTlsStrategyFactory.apply(SSLTestContexts.createServerSSLContext()) :
+ new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
.setIOReactorConfig(
IOReactorConfig.custom()
.setSoTimeout(TIMEOUT)
@@ -89,7 +104,9 @@ public H2ProtocolNegotiationTest() {
this.clientResource = new H2AsyncRequesterResource();
this.clientResource.configure(bootstrap -> bootstrap
.setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
- .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
+ .setTlsStrategy(clientTlsStrategyFactory != null ?
+ clientTlsStrategyFactory.apply(SSLTestContexts.createServerSSLContext()) :
+ new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
.setIOReactorConfig(IOReactorConfig.custom()
.setSoTimeout(TIMEOUT)
.build())
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTests.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AlpnTests.java
similarity index 56%
rename from httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTests.java
rename to httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AlpnTests.java
index f168cfc9c..4bcae56d9 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTests.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/AlpnTests.java
@@ -27,41 +27,48 @@
package org.apache.hc.core5.testing.nio;
+import org.apache.hc.core5.http2.ssl.ConscryptClientTlsStrategy;
+import org.apache.hc.core5.http2.ssl.ConscryptServerTlsStrategy;
+import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
+import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
+import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.condition.OS;
-class JSSEProviderIntegrationTests {
+class AlpnTests {
@Nested
- @DisplayName("Oracle (default)")
- class Oracle extends JSSEProviderIntegrationTest {
+ @DisplayName("ALPN Oracle")
+ class OracleJSSEAlpnTest extends AlpnTest {
- public Oracle() {
- super("Oracle", null);
+ public OracleJSSEAlpnTest() throws Exception {
+ super("Oracle", H2ServerTlsStrategy::new, H2ClientTlsStrategy::new);
}
}
@Nested
- @DisplayName("Conscrypt (TLSv1.2)")
+ @DisplayName("ALPN Conscrypt (Java 9 or newer)")
@DisabledOnOs(OS.MAC)
- class ConscryptTlsV1_2 extends JSSEProviderIntegrationTest {
+ @EnabledForJreRange(min = JRE.JAVA_9)
+ class ConscryptJSSEAlpnTest extends AlpnTest {
- public ConscryptTlsV1_2() {
- super("Conscrypt", "TLSv1.2");
+ public ConscryptJSSEAlpnTest() throws Exception {
+ super("Conscrypt", H2ServerTlsStrategy::new, H2ClientTlsStrategy::new);
}
}
@Nested
- @DisplayName("Conscrypt (TLSv1.3)")
+ @DisplayName("ALPN Conscrypt (Conscrypt specific TLS strategies)")
@DisabledOnOs(OS.MAC)
- class ConscryptTlsV1_3 extends JSSEProviderIntegrationTest {
+ class ConscryptJSSEAndStrategiesAlpnTest extends AlpnTest {
- public ConscryptTlsV1_3() {
- super("Conscrypt", "TLSv1.3");
+ public ConscryptJSSEAndStrategiesAlpnTest() throws Exception {
+ super("Conscrypt", ConscryptServerTlsStrategy::new, ConscryptClientTlsStrategy::new);
}
}
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTest.java
deleted file mode 100644
index 36a3d9f5c..000000000
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/JSSEProviderIntegrationTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.hc.core5.testing.nio;
-
-import java.net.InetSocketAddress;
-import java.net.URL;
-import java.security.Provider;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.Future;
-
-import org.apache.hc.core5.http.HttpHeaders;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.Message;
-import org.apache.hc.core5.http.message.BasicHttpRequest;
-import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
-import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder;
-import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
-import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
-import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
-import org.apache.hc.core5.http.protocol.RequestValidateHost;
-import org.apache.hc.core5.http.support.BasicRequestBuilder;
-import org.apache.hc.core5.reactor.IOReactorConfig;
-import org.apache.hc.core5.ssl.SSLContextBuilder;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.hc.core5.util.Timeout;
-import org.conscrypt.Conscrypt;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Assumptions;
-import org.junit.jupiter.api.Order;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.AfterEachCallback;
-import org.junit.jupiter.api.extension.BeforeEachCallback;
-import org.junit.jupiter.api.extension.ExtensionContext;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-abstract class JSSEProviderIntegrationTest {
-
- private final String securityProviderName;
- private final String protocolVersion;
-
- public JSSEProviderIntegrationTest(final String securityProviderName, final String protocolVersion) {
- super();
- this.securityProviderName = securityProviderName;
- this.protocolVersion = protocolVersion;
- }
-
- private static final Timeout TIMEOUT = Timeout.ofMinutes(1);
- private static final int REQ_NUM = 25;
-
- private Provider securityProvider;
-
- class SecurityProviderResource implements BeforeEachCallback, AfterEachCallback {
-
- @Override
- public void beforeEach(final ExtensionContext context) throws Exception {
- if ("Conscrypt".equalsIgnoreCase(securityProviderName)) {
- final Set<String> supportedArchitectures = new HashSet<>(Arrays.asList("x86", "x86_64",
- "x86-64", "amd64", "aarch64", "armeabi-v7a", "arm64-v8a"));
- Assumptions.assumeTrue(supportedArchitectures.contains(System.getProperty("os.arch")));
- try {
- securityProvider = Conscrypt.newProviderBuilder().provideTrustManager(true).build();
- } catch (final UnsatisfiedLinkError e) {
- Assertions.fail("Conscrypt provider failed to be loaded: " + e.getMessage());
- }
- } else {
- securityProvider = null;
- }
- if (securityProvider != null) {
- Security.insertProviderAt(securityProvider, 1);
- }
- }
-
- @Override
- public void afterEach(final ExtensionContext context) throws Exception {
- if (securityProvider != null) {
- Security.removeProvider(securityProvider.getName());
- securityProvider = null;
- }
- }
-
- }
-
- @RegisterExtension
- @Order(1)
- private final SecurityProviderResource securityProviderResource = new SecurityProviderResource();
-
- private Http1TestServer server;
-
- class ServerResource implements BeforeEachCallback, AfterEachCallback {
-
- @Override
- public void beforeEach(final ExtensionContext context) throws Exception {
- final URL keyStoreURL = getClass().getResource("/test-server.p12");
- final String storePassword = "nopassword";
-
- server = new Http1TestServer(
- IOReactorConfig.custom()
- .setSoTimeout(TIMEOUT)
- .build(),
- SSLContextBuilder.create()
- .setProvider(securityProvider)
- .setKeyStoreType("pkcs12")
- .loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
- .loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
- .setSecureRandom(new SecureRandom())
- .build(),
- (endpoint, sslEngine) -> {
- if (protocolVersion != null) {
- sslEngine.setEnabledProtocols(new String[]{protocolVersion});
- }
- },
- null);
- }
-
- @Override
- public void afterEach(final ExtensionContext context) throws Exception {
- if (server != null) {
- server.shutdown(TimeValue.ofSeconds(5));
- }
- }
-
- }
-
- @RegisterExtension
- @Order(2)
- private final ServerResource serverResource = new ServerResource();
-
- private Http1TestClient client;
-
- class ClientResource implements BeforeEachCallback, AfterEachCallback {
-
- @Override
- public void beforeEach(final ExtensionContext context) throws Exception {
- final URL keyStoreURL = getClass().getResource("/test-client.p12");
- final String storePassword = "nopassword";
-
- client = new Http1TestClient(
- IOReactorConfig.custom()
- .setSoTimeout(TIMEOUT)
- .build(),
- SSLContextBuilder.create()
- .setProvider(securityProvider)
- .setKeyStoreType("pkcs12")
- .loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
- .setSecureRandom(new SecureRandom())
- .build(),
- (endpoint, sslEngine) -> {
- if (protocolVersion != null) {
- sslEngine.setEnabledProtocols(new String[]{protocolVersion});
- }
- },
- null);
- }
-
- @Override
- public void afterEach(final ExtensionContext context) throws Exception {
- if (client != null) {
- client.shutdown(TimeValue.ofSeconds(5));
- }
- }
-
- }
-
- @RegisterExtension
- @Order(3)
- private final ClientResource clientResource = new ClientResource();
-
- private HttpHost target(final InetSocketAddress serverEndpoint) {
- return new HttpHost("https", null, "localhost", serverEndpoint.getPort());
- }
-
- @Test
- void testSimpleGet() throws Exception {
- server.register("/hello", () -> new SingleLineResponseHandler("Hi there"));
- final InetSocketAddress serverEndpoint = server.start();
-
- final HttpHost target = target(serverEndpoint);
-
- client.start();
- final Future<ClientSessionEndpoint> connectFuture = client.connect(target, TIMEOUT);
- final ClientSessionEndpoint streamEndpoint = connectFuture.get();
-
- for (int i = 0; i < REQ_NUM; i++) {
- final BasicHttpRequest request = BasicRequestBuilder.get()
- .setHttpHost(target)
- .setPath("/hello")
- .build();
- final Future<Message<HttpResponse, String>> future = streamEndpoint.execute(
- new BasicRequestProducer(request, null),
- new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
- final Message<HttpResponse, String> result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- Assertions.assertNotNull(result);
- final HttpResponse response1 = result.getHead();
- final String entity1 = result.getBody();
- Assertions.assertNotNull(response1);
- Assertions.assertEquals(200, response1.getCode());
- Assertions.assertEquals("Hi there", entity1);
- }
- }
-
- @Test
- void testSimpleGetConnectionClose() throws Exception {
- server.register("/hello", () -> new SingleLineResponseHandler("Hi there"));
- final InetSocketAddress serverEndpoint = server.start();
-
- final HttpHost target = target(serverEndpoint);
-
- client.start();
- for (int i = 0; i < REQ_NUM; i++) {
- final Future<ClientSessionEndpoint> connectFuture = client.connect(
- "localhost", serverEndpoint.getPort(), TIMEOUT);
- try (final ClientSessionEndpoint streamEndpoint = connectFuture.get()) {
- final Future<Message<HttpResponse, String>> future = streamEndpoint.execute(
- AsyncRequestBuilder.get()
- .setHttpHost(target)
- .setPath("/hello")
- .addHeader(HttpHeaders.CONNECTION, "close")
- .build(),
- new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
- final Message<HttpResponse, String> result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- Assertions.assertNotNull(result);
- final HttpResponse response1 = result.getHead();
- final String entity1 = result.getBody();
- Assertions.assertNotNull(response1);
- Assertions.assertEquals(200, response1.getCode());
- Assertions.assertEquals("Hi there", entity1);
- }
- }
- }
-
- @Test
- void testSimpleGetIdentityTransfer() throws Exception {
- server.register("/hello", () -> new SingleLineResponseHandler("Hi there"));
- server.configure(new DefaultHttpProcessor(new RequestValidateHost()));
- final InetSocketAddress serverEndpoint = server.start();
-
- final HttpHost target = target(serverEndpoint);
-
- client.start();
-
- for (int i = 0; i < REQ_NUM; i++) {
- final Future<ClientSessionEndpoint> connectFuture = client.connect(
- "localhost", serverEndpoint.getPort(), TIMEOUT);
- try (final ClientSessionEndpoint streamEndpoint = connectFuture.get()) {
- final BasicHttpRequest request = BasicRequestBuilder.get()
- .setHttpHost(target)
- .setPath("/hello")
- .build();
- final Future<Message<HttpResponse, String>> future = streamEndpoint.execute(
- new BasicRequestProducer(request, null),
- new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
- final Message<HttpResponse, String> result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
- Assertions.assertNotNull(result);
- final HttpResponse response = result.getHead();
- final String entity = result.getBody();
- Assertions.assertNotNull(response);
- Assertions.assertEquals(200, response.getCode());
- Assertions.assertEquals("Hi there", entity);
- }
- }
- }
-
-}