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