Reputation: 5070
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
Upvotes: 2
Views: 3535
Reputation: 3889
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.
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.
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>
Upvotes: 2