14
votes

Does anyone know how to get a service ticket from the Key Distribution Center (KDC) using the Java GSS-API?

I have a thick-client-application that first authenticates via JAAS using the Krb5LoginModule to fetch the TGT from the ticket cache (background: Windows e.g. uses a kerberos implementation and stores the ticket granting ticket in a secure memory area). From the LoginManager I get the Subject object which contains the TGT. Now I hoped when I create a specific GSSCredential object for my service, the service ticket will be put into the Subject's private credentials as well (I've read so somewhere in the web). So I have tried the following:

// Exception handling ommitted
LoginContext lc = new LoginContext("HelloEjbClient", new DialogCallbackHandler());
lc.login()
Subject.doAs(lc.getSubject(), new PrivilegedAction() {

    public Object run() {
        GSSManager manager = GSSManager.getInstance();
        GSSName clientName = manager.createName("clientUser", GSSName.NT_USER_NAME);
        GSSCredential clientCreds = manager.createCredential(clientName, 8 * 3600, createKerberosOid(), GSSCredential.INITIATE_ONLY);

        GSSName serverName = manager.createName("myService@localhost", GSSName.NT_HOSTBASED_SERVICE);
        manager.createCredential(serverName, GSSCredential.INDEFINITE_LIFETIME, createKerberosOid(), GSSCredential.INITIATE_ONLY);
        return null;
    }

    private Oid createKerberosOid() {
        return new Oid("1.2.840.113554.1.2.2");
    }

});

Unfortunately I get a GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt).

2

2 Answers

17
votes

My understanding of getting the service ticket was wrong. I do not need to get the credentials from the service - this is not possible on the client, because the client really doesn't have a TGT for the server and therefore doesn't have the rights to get the service credentials. What's just missing here is to create a new GSSContext and to initialize it. The return value from this method contains the service ticket, if I have understood that correctly. Here is a working code example. It must be run in a PrivilegedAction on behalf of a logged in subject:

    GSSManager manager = GSSManager.getInstance();
    GSSName clientName = manager.createName("clientUser", GSSName.NT_USER_NAME);
    GSSCredential clientCred = manager.createCredential(clientName,
                                                        8 * 3600,
                                                        createKerberosOid(),
                                                        GSSCredential.INITIATE_ONLY);

    GSSName serverName = manager.createName("http@server", GSSName.NT_HOSTBASED_SERVICE);

    GSSContext context = manager.createContext(serverName,
                                               createKerberosOid(),
                                               clientCred,
                                               GSSContext.DEFAULT_LIFETIME);
    context.requestMutualAuth(true);
    context.requestConf(false);
    context.requestInteg(true);

    byte[] outToken = context.initSecContext(new byte[0], 0, 0);
    System.out.println(new BASE64Encoder().encode(outToken));
    context.dispose();

The outToken contains then contains the Service Ticket. However this is not the way the GSS-API was meant to be used. Its goal was to hide those details to the code, so it is better to establish a GSSContext using the GSS-API on both sides. Otherwise you really should know what you are doing because of potential security holes. For more information read the Sun SSO tutorial with kerberos more carefully than I did.

EDIT: Just forgot that I am using Windows XP with SP2. There is a new "feature" in this version of Windows that disallows using the TGT in the Windows RAM. You have to edit the registry to allow this. For more information have a look at the JGSS Troubleshooting page topic in case you experience a "KrbException: KDC has no support for encryption type (14)" like I did.

9
votes

I had a lot of problems to use this code, but I have at least a solution. I post it here, perhaps it will help some of you...

/**
 * Tool to retrieve a kerberos ticket. This one will not be stored in the windows ticket cache.
 */
public final class KerberosTicketRetriever
{
    private final static Oid KERB_V5_OID;
    private final static Oid KRB5_PRINCIPAL_NAME_OID;

    static {
        try
        {
            KERB_V5_OID = new Oid("1.2.840.113554.1.2.2");
            KRB5_PRINCIPAL_NAME_OID = new Oid("1.2.840.113554.1.2.2.1");

        } catch (final GSSException ex)
        {
            throw new Error(ex);
        }
    }

    /**
     * Not to be instanciated
     */
    private KerberosTicketRetriever() {};

    /**
     *
     */
    private static class TicketCreatorAction implements PrivilegedAction
    {
        final String userPrincipal;
        final String applicationPrincipal;

        private StringBuffer outputBuffer;

