Archive for the ‘SASL’ Category

Utilize SASL GSSAPI mechanism to achieve Single Sign-on (SSO) for JNDI/LDAP client

April 10, 2010

I was having a bit hard time to pick up the title for this post, since not wanting to create the impression that it would talk about using LDAP server to achieve SSO for any applications. This post is actually about implementing SSO for a client application that happens to connect to any LDAP server (specifically servers with LDAPv3 capability).

Over years, I have the need to consult the corporate LDAP server (running from Windows 2003 AD) to get user information, such as email address, status, group membership. I was also granted the permission to update a portion of our LDAP structure to support LDAP authentication for Documentum users. To alleviate my burden to query LDAP server for so many users, I have implemented a Java utility to search and modify the portion of LDAP server. Not surprisingly, I had used simple binding for years. Since that’s the only binding option I knew at the very beginning and it had worked for me quite well.

During the procedure to work on SSO solution for Documentum Webtop and Kerberos setup, the word SASL popped up many times during my searching and cought my eyes. From then I had been thinking that since LDAPv3 supports SASL, there must be a way to use it to talk to LDAP server. Given the fact that SASL GSSAPI mechanism is actually using Kerberos, it was very likely that SSO could be achieved for my LDAP utility. Here is the journey for me to reach that goal.

Simple binding

With the help of JNDI/LDAP service provider, binding to LDAP server is very straitforward. Here is the way to do simple binding:

// Set up the environment for creating the initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

// Must use fully qualified domain name of the LDAP server within the URL
env.put(Context.PROVIDER_URL, "ldap://FQDN:389);

// Authenticate as mypersonal account using the password
env.put(Context.SECURITY_PRINCIPAL, "CN=mypersonal,DC=corpnet,DC=mycorp,DC=com");
env.put(Context.SECURITY_CREDENTIALS, "mypassword");

// Specify simple binding is used
env.put(Context.SECURITY_AUTHENTICATION, "simple");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

When using above ‘simple’ binding, the credential is passed through network as plain text. With the following line adding to the environment configuration, you could see the password is just passed around for the authentication.

env.put("com.sun.jndi.ldap.trace.ber", System.out);

Fortunately, we have SASL supported by many commonly used LDAP servers, which provides a security layer for authentication to LDAP server.

DIGEST-MD5 SASL mechanism

SASL authentication supports several mechanisms, such as DIGEST-MD5, EXTERNAL, and GSSAPI. Many literatures talked about how to use DIGEST-MD5 mechanism to bind to LDAP server. A discussion topic started by SteveHB from Why DIGEST-MD5 Authentication Does Work covers many aspects of this mechanism. Interestingly, all the three formats of security pricipal listed by SteveHB for Windows 2003 AD worked for me under my environment with both JDK1.6.0_18 and JDK1.5.0_12. Maybe the AD configuration contributed to the difference. In order to use SASL DIGEST-MD5, the only change to the above simple binding example is to use the following line for Context.SECURITY_AUTHENTICATION (both successful and failed principal formats are also listed):

// Specify SASL DIGEST5-MD authentication
env.put(Context.SECURITY_AUTHENTICATION, "DIGEST5-MD");

// Principal formats successful
env.put(Context.SECURITY_PRINCIPAL, "mypersonal");
env.put(Context.SECURITY_PRINCIPAL, "FULL_DOMAIN_NAME\\mypersonal");
env.put(Context.SECURITY_PRINCIPAL, "mypersonal@REALM_NAME");

// Principal format failed for me
// env.put(Context.SECURITY_PRINCIPAL, "DN for mypersonal");

You may refer to SteveHB’s post for other formats to provide Context.SECURITY_PRINCIPAL for SunOne Directory Server or AD2000.

GSS-API SASL mechanism

The GSS-API SASL mechanism was actually retrofitted to mean only Kerberos v5, although it was originally intended to support any GSS-API implementation. Most of the configuration here is similar to that presented in my previous post. For the sake of completeness, all the necessary configuration is listed below.

krb5.ini file

File krb5.ini is saved under C:\Windows, and it contains the following:

[libdefaults]
        default_realm = CORPNET.MYCORP.COM
        default_tkt_enctypes = rc4-hmac
        default_tgs_enctypes = rc4-hmac
[realms]
        CORPNET.MYCORP.COM  = {
               kdc = myonedomaincontroller.corpnet.mycorp.com
               default_domain = CORPNET.MYCORP.COM
}

[domain_realm]
       .CORPNET.MYCORP.COM = CORPNET.MYCORP.COM

gssapi-jaas.conf

The content of the file is as following:

SaslSso2Ldap {
  com.sun.security.auth.module.Krb5LoginModule required 
  	client=TRUE
  //	debug=true
  	useTicketCache=true
  	principal="mypersonal";
};

It should be put under the same location as the JNDI/LDAP client class file.

import javax.naming.*;
import javax.naming.directory.*;
import javax.security.auth.login.*;
import javax.security.auth.Subject;

import java.util.Hashtable;

class SaslSso2Ldap {

    public static void main(String[] args) {

        LoginContext context = null;
        try {
            // 1. Log in (to Kerberos)
            context = new LoginContext(SaslSso2Ldap.class.getName());
            context.login();

            // 2. Perform JNDI work as logged in Subject
            Subject.doAs(context.getSubject(), new JndiAction());
	
            // 3. Logout the Subject
            context.logout();
			
        } catch (LoginException e) {
            e.printStackTrace();
        }
    }
}

/**
 * The application must supply a PrivilegedAction that is to be run
 * inside a Subject.doAs() or Subject.doAsPrivileged().
 */
class JndiAction implements java.security.PrivilegedAction {

    public Object run() {
        performJndiOperation(args);
        return null;
    }

    private static void performJndiOperation() {
        // Set up environment for creating initial context
        Hashtable env = new Hashtable(11);

        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");

        // Must use fully qualified hostname
        env.put(Context.PROVIDER_URL, "ldap://myldapserver.corpnet.mycorp.com:389");

        // Request the use of the "GSSAPI" SASL mechanism
        // Authenticate by using already established Kerberos credentials
        env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");

        env.put("com.sun.jndi.ldap.trace.ber", System.out);


        try {
            /* Create initial context */
            DirContext ctx = new InitialDirContext(env);

            System.out.println(ctx.getAttributes(""));

            // do something useful with ctx

            // Close the context when we're done
            ctx.close();
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

You may notice that there are probably more comments than the code itself. It’s just that simple to achieve SSO for JNDI/LDAP client. The following command is used to test the program:

java -Djava.security.auth.login.config=gssapi_jaas.conf SaslSso2Ldap

Something could go wrong

– Make sure the LDAP server you are testing against supports SASL. Since Windows AD does not support anonymous binding, you would need some other means to connect to AD to get the information. The following was obtained using LDAP Browser.

– The value provided for Context.PROVIDER_URL has to be a welformed URL, and the server host must be the fully qualified domain name. The reason for this is that the Java Service provider will derive the service principal from the URL (not surprisingly, the service name is ldap). You may refer to How should I configure my DNS for Kerberos? for further explanation.

– Make sure the LDAP server is known to KDC and SPN associated with it should be resolvable from the URL of the LDAP server.

With the above configuration, my LDAP client never needs me to provide my login credential. In addition to increase the security of my connection to AD, I do not need worry about the password change periodically for my utility any more.

References
JNDI/LDAP Service Provider
GSS-API/Kerberos v5 Authentication
SASL Authentication
JNDI/LDAP SASL Examples
How should I configure my DNS for Kerberos?
What is SASL?