How to obtain and authenticate Kerberos and SPNEGO tokens with JGSS

When working on solutions using Kerberos/SPENGO to achieve user authentication, one side of the equation is for the client to obtain Kerberos/SPNEGO token from KDC. The another side is for the server (also called service provider) to authenticate the token presented by the client.

Probably the most familiar client application is web browsers, such as Misrosoft Internet Explorer (IE) and Firefox. Both could obtain SPNEGO token from KDC on the login user’s behalf and send it to a web server when negotiation authentication is triggered (when the browser receives HTTP request header, authentication : negatiate). As for the server side implementation, mod_auth_kerb may be one of the most discussed solutions dealing with SPNEGO token.

In order to easily verify SPNEGO-based user authentication solutions, many times I felt the need to get SPNEGO/Kerberos tokens created for the clients and then verified by the servers. Here are two Java programs to address the need, TokenCreation and TokenConsumption.

The setup

You may refer to Kerberos setup for ways to get kerberso configured properly. The configuration file, bcsLogin.conf used by the code is as following:

/**
 * Login Configuration for JAAS.
 */

com.sun.security.jgss.initiate {
  com.sun.security.auth.module.Krb5LoginModule required
  	debug=true
	useTicketCache=true
	principal="mypersonal";
};

com.sun.security.jgss.accept {
  com.sun.security.auth.module.Krb5LoginModule required storeKey=true
  	debug=true
  	useKeyTab=true
  	storeKey=true
  	keyTab=mykeytab.keytab
  	principal="HTTP/myserver.mycorp.com"
	isInitiator=false;
};

The usage of the program

The usage of the two program is very simple. TokenCreation would create either Kerberos or SPNEGO token for the user. The token will be also output in Base64 encoded format. TokenConsumption will take the token created by TokenCreation and authenticate to resolve who the client is.

TokenCreation

import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;

import sun.misc.BASE64Encoder;
import org.apache.commons.codec.binary.Base64; 

public class TokenCreation {

    public static GSSCredential createCredential(Oid mechOid, String userName)
    {
        System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
        System.setProperty("java.security.auth.login.config", "bcsLogin.conf");

        GSSCredential clientGssCreds = null;
        try {
            GSSManager manager = GSSManager.getInstance();

            GSSName gssUserName = manager.createName(
                    userName,
                    GSSName.NT_USER_NAME,
                    mechOid);

            clientGssCreds = manager.createCredential(
                    gssUserName.canonicalize(mechOid),
                    GSSCredential.INDEFINITE_LIFETIME,
                    mechOid,
                    GSSCredential.INITIATE_ONLY);

        } catch (GSSException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return clientGssCreds;
    }

    /**
     *
     * @param sUserName
     * @param sServerSpn
     * @param sTokenType
     * @return
     */
    public static void createToken(String sUserName, String sServerSpn, String sTokenType)
    {
        try {
            Oid mechOid = null;
            if (sTokenType.compareTo("krb5") == 0) {
                mechOid = new Oid("1.2.840.113554.1.2.2");
            } else {
                if (sTokenType.compareTo("spnego") == 0) {
                    mechOid = new Oid("1.3.6.1.5.5.2");
                } else {
                    System.out.println("Token type [" + sTokenType + "] was not supported.");
                    System.out.println("Usage: java TokenCreation krb5 | spnego");
                }
            }
            if (mechOid != null) {
                GSSCredential gsscredential = createCredential(mechOid, sUserName);

                if (gsscredential != null) {
                    byte[] token = createToken(gsscredential, sServerSpn, mechOid);

                    if (token != null) {
                        outputToken(token, sTokenType);
                    }
                }
            }

        } catch (GSSException e) {
            e.printStackTrace();
        }

    }

    public static byte[] createToken(GSSCredential clientGssCreds,
            String sServerSpn, Oid mechOid)
    {
        byte[] token = new byte[0];
        try {
            GSSManager manager = GSSManager.getInstance();

            // create target server SPN
            GSSName gssServerName = manager.createName(
                    sServerSpn,
                    GSSName.NT_USER_NAME);

            GSSContext clientContext = manager.createContext(
                    gssServerName.canonicalize(mechOid),
                    mechOid,
                    clientGssCreds,
                    GSSContext.DEFAULT_LIFETIME);

            // optional enable GSS credential delegation
            clientContext.requestCredDeleg(true);

            // create a SPNEGO token for the target server
            token = clientContext.initSecContext(token, 0, token.length);

        } catch (GSSException e) {
            e.printStackTrace();
        }
        return token;
    }

    public static void outputToken(byte[] token, String sType)
    {
        if (token != null) {
            System.out.println("Token Length = " + token.length);

            Hexdump.hexdump(System.out, token, 0, token.length);
            FileUtil.writeByte2File(token, sType + "_token.txt");

            BASE64Encoder encoder = new BASE64Encoder();
            String encodedToken = encoder.encode( token);
            System.out.println("Token Base64 = \n" + encodedToken);

            Base64 base64 = new Base64(0);
            String encodedToken2 = base64.encodeToString(token);
            FileUtil.writeByte2File(encodedToken2.getBytes(), sType + "_token_64.txt");
        }
    }

    private static final String getHexBytes(byte[] bytes, int pos, int len)
    {
        StringBuffer sb = new StringBuffer();
        for (int i = pos; i >4) & 0x0f;
            int b2 = bytes[i] & 0x0f;

            sb.append(Integer.toHexString(b1));
            sb.append(Integer.toHexString(b2));
            sb.append(' ');
        }
        return sb.toString();
        }

        private static final String getHexBytes(byte[] bytes) {
        return getHexBytes(bytes, 0, bytes.length);
    }

