I've got a program that makes use of the java.net.http.HttpClient
, which was introduced in Java 11, to connect and send requests to internal services. These services are mutually authenticated, both presenting certificates issued by an internal CA.
For example,
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
KeyManager keys = /* load our cert and key */;
TrustManager trust = /* load our trusted CA */;
sslContext.init(keys, trust, secureRandom);
HttpClient.Builder builder = HttpClient.newBuilder().sslContext(sslContext);
HttpClient client = builder.build();
On our hosts, the client's certificate and private key get rotated pretty regularly, more often than the host or application gets a chance to restart. I'd like to be able to reload the HttpClient
's SSLContext
with the new cert/key pair while it's still running, but can't see any way to do so.
After the HttpClient
has been built, it only provides an sslContext()
getter to retrieve the SSLContext
. It doesn't seem to have an API to set a new one.
Is there any other mechanism to achieve this?
(I'm thinking of something like Jetty's SslContextFactory#reload(SSLContext)
method.)
CodePudding user response:
I think this question is similar to How to renew keystore (SSLContext) in Spring Data Geode connections without restarting? the answer I have provided there is similar to this one.
This option is unfortunately not available by default. After you have supplied the SSLContext to the HttpClient and build the client you cannot change the SSLContext. You will need to create a new SSLContext and a new HttpClient.
I had the same challenge for one of my projects and I solved it by using a custom trustmanager and keymanager which wraps around the actual trustmanager and keymanager while having the capability of swapping the actual trustmanager and trustmanager. So you can use the following setup if you still want to accomplish it without the need of recreating the HttpClient and SSLContext:
SSLFactory sslFactory = SSLFactory.builder()
.withSwappableIdentityMaterial()
.withIdentityMaterial("identity.jks", "password".toCharArray())
.withSwappableTrustMaterial()
.withTrustMaterial("truststore.jks", "password".toCharArray())
.build();
HttpClient httpClient = HttpClient.newBuilder()
.sslParameters(sslFactory.getSslParameters())
.sslContext(sslFactory.getSslContext())
.build()
// execute https request
HttpResponse<String> response = httpClient.send(aRequest, HttpResponse.BodyHandlers.ofString());
// swap identity and trust materials and reuse existing http client
KeyManagerUtils.swapKeyManager(sslFactory.getKeyManager().get(), anotherKeyManager);
TrustManagerUtils.swapTrustManager(sslFactory.getTrustManager().get(), anotherTrustManager);
// Cleanup old ssl sessions by invalidating them all. Forces to use new ssl sessions which will be created by the swapped KeyManager/TrustManager
SSLSessionUtils.invalidateCaches(sslFactory.getSslContext());
HttpResponse<String> response = httpClient.send(aRequest, HttpResponse.BodyHandlers.ofString());
In the above code example you need to replace the second parameter of the swapKeyManager
and swapTrustManager
method with the newly created KeyManager and TrustManager when you have new certificates.
See here for the documentation of this option: Swapping KeyManager and TrustManager at runtime
And here for an actual working example: Example swapping certificates at runtime with HttpUrlConnection
You can add the library to your project with:
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart</artifactId>
<version>7.0.2</version>
</dependency>
You can view the full documentation and other examples here: GitHub - SSLContext Kickstart
By the way I need to add a small disclaimer I am the maintainer of the library.