javakerberosgssapi

How to use a cached Kerberos TGS ticket with GSS API in Java?


I am trying to get a Kerberos TGS ticket from the cache (without contacting the KDC). I create a TGT ticket using kinit and a TGS ticket, using kvno cifs/. To ensure I have cached the tickets, I use klist and get the following result:

Ticket cache: FILE:/tmp/krb5cc_0
Default principal: Administrator@PROXYREALM.TEST

Valid starting     Expires            Service principal
22/06/25 12:48:10  22/06/25 22:48:10  krbtgt/PROXYREALM.TEST@PROXYREALM.TEST
    renew until 29/06/25 12:48:06
22/06/25 12:49:02  22/06/25 22:48:10  cifs/DC1.proxyrealm.test@PROXYREALM.TEST
    renew until 29/06/25 12:48:06

But when I try to use the Java GSS API to load the tickets from the cache and use them. The only ticket found is the TGT:

Java config name: null
Native config name: /etc/krb5.conf
Loading config file from /etc/krb5.conf
Loading krb5 profile at /etc/krb5.conf
logging = {
}
libdefaults = {
default_realm = PROXYREALM.TEST
dns_lookup_kdc = false
dns_lookup_realm = false
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
}
realms = {
PROXYREALM.TEST = {
default_domain = PROXYREALM.TEST
kdc = https://kdcproxy.proxyrealm.test/KdcProxy
admin_server = kdcproxy.proxyrealm.test
http_anchors = FILE:/etc/ssl/certs/ca-certificates.crt
}
}
domain_realm = {
.PROXYREALM.TEST = PROXYREALM.TEST
PROXYREALM.TEST = PROXYREALM.TEST
.proxyrealm.test = PROXYREALM.TEST
proxyrealm.test = PROXYREALM.TEST
}
appdefaults = {
pam = {
debug = false
ticket_lifetime = 36000
renew_lifetime = 36000
forwardable = true
krb4_convert = false
}
}
Loaded from native config
>>>KinitOptions cache name is /tmp/krb5cc_0
>>>DEBUG <CCacheInputStream>  client principal is Administrator@PROXYREALM.TEST
>>>DEBUG <CCacheInputStream> server principal is krbtgt/PROXYREALM.TEST@PROXYREALM.TEST
>>>DEBUG <CCacheInputStream> key type: 18
>>>DEBUG <CCacheInputStream> auth time: Sun Jun 22 12:48:10 IDT 2025
>>>DEBUG <CCacheInputStream> start time: Sun Jun 22 12:48:10 IDT 2025
>>>DEBUG <CCacheInputStream> end time: Sun Jun 22 22:48:10 IDT 2025
>>>DEBUG <CCacheInputStream> renew_till time: Sun Jun 29 12:48:06 IDT 2025
>>> CCacheInputStream: readFlags()  FORWARDABLE; RENEWABLE; INITIAL; PRE_AUTH;
>>>DEBUG <CCacheInputStream>  client principal is Administrator@PROXYREALM.TEST
>>>DEBUG <CCacheInputStream> server principal is krb5_ccache_conf_data/pa_type/krbtgt/PROXYREALM.TEST\@PROXYREALM.TEST@X-CACHECONF:
>>>DEBUG <CCacheInputStream> key type: 0
>>>DEBUG <CCacheInputStream> auth time: Thu Jan 01 02:00:00 IST 1970
>>>DEBUG <CCacheInputStream> start time: null
>>>DEBUG <CCacheInputStream> end time: Thu Jan 01 02:00:00 IST 1970
>>>DEBUG <CCacheInputStream> renew_till time: null
>>> CCacheInputStream: readFlags() 
>>>DEBUG <CCacheInputStream>  client principal is Administrator@PROXYREALM.TEST
>>>DEBUG <CCacheInputStream> server principal is cifs/DC1.proxyrealm.test@PROXYREALM.TEST
>>>DEBUG <CCacheInputStream> key type: 18
>>>DEBUG <CCacheInputStream> auth time: Sun Jun 22 12:48:10 IDT 2025
>>>DEBUG <CCacheInputStream> start time: Sun Jun 22 12:49:02 IDT 2025
>>>DEBUG <CCacheInputStream> end time: Sun Jun 22 22:48:10 IDT 2025
>>>DEBUG <CCacheInputStream> renew_till time: Sun Jun 29 12:48:06 IDT 2025
>>> CCacheInputStream: readFlags()  FORWARDABLE; RENEWABLE; PRE_AUTH;
get normal credential
Found ticket for Administrator@PROXYREALM.TEST to go to krbtgt/PROXYREALM.TEST@PROXYREALM.TEST expiring on Sun Jun 22 22:48:10 IDT 2025
Entered Krb5Context.initSecContext with state=STATE_NEW
Service ticket not found in the subject

