Authentification Kerberos en JNDI

La documentation officiel java au sujet de l’authentification kerberos en JNDI est très bien faite. Mais je vais quand même me faire un petit mémo à ce sujet.

Le but du jeu est de mettre en place un processus d’authentification sécurisé entre une application java sur un poste en Windows XP et un controleur de domaine active directory sur Windows 2003. Le login et mot de passe utilisé sont ceux de l’utilisateur définis dans l’AD. Tout ceci en utilisant JNDI pour les requêtes LDAP.

Il existe plusieurs méthode d’authentification sécurisé SASL pour JNDI. Je vais ici parler uniquement de l’authentification en Kerberos v5 à travers la GSSAPI.

1 / Connaitre les protocoles accepter par le serveur.

Dans un premier temps, il faut être sur que le serveur accepte le GSSAPI.

Une requete LDAP permet d’apporter la réponse (remplacer localhost par l’ip ou le nom de domaine de votre serveur) :

import javax.naming.*;
import javax.naming.directory.*;

import java.util.Hashtable;

/**
 * Demonstrates how to discover a server's supported SASL mechanisms.
 *
 * usage: java ServerSasl
 */
class ServerSasl {
    public static void main(String[] args) {

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

	    // Read supportedSASLMechanisms from root DSE
	    Attributes attrs = ctx.getAttributes(
		"ldap://localhost:389", new String[]{"supportedSASLMechanisms"});

	    System.out.println(attrs);

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

Le serveur devrait répondre quelque chose dans ce gout la :

{supportedsaslmechanisms=supportedSASLMechanisms: GSSAPI, GSS-SPNEGO, EXTERNAL, DIGEST-MD5}

Le support du Kerberos (GSSAPI) est donc confirmé.

2 / Configuration client kerberos

A présent, il faut configurer le poste client pour discuter avec le serveur Kerberos. Pour cela il faut créer le fichier de configuration de kerberos : krb5.ini. Sans ce fichier aucune transaction kerberos en LDAP ne peut être faite.

Créer le fichier dans la racine de Windows.

Exemple c:\WINNT\kerb5.ini

Il doit contenir au minimum ces informations : (remplacer « exemple » par vos informations)

[libdefaults]
default_realm = EXEMPLE.LAN
default_checksum = rsa-md5

[realms]
EXEMPLE.LAN = {
kdc = SRV01.EXEMPLE.LAN
}

[domain_realm]
.EXEMPLE.LAN = EXEMPLE.LAN

3 / Test d’authentification

Maintenant que le fichier de configuration est créé, il faut le tester. Si vous avez correctement configuré le PATH de votre poste client vous devez avoir accés aux commande kinit et klist si ce n’est pas le cas, ces binaires ce trouve dans le répertoire d’installation du JDK ou vous pouvez configurer votre path une bonne fois pour toute en suivant ce tuto.

Il faut dans un premier temps initialiser la connexion avec kinit.
Dans l’interpreteur de commande windows rentrer kinit :

C:\Documents and Settings\artiflo>kinit

Le mot de passe pour l’utilisateur courant est demandé :

Password for artiflo@EXEMPLE.LAN:

Si tout c’est bien passé un message d’indication est renvoyé :

New ticket is stored in cache file C:\Documents and Settings\artiflo\krb5cc_artiflo

On peut vérifier a présent que le ticket est bien arrivé en utilisant klist :

C:\Documents and Settings\artiflo>klist

Ce qui devrait donner ceci :

Credentials cache: C:\Documents and Settings\artiflo\krb5cc_artiflo

Default principal:artiflo@EXEMPLE.LAN, 1 entry found.

[1]  Service Principal:  krbtgt/EXEMPLE.LAN@EXEMPLE.LAN
Valid starting:  Aug 11,  2009 14:49
Expires:         Aug 12,  2009 00:49

Tout c’est bien passer on peut retourner sur eclipse.

4 / L’Autentification

A présent je vais réutiliser les exemples fournis par java. Pour cela j’ai besoin de ces 3 fichiers.

1 / Le fichier de configuration : gsseg_jaas.conf

GssExample { com.sun.security.auth.module.Krb5LoginModule required client=TRUE;};

2 / Le dialogue : SampleCallbackHandler.java

import javax.security.auth.callback.*;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * Demonstrates how to write a callback handler for use with SASL.
 * Used with UseCallback.java, GssExample.java, and Mutual.java.
 *
 * Standalone test: java SampleCallbackHandler
 */
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(getInput(cb.getPrompt()));

		} else if (callbacks[i] instanceof PasswordCallback) {
		    PasswordCallback cb = (PasswordCallback)callbacks[i];

		    String pw = getInput(cb.getPrompt());
		    char[] passwd = new char[pw.length()];
		    pw.getChars(0, passwd.length, passwd, 0);

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

    /**
     * A reader from Standard Input. In real world apps, this would
     * typically be a TextComponent or similar widget.
     */
    private String getInput(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);
    }
}

