In some applications it is desirable to use multiple sources of Trust material. An application may want to use Java's default trust store as a fallback or first lookup of CA Root/Parent Certs but for some internal services also use additional, managed, shared trust and key stores. In fact in a pool of micro-services each service may contribute a self managed source of trust material. To address these types of screnarios support the following:
- static SslBundle (may be via a auto-configured Registrar) that basically wraps Java's lib\security\cacerts
- a composite, delegating SslBundle implementation that can delegate to ordered list of SslBundles
- allow Profile or Conditional based inclusion of delegate SslBundles
- it should be possible to configure such composite SslBundles via configuration
Comment From: wilkinsona
a composite, delegating SslBundle implementation that can delegate to ordered list of SslBundles allow Profile or Conditional based inclusion of delegate SslBundles
How would you expect such a composite bundle to behave? For example, what would be returned by SslBundle.getKey() or by SslBundle.getStores().getKeyStore()? There does not appear to be an opportunity for ordered delegation.
Comment From: sandipchitale
Good point. Presumably the notion of delegate objects will have to go deeper. The key point is, now that SslBundle usage is becoming ubiquitous throughout the Spring Framework and in most places only one SslBundle can be consumed e.g. RestClient.Builder then to support a use case of composite trust material sources fronted by a single SslBundle is what I am after. Hope this helps clarifying what I am looking for.
For example, a CA root certificate may be from a public, well-known entity like Verisign, and some CA root certs may be intranet only for a shared, private truststore.
Comment From: wilkinsona
Thanks. Unfortunately, I'm not sure that really helps.
Ultimately, an SslBundle is just a convenient way of creating instances of standard Java SSL constructs such as an SSLContext instance. It's these instances that are consumed by things like HTTP client libraries and embedded web servers to configure their SSL support.
Ignoring the SslBundle abstraction, can you describe how you would achieve what you have described using Java's standard APIs?
Comment From: sandipchitale
Something along the lines of
https://stackoverflow.com/a/65323132/2097168
The idea is at low level Java APIs use private final List
Hopefully that gives an idea.
A combo of these to repos shows the idea implemented using Java's standard APIs in conjunction with SslBundles:
https://github.com/sandipchitale/multitrustsslbundle https://github.com/sandipchitale/serversslbundle
Comment From: sandipchitale
SSL has two sides. Server side and Client side. The server side can have a single key/certificate in a single keystore, but client could have multiple truststores. Thus the signature of:
KeyStore org.springframework.boot.ssl.SslStoreBundle#getTrustStore()
could be changed to
List
and the managers built from these multiple TrustStores. Interestingly:
TrustManager[] org.springframework.boot.ssl.SslManagerBundle#getTrustManagers()
already seem to be dealing with multiple TrustManagers.
And then
@Override
public TrustManagerFactory org.springframework.boot.ssl.DefaultSslManagerBundle#getTrustManagerFactory.getTrustManagerFactory() {
try {
List<KeyStore> store = this.storeBundle.getTrustStores(); <---------------------------
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory factory = getTrustManagerFactoryInstance(algorithm);
factory.init(store); <------------------------- change here to accept multiple truststores or a composite trust store
return factory;
}
catch (Exception ex) {
throw new IllegalStateException("Could not load trust manager factory: " + ex.getMessage(), ex);
}
}
This is a general outline.
Hope this additional commentary helps.
Comment From: sandipchitale
Java's cacerts truststore can be easily loaded as SslBundle like this:
spring:
ssl:
bundle:
jks:
cacerts:
truststore:
type: PKCS12
location: "${java.home}/lib/security/cacerts"
password: changeit
Not sure why I did not think of that.
I was only addressing the "ability to wrap Java's lib\security\cacerts in SslBundle" part of the issue title here.
Comment From: mhalbritter
But that doesn't solve the problem with mixing the system CA certs with self created ones, right?
Comment From: mhalbritter
I have a setup which creates a RestClient backed by the JDK client which trusts the server self-signed certificate and also the global cacerts file.
Code is here: https://github.com/mhalbritter/mtls-with-sslbundles/
The client uses the client and the cacerts bundle: https://github.com/mhalbritter/mtls-with-sslbundles/blob/main/src/main/java/com/example/mtls/Client.java, which are defined here: https://github.com/mhalbritter/mtls-with-sslbundles/blob/main/src/main/resources/application.yaml
I had to create the SSLContext myself to use the CompositeX509TrustManager, see here: https://github.com/mhalbritter/mtls-with-sslbundles/blob/main/src/main/java/com/example/mtls/SslContextFactory.java
All together this even works with mTLS (client sends own certificate to server).
While this is some code, I'm not sure if we can get that integrated into the SslBundle abstraction without breaking all the APIs.
Comment From: sandipchitale
Ah, the following was the key. Maybe this example should be documented, but it works. Thanks @mhalbritter
I just cannot use the SslBundles directly at higher builder level thru various builders. Of course if this was made possible in long run, it would be even great. But I understand this may mean breaking the APIs. May be a compatible evolution could be thought of.
With that you can close the issue as there is a way to do this fairly easily at HttpClient level.
Comment From: wilkinsona
We discussed this today and decided that we don't want to do anything here. Thank you for the suggestion though.
Our feeling is that it would be difficult to evolve the SSL bundle API into one where the trust material could be composed. Even after this evolution, it would then be harder to use for the common case where composition is not required. Instead of requiring composition, we recommend creating different clients (such as multiple RestTemplates), one for each external service that has different trust requirements.