Reloading a java.net.http.HttpClient's SSLContext
Asked Answered
M

1

3

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.)

Magruder answered 9/10, 2021 at 12:48 Comment(0)
A
6

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.

Allotropy answered 9/10, 2021 at 13:27 Comment(2)
Interesting approach.Magruder
Indeed the approach is different compared to Jersey's solution. It is more verbose but has less overhead. I hope this solution will do the trick for youAllotropy

© 2022 - 2024 — McMap. All rights reserved.