2

Java 8u181 introduced a change that enables certificate hostname verification when using the Java JNDI LDAP API connecting to LDAPS (TLS) servers.

See: https://www.oracle.com/technetwork/java/javase/8u181-relnotes-4479407.html#JDK-8200666

How can this hostname verification be disabled, or better yet specify a custom javax.net.ssl.HostnameVerifier class. Oracle's documentation only specifies a Java environment property to disable the verification, but does not indicate any way to accomplish this problematically which is critical for environments that don't (or don't want) ability to change bits/switches of the JVM they are running on.

This question: How to disable endpoint identification for java 1.8.181 version Asks a similar question but the solution is a java environment change via the command line. I'm asking how it can be done programmatically without the environment switch.

There are other questions/answers about disabling hostname verification for other types of SSL connections in Java but the answers do not work with the JNDI LDAP API.

amb
  • 141
  • 2
  • 10
  • "How can this hostname verification be disabled," Just don't (disable it). For eye opening: https://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html – Patrick Mevzek Oct 02 '18 at 16:42
  • Thanks @Patrick-Mevzek, that is of course correct security advice but it doesn't actually answer the questions. This change instantly breaks existing applications and I'd like to change our app so it only applies to newly configured certificates or similar. Many enterprise certificate systems dont even support subjectAltNames yet. In any case this question is 'how' not 'should'. – amb Oct 03 '18 at 01:02

2 Answers2

6

As @Patrick-Mevzek already stated: DON'T DO THIS!

But if you really must, here is how you would do it:

You need a SocketFactory that includes a dummy TrustManager that just ignores anything. There are many examples out there that show how to create such a thing. Unfortunatly most (all?) of them use a X509TrustManager for the job. This will work for invalid certificates but will not handle wrong or missing hostnames. For that you need a ```X509ExtendedTrustManager`:

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;

/**
 * This Socket factory will accept all certificates and all hostnames
 */
public class NonVerifyingSSLSocketFactory extends SocketFactory {
   private static SocketFactory nonVerifyingSSLSochetFactory;

   static {
      TrustManager [] distrustManager = new TrustManager [] {new X509ExtendedTrustManager () {
         @Override
         public void checkClientTrusted (X509Certificate [] chain, String authType, Socket socket) {

         }

         @Override
         public void checkServerTrusted (X509Certificate [] chain, String authType, Socket socket) {

         }

         @Override
         public void checkClientTrusted (X509Certificate [] chain, String authType, SSLEngine engine) {

         }

         @Override
         public void checkServerTrusted (X509Certificate [] chain, String authType, SSLEngine engine) {

         }

         public X509Certificate [] getAcceptedIssuers () {
            return null;
         }

         public void checkClientTrusted (X509Certificate [] c, String a) {
         }

         public void checkServerTrusted (X509Certificate [] c, String a) {
         }
      }};

      try {
         SSLContext sc = SSLContext.getInstance ("SSL");
         sc.init (null, distrustManager, new java.security.SecureRandom ());
         nonVerifyingSSLSochetFactory = sc.getSocketFactory ();
      } catch (GeneralSecurityException e) {
         throw new RuntimeException (e);
      }
   }

   /**
    * This method is needed. It is called by the LDAP Context to create the connection
    *
    * @see SocketFactory#getDefault()
    */
   @SuppressWarnings ("unused")
   public static SocketFactory getDefault () {
      return new NonVerifyingSSLSocketFactory ();
   }

   /**
    * @see SocketFactory#createSocket(String, int)
    */
   public Socket createSocket (String arg0, int arg1) throws IOException {
      return nonVerifyingSSLSochetFactory.createSocket (arg0, arg1);
   }

   /**
    * @see SocketFactory#createSocket(java.net.InetAddress, int)
    */
   public Socket createSocket (InetAddress arg0, int arg1) throws IOException {
      return nonVerifyingSSLSochetFactory.createSocket (arg0, arg1);
   }

   /**
    * @see SocketFactory#createSocket(String, int, InetAddress, int)
    */
   public Socket createSocket (String arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
      return nonVerifyingSSLSochetFactory.createSocket (arg0, arg1, arg2, arg3);
   }

   /**
    * @see SocketFactory#createSocket(InetAddress, int, InetAddress, int)
    */
   public Socket createSocket (InetAddress arg0, int arg1, InetAddress arg2,
                               int arg3) throws IOException {
      return nonVerifyingSSLSochetFactory.createSocket (arg0, arg1, arg2, arg3);
   }

}

Use that in your InitialLdapContext environment to activate it:

env.put ("java.naming.ldap.factory.socket", NonVerifyingSSLSocketFactory.class.getName ());

Tested with:

  • openjdk version "1.8.0_191"
  • oraclejdk version "1.8.0_25" (this version won't need it, but it works anyway and doesn't break anything)
Markus Heberling
  • 835
  • 13
  • 18
0

set the system property before httpclient instance creation

System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");
Noa
  • 315
  • 1
  • 7
  • 31