east825
east825

Reputation: 909

Java key store is not found when default SSL context is redefined

Suppose, that I want all connections via SSL in my Java application ask user permission, when untrusted or expired server certificate is encountered (like most of web browsers do).

It seemed, that the most natural way to do so is to substitute default SSL context on application startup.

Here is the minimal working example:

public class ClientAuthentication {
    public static final String SERVER_URL = "https://my.test.server";

    public static void main(String[] args) throws Exception {
        SSLContext defaultContext = SSLContext.getInstance("TLS");
        defaultContext.init(null, new TrustManager[]{new MyX509TrustManager()}, null);
        // defaultContext.init(null, null, null);
        SSLContext.setDefault(defaultContext);
        HttpURLConnection connection = (HttpURLConnection) new URL(SERVER_URL).openConnection();
        System.out.println(connection.getResponseCode());
    }

    private static class MyX509TrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            throw new UnsupportedOperationException("Won't be called by client");
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String s) throws CertificateException {
            if (userAcceptsCertificate(chain)) {
                System.out.format("Certificate '%s' accepted%n", chain[0].getIssuerX500Principal().getName());
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }

        private boolean userAcceptsCertificate(X509Certificate[] x509Certificates) {
            // ...
            return true;
        }
    }
}

Unfortunately, it doesn't work, when server requires client authentication. Even, when key store containing client certificate is properly specified via -Djavax.net.ssl.keyStore VM option, connection fails with javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure, because client certificate wasn't sent at all (traced in Wireshark).

Such behavior is a bit surprising, because JavaDoc for SSLContext#init(KeyManager[], TrustManager[], SecureRandom) states, that passing null instead of either of key managers or trust managers means, that default lookup procedure will be performed.

Сontradictorily, if I pass null instead of array with my custom trust manager as second argument to init and specify JKS trust store, containing server certificate, using
-Djavax.net.ssl.trustStore VM parameters, server certificate will be found there and accepted successfully.

So, can you advise a way to redefine default SSL context, substituting only trust manager and leaving default behavior regarding the search of key managers? Or may be some robust way to find key manager specified via VM options.

Notes

  1. If I don't customize default SSL context at all, connection is successful, i.e. all paths, password and store types are correct in VM options.

  2. I can't in any way call SSLContext.getDefault() or SSLContext.getInstance("Default"), because default context can be initialized only once.

Upvotes: 0

Views: 2017

Answers (1)

user207421
user207421

Reputation: 310957

You've initialized the context with a null key manager. That means there is no key manager. It doesn't mean there is a default key manager. So, when the functions of a key manager are requested, nothing happens. Those functions include supplying the client certificate.

The Javadoc is wrong. The IBM JSSE behaves as described there, but the Sun/Oracle JSSE doesn't, and never has.

Upvotes: 1

Related Questions