        /**
         *
         * @param userPrincipal  p.ex. <tt>[email protected]</tt>
         * @param applicationPrincipal  p.ex. <tt>HTTP/webserver.myfirm.com</tt>
         */
        private TicketCreatorAction(final String userPrincipal, final String applicationPrincipal)
        {
            this.userPrincipal = userPrincipal;
            this.applicationPrincipal = applicationPrincipal;
        }

        private void setOutputBuffer(final StringBuffer newOutputBuffer)
        {
            outputBuffer = newOutputBuffer;
        }

        /**
         * Only calls {@link #createTicket()}
         * @return <tt>null</tt>
         */
        public Object run()
        {
            try
            {
                createTicket();
            }
            catch (final GSSException  ex)
            {
                throw new Error(ex);
            }

            return null;
        }

        /**
         *
         * @throws GSSException
         */
        private void createTicket () throws GSSException
        {
            final GSSManager manager = GSSManager.getInstance();
            final GSSName clientName = manager.createName(userPrincipal, KRB5_PRINCIPAL_NAME_OID);
            final GSSCredential clientCred = manager.createCredential(clientName,
                    8 * 3600,
                    KERB_V5_OID,
                    GSSCredential.INITIATE_ONLY);

            final GSSName serverName = manager.createName(applicationPrincipal, KRB5_PRINCIPAL_NAME_OID);

            final GSSContext context = manager.createContext(serverName,
                    KERB_V5_OID,
                    clientCred,
                    GSSContext.DEFAULT_LIFETIME);
            context.requestMutualAuth(true);
            context.requestConf(false);
            context.requestInteg(true);

            final byte[] outToken = context.initSecContext(new byte[0], 0, 0);

            if (outputBuffer !=null)
            {
                outputBuffer.append(String.format("Src Name: %s\n", context.getSrcName()));
                outputBuffer.append(String.format("Target  : %s\n", context.getTargName()));
                outputBuffer.append(new BASE64Encoder().encode(outToken));
                outputBuffer.append("\n");
            }

            context.dispose();
        }
    }

    /**
     *
     * @param realm p.ex. <tt>MYFIRM.COM</tt>
     * @param kdc p.ex. <tt>kerbserver.myfirm.com</tt>
     * @param applicationPrincipal   cf. {@link #TicketCreatorAction(String, String)}
     * @throws GSSException
     * @throws LoginException
     */
    static public String retrieveTicket(
            final String realm,
            final String kdc,
            final String applicationPrincipal)
    throws GSSException, LoginException
    {

        // create the jass-config-file
        final File jaasConfFile;
        try
        {
            jaasConfFile = File.createTempFile("jaas.conf", null);
            final PrintStream bos = new PrintStream(new FileOutputStream(jaasConfFile));
            bos.print(String.format(
                    "Krb5LoginContext { com.sun.security.auth.module.Krb5LoginModule required refreshKrb5Config=true useTicketCache=true debug=true ; };"
            ));
            bos.close();
            jaasConfFile.deleteOnExit();
        }
        catch (final IOException ex)
        {
            throw new IOError(ex);
        }

        // set the properties
        System.setProperty("java.security.krb5.realm", realm);
        System.setProperty("java.security.krb5.kdc", kdc);
        System.setProperty("java.security.auth.login.config",jaasConfFile.getAbsolutePath());

        // get the Subject(), i.e. the current user under Windows
        final Subject subject = new Subject();
        final LoginContext lc = new LoginContext("Krb5LoginContext", subject, new DialogCallbackHandler());
        lc.login();

        // extract our principal
        final Set<Principal> principalSet = subject.getPrincipals();
        if (principalSet.size() != 1)
            throw new AssertionError("No or several principals: " + principalSet);
        final Principal userPrincipal = principalSet.iterator().next();

        // now try to execute the SampleAction as the authenticated Subject
        // action.run() without doAsPrivileged leads to
        //   No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)
        final TicketCreatorAction action = new TicketCreatorAction(userPrincipal.getName(), applicationPrincipal);
        final StringBuffer outputBuffer = new StringBuffer();
        action.setOutputBuffer(outputBuffer);
        Subject.doAsPrivileged(lc.getSubject(), action, null);

        return outputBuffer.toString();
    }

    public static void main (final String args[]) throws Throwable
    {
        final String ticket = retrieveTicket("MYFIRM.COM", "kerbserver", "HTTP/webserver.myfirm.com");
        System.out.println(ticket);
    }
}