At work we’re moving to a centralized LDAP tree for all authentication. The infrastructure guys are still working out some of the kinks so in the mean time, they gave us access to a test server that we can use for development.

I have accessed LDAP trees from Java a number of times. I can’t say that I love the JNDI API but it works well enough and my complaints with it are too insignificant to enumerate. The test tree we’re using only accepts TLS connections. No problem. JNDI has support for TLS out of the box. Much to my surprise, however, I was getting a javax.net.ssl.SSLHandshakeException when I tried to connect. I realized very quickly that the infrastructure guys that set up the server were using a self-signed certificate and Java couldn’t validate the certificate. Rather than trying to import the certificate into a keystore so that Java will trust it, I bypassed the default Java TrustManager.

To do this, you first need to create a dummy X509TrustManager class. I use the following:

public class DummyTrustManager implements javax.net.ssl.X509TrustManager {
    public void checkClientTrusted(java.security.cert.X509Certificate[] cert, String authType) {
        return;
    }

    public void checkServerTrusted(java.security.cert.X509Certificate[] cert, String authType) {
        return;
    }

    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return new java.security.cert.X509Certificate[0];
    }
}

As you can see, this creates a TrustManager class that does nothing. By doing nothing, it implies that all certificates are trusted. In our case, this is exactly what we want. We’re just doing development and don’t have sensitive data. Now how do we get JNDI to use our dummy TrustManager? We need to create a custom javax.net.ssl.SSLSocketFactory that uses our DummyTrustManager. The following class will give us just what we need.

public class DummySSLSocketFactory extends javax.net.ssl.SSLSocketFactory {
    private javax.net.ssl.SSLSocketFactory factory;

    public DummySSLSocketFactory() {
        try {
            javax.net.ssl.SSLContext sslContext = javax.net.ssl.SSLContext.getInstance("TLS");
            sslContext.init(
                null, // No KeyManager required
                new TrustManager[] { new DummyTrustManager() },
                new java.security.SecureRandom());

           factory = (javax.net.ssl.SSLSocketFactory)sslContext.getSocketFactory();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public Socket createSocket(Socket socket, String s, int i, boolean flag) throws IOException {
        return factory.createSocket(socket, s, i, flag);
    }

    public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr1, int j) throws IOException {
        return factory.createSocket(inaddr, i, inaddr1, j);
    }

    public Socket createSocket(InetAddress inaddr, int i) throws IOException {
        return factory.createSocket(inaddr, i);
    }

    public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
        return factory.createSocket(s, i, inaddr, j);
    }

    public Socket createSocket(String s, int i) throws IOException {
        return factory.createSocket(s, i);
    }

    public String[] getDefaultCipherSuites() {
        return factory.getSupportedCipherSuites();
    }

    public String[] getSupportedCipherSuites() {
        return factory.getSupportedCipherSuites();
    }
}

This gives us a proxy to an SSLSocketFactory that uses our DummyTrustManager. To tell JNDI to use our DummySSLSocketFactory set the following in the properties you pass to your InitialDirContext constructor.

properties.put("java.naming.ldap.factory.socket", DummySSLSocketFactory.class.getName());

And voila! No more SSLHandshakeExceptions.