LDAP and JNDI

Today, instead of just dealing with people and papers, I actually worked on something a bit more interesting. I needed to allow users to change the passwords of their Active Directory accounts using a web page. Being a Java zealot, the solution I chose was JNDI which made things pretty much straightforward.

Of course it wouldn’t have been fun without problems and I encountered two, albeit minor, hitches with LDAP and Active Directory. LDAP is supposedly easy but it never ceases to give me just enough trouble to roundly irritate me. Active Directory only allows modification of passwords via a secure connection, either TLS or SSL. The latter is actually a good thing but I had to do a few extra steps. Besides, like I said, they were minor hitches.

So how to do it?

First, install Certificate Services on your Active Directory server[1]. Just follow the instructions and a certificate will be created in file servername_server.crt in the root directory. Copy this file.

Second, you need to establish trust between your JNDI application and your Active Directory server. Add the server’s certificate to your keystore:

keytool -import -alias server -file “servername_server.crt” -keystore “keystorefile

Third, write the code. To reset the password of users, I used this code snippet[2]:

public static void setPassword(String adminName, String adminPassword, String userName, String newPassword) {

	// kludge to convert username in "[email protected]" format to "firstname lastname" format
	StringTokenizer strtok = new StringTokenizer(userName,".@");
	String firstName = strtok.nextToken();
	String lastName = strtok.nextToken();
	userName = "CN="+firstName+" "+lastName+",OU=Users,OU=ou,DC=domain,DC=local";

	//Access the keystore, this is where the Root CA public key cert was installed
	//Could also do this via command line java -Djavax.net.ssl.trustStore....
	String keystore = "C:/Program Files/Java/jdk1.6.0_01/jre/lib/security/cacerts";
	System.setProperty("javax.net.ssl.trustStore",keystore);

	Hashtable env = new Hashtable();

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

	//set security credentials, note using simple cleartext authentication
	env.put(Context.SECURITY_AUTHENTICATION,"simple");
	env.put(Context.SECURITY_PRINCIPAL,adminName);
	env.put(Context.SECURITY_CREDENTIALS,adminPassword);

	//set url of my domain controller
	env.put(Context.PROVIDER_URL,"ldap://server:389");

	try {

		// Create the initial directory context
		LdapContext ctx = new InitialLdapContext(env,null);

		//Secure the session with TLS
		StartTlsResponse tls = (StartTlsResponse)ctx.extendedOperation(new StartTlsRequest());
		tls.negotiate();

		//set password is a ldap modfy operation
		ModificationItem[] mods = new ModificationItem[1];

		//Replace the "unicdodePwd" attribute with a new value
		//Password must be both Unicode and a quoted string
		String newQuotedPassword = "\"" + newPassword + "\"";
		byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");

		mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword));

		// Perform the update
		ctx.modifyAttributes(userName, mods);

		System.out.println("Reset Password for: " + userName);
		tls.close();
		ctx.close();

	} catch (NamingException e) {
		System.err.println("Error resetting password: " + e);
	} catch (UnsupportedEncodingException e) {
		System.err.println("Error encoding password: " + e);
	} catch (IOException e) {
		System.err.println("Error with TLS: " + e);
	}
}

To authenticate against Active Directory/LDAP, I used this code snippet[3]:

public static boolean checkLogin(String userName, String password) throws Exception {
	// username in "[email protected]" format

	boolean found = false;

	Hashtable env = new Hashtable();

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

	//set security credentials, note using simple cleartext authentication
	env.put(Context.SECURITY_AUTHENTICATION, "simple");
	env.put(Context.SECURITY_PRINCIPAL, userName);
	env.put(Context.SECURITY_CREDENTIALS, password);

	//set url of my domain controller
	env.put(Context.PROVIDER_URL,"ldap://server:389");

	try {
		DirContext ctx = new InitialDirContext(env);
		found = true;
	} catch (NamingException e) {
	} catch (Exception e) {
		System.err.println("Error: " + e);
		throw e;
	}
	return found;

Finally, package the whole thing into a JavaBean and set up your JSPs.

That’s it!

References:
[1] Installing and Configuring a Windows Server 2003 Stand-alone Certification Authority
[2] JNDI, Active Directory & Authentication
[3] Active Directory Authentication Problem