JDK-8022582 : Relax response flags checking in sun.security.krb5.KrbKdcRep.check.
  • Type: Enhancement
  • Component: security-libs
  • Sub-Component: org.ietf.jgss:krb5
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2013-08-07
  • Updated: 2016-10-13
  • Resolved: 2015-01-27
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u112Fixed 9 b49Fixed
Description
A DESCRIPTION OF THE REQUEST :
Currently a forwardable TGT is requested by setting:

  forwardable = true

in krb5.conf.  This works fine when requesting a TGT from Microsoft
Windows AD.  Unfortunately Windows does not honor that bit when
processing other requests for other services ... this causes:

KrbException: Message stream modified (41)
        at sun.security.krb5.KrbKdcRep.check(KrbKdcRep.java:80)

when gssContext.initSecContext requests a ticket from Microsoft Windows
AD for the middle tier service.

The intent of the enclosed program is to:

  obtain a forwardable TGT
  obtain a ticket for the middle tier service
  use the two tickets to produce a token to be sent to the middle tier service

Note that the ticket for the service need not be forwardable ... it's
only the TGT which needs to be forwardable.  If I comment out:

        for (int i = 1; i < 6; i++) {
            if (req.reqBody.kdcOptions.get(i) !=
                rep.encKDCRepPart.flags.get(i)) {
                throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
            }

from KrbKdcRep.java, then things work as expected.


JUSTIFICATION :
In some multi-tier configurations it's necessary to obtain a forwardable TGT to supply to a service
as part of an Kerberos AP token.  Currently the strict checking performed by KrbKdcRep.java
prevents this from working with a Windows 2008 R2 domain controller.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
It would improve interoperability with the Microsoft Windows AD if
KrbKdcRep.java skipped checking the forwardable bit on responses for
non TGT services.

Note .... I did work this issue with Microsoft support before considering
opening up a Java RFE.
ACTUAL -
If forwardable is set in krb5.conf, then the enclosed program fails with:

KrbException: Message stream modified (41)
        at sun.security.krb5.KrbKdcRep.check(KrbKdcRep.java:80)

when gssContext.initSecContext requests a non-TGT ticket.
Note if the code is changed so that gssContext.initSecContext requests
a TGT ticket, then things work.  Things also work if the check is commented
out of KrbKdcRep.java.

---------- BEGIN SOURCE ----------
/*
 * Sample servlet which uses a hard coded username / password
 * to authenticate the user and retrieve Kerberos credentials
 * which support delegation.
 */

import java.io.*;
import java.security.*;
import java.util.*;

import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.Subject;

import javax.security.auth.kerberos.KerberosPrincipal;

import java.sql.* ;

import org.ietf.jgss.*;

class KDT {

 static private String serviceName =  " sp@jwlab.feith.com " ;
 //static private String serviceName =  " krbtgt@FEITH.COM " ;

 static private String pricipalName = null;

static private class KUPCallbackHandler implements CallbackHandler {

 private String userName = null;
 private String password = null;

 public KUPCallbackHandler(String luserName, String lpassword) {
   userName = luserName;
   password = lpassword;
   }
 public void handle (Callback callbacks[]) throws IOException,
                                              UnsupportedCallbackException {

   for (int i = 0; i < callbacks.length; i++) {
     if (callbacks[i] instanceof NameCallback) {
       NameCallback nc = (NameCallback)callbacks[i];
       nc.setName (userName);
       }
     else if (callbacks[i] instanceof PasswordCallback) {
       PasswordCallback pc = (PasswordCallback)callbacks[i];
       pc.setPassword (password.toCharArray());
       }
     else {
       throw (new UnsupportedCallbackException(
         callbacks[i],  " Callback handler not supported " ));
       }
     }
   }
 }
 static private class RetreiveTicketAction implements PrivilegedExceptionAction
  {
    public byte[] run() {
        byte[] byteToken = null;
        GSSContext gssContext = null;
        try {
            Oid krb5Mechanism = new Oid( " 1.2.840.113554.1.2.2 " );
            GSSManager manager = GSSManager.getInstance();
            GSSName clientName = manager.createName(pricipalName, GSSName.NT_USER_NAME);
            System.out.println( " RetreiveTicketAction: createCredential: "  + pricipalName);
            GSSCredential clientCred = manager.createCredential(clientName,
                    GSSContext.DEFAULT_LIFETIME, krb5Mechanism,
                    GSSCredential.INITIATE_ONLY);

            System.out.println( " RetreiveTicketAction: createName: "  + serviceName);
            Oid krb5PrincipalNameType = new Oid( " 1.2.840.113554.1.2.2.1 " );
            GSSName serverName = manager.createName(serviceName,
                   GSSName.NT_HOSTBASED_SERVICE);

            gssContext = manager.createContext(serverName, krb5Mechanism,
                    clientCred, GSSContext.DEFAULT_LIFETIME);

//            gssContext.requestMutualAuth(true);
            gssContext.requestCredDeleg(true);

            byteToken = gssContext.initSecContext(new byte[0], 0, 0);

        } catch (GSSException e) {
            System.out.println( " Exception: "  + e);
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            if (gssContext != null) {
                try {
                    gssContext.dispose();
                } catch (GSSException e) {
                    System.out.println( " Exception: "  + e);
                    e.printStackTrace();
                }
            }
        }

        return byteToken;
    }
 }
 public static void main( String args[] )
 {

   /*
    * Authenticate the user.
    *
    * isInitiator should be set to true and useFirstPass, tryFirstPass,
    * storePass, and useTicketCache should be set to false in jaas.conf.
    */

   LoginContext lcontext = null;
   CallbackHandler loginHandler = new KUPCallbackHandler (args[0], args[1]);

   try {
       lcontext = new LoginContext( " KDT " , loginHandler);
       lcontext.login();
     } catch (LoginException e) {
       System.out.println( " login failed " );
       return;
     }

  /*
   * Note who the user is.
   */

  Subject lSubject = lcontext.getSubject();
  Set lPrincipals = lSubject.getPrincipals();
  Iterator lIt = lPrincipals.iterator();

  pricipalName = lIt.next().toString();

  try {
     PrivilegedExceptionAction action = new RetreiveTicketAction();

     Subject.doAs(lSubject, action);
    } catch (PrivilegedActionException e) {
     System.out.println( " Exception: "  + e);
     e.printStackTrace();
    }

  System.out.println( " Suceeded " );
 }
}


---------- END SOURCE ----------