I would like to use a custom TrustManager, such as one that only accepts certain issuers, accept-all, etc.
With current SslManagerBundle, I need to write something like this to use a custom TrustManager:
TrustManager myTrustManager = ...
// Cannot use DefaultSslManagerBundle as it's package private
KeyManagerFactory keyManagerFactory = getDefaultKeyManagerFactory();
// using netty impl
TrustManagerFactory trustManagerFactory = new TrustManagerFactoryWrapper(myTrustManager);
SslManagerBundle sslManagerBundle = SslManagerBundle.of(keyManagerFactory, trustManagerFactory);
SslBundle sslBundle = SslBundle.of(SslStoreBundle.NONE, SslBundleKey.NONE, SslOptions.NONE,
SslBundle.DEFAULT_PROTOCOL, sslManagerBundle);
...
private KeyManagerFactory getDefaultKeyManagerFactory() {
String algorithm = KeyManagerFactory.getDefaultAlgorithm();
try {
return KeyManagerFactory.getInstance(algorithm);
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("Could not load key manager factory: " + ex.getMessage(), ex);
}
}
This is a lot of boilerplate code just to use a custom TrustManager.
It would be great if the SslManagerBundle API could be improved to support custom TrustManager usage without requiring a KeyManagerFactory. This would simplify configuring SSL/TLS settings when custom TrustManager configurations are needed.
Comment From: mhalbritter
So, something like this on SslManagerBundle?
/**
* Factory method to create a new {@link SslManagerBundle} using the given
* {@link TrustManagerFactory} and the default {@link KeyManagerFactory}.
* @param trustManagerFactory the trust manager factory
* @return a new {@link SslManagerBundle} instance
* @since 3.5.0
*/
static SslManagerBundle from(TrustManagerFactory trustManagerFactory) {
Assert.notNull(trustManagerFactory, "TrustManagerFactory must not be null");
KeyManagerFactory defaultKeyManagerFactory = createDefaultKeyManagerFactory();
return of(defaultKeyManagerFactory, trustManagerFactory);
}
/**
* Factory method to create a new {@link SslManagerBundle} using the given
* {@link TrustManager TrustManagers} and the default {@link KeyManagerFactory}.
* @param trustManagers the trust managers to use
* @return a new {@link SslManagerBundle} instance
* @since 3.5.0
*/
static SslManagerBundle from(TrustManager... trustManagers) {
Assert.notNull(trustManagers, "TrustManagers must not be null");
KeyManagerFactory defaultKeyManagerFactory = createDefaultKeyManagerFactory();
TrustManagerFactory defaultTrustManagerFactory = createDefaultTrustManagerFactory();
return of(defaultKeyManagerFactory, FixedTrustManagerFactory.of(defaultTrustManagerFactory, trustManagers));
}
The FixedTrustManagerFactory just returns the given TrustManagers on the getTrustManagers call.
You can then invoke it like this:
SslBundle bundle = SslBundle.of(SslStoreBundle.NONE, SslBundleKey.NONE, SslOptions.NONE, SslBundle.DEFAULT_PROTOCOL, SslManagerBundle.from(myTrustManager));
You can play around with it here: https://github.com/mhalbritter/spring-boot/tree/mh/43064-provide-user-friendly-api-to-use-custom-trustmanager-in-ssl-manager-bundle
Comment From: ttddyy
Thanks @mhalbritter
It looks great and makes it easy to set up a SslBundle with custom TrustManagers.
Comment From: ttddyy
Hi @mhalbritter
I played around with multiple TrustManager and realized that when interacting with multiple trust-managers, I need to create a composite TrustManager which determines what to do when CertificateException is thrown - continue to check on the next trust-manager, etc.
Sample:
public class CompositeX509TrustManager implements X509TrustManager {
private final X509TrustManager[] trustManagers;
public CompositeX509TrustManager(X509TrustManager... trustManagers) {
this.trustManagers = trustManagers;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509TrustManager trustManager : this.trustManagers) {
try {
trustManager.checkClientTrusted(chain, authType);
return;
}
catch (CertificateException ex) {
// Ignore and try the next trust manager
}
}
throw new CertificateException("None of the trust managers could validate the certificate chain");
}
...
}
Therefore, the method here which accepts multiple trust-managers, doesn't really use them. The check only happened by the first trust-manager, and if it throws CertificateException, the rest of trust-managers are ignored and simply validation fails.
So, the new API on SslManagerBundle:
static SslManagerBundle from(TrustManager... trustManagers) {
should be only accepting a single TrustManager.
static SslManagerBundle from(TrustManager trustManager) {