简体   繁体   中英

how to accept self-signed certificates for JNDI/LDAP connections?

I need to connect to an LDAP directory over SSL.

In non-production environments, we use self-signed certificates which, of course, fails to validate with:

javax.naming.CommunicationException: simple bind failed: ldapserver:636 [Root exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target]
 at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:197)
 at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2694)
 at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:293)
 at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:175)
 at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:193)
 at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:136)
 at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:66)
 at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:667)
 at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:288)
 at javax.naming.InitialContext.init(InitialContext.java:223)
 at javax.naming.ldap.InitialLdapContext.<init>(InitialLdapContext.java:134)

I am aware of how to use a custom trust manager for SSL-enabled connections , but don't know how to use one in connection with the JNDI API where I don't manage the actual connection. That is, where is the following standard setup will I be able to plug the trust manager?

Thanks in advance.

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldaps://ldapserver:636");
env.put(Context.SECURITY_PROTOCOL, "ssl");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "myUser");
env.put(Context.SECURITY_CREDENTIALS, "myPassword");
LdapContext ctx = new InitialLdapContext(env, null);
ctx.search (...)

According to the JNDI documentation it seems possible to set a custom SSLSocketFactory

http://download.oracle.com/javase/1.5.0/docs/guide/jndi/jndi-ldap-gl.html#socket

public class MySSLSocketFactory extends SocketFactory {
    private static final AtomicReference<MySSLSocketFactory> defaultFactory = new AtomicReference<>();

    private SSLSocketFactory sf;

    public MySSLSocketFactory() {
        KeyStore keyStore = ... /* Get a keystore containing the self-signed certificate) */
        TrustManagerFactory tmf = TrustManagerFactory.getInstance();
        tmf.init(keyStore);
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, tmf.getTrustManagers(), null);
        sf = ctx.getSocketFactory();
    }

    public static SocketFactory getDefault() {
        final MySSLSocketFactory value = defaultFactory.get();
        if (value == null) {
            defaultFactory.compareAndSet(null, new MySSLSocketFactory());
            return defaultFactory.get();
        }
        return value;
    }

    @Override
    public Socket createSocket(final String s, final int i) throws IOException {
        return sf.createSocket(s, i);
    }

    @Override
    public Socket createSocket(final String s, final int i, final InetAddress inetAddress, final int i1) throws IOException {
        return sf.createSocket(s, i, inetAddress, i1);
    }

    @Override
    public Socket createSocket(final InetAddress inetAddress, final int i) throws IOException {
        return sf.createSocket(inetAddress, i);
    }

    @Override
    public Socket createSocket(final InetAddress inetAddress, final int i, final InetAddress inetAddress1, final int i1) throws IOException {
        return sf.createSocket(inetAddress, i, inetAddress1, i1);
    }
}

Configure the environment to use this socket factory

env.put("java.naming.ldap.factory.socket", "com.example.MySSLSocketFactory");

You could accept any certificate when you override the Trustmanager:

DummyTrustmanager.java

public class DummyTrustmanager implements X509TrustManager {
    public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
        // do nothing
    }

    public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
        // do nothing
    }

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

MySSLSocketFactory.java

public class MySSLSocketFactory extends SSLSocketFactory {
    private SSLSocketFactory socketFactory;

    public MySSLSocketFactory() {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(null, new TrustManager[] { new DummyTrustmanager() }, new SecureRandom());
            socketFactory = ctx.getSocketFactory();
        } catch (Exception ex) {
            ex.printStackTrace(System.err);
            /* handle exception */
        }
    }

    public static SocketFactory getDefault() {
        return new MySSLSocketFactory();
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return socketFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return socketFactory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException {
        return socketFactory.createSocket(socket, string, i, bln);
    }

    @Override
    public Socket createSocket(String string, int i) throws IOException, UnknownHostException {
        return socketFactory.createSocket(string, i);
    }

    @Override
    public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException, UnknownHostException {
        return socketFactory.createSocket(string, i, ia, i1);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i) throws IOException {
        return socketFactory.createSocket(ia, i);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException {
        return socketFactory.createSocket(ia, i, ia1, i1);
    }

    @Override
    public Socket createSocket() throws IOException {
        return socketFactory.createSocket();
    }
}

Main.java

public class Main {
    public static void main(String[] args) throws NamingException {
        Hashtable env = new Hashtable(11);
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldaps://ldapserver:636/");
        env.put(Context.SECURITY_PROTOCOL, "ssl");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, "myUser");
        env.put(Context.SECURITY_CREDENTIALS, "myPassword");
        env.put("java.naming.ldap.factory.socket", "ldapsecure.MySSLSocketFactory");
        LdapContext ctx = new InitialLdapContext(env, null);
    }
}

No code required. Just add the certificate to the test client truststores.

Don't write extra code for this. It is insecure, and you run a major risk of the code leaking into production. You really don't want that.

@Jcs answer is correct, but if you don't want to use a custom TrustManager, and if you're willing for that particular self-signed certificate to be a trusted CA for other uses within the VM, you can either:

  • add this certificate to your default truststore (usually cacerts under the JRE security directory) or
  • create a new truststore in a different location (possibly based on a copy of the default cacerts ) which would contain that particular certificate, and use this as your default truststore, but setting the javax.net.ssl.trustStore* system properties.

Its been about 5 years since I last did this so my answer will be a bit vague I'm afraid; but I think if you impliment a trusted provider in the java.security which you should be able to find under /usr/java/jre/lib/security/; it will then accept the certificate as trusted.

I dont have access to my notes at the moment, but I'll have a dig through them later

Setting aside JNDI entirely, some frameworks will refer to the LOCAL SYSTEM LDAP configuration (this is typically /etc/ldap.conf or something like that). When I say LOCAL, I mean the system(s) on which your JNDI operation is running.

At least in the case of most LDAP-enabled Linux OSs, inside of such an LDAP config file is (usually) a line that reads:

TLS_REQCERT demand

This is the default TLS/SSL criticality setting (and the most strict), as it will fail the connection if ANYTHING WHATSOEVER is wrong with the certificate (this includes being self-signed).

You can experiment with different settings instead of 'demand' (play with 'allow' or 'never'). Once this is done, try your operation again and see if your issue subsides. Again, not all things like this will read or even detect your local system settings. Some do, some don't. Worth a look.

I hope this helps...

Max

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM