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[i];
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.