13 Replies Latest reply: Aug 20, 2008 11:20 AM by 843793 RSS

    JNDI, Active Directory and Authentication (part 1)

    800477
      Disclaimer, the information presented here is about 3 years old, however it will adequately describe how to use Kerberos & GSS-API to authenticate a Java/JNDI application against the Active Directory.

      The sample code presented here is adapted from the original Sun Java/JAAS/GSS_API code samples.

      There is a ton of information for integrating non-Windows Kerberos implementaions with Active Directory.

      http://www.microsoft.com/windowsserver2003/technologies/security/kerberos/default.mspx

      In particular for configuring the MIT Kerberos implementation to work with Active Directory, I would recomend:
      http://www.microsoft.com/windows2000/techinfo/planning/security/kerbsteps.asp

      And for general Unix/Windows integration I would highly recomend the "Solution Guide for Windows Security and Directory Services for UNIX"

      I would also recomend the Vintela suite of products that simplify the integration of Linux/Unix clients with Active Directory.

      http://www.vintela.com/products/vas/index.php

      Anyway, here's a working step-by step guide. If I get some free time I may upgrade this information to include more recent releases of MIT Kerberos V, using PAM and any applicable updates to JAAS.

      Note that this assume the Linux host will be joined to an Active Directory domain. If the host is joined to a Kerberos realm, then all you need to do is to establisht a Kerberos trust between the Active Directory & the Kerberos realm.

      Step 1.
      Create a user object in the Active Directory to represent the Unix/Linux host from which the Java application will be run. In this case the user object is called "linux". If you are using the Active Directory Users & Computers snap-in, it doesn't matter what the givenName, Surname, Display names are, but the User Logon (pre-Windows 2000) name in thus example is set to "linux" (without the quotes of course !)

      Also remember to have created a user object to represent the user that you will log in as. (Hopefully not root !)

      Step 2.
      Create the keytab from Active Directory. In this example, my Linux host is called "linux", it's fully qualified dns name is linux.antipodes.com, my Active Directory domain is called antipodes.com, the password I have used for the host account is "password", and the resultant keytab is named linux.keytab. (Note that the Active Directory domain, or kerberos realm, must be uppercased, this will make sense later on in the krb5.conf description)

      c:> ktpass -princ host/linux.antipodes.com@ANTIPODES.COM -mapuser linux
      -pass password -out linux.keytab

      Note that if you look at the Users & Computer snap-in, the "linux" account will now have a Login name of the form:
      "host/linux.antipodes.com@ANTIPODES.COM"

      Step 3.
      Securely transfer the linux.keytab file to the Linux host, and install it using ktutil.
      # ktutil
      ktutil: read_kt linux.keytab
      ktutil: write_kt /etc/krb5.keytab
      ktutil: quit

      To check that the keytab has been merged, use the ktutil list command.
      # ktutil
      ktutil: list
      slot KVNO Principal
      ----- ---- ----------------------------------------
      1 1 host/linux.antipodes.com@ANTIPODES.COM

      Step 4.
      Configure Kerberos on the linux host. My /etc/krb5.conf file looks like this.
      [libdefaults]
      default_realm = ANTIPODES.COM
      default_tgs_enctypes = des-cbc-crc
      default_tkt_enctypes = des-cbc-crc

      [realms]
      ANTIPODES.COM = {
      kdc = mydc.antipodes.com:88
      admin_server = mydc.antipodes.com:88
      kpasswd_server = mydc.antipodes.com:464
      default_domain = ANTIPODES.COM
      }

      Note that this is using a really old version of the MIT Kerberos V that used DES as the common encryption algorithm supported by both Active Directory & the MIT release. Newer MIT releases support stronger encryption algorithms that may or may not be supported by Active Directory such as 3DES or RC4-HMAC. (RC4-HMAC is the default encryption algorithm used by Active Directory & Windows clients)

      Step 5.
      Test that Kerberos actually works.
      # kinit
      Password for steven@ANTIPODES.COM
      # klist
      Ticket cache: /tmp/krb5cc_0
      Default principal: steven@ANTIPODES.COM
      Valid Starting Expires Service Principal
      12/15/2004 09:26:02 12/15/2004 19:24:04 krbtgt/ANTIPODES.COM

      You could also configure PAM to use Kerberos as the authentication mechanism.

      One common gotcha, the time needs to be synchronized between the Linux client and Active Directory domain controller so that it does not exceed the Kerberos Clock Skew (default 5 minutes).

      Step 6.
      The application. As I said, a simple modification of the Sun sample code:
      /**
      * SearchWithAuth.java
      *  10 July 2001
      * Sample JNDI application to use Kerberos & GSS-APi for authentication
      **/
      
      import javax.naming.*;
      import javax.naming.directory.*;
      import javax.security.auth.login.*;
      import javax.security.auth.Subject;
      import java.util.Hashtable;
      
      class SearchWithAuth {
      
          public static void main(String[] args) {
      
           LoginContext lc = null;
           try {
      
               lc = new LoginContext(SearchWithAuth.class.getName(),
                new SampleCallbackHandler());
      
               lc.login();
      
           } catch (LoginException le) {
               System.err.println("Authentication attempt failed" + le);
               System.exit(-1);
           }
           System.out.println("Authenticated via GSS-API");
           Subject.doAs(lc.getSubject(), new LDAPSearch());
          }
      }
      
      
      //The privileged action that is executed under doAs
      class LDAPSearch implements java.security.PrivilegedAction {
      
          public LDAPSearch() {
          }
      
          public Object run() {
           performLDAPSearch();
           return null;
          }
      
          private static void performLDAPSearch() {
      
           // Set up environment for creating initial context
           Hashtable env = new Hashtable();
      
           env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
      
           // Connect to my domain controller
           env.put(Context.PROVIDER_URL, "ldap://mydc.antipodes.com:389");
          
           // Specify GSSAPI as the SASL provider
           env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
      
           try {
                 // Create the initial directory context
                DirContext ctx = new InitialDirContext(env);
               
                // Create the search controls           
                SearchControls searchCtls = new SearchControls();
                
                //Specify the attributes to return
                String returnedAtts[]={"sn","givenName","mail"};
                searchCtls.setReturningAttributes(returnedAtts);
                
                //Specify the search scope
                searchCtls.setSearchScope(SearchControls.SUBTREE_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();
      
                     totalResults++;
      
                     System.out.println(">>>" + sr.getName());
      
                }
      
                 System.out.println("Total results: " + totalResults);
                ctx.close();
      
      
               ctx.close();
      
               } catch (NamingException e) {
              // e.printStackTrace();
               }
      
           }     
      }
      And the corresponding Callback handler. (Question: how does one create a password entry reader, which echos * instead of the password charcters ?)
      /**
      * SampleCallbackHandler.java
      *  10 July 2001
      * Sample JNDI application to use Kerberos & GSS-APi for authentication
      **/
      import javax.security.auth.callback.*;
      import javax.security.auth.callback.PasswordCallback;
      import javax.security.auth.callback.NameCallback;
      import java.io.IOException;
      import java.io.BufferedReader;
      import java.io.InputStreamReader;
      
      public class SampleCallbackHandler implements CallbackHandler {
          public void handle(Callback[] callbacks) 
           throws java.io.IOException, UnsupportedCallbackException {
               for (int i = 0; i < callbacks.length; i++) {
                if (callbacks[i] instanceof NameCallback) {
                    NameCallback cb = (NameCallback)callbacks;
                cb.setName(getClearInput(cb.getPrompt()));

                } else if (callbacks[i] instanceof PasswordCallback) {
                PasswordCallback cb = (PasswordCallback)callbacks[i];
      if (cb.isEchoOn()) {
      System.out.println("Echo On");
      }
                String pw = getProtectedInput(cb.getPrompt());
                char[] passwd = new char[pw.length()];
                pw.getChars(0, passwd.length, passwd, 0);

                cb.setPassword(passwd);
                } else {
                throw new UnsupportedCallbackException(callbacks[i]);
                }
           }
      }

      private String getClearInput(String prompt) throws IOException {
           System.out.print(prompt);
           BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
           return in.readLine();
      }

      private String getProtectedInput(String prompt) throws IOException {
           System.out.print(prompt);
           BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
           return in.readLine();
      }


      public static void main(String[] args) throws IOException,
      UnsupportedCallbackException {

           // Test handler

           CallbackHandler ch = new SampleCallbackHandler();
           Callback[] callbacks = new Callback[]{
           new NameCallback("user id:"),
                new PasswordCallback("password:", true)};

           ch.handle(callbacks);
      NameCallback ncb = (NameCallback)callbacks[0];
      System.out.println("Debug: " + ncb.getName());
      }
      }

      And finally the Java application configuration file, named SearchWithAuh.conf
      /** 
       * Login Configuration for JAAS. 
       *
       * Specify that Kerberos v5 is a required login module for the 
       * sample application SearchWithAuth.
       */
      SearchWithAuth {
        com.sun.security.auth.module.Krb5LoginModule required client=TRUE useTicketCache=TRUE;
      };
      Note the useTicketCache = TRUE value means that if the user already has a Kerberos ticket they will not be prompted for credentials. If the user does not have a ticket (for example use kdestroy to logout of Kerberos), then the user will be prompted for credentials.

      Step 8.
      You can avoid steps 1-4 with the Vintela software. It simplifies the integration of Unix & Linux clients with the Active Directory.
      It extends the schema with Unix GID, UID attributes, enhances the Active Directory Users & Computers snap-in with additional property pages to enable editing of Unix UID, GID, shell script atributes and allows Active Directory Group Policy to be applied to Unix & Linux clients (including password policy)

      There is also a Linux/Unix command line utility that "joins" the machine to the domain (essentially automates the creation of computer account, generation, transfer and import of the keytab, configuration of the kr5.conf ) and extends the crypto libraries to support RC4-HMAC.
        • 1. Re: JNDI, Active Directory and Authentication (part 1)
          843793
          Hello,

          Great example... I am trying to run the example from a Java program on my Windows XP box (client) connecting to a Windows 2003 ADS and am getting the following error...
          javax.naming.AuthenticationException: GSSAPI [Root exception com.sun.security.sasl.preview.SaslException: Failure to
          initialize security context [Caused by GSSException: Invalid name provided 
          (Mechanism level: Could not load configuration file c:\winnt\krb5.ini (The system cannot find the path specified))]]
                  at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:158)
                  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                  at java.lang.reflect.Method.invoke(Method.java:324)
                  at com.sun.jndi.ldap.LdapClient.saslBind(LdapClient.java:399)
                  at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:215)
                  at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2640)
                  at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:290)
                  at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:175)
                         .
                         .
                         .
                 at javax.security.auth.Subject.doAs(Subject.java:320)
                 at resoft.utilities.jndi.SearchWithAuth.main(SearchWithAuth.java:58)
          I think the main problem is that I skipped the first couple of step because I did not know how to do them on a Windows Box.

          So how do I build a valid krb5.ini file on my Windows Client box?
          • 2. Re: JNDI, Active Directory and Authentication (part 1)
            800477
            It appears as though Sun's Kerb5Module does work with the WIndows XP ticket cache.

            Refer to http://forum.java.sun.com/thread.jspa?threadID=626847&tstart=0

            You may want to ask the author of that original post for further details.
            • 3. Re: JNDI, Active Directory and Authentication (part 1)
              843793
              I am trying to do something similar to this, with a few exceptions. It worked perfectly in Java 1.4.2, but now that I have migrated to 1.5.0 , things are not working as well.

              I get the following stack trace:
              javax.naming.NamingException: [LDAP: error code 80 - SASL(-1): generic failure: GSSAPI Error:  A token had an invalid MIC (Success)]
                   at com.sun.jndi.ldap.LdapCtx.mapErrorCode(Unknown Source)
                   at com.sun.jndi.ldap.LdapCtx.processReturnCode(Unknown Source)
                   at com.sun.jndi.ldap.LdapCtx.processReturnCode(Unknown Source)
                   at com.sun.jndi.ldap.LdapCtx.connect(Unknown Source)
                   at com.sun.jndi.ldap.LdapCtx.<init>(Unknown Source)
                   at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(Unknown Source)
                   at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(Unknown Source)
                   at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(Unknown Source)
                   at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(Unknown Source)
                   at javax.naming.spi.NamingManager.getInitialContext(Unknown Source)
                   at javax.naming.InitialContext.getDefaultInitCtx(Unknown Source)
                   at javax.naming.InitialContext.init(Unknown Source)
                   at javax.naming.InitialContext.<init>(Unknown Source)
                   at javax.naming.directory.InitialDirContext.<init>(Unknown Source)
                   at LDAPQuery.run(LDAPQuery.java:29)
                   at java.security.AccessController.doPrivileged(Native Method)
                   at javax.security.auth.Subject.doAs(Unknown Source)
                   at Auth.main(Auth.java:29)
              Is there something that changed between the two SDK's that I should know about?

              Thanks,
              Matt
              • 4. Re: JNDI, Active Directory and Authentication (part 1)
                843793
                I thought some of the code might help as well. Sorry I did not include it in the previous post.

                This is the action that I run as an authenticated subject. And I am getting a valid authenticated subject via Kerberos.
                public class LDAPQuery implements PrivilegedAction {
                     public Object run() {
                          InitialDirContext context=null;
                          Hashtable env = new Hashtable();
                          Attributes attributes;
                          env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
                          env.put("javax.security.sasl.server.authentication", "true");
                          env.put("java.naming.security.sasl.authorizationId", "dn:cn=****,ou=****,ou=****,dc=umich,dc=edu");
                          env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
                          env.put(Context.PROVIDER_URL, "ldap://ldap.itd.umich.edu:389/ou=People,dc=umich,dc=edu");
                          env.put(Context.AUTHORITATIVE, "true");
                          env.put(Context.SECURITY_PRINCIPAL, "dn:cn=****,ou=****,ou=****,dc=umich,dc=edu");
                          try {
                               context = new InitialDirContext(env);
                               attributes = context.getAttributes("uid=username");
                               System.out.println(attributes.get("mailForwardingAddress"));
                          } catch (NamingException e) {
                               context = null;
                               e.printStackTrace();
                          }
                          return null;
                     }
                }
                • 5. Re: JNDI, Active Directory and Authentication (part 1)
                  843793
                  Still looking for help on the above issue.

                  Thanks.
                  • 6. Re: JNDI, Active Directory and Authentication (part 1)
                    843793
                    Hello,
                    I know this link is almost 6 months old but I was hoping someone could help me.

                    I went through the exmaple that adler_steven had provided, in that we are assuming we have a linux host.

                    I have a Windows -XP box, and I would like to get that example working.

                    What are the things that I would need to do on the active directory end to enable kerberos, what do I need to do at the client end to user kerberos.

                    Please proivde help.

                    Thanks in advance
                    • 7. Re: JNDI, Active Directory and Authentication (part 1)
                      800477
                      If you have a Windows XP client that is joined to a Windows Active Directory domain, then Kerberos is automatically used. Other than joining the Windows XP box to the domain, nothing else ir required.

                      All of the information at the start of this thread is related to integrating non-Windows machines with Active Directory.

                      From a Java perspective, I believe that Sun have integrated the latest Sun Krb5Login module to use the native Windows keystore so you should not need to perform any magical incantations to get this to work, other than making sure your krb5.conf configuration is correct.

                      I haven't installed Java on my Windows boxes so I personally have never tried this.

                      You may find additional information on Kerberos in the Java security foum located at: http://forum.java.sun.com/forum.jspa?forumID=60&start=0
                      • 8. Re: JNDI, Active Directory and Authentication (part 1)
                        843793
                        Geez I'm frigged off with this. I've been at this for 4 hours now and have not had one single Active Directory connection. I've tried at least 6 bits of sample code and none have worked. My latest is:
                                  Hashtable ldapProperties = new Hashtable();
                                  ldapProperties.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
                                  ldapProperties.put(Context.PROVIDER_URL,"ldap://" + serverName + ":" + serverPort + "");
                                  ldapProperties.put(Context.SECURITY_AUTHENTICATION,"simple");
                                  ldapProperties.put(Context.SECURITY_PRINCIPAL,"uid=manager,ou=Lonely Planet,dc=lpint,dc=net");
                                  ldapProperties.put(Context.SECURITY_CREDENTIALS,"");
                                  
                                  try {
                                   // Create the initial directory context
                                       DirContext ldap = new InitialDirContext(ldapProperties);
                                       ldap.bind("uid=manager,ou=Lonely Planet,dc=lpint,dc=net", "");
                                 
                                       ldap.search("ou=Lonely Planet,dc=lpint,dc=net", "(&(objectclass=person)(samaccountname=derekc))", null);
                                 //log.debug((String) attrs.get("sn").get());
                                  
                                  ldap.close();
                         
                         
                                 } catch (NamingException e) {
                                e.printStackTrace();
                                 }          
                        Which produces:
                             [java] javax.naming.NamingException: [LDAP: error code 1 - 00000000: LdapErr: DSID-0C090627, comment: 
                        In order to perform this operation a successful bind must be completed on the connection., data 0, vece
                        Which is being generated by the search() method and is a totally useless message. Clearly the bind has worked or it would have thrown an exception.

                        I have no idea where to look next and quite frankly I'm about ready to put my users in my database because I don't have the time to waste on this. It should have taken less than an hour to do such a simple thing. It's now been half a day and it's still doesn't work.

                        I've looked at some of the mentioned doco and I don't have the time to waste wandering through hundreds of pages of vague waffle, trying to figure out which bits are applicable and how to put them together. I just need to authenticate a user. Period.

                        I'm using a tomcat system and getting users to logon with a userid and password. It should not be that hard.

                        Tomcat 5.5.9
                        Java 5
                        AD:

                        Message was edited by:
                        drekka
                        • 9. Re: JNDI, Active Directory and Authentication (part 1)
                          800477
                          1. Is your Active Directory domain really called dc=lpint,dc=net ? (Does it really have the dns domain name lpint.net ?)

                          2. Is there really an OU called Lonely Planet ?

                          3. The LDAP standard defines a user with null password, as an anonymous user. The default setting for AD in Windows Server 2003 does not permit anonymous access to the directory. And unless you have created your own object class (derived from InetOrgPerson or User) , with uid as the naming attribute, then there will not be an object named uid=manager.

                          That is why the AD error message states "In order to perform this operation a successful bind must be completed on the connection"

                          With simple auth you can use three forms of user credentials:
                          a. Distinguished Name
                          eg. cn=administrator,ou=users,dc=antipodes,dc=com
                          b. user principal name
                          eg. administrator@antipodes.com
                          c. NT style domain name
                          eg. ANTIPODES\administrator

                          Note, from a security perspective I would not recomend simple authentication as the credentials are sent in the clear, making it susceptible to eavesdropping attacks. Either protect the communications using TLS or SSL (refer to JNDI, Active Directory & Authentication (part 2) (SSL)
                          http://forum.java.sun.com/thread.jspa?threadID=581425&tstart=50 or use a more secure authentication mechanism such as Kerberos, which is what this post actually describes.

                          Also a simple google search reveals http://forum.java.sun.com/thread.jspa?threadID=672135&tstart=105 in which you will find a Tomcat configuration that someone has used successfully against AD.
                          • 10. Re: JNDI, Active Directory and Authentication (part 1)
                            843793
                            Ok, maybe this makes me a complete newbie, but if I were writing a Swing-based application to do some LDAP stuff, I don't understand where I would put the jaas configuration file at. Does it go in the same directory as the .class files?
                            • 11. Re: JNDI, Active Directory and Authentication (part 1)
                              843793
                              Hi, i don't have experience with JAVA, and i try run this code and receive the error:

                              Exception in thread "main" java.lang.SecurityException: Unable to locate a login configuration
                              at com.sun.security.auth.login.ConfigFile.<init>(Unknown Source)
                              at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
                              at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
                              at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
                              at java.lang.reflect.Constructor.newInstance(Unknown Source)
                              at java.lang.Class.newInstance0(Unknown Source)
                              at java.lang.Class.newInstance(Unknown Source)
                              at javax.security.auth.login.Configuration$3.run(Unknown Source)
                              at java.security.AccessController.doPrivileged(Native Method)
                              at javax.security.auth.login.Configuration.getConfiguration(Unknown Source)
                              at javax.security.auth.login.LoginContext$1.run(Unknown Source)
                              at java.security.AccessController.doPrivileged(Native Method)
                              at javax.security.auth.login.LoginContext.init(Unknown Source)
                              at javax.security.auth.login.LoginContext.<init>(Unknown Source)
                              at br.com.faculdademodulo.SearchwithAuth.main(SearchwithAuth.java:19)
                              Caused by: java.io.IOException: Unable to locate a login configuration
                              at com.sun.security.auth.login.ConfigFile.init(Unknown Source)
                              ... 15 more


                              Help me please...


                              ps: i`m sorry, but my english is little bad. :-(


                              Tks,

                              Bruno.
                              • 12. Re: JNDI, Active Directory and Authentication (part 1)
                                843793
                                This may be a little late but, here goes anyway.
                                Place the Jaas.conf text file in the same folder as your .java files before you compile.
                                Use the following for your Swing-based app.

                                import java.net.URL;

                                URL url = getClass().getResource("Jaas.conf");

                                java.util.Properties p =new java.util.Properties(System.getProperties());
                                p.setProperty("java.security.auth.login.config", url.toExternalForm());

                                I hope this helps someone.
                                • 13. Re: JNDI, Active Directory and Authentication (part 1)
                                  843793
                                  Follow this link: http://java.sun.com/j2se/1.5.0/docs/guide/security/jgss/tutorials/Troubleshooting.html