I tried using the following class to authenticate with the cached Kerberos tickets:

public class GSSKdcProxyClient {

  public static final Oid KRB5_MECH_OID;
    static {
        try {
            KRB5_MECH_OID = new Oid("1.2.840.113554.1.2.2"); // Kerberos V5 OID
        } catch (Exception e) {
            throw new RuntimeException("Failed to initialize Kerberos OID", e);
        }
    }
    
public static void main(String[] args) throws GSSException {
     System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
     System.setProperty("sun.security.krb5.debug", "true"); 
    try {
        
      GSSManager manager = GSSManager.getInstance();
      
       GSSName serverName = manager.createName("cifs/DC1.proxyrealm.test@PROXYREALM.TEST",GSSName.NT_HOSTBASED_SERVICE);
       // Acquire credentials and create context
       GSSContext context = manager.createContext(serverName,KRB5_MECH_OID, null, GSSContext.DEFAULT_LIFETIME);
       context.requestMutualAuth(false);
       context.initSecContext(new byte[0], 0, 0);
     
     if (context.isEstablished()) {
         System.out.println("GSS context established with: " + context.getSrcName());
     }
     context.dispose();
        

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

I Also tried a different name convention with:

GSSName serverName = manager.createName("DC1.proxyrealm.test",KRB5_PRINCIPAL_NT);

I tried to use many different configurations, but always got the same result: "Service ticket not found in the subject".


Solution

  • Eventually I used the workaround mentioned here: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8180144 If you manually create a TGT, using kinit <user_name> and then a TGS usingkvno cifs/<fQDN_of_the host>, the following method works, but only when I used Oracle JDK:

    private String getGSSwJAASServiceTicket()  {
        
        byte[] ticket = null;
        String encodedTicket = null;
        LoginContext loginContext = null;
        Subject mySubject =  null;
        
        try {
          TextCallbackHandler cbHandler = new TextCallbackHandler();
          loginContext = new LoginContext("wSOXClientGSSJAASLogin", cbHandler);
          loginContext.login();
          mySubject = loginContext.getSubject();
          
          // assumes 1 TGT and 1 SGT exist in credentials cache (added using python py-curl + GSS)
          // Demos that if SGTs are stored in the subject's privateCredentials, they can be accessed during context.initSecContext() below, without a call to the KDC.
          sun.security.krb5.internal.ccache.CredentialsCache ccache = sun.security.krb5.internal.ccache.CredentialsCache.getInstance("/tmp/krb5cc_9337");
          sun.security.krb5.internal.ccache.Credentials[] creds = ccache.getCredsList();    
          mySubject.getPrivateCredentials().add(sun.security.jgss.krb5.Krb5Util.credsToTicket(creds[0].setKrbCreds()));
          mySubject.getPrivateCredentials().add(sun.security.jgss.krb5.Krb5Util.credsToTicket(creds[1].setKrbCreds()));
        
          GSSManager manager = GSSManager.getInstance();
          
          GSSName serverName = manager.createName("HTTP/app-srv.acme.com@ACME.COM", null);
          Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
          
         
          ticket = Subject.doAs(mySubject, new PrivilegedAction<byte[]>(){
            public byte[] run(){
              try{
                
                System.setProperty("javax.security.auth.useSubjectCredsOnly","true");
                GSSContext context = manager.createContext(serverName,
                    krb5Oid,
                    null,
                    GSSContext.DEFAULT_LIFETIME);
                
                context.requestMutualAuth(false);
                context.requestConf(false);
                context.requestInteg(true);
                
                byte[] token = new byte[0];     
                return context.initSecContext(token, 0, token.length);
                
              }
              catch(Exception e){
                Log.log(Log.ERROR, e);
                throw new otms.util.OTMSRuntimeException("Start wSOXclient (privileged) failed, cause: " + e.getMessage());
              }
            }
          });
        } catch (LoginException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        } catch (GSSException e1) {
          // TODO Auto-generated catch block
          e1.printStackTrace();
        }
        encodedTicket = Base64.getEncoder().encodeToString(ticket);
        return encodedTicket;
      }