Как получить билет службы Kerberos через GSS-API? - PullRequest
14 голосов
/ 16 декабря 2008

Кто-нибудь знает, как получить билет службы из Центра распространения ключей (KDC) с использованием Java GSS-API?

У меня есть приложение-толстый клиент, которое сначала проходит аутентификацию через JAAS, используя Krb5LoginModule для извлечения TGT из кэша билетов (справочная информация: Windows, например, использует реализацию kerberos и сохраняет билет на выдачу билетов в безопасной области памяти). Из LoginManager я получаю объект Subject, который содержит TGT. Теперь я надеялся, что когда я создам конкретный объект GSSCredential для моей службы, билет службы будет также добавлен в личные учетные данные субъекта (я читал об этом где-то в Интернете). Поэтому я попробовал следующее:

// 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");
    }

});

К сожалению, я получаю GSSException: действительные учетные данные не предоставлены (уровень механизма: не удалось найти любой Kerberos tgt).

Ответы [ 2 ]

16 голосов
/ 18 декабря 2008

Мое понимание получения сервисного билета было неверным. Мне не нужно получать учетные данные от службы - это невозможно на клиенте, потому что клиент на самом деле не имеет TGT для сервера и, следовательно, не имеет прав на получение учетных данных службы. Чего здесь не хватает, так это создать новый GSSContext и инициализировать его. Возвращаемое значение из этого метода содержит билет службы, если я правильно понял. Вот пример рабочего кода. Он должен быть запущен в привилегированном действии от имени зарегистрированного субъекта:

    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();

Содержимое outToken содержит служебный билет. Однако это не тот способ, которым GSS-API предназначался для использования. Его целью было скрыть эти детали в коде, поэтому лучше установить GSSContext, используя GSS-API с обеих сторон. В противном случае вы действительно должны знать, что вы делаете из-за потенциальных дыр в безопасности. Для получения дополнительной информации прочитайте учебник Sun SSO с Kerberos более внимательно, чем я.

EDIT: Просто забыл, что я использую Windows XP с SP2. В этой версии Windows есть новая «функция», которая запрещает использование TGT в оперативной памяти Windows. Вы должны отредактировать реестр, чтобы разрешить это. Для получения дополнительной информации загляните в раздел Устранение неполадок JGSS на случай, если у вас возникнет «KrbException: KDC не поддерживает тип шифрования (14)», как я.

9 голосов
/ 18 октября 2012

У меня было много проблем с использованием этого кода, но у меня есть хотя бы решение. Я размещаю это здесь, возможно, это поможет некоторым из вас ...

/**
 * 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>MuelleHA@MYFIRM.COM</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);
    }
}
...