    /**
     * @param args
     */
    public static void main(String[] args)
    {
        String sServerSpn = "HTTP/myserver.mycorp.com";

        if (args.length < 1) {
            System.out.println("Usage: java TokenCreation krb5 | spnego");
        } else {
            if (args[0].compareTo("krb5") == 0 || args[0].compareTo("spnego") == 0) {
                TokenCreation.createToken("mypersonal", sServerSpn, args[0]);
                System.out.println("\n\nToken file [" + args[0] + "_token.txt] was created");
            } else {
                System.out.println("Usage: java TokenCreation krb5 | spnego");
            }
        }
    }

}

TokenConsumption

import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;

import org.apache.commons.codec.binary.Base64; 

/**
 * @author Allen
 *
 */
public class TokenConsumption {

    public static void consumeToken(byte[] token, String sTokenType)
    {
        try {
            Oid mechOid = null;
            if (sTokenType.compareTo("krb5") == 0) {
                mechOid = new Oid("1.2.840.113554.1.2.2");
            } else {
                if (sTokenType.compareTo("spnego") == 0) {
                    mechOid = new Oid("1.3.6.1.5.5.2");
                } else {
                    System.out.println("Token type [" + sTokenType + "] was not supported.");
                    System.out.println("Usage: java TokenCreation krb5 | spnego");
                }
            }
            if (mechOid != null) {
                consumeToken(token, mechOid);
            }

        } catch (GSSException e) {
            e.printStackTrace();
        }

    }

    public static void consumeToken(byte[] token, Oid mechOid)
    {
        String endpointSPN = null;

        System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
        System.setProperty("java.security.auth.login.config", "bcsLogin.conf");

        try {
            GSSManager manager = GSSManager.getInstance();

            //first obtain it's own credentials...
            GSSCredential myCred = manager.createCredential(null,
                  GSSCredential.DEFAULT_LIFETIME,
                  mechOid,
                  GSSCredential.ACCEPT_ONLY);

            //...and create a context for this credentials...
            GSSContext context = manager.createContext(myCred);

            //...then use that context to authenticate the calling peer by reading his token
            byte[] tokenForPeer = context.acceptSecContext(token, 0, token.length);

            if (!context.isEstablished()) {
              System.out.println("Context was not established!");
              return;
          }

          if (tokenForPeer != null) {
              System.out.println("there is a token to send back to the peer, but I leave this out for now");
              Hexdump.hexdump(System.out, tokenForPeer, 0, tokenForPeer.length);
          }

          //...then obtain information from the context
          System.out.println("Client principal is " + context.getSrcName());
          System.out.println("Server principal is " + context.getTargName());

          if (context.getCredDelegState()) {
              System.out.println("The token is delegatable.");
          } else {
              System.out.println("The token is NOT delegatable");
          }

        } catch (GSSException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        if ( args.length < 1 ||
                ( args[0].compareTo("krb5") != 0 && args[0].compareTo("spnego") != 0) ) {
            System.out.println("Usage: java TokenConsumption krb5 | spnego [64]");
        } else {
            if (args.length == 2) {
                if (args[1].compareTo("64") == 0) {
                    System.out.println("\n\n *** Token file [" + args[0] + "_token_64.txt] will be consumed *** \n\n");
                    byte[] token64 = FileUtil.readByteFromFile(args[0] + "_token_64.txt");
                    byte[] token = Base64.decodeBase64(token64);
                    TokenConsumption.consumeToken(token, args[0]);
                }
            } else {
                // process normal token from the file
                System.out.println("\n\n *** Token file [" + args[0] + "_token.txt] will be consumed *** \n\n");
                TokenConsumption.consumeToken(FileUtil.readByteFromFile(args[0] + "_token.txt"), args[0]);
            }
        }
    }
    }
}

FileUtil.java

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class FileUtil {
    public static void writeByte2File(byte bytes[], String strFilePath)
    {
        try {
            FileOutputStream fos = new FileOutputStream(strFilePath);
            fos.write(bytes);
            fos.close();
        } catch (FileNotFoundException ex) {
            System.out.println("FileNotFoundException : " + ex);
        } catch (IOException ioe) {
            System.out.println("IOException : " + ioe);
        }
    }

    public static byte[] readByteFromFile(String strFilePath)
    {
        byte[] bytes = null;
        try {
           File file = new File(strFilePath);
           InputStream is = new FileInputStream(file);
           DataInputStream dis = new DataInputStream(is);
           long length = file.length();
           if (length > Integer.MAX_VALUE) {
              throw new IOException("File is too large");
           } else {
              bytes = new byte[(int)length];
              int offset = 0;
              int numRead = 0;
              while (offset = 0) {
                 offset += numRead;
              }
              if (offset < bytes.length) {
                 throw new IOException("Could not completely read file "+file.getName());
              }
              dis.close();
              is.close();
           }
        } catch (Exception e) {
           e.printStackTrace();
        }
        return bytes;
    }
}

Hexdump.java

Hexdump from Souce code for Hexdump.java was also used in the programs with a bit change to remove the package definition to make it simpler to test.

Codec from Apache commons was used to encode and decode the Base64 string.

References

The Jave Monkey Client/Server Hello World in Kerberos, Java and GSS!
Creating SPNEGO tokens

Advertisement

Tags: , , ,

2 Responses to “How to obtain and authenticate Kerberos and SPNEGO tokens with JGSS”

  1. glennquagmire Says:

    sTokenType.compareTo(“krb5″) == 0
    can be written as
    sTokenType.equals(“krb5″)

  2. Allen Says:

    Thanks for pointing it out.
    Looks like I really need grab a good Java book during the holidays. :)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.