3 / La création du context JNDI : GssExample.java

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

import java.util.Hashtable;

/**
 * Demonstrates how to create an initial context to an LDAP server
 * using "GSSAPI" SASL authentication (Kerberos v5).
 * Requires J2SE 1.4, or JNDI 1.2 with ldapbp.jar, JAAS, JCE, an RFC 2853
 * compliant implementation of J-GSS and a Kerberos v5 implementation.
 * Uses SampleCallbackHandler.
 *
 * usage: java
 *    -Djava.security.auth.login.config=gssapi_jaas.conf \
 *    -Djava.security.krb5.conf=krb5.conf \
 *      GssExample [qop [dn]]
 *
 * The first property indicates which JAAS login module the application needs
 * to use; the second property is for configuration of the Kerberos subsystem.
 *
 * 'qop' is a comma separated list of tokens, each of which is one of
 * auth, auth-int, or auth-conf. If none is supplied, the default is 'auth'.
 */
class GssExample {

    public static void main(String[] args) {

	// 1. Log in (to Kerberos)
	LoginContext lc = null;
	try {
	    lc = new LoginContext(GssExample.class.getName(),
		new SampleCallbackHandler());

	    // Attempt authentication
	    // You might want to do this in a "for" loop to give
	    // user more than one chance to enter correct username/password
	    lc.login();

	} catch (LoginException le) {
	    System.err.println("Authentication attempt failed" + le);
	    System.exit(-1);
	}

	// 2. Perform JNDI work as logged in subject
	Subject.doAs(lc.getSubject(), new JndiAction(args));
    }
}

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

    public JndiAction(String[] origArgs) {
	this.args = (String[])origArgs.clone();
    }

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

    private static void performJndiOperation(String[] args) {
	String dn;

	// 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://SRV01.EXEMPLE.LAN:389");

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

	// Optional first argument is comma-separated list of auth, auth-int,
	// auth-conf
	if (args.length > 0) {
	    env.put("javax.security.sasl.qop", args[0]);
	    dn = args[1];
	} else {
	    dn = "";
	}

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

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

	    // do something useful with ctx

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

Avec eclipse il ne reste plus qu’a rajouter quelques argument dans VM arguments  avant la compilation :

-Djava.security.auth.login.config=C:\Workbench\GSS\src\GSS1\gsseg_jaas.conf
-Djava.security.krb5.conf=C:\WINNT\krb5.ini

Compiler. Indiquez votre nom d’utilisateur, puis votre mot de passe. Fini.

Par défaut la requête renverra tous les arguments. Mais vous pouvez par la suite créer ce que vous voulez comme requete, comme par exemple une requete qui cherche à renvoyer tous les groupes d’un USER :

// Create the default search controls
SearchControls ctls = new SearchControls();
String filter = "(&(member=CN=Arti flo,OU=ou_DLW_USER,OU=ou_DLW,DC=EXEMPLE,DC=LAN)(objectclass=group)))";

// Search for objects using the filter
NamingEnumeration answers = ctx.search("OU=ou_DLW_USER,OU=ou_DLW,DC=EXEMPLE,DC=LAN",filter, ctls);
while (answers.hasMore()) {
       SearchResult answer = answers.nextElement();
       System.out.println("> " + answer.getNameInNamespace());
}

Je dédicace mon premier billet java à mon bon lolo 😉