2

Small question regarding Netty, Spring Webflux, and how to send http requests to multiples downstream systems, when each of the downstream require mTLS and a different client certificate is required to send requests to each please?

What I have so far in my Java 11 Spring Webflux 2.4.2 app for sending request is:

@Bean
    @Primary
    public WebClient getWebClient() {
        return WebClient.create().mutate().defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).clientConnector(new ReactorClientHttpConnector(HttpClient.create().wiretap(true).secure(sslContextSpec -> sslContextSpec.sslContext(getSslContext())))).build();
    }

And for the Netty SslContext (it is not an apache SSLContext btw)

 public SslContext getSslContext() {
        try {
            final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            try (InputStream file = new FileInputStream(keyStorePath)) {
                final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
                keyStore.load(file, keyStorePassPhrase.toCharArray());
                keyManagerFactory.init(keyStore, keyPassPhrase.toCharArray());
            }

            final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            try (InputStream trustStoreFile = new FileInputStream(trustStorePath)) {
                final KeyStore trustStore = KeyStore.getInstance(trustStoreType);
                trustStore.load(trustStoreFile, trustStorePassPhrase.toCharArray());
                trustManagerFactory.init(trustStore);
            }

            return SslContextBuilder.forClient().keyManager(keyManagerFactory).trustManager(trustManagerFactory).build();
        } catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException | UnrecoverableKeyException e) {
            return null;
        }
    }

This is even working perfectly fine when we only need to send request to only one downstream.

This is even working if there are multiple downstream, and they accept the same client certificate!

But problem arise when each downstream requires me to use their respective client certificate.

May I ask how to achieve this please?

Thank you

PatPanda
  • 3,644
  • 9
  • 58
  • 154
  • 1
    I would suggest using different WebClient for different downstream which need different certificates. – dkb Feb 06 '21 at 04:21

1 Answers1

2

Option 1

The most straightforward solution would be using a specific client for each downstream api. And having each client configured with their specific client key and trust material.

Option 2

But your question is: how to use SslContext with multiple client certificates please?

So I want to give you some code examples to have a working setup. But the short answer is: yes it is possible!

The long answer is that you need some additional configuration to make it working. Basically what you need to do is create a keymanagerfactory from your keystore-1 and get the keymanager from the keymanagerfactory and repeat that for the other two keystores. Afterwords you will have 3 keymanagers. The next step is to have a special kind of keymanager which can be supplied to the Netty SslContext. This special kind of keymanager has the ability to iterate through the 3 keymanagers which you have created earlier and it will select the correct key material to communicate with the server. What you need is a CompositeKeyManager and CompositeTrustManager which is mentioned at the following stackoverflow answer here: Registering multiple keystores in JVM

The actual code snippet will be the below. I disregarded the loading file with inputstream and creating the keystore file and creating the keymanagerfactory as you already know how to do that.

KeyManager keyManagerOne = keyManagerFactoryOne.getKeyManagers()[0]
KeyManager keyManagerTwo = keyManagerFactoryTwo.getKeyManagers()[0]
KeyManager keyManagerThree = keyManagerFactoryThree.getKeyManagers()[0]

List<KeyManager> keyManagers = new ArrayList<>();
keyManagers.add(keyManagerOne);
keyManagers.add(keyManagerTwo);
keyManagers.add(keyManagerThree);

CompositeX509KeyManager baseKeyManager = new CompositeX509KeyManager(keyManagers);

//repeat the same for the trust material
TrustManager trustManagerOne = trustManagerFactoryOne.getTrustManagers()[0]
TrustManager trustManagerTwo = trustManagerFactoryTwo.getTrustManagers()[0]
TrustManager trustManagerThree = trustManagerFactoryThree.getTrustManagers()[0]

List<TrustManager> trustManagers = new ArrayList<>();
trustManagers.add(trustManagerOne);
trustManagers.add(trustManagerTwo);
trustManagers.add(trustManagerThree);

CompositeX509TrustManager baseTrustManager = new CompositeX509TrustManager(trustManagers);

SslContext sslContext = SslContextBuilder.forClient()
    .keyManager(baseKeyManager)
    .trustManager(baseTrustManager)
    .build();

And the above code should give you the capability of using multiple key and trust for a single client. This client will be able to communicate with the different downstream api's with the different key and trust material.

The downside of this setup is that you require to copy and paste the CompositeKeyManager and CompositeTrustManager into your code base and that the setup is a bit verbose. Java does not provide something out of the box for this use-case.

Option 3

If you want a a bit simpeler setup I would suggest you the code snippet below:

import io.netty.handler.ssl.SslContext;
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.NettySslUtils;

public class App {

    public static void main(String[] args) {
        SSLFactory sslFactory = SSLFactory.builder()
                .withIdentityMaterial(keyStorePathOne, password)
                .withIdentityMaterial(keyStorePathTwo, password)
                .withIdentityMaterial(keyStorePathThree, password)
                .withTrustMaterial(trustStorePathOne, password)
                .withTrustMaterial(trustStorePathTwo, password)
                .withTrustMaterial(trustStorePathThree, password)
                .build();

        SslContext sslContext = NettySslUtils.forClient(sslFactory).build();
    }

}

I need to provide some disclaimer, I am the maintainer of the library of the code snippet above. The library is available here: GitHub - SSLContext Kickstart and it uses the same CompositeKeyManager and CompositeTrustManager under the covers which I mentioned earlier for option 2.

And you can add it to your pom with the following snippet:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart-for-netty</artifactId>
    <version>7.4.9</version>
</dependency>
Hakan54
  • 3,121
  • 1
  • 23
  • 37
  • Very clear answer. (upvote) Trying the options – PatPanda Feb 06 '21 at 16:54
  • Just asking, for option 3, instead of hardcoding a fixed number of store (3 in your example), is it possible to put in a for loop? ( I have some 50 of stores ) and this is also why I would like to avoid option 1 :) – PatPanda Feb 07 '21 at 01:30
  • Yes, you can have a list of for example of KeyStore as trust material and add every keystore with a for each loop into the builder with the method [withTrustMaterial(trustStore)](https://github.com/Hakky54/sslcontext-kickstart/blob/efb75d98929f036c03b6d0c2b6cafb1625d988f1/sslcontext-kickstart/src/main/java/nl/altindag/ssl/SSLFactory.java#L236). The same is possible for the identity material but there you need to supply the key password as additional parameter. Other options as list is also possible such as KeyManager, TrustManager, InputStream etc – Hakan54 Feb 07 '21 at 05:01
  • I am trying option 3 and not quite getting it. Probably my fault, I put more description in https://github.com/Hakky54/sslcontext-kickstart/issues/58. Thank you! – PatPanda Feb 07 '21 at 08:53
  • 1
    Option 3 is the way to go. This project github.com/Hakky54/sslcontext-kickstart. is very well done – PatPanda Feb 12 '21 at 15:15