I have a scenario where I need to communicate with a server using legacy encryption protocols, specifically TLSv1 and SSLv3, through Apache HttpClient 5. By default, these protocols are disabled in Java 17. While modifying the java.security file is an option, it applies globally across the JVM or process, so I prefer not to use this approach.
I’ve verified that it’s possible to configure these settings programmatically:
Security.setProperty("jdk.tls.disabledAlgorithms", "");
Security.setProperty("jdk.certpath.disabledAlgorithms", "");
System.setProperty("jdk.tls.client.protocols", "SSLv3"); // if using SSLv3
While this is better than modifying the java.security file, it still applies at the process-wide level. My goal is to configure the settings at the client instance level, so I can manage a separate instance in memory dedicated to communication with legacy systems.
How can I achieve client-specific configuration? Below is the complete test code for reference.
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.core5.http.ConnectionReuseStrategy;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.http2.HttpVersionPolicy;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.ssl.TrustStrategy;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HttpsTest03 {
private static final Logger LOG = LoggerFactory.getLogger(HttpsTest01.class.getName());
@BeforeAll
static void beforeAll() throws Exception {
System.setProperty("javax.net.debug", "ssl:handshake:verbose"); // for handshake debug
// Security.setProperty("jdk.tls.disabledAlgorithms", "");
// Security.setProperty("jdk.certpath.disabledAlgorithms", "");
// System.setProperty("jdk.tls.client.protocols", "SSLv3"); // if Using SSLv3
}
@Test
void testUsingApacheClient() throws Exception {
TlsStrategy tlsStrategy = acceptAllTlsStrategy();
CloseableHttpAsyncClient httpClient = createHttp11Client(tlsStrategy);
String urlStr = "https://192.168.0.25:8443";
@SuppressWarnings("deprecation")
RequestConfig reqConfig = RequestConfig.custom()
.setConnectTimeout(Timeout.of(10000, TimeUnit.MILLISECONDS))
.setConnectionRequestTimeout(10000, TimeUnit.MILLISECONDS)
.setResponseTimeout(10, TimeUnit.SECONDS)
.setConnectionKeepAlive(TimeValue.of(30, TimeUnit.SECONDS))
.build();
SimpleHttpRequest req = SimpleRequestBuilder.create("GET")
.setUri(urlStr)
.setRequestConfig(reqConfig)
.build();
try {
Future<SimpleHttpResponse> respFuture = httpClient.execute(req, null);
SimpleHttpResponse resp = respFuture.get();
int respCd = resp.getCode();
String respMsg = resp.getReasonPhrase();
Header[] headers = resp.getHeaders();
byte[] body = resp.getBodyBytes();
int bodyLen = body == null ? -1 : body.length;
LOG.info("response code: {} {} - body length: {} ", respCd, respMsg, bodyLen);
if (LOG.isDebugEnabled()) {
for (Header header : headers) {
LOG.debug("Response Header : {} {}", header.getName(), header.getValue());
}
}
} catch (Throwable t) {
throw t;
}
}
private CloseableHttpAsyncClient createHttp11Client(TlsStrategy tlsStrategy) throws Exception {
ConnectionConfig connConfig = ConnectionConfig.custom()
.setConnectTimeout(Timeout.of(3, TimeUnit.SECONDS))
.setValidateAfterInactivity(TimeValue.of(3, TimeUnit.SECONDS))
.setTimeToLive(TimeValue.of(30, TimeUnit.SECONDS))
.build();
TlsConfig tlsConfig = TlsConfig.custom()
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
.setHandshakeTimeout(Timeout.of(1, TimeUnit.SECONDS))
.build();
PoolingAsyncClientConnectionManagerBuilder connManagerBuilder = PoolingAsyncClientConnectionManagerBuilder.create()
.setMaxConnTotal(4000)
.setMaxConnPerRoute(2000)
.setDefaultConnectionConfig(connConfig)
.setConnPoolPolicy(PoolReusePolicy.LIFO)
.setPoolConcurrencyPolicy(PoolConcurrencyPolicy.LAX)
.setDefaultTlsConfig(tlsConfig);
connManagerBuilder.setTlsStrategy(tlsStrategy);
PoolingAsyncClientConnectionManager connManager = connManagerBuilder.build();
ConnectionReuseStrategy connReuseStrategy = (request, response, context) -> false;
final CloseableHttpAsyncClient client = HttpAsyncClients.custom()
.setUserAgent("TEST-AGENT")
.setConnectionManager(connManager)
.setConnectionReuseStrategy(connReuseStrategy)
.disableAuthCaching()
.disableRedirectHandling()
.disableConnectionState()
.disableCookieManagement()
.evictExpiredConnections()
.evictIdleConnections(TimeValue.of(1, TimeUnit.SECONDS))
.build();
client.start();
return client;
}
private TlsStrategy acceptAllTlsStrategy() throws Exception {
final TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
final SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
return ClientTlsStrategyBuilder.create()
.setSslContext(sslContext)
.setHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();
}
}
The Gradle dependency configuration is as follows.
implementation 'org.apache.commons:commons-pool2:2.12.0'
implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
implementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.2.1'
I've searched extensively, including on Google, but I just can't come up with any ideas. I would greatly appreciate it if the experts could help.
As of version 5.3 HttpClient supports TLS configuration on a per host basis
final PoolingAsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create()
.setTlsConfigResolver(host -> {
// Use different settings for specific hosts
if (host.getSchemeName().equalsIgnoreCase("httpbin.org")) {
return TlsConfig.custom()
.setSupportedProtocols("SSLv3")
.setHandshakeTimeout(Timeout.ofSeconds(10))
.build();
}
return TlsConfig.DEFAULT;
})
.build();