4

I've written a simple Java HTTP Client that is running under Windows. The client communicates with a web server which requires Kerberos authentication through SPNego.

I'm experiencing two problems:

  • The service ticket is not stored in my credentials cache. After performing a request, I expected to see a Kerberos Service Ticket stored in my credentials cache under C:\Users\<user>\krb5cc_<user> - was I wrong to assume that Java stores service tickets in the credential cache? I'd like to reuse a Service Ticket obtained in Client A for requests in Client B (where both Clients are Java applications on the same machine). Is this possible with Java?

  • If I run the code below one hundred times in a loop, it only works n-times (where n is a random number between 1 and 100). The failing request returns a 401 error message, because Java wasn't able to retrieve a Service ticket (remember: since my application doesn't store service tickets between requests, it tries to obtain a new service ticket from the TGT for every request). I've added the error message to the bottom of this question.

I've created a TGT via kinit in my JDK's bin folder. The following code snippet is used for making simple GET requests:

  static void testJavaHttpKerberosAuthentication() throws IOException {
    URL obj = new URL(URI);
    HttpURLConnection con = (HttpURLConnection) obj.openConnection();
    int responseCode = con.getResponseCode();
    System.out.println("\nSending 'GET' request to URL : " + URI);
    System.out.println("Response Code : " + responseCode);

    BufferedReader in = new BufferedReader(
    new InputStreamReader(con.getInputStream()));
    String inputLine;
    StringBuffer response = new StringBuffer();
    while ((inputLine = in.readLine()) != null) {
      response.append(inputLine);
    }
    in.close();

    //print result
    System.out.println(response.toString());
  }

Here's the content of my jaas.conf (as described here):

com.sun.security.jgss.krb5.initiate {
com.sun.security.auth.module.Krb5LoginModule required doNotPrompt=false useTicketCache=true;
};

I'm running my application with the following parameters:

-Djava.security.auth.login.config=D:\jaas.conf
-Dsun.security.krb5.debug=true
-Djavax.security.auth.useSubjectCredsOnly=false

I'm not using as a krb5.ini since my client obtains the correct KDC from the domain configuration.

I can generate a TGT for my credentials cache via the following command:

C:\Program Files\Java\jdk1.8.0_77\bin>kinit
Password for <user>@<domain>:
New ticket is stored in cache file C:\Users\<user>\krb5cc_<user>

And finally, here's the exception and Kerberos Debug Output for the case where authorization fails (ref. Problem 2). Please note that ctime is obviously wrong. I've had many different attempts and the timespan for the ctime ranges from 1970 to 2040. Interestingly enough, this doesn't happen for every request.

>>>KRBError:
 cTime is Wed Jun 07 12:24:03 CEST 2017 1496831043000
 sTime is Tue Mar 29 16:38:24 CEST 2016 1459262304000
 suSec is 283371
 error code is 34
 error Message is Request is a replay
 sname is HTTP/<spn>@<domain>
 msgType is 30
 KrbException: Request is a replay (34) - PROCESS_TGS

I've already tried to work with JAAS using Subject.doAs, but this is causing the same problems. Accesing the server via the browser works fine (although this is not comparable, as the browsers are using the Windows native credentials cache AFAICT).

I'd be thankful for some advice on how to debug a problem like this.

EDIT: Specifiying the path to the credentials cache via the KRB5CCNAME environment variable explicitly, does not change the behavior. It seems like the TGT is obtained from the Credentials Cache but Service Tickets are not stored there.

Thomas
  • 41
  • 1
  • 4
  • BTW, there are two extra trace flags that can prove useful: `-Djava.security.debug=gssloginconfig,configfile,configparser,logincontext` for debugging JAAS config issues, and of course `-Dsun.security.spnego.debug=true` – Samson Scharfrichter Mar 30 '16 at 08:17

3 Answers3

0

About the cache >> Looks like you don't specify what is the default cache on your system (cf. env variable KRB5CCNAME) so Java and kinit revert to a hard-coded default. And that's not the same default...

  • your version of kinit clearly uses the Linux standard i.e. a FILE:
  • Java typically uses the Windows standard i.e. the API: managed by the MIT-Kerberos-for-Windows service

Possible workaround: either use the Kerberos UI on Windows to create the TGT, or force Java to use the file cache by setting KRB5CCNAME.

