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.)
Advertisement
Answer
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, but there is a workaround which will do the trick to apply a reload/update.
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 keymanager. So you can use the following setup if you still want to accomplish it without the need of recreating the HttpClient and SSLContext:
SSLFactory baseSslFactory = SSLFactory.builder() .withDummyIdentityMaterial() .withDummyTrustMaterial() .withSwappableIdentityMaterial() .withSwappableTrustMaterial() .build(); HttpClient httpClient = HttpClient.newBuilder() .sslParameters(sslFactory.getSslParameters()) .sslContext(sslFactory.getSslContext()) .build() Runnable sslUpdater = () -> { SSLFactory updatedSslFactory = SSLFactory.builder() .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray()) .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray()) .build(); SSLFactoryUtils.reload(baseSslFactory, updatedSslFactory) }; // initial update of ssl material to replace the dummies sslUpdater.run(); // update ssl material every hour Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(sslUpdater, 1, 1, TimeUnit.HOURS); // execute https request HttpResponse<String> response = httpClient.send(aRequest, HttpResponse.BodyHandlers.ofString());
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
And here for a server side example: Example swapping certificates at runtime with Spring Boot and Jetty Also other servers are possible such as Netty or Vert.x as long as they can either use SSLContext, SSLServerSocketFactory, TrustManager or KeyManager
You can add the library to your project with:
<dependency> <groupId>io.github.hakky54</groupId> <artifactId>sslcontext-kickstart</artifactId> <version>7.4.8</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.