5 Replies Latest reply: Dec 8, 2010 4:53 PM by EJP RSS

    JNDI, Active Directory and Authentication (Part 4) (SASL EXTERNAL)

      After months of trying on and off, I thought there was a problem with Active Directory even though it advertised EXTERNAL auth as one of its supported SASL mechanisms.

      So after pulling out some of my old LDAP 'C' code and confirming that indeed it did work, I decided to revisit this Java/JNDI stuff.

      Active Directory definitely does supports EXTERNAL auth over TLS.

      I don't understand why the JNDI tutorial includes an example of EXTERNAL auth over SSL, (refer to http://java.sun.com/products/jndi/tutorial/ldap/security/ssl.html and for the source refer to http://java.sun.com/products/jndi/tutorial/ldap/security/src/External.java) as the LDAP Authentication RFC (rfc 2289, available at http://www.ietf.org/rfc/rfc2829.txt) is quite explicit about requirements for TLS.

      Absolutely no magic for the certificates, especially if you have installed a Microsoft Windows Server 2003 Enterprise Certificate Authority. If you are using a 3rd party CA, then refer to http://support.microsoft.com/default.aspx?scid=kb;en-us;321051

      To establish trust with the Windows CA, follow step 2 described in http://forum.java.sun.com/thread.jspa?threadID=581425&tstart=50.

      For completeness, here are the complete list of steps.
      Step 1. Establish Trust with the Certificate Authority

      I have installed a Windows Enterprise CA, with the distinguished name of cn=Antipodes Root,dc=antipodes,dc=com"

      Download the Certificate Authority public key certificate via the Certicate Authority Web Enrollment tool.

      Assuming the Certificate Authority is installed on myca.antipodes.com (and that the Web Interface is also running on the same server), via a browser connect to http://myca.antipodes.com/certsrv. From the page, go to "Download a CA Certificate" and then select "Download CA Certificate". Leave the encoding method as Distinguished Encoding Rules (DER).

      By default the certificate file name is "certnew.cer"

      If you wish to verify the certificate:
      #keytool -printcert -file certnew.cer

      import the cert using:
      #keytool -import -alias antipodes -file certnew.cer -keystore /usr/java/jdk1.5.0_01/jre/lib/security/cacerts

      (Note the default password for the java keystore is "changeit")

      If you have done this step correctly, then you should be able to connect to the domain controller using SSL or TLS as demonstrated in http://forum.java.sun.com/thread.jspa?threadID=581425&tstart=50 and in http://forum.java.sun.com/thread.jspa?threadID=582103&tstart=15 respectively.

      Step 2. Create the users private key, submit a certificate signing request and download the signed client certificate.

      Step 2a. Logon as a normal user and generate your private key
      $keytool -genkey -keyalg RSA -keysize 1024 -alias alberte -dname "CN=Albert Einstein,OU=Research,DC=Antipodes,DC=Com" -keystore $HOME/.mystore -storetype JKS

      Step 2b. Create your certificate signing request
      $keytool -certreq -alias alberte -file alberte.csr -keystore $HOME/.mystore

      Step 2c. Submit the signing request.
      Logon as AlbertE, and browse to the Certificate Authority Web Enrollment Tool.
      Select "Request A certificate" -> "Advanced certificate Request" -> "Submit a request by using a base 64 encoded file....."
      Paste the request (alberte.csr) into the "Base 64 encoded certificate" text box.
      It will look something like:


      Select the User template and save the resulting file, alberte.cer

      (It should be possible to do this directly from the Certificate Authority snap-in, or via Windows CAPICOM without using the Web interface, but I haven't worked out how to add the certificate template to the CSR via keytool.)

      In anycase see step 3 for my favourite option :-)

      Step 2d. Save the signed certificate into the keystore
      $keytool -import -file alberte.cer -alias alberte -trustcacerts -keystore $HOME/.mystore

      Note that you may want to verify the certificate before saving to the keystore
      $keytool -printcert -file alberte.cer

      After importing the certificate you can verify the keystore with the command
      $keytool -list -alias alberte -keystore $HOME/.mystore
      which will result in output similar to:

      alberte, 30-Jun-2005, keyEntry,
      Certificate fingerprint (MD5): A7:61:27:07:02:64:E3:C7:2F:6E:2B:8F:DA:F1:D8:C5

      Step 3 (The easier alternative to step 2 !)
      If your Windows users have already been issued with certificates (including auto-enrollment), just export a user certificate (including the private key) in the pkcs12 format and use it as the keystore.

      From the Windows certificate snap-in, select the appropriate personal certificate, and export it. Select the "Export private key option", uncheck the "Enable strong protection" option, provide a password (which I believe is due to a JSSE requirement must be > 6 characters?), and save the resulting .pfx file.

      Here is some sample code demonstrating SASL EXTERNAL auth.
       * searchexternal.java
       * June 2005
       * Sample JNDI application to perform a search against the Active Directory
       * over TLS, and using client side certs for authentication
      import java.util.Hashtable;
      import java.io.*;
      import javax.naming.*;
      import javax.naming.ldap.*;
      import javax.naming.directory.*;
      public class searchexternal
           public static void main (String[] args)
                Hashtable env = new Hashtable();
                //Access the trust store where the Root CA public key cert was installed
                //For JDK1.5.0 this is the default, so you could omit this.
                String trustStore = "/usr/java/jdk1.5.0_01/jre/lib/security/cacerts";
                //Now access my user certificate, it must be the first cert in the store
                String myStore = System.getProperty("user.home") + "/.mystore";
                String myStoreType = "JKS";
                String myStorePassword = "xxxxx"; 
                //Alternatively if we were using a PKCS12 store
                //String myStore = System.getProperty("user.home") + "/alberte.pfx";
                //String myStoreType = "pkcs12";
                //String myStorePassword = "xxxxx"; 
                //connect to my domain controller
                String ldapURL = "ldap://mydc.antipodes.com:389";
                try {
                     // Create the initial directory context
                     LdapContext ctx = new InitialLdapContext(env,null);
                     // Start TLS
                     StartTlsResponse tls = (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest());
                         // Perform client authentication using TLS credential
                         ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "EXTERNAL");
                     //Create the search controls           
                     SearchControls searchCtls = new SearchControls();
                     //Specify the attributes to return
                     String returnedAtts[]={"sn","givenName","mail"};
                     //Specify the search scope
                     //specify the LDAP search filter
                     String searchFilter = "(&(objectClass=user)(mail=*))";
                     //Specify the Base for the search
                     String searchBase = "DC=ANTIPODES,DC=COM";
                     //initialize counter to total the results
                     int totalResults = 0;
                     // Search for objects using the filter
                     NamingEnumeration answer = ctx.search(searchBase, searchFilter, searchCtls);
                     //Loop through the search results
                     while (answer.hasMoreElements()) {
                              SearchResult sr = (SearchResult)answer.next();
                          System.out.println(">>>" + sr.getName());
                          // Print out some of the attributes, catch the exception if the attributes have no values
                          Attributes attrs = sr.getAttributes();
                          if (attrs != null) {
                               try {
                               System.out.println("   surname: " + attrs.get("sn").get());
                               System.out.println("   firstname: " + attrs.get("givenName").get());
                               System.out.println("   mail: " + attrs.get("mail").get());
                               catch (NullPointerException e)     {
                               System.err.println("Error listing attributes: " + e);
                      System.out.println("Total results: " + totalResults);
                catch (NamingException e) {
                     System.err.println("Problem searching directory: " + e);               
                catch (IOException e) {
                     System.err.println("Problem searching directory: " + e);