Reference: MIT Kerberos documentation and especially the very last link about hard-coded default

~~~~~~~

About the random time values >> I have no clue.

Samson Scharfrichter
  • 8,884
  • 1
  • 17
  • 36
  • Thanks for your response, Samson. I was under the impression that Java also uses the Linux standard by default for the credentials cache (ref. [The Initial Credentials](http://cr.openjdk.java.net/~weijun/special/krb5winguide-2/raw_files/new/kwin)). At least Java was already able to find the TGT in the credentials cache in the user folder. Nonetheless, I've tried specifying the credentials cache explicitly via the `KRB5CCNAME` and it did not change the behaviour. My HTTP requests are obtaining a service ticket, but it's not stored in the credentials cache. – Thomas Mar 31 '16 at 14:14
  • Hmm... any chance that your HTTP library uses a different JAAS config entry for managing the service ticket (e.g. `Client { ... }`) while delegating the management of the TGT to the default JAAS routine, that uses the default config entry (i.e. `com.sun.security.jgss.krb5.initiate { ... }`)?!? – Samson Scharfrichter Mar 31 '16 at 16:13
  • I'm only using java.net components and considering [this documentation](http://docs.oracle.com/javase/8/docs/technotes/guides/security/jgss/lab/part6.html) I don't believe that any other entries in the `jaas.conf ` should be relevant for the example at hand. I can't find any sources that describe the process of storing service tickets in the credentials cache with Java, either it is the default behavior that's not working for me for some reason or it seems as though it's not possible. – Thomas Apr 01 '16 at 11:03
  • 1
    Side note: https://docs.oracle.com/javase/8/docs/jre/api/security/jaas/spec/com/sun/security/auth/module/Krb5LoginModule.html has details on *(a)* the search order for ticket cache on Windows and *(b)* the fact that Java expects only the TGT to be cached -- nothing about service tickets – Samson Scharfrichter Apr 01 '16 at 12:22
  • Other side note: you can skip the `kinit` step and use a "keytab" file to create a TGT automatically -- either a private TGT or a cached TGT depending on the JAAS conf. The trick is that you need a Linux box to create the "keytab" with `ktutil`. And of course you must carefully restrict access to the "keytab"... [Caveat: AFAIK Java always creates non-renewable TGT, but since you can access the "keytab" at any time, it's no big deal] – Samson Scharfrichter Apr 01 '16 at 12:27
  • Final side note: now I recall that all Hadoop UIs and REST APIs using SPNEGO implement their **own cache** (based on signed cookies or sthg like that) to lower the stress on KDC service. The reason is that even if you cached the service ticket, it would have to be validated server-side on each request (cf. statelessness). Good reason for an application-level cache. – Samson Scharfrichter Apr 01 '16 at 12:35
  • Samson, I've already realized that storing the service ticket is not specified explicitly in the Oracle documentation. It was my understanding of Kerberos, that every client uses a credentials cache for obtaining/storing TGTs and service tickets. Retrieving a new ticket for each request defeats one of the main purposes of Kerberos. Unfortunately, we can't use Keytabs for our use-case. – Thomas Apr 07 '16 at 14:06
  • For our use-case (client-side application that communicates with a kerberized REST API) it also doesn't make sense to have an application level cache, as we have to send a service ticket to the REST API for every request anyway. Anyway, I really appreciate your help with this issue. Thanks a lot for the input. – Thomas Apr 07 '16 at 14:12
0

Regarding the random time values that occassionally appear: we've found out that setting the udp_preference_limit = 1 in the krb5.ini resolves the problem. This effectively tells Kerberos to always try to use TCP first for sending packages. Apparently there is a problem when switching to UDP (not sure whether UDP is the problem or switching between protocols).

Thomas
  • 41
  • 1
  • 4
  • Uh - looks like UDP is involved in many, and seemingly unrelated, issues https://steveloughran.gitbooks.io/kerberos_and_hadoop/content/sections/errors.html – Samson Scharfrichter Apr 07 '16 at 15:42
0

JAAS is not going to persist tickets into the cache, you have to use kinit or invoke kinit code programmatically by your code. I have wrote a SO question/answer on this problem here.

Fabiano Tarlao
  • 3,024
  • 33
  • 40