JAAS - fails to persist Kerberos ticket to cache file, and unable to create cache from scratch.. and other details
Asked Answered
S

4

6

I'm developing a Java application that performs authentication with JAAS, should work as follows: (i) when the ticket for user uclient is already in local cache it should authenticate the user without asking credentials, (ii) when no ticket for 'uclient' is in cache it should ask for username/password and save the acquired ticket into the local cache.

My application is able to perform 'i' but is not able to perform 'ii', it authenticates correctly the user (creates the Subject/Principal) but it doesn't persist the Krb ticket into the cache.

Questions

  1. How do I achieve/implement this?
  2. And.. is this going to create the Kerberos cache file when empty/nonexistent? - How do I realize the cache file creation/initialization programmatically from Java?
  3. And.. just for curiosity, is the Java JaaS able to manage the linux KEYRINGs ? (At the moment Jaas was not able to automatically manage them)
  4. Is Java JaaS only able to manage/persist tickets for the Default principal in the cache? - Or how do I manage with JaaS a situation where I have tickets for a lot of principals in a single cache file?

Please note that my application has to work in Windows AD and Linux Realms both.

More datails on my enviroment and my current code

I'm testing the client app in a Linux Kerberos Realm configured with FreeIPA client and server. I have a Linux VM that provides the KDC for a realm AUTHDEMO.IT , and a linux VM that is endorsed into the AUTHDEMO.IT realm. krb5.conf configuration:

includedir /var/lib/sss/pubconf/krb5.include.d/

[libdefaults]
  default_realm = AUTHDEMO.IT
  dns_lookup_realm = true
  dns_lookup_kdc = true
  rdns = false
  ticket_lifetime = 24h
  forwardable = true
  udp_preference_limit = 0
  default_ccache_name = KEYRING:persistent:%{uid}


[realms]
  AUTHDEMO.IT = {
    pkinit_anchors = FILE:/etc/ipa/ca.crt

  }


[domain_realm]
  .authdemo.it = AUTHDEMO.IT
  authdemo.it = AUTHDEMO.IT

This is jaas.conf for the application:

JaasDemo {
   com.sun.security.auth.module.Krb5LoginModule required 
   useTicketCache=true
   principal=uclient
   debug=true; 
};

I have not specified the default cache file name, I have verified in debug that it defaults to: /tmp/krb5cc_1000 where 1000 is the uid of running user.

In the JaasDemo class instance I perform authentication with this login method:

public LoginContext login(){
        LoginContext lc = null;
        try {
            System.out.println("Initialize logincontext");
            lc = new LoginContext("JaasLogin",
                    new TextCallbackHandler());
        } catch (LoginException | SecurityException le) {
            System.err.println("Cannot create LoginContext."
                    + le.getMessage());
            return lc;
        }

        try {
            // attempt authentication
            System.out.println("Attempt login");
            lc.login();
        } catch (LoginException le) {
            System.err.println("Authentication failed:");
            System.err.println("  " + le.getMessage());
            return lc;
        }

        System.out.println("Authentication succeeded!");
        return lc;
    }

I have executed my application with this command (note the options for verbose kerberos logging):

java  -Dsun.security.krb5.debug=true -Dsun.security.jgss.debug=true -Djava.security.auth.login.config=jaas.conf -jar myapp.jar

Here follows the output of the application in different cases, please note that when asked, the user interactively provides the right credentials. First case nonexistent /tmp/krb5cc_1000 file:

Initialize logincontext
Attempt login
Debug is  true storeKey false useTicketCache true useKeyTab false doNotPrompt false ticketCache is null isInitiator true KeyTab is null refreshKrb5Config is false principal is uclient tryFirstPass is false useFirstPass is false storePass is false clearPass is false
Acquire TGT from Cache
>>>KinitOptions cache name is /tmp/krb5cc_1000
Principal is [email protected]
null credentials from Ticket Cache
**Login Handler invoked, providing username and password to login manager..**
        [Krb5LoginModule] user entered username: uclient

Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 18 17 16 23.
>>> KrbAsReq creating message
getKDCFromDNS using UDP
>>> KrbKdcReq send: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000, number of retries =3, #bytes=143
>>> KDCCommunication: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000,Attempt =1, #bytes=143
>>> KrbKdcReq send: #bytes read=283
>>>Pre-Authentication Data:
     PA-DATA type = 136

>>>Pre-Authentication Data:
     PA-DATA type = 19
     PA-ETYPE-INFO2 etype = 18, salt = REMOVED 3@, s2kparams = null
     PA-ETYPE-INFO2 etype = 17, salt = REMOVED, s2kparams = null

>>>Pre-Authentication Data:
     PA-DATA type = 2
     PA-ENC-TIMESTAMP
>>>Pre-Authentication Data:
     PA-DATA type = 133

>>> KdcAccessibility: remove authdemo2.authdemo.it.:88
>>> KDCRep: init() encoding tag is 126 req type is 11
>>>KRBError:
     cTime is Wed Jun 29 17:12:49 CEST 1988 583600369000
     sTime is Wed Aug 02 15:53:28 CEST 2017 1501682008000
     suSec is 981130
     error code is 25
     error Message is Additional pre-authentication required
     cname is [email protected]
     sname is krbtgt/[email protected]
     eData provided.
     msgType is 30
>>>Pre-Authentication Data:
     PA-DATA type = 136

>>>Pre-Authentication Data:
     PA-DATA type = 19
     PA-ETYPE-INFO2 etype = 18, salt = REMOVED 3@, s2kparams = null
     PA-ETYPE-INFO2 etype = 17, salt = REMOVED, s2kparams = null

>>>Pre-Authentication Data:
     PA-DATA type = 2
     PA-ENC-TIMESTAMP
>>>Pre-Authentication Data:
     PA-DATA type = 133

KRBError received: NEEDED_PREAUTH
KrbAsReqBuilder: PREAUTH FAILED/REQ, re-send AS-REQ
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 18 17 16 23.
Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 18 17 16 23.
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> KrbAsReq creating message
getKDCFromDNS using UDP
>>> KrbKdcReq send: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000, number of retries =3, #bytes=225
>>> KDCCommunication: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000,Attempt =1, #bytes=225
>>> KrbKdcReq send: #bytes read=674
>>> KdcAccessibility: remove authdemo2.authdemo.it.:88
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> KrbAsRep cons in KrbAsReq.getReply uclient
principal is [email protected]
Commit Succeeded 

Authentication succeeded!


Subject.toString:
    Principal: [email protected]
    Private Credential: Ticket (hex) = 
REMOVED TICKET DETAILS                                             K.

Client Principal = [email protected]
Server Principal = krbtgt/[email protected]
Session Key = EncryptionKey: keyType=18 keyBytes (hex dump)=
REMOVED

Forwardable Ticket true
Forwarded Ticket false
Proxiable Ticket false
Proxy Ticket false
Postdated Ticket false
Renewable Ticket false
Initial Ticket false
Auth Time = Wed Aug 02 15:53:28 CEST 2017
Start Time = Wed Aug 02 15:53:28 CEST 2017
End Time = Thu Aug 03 15:53:28 CEST 2017
Renew Till = null
Client Addresses  Null 

Second case /tmp/krb5cc_1000 file exists that contains ticket for another user (created with kinit -c); the application correctly authenticates, but the acquired ticket is not persisted to the cache file.

klist status first than application execution:

klist -c /tmp/krb5cc_1000 

Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: [email protected]

Valid starting       Expires              Service principal
08/02/2017 16:05:19  08/03/2017 16:05:13  krbtgt/[email protected]

Output of application:

Initialize logincontext
Attempt login
Debug is  true storeKey false useTicketCache true useKeyTab false doNotPrompt false ticketCache is null isInitiator true KeyTab is null refreshKrb5Config is false principal is uclient tryFirstPass is false useFirstPass is false storePass is false clearPass is false
Acquire TGT from Cache
>>>KinitOptions cache name is /tmp/krb5cc_1000
java.io.IOException: Primary principals don't match.
    at sun.security.krb5.internal.ccache.FileCredentialsCache.load(FileCredentialsCache.java:179)
    at sun.security.krb5.internal.ccache.FileCredentialsCache.acquireInstance(FileCredentialsCache.java:82)
    at sun.security.krb5.internal.ccache.CredentialsCache.getInstance(CredentialsCache.java:83)
    at sun.security.krb5.Credentials.acquireTGTFromCache(Credentials.java:333)
    at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:665)
    at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:617)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755)
    at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195)
    at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682)
    at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680)
    at javax.security.auth.login.LoginContext.login(LoginContext.java:587)
    at it.kerberosdemo.login.JaasDemo.login(JaasDemo.java:45)
    at it.kerberosdemo.login.JaasDemo.login(JaasDemo.java:27)
    at it.male.kerberosdemo.client.ClientMain.main(ClientMain.java:29)
Principal is [email protected]
null credentials from Ticket Cache
Login Handler invokerd, providing username and password to login manager..
        [Krb5LoginModule] user entered username: uclient

Using builtin default etypes for default_tkt_enctypes
default etypes for default_tkt_enctypes: 18 17 16 23.
>>> KrbAsReq creating message
getKDCFromDNS using UDP
>>> KrbKdcReq send: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000, number of retries =3, #bytes=143
>>> KDCCommunication: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000,Attempt =1, #bytes=143
>>> KrbKdcReq send: #bytes read=283
>>>Pre-Authentication Data:
     PA-DATA type = 136

>>>Pre-Authentication Data:
     PA-DATA type = 19
     PA-ETYPE-INFO2 etype = 18, salt = REMOVED, s2kparams = null
     PA-ETYPE-INFO2 etype = 17, salt = REMOVED, s2kparams = null

>>>Pre-Authentication Data:
     PA-DATA type = 2
     PA-ENC-TIMESTAMP
>>>Pre-Authentication Data:
     PA-DATA type = 133

>>> KdcAccessibility: remove authdemo2.authdemo.it.:88
>>> KDCRep: init() encoding tag is 126 req type is 11
>>>KRBError:
     cTime is Mon Sep 22 16:38:56 CEST 2031 1947854336000
     sTime is Wed Aug 02 16:07:05 CEST 2017 1501682825000
     suSec is 803283
     error code is 25
     error Message is Additional pre-authentication required
     cname is [email protected]
     sname is krbtgt/[email protected]
     eData provided.
     msgType is 30
>>>Pre-Authentication Data:
     PA-DATA type = 136
...OMITTED IDENTICAL

klist confirms that no ticket is added for 'uclient' into the cache file.

Regards

Scalenus answered 2/8, 2017 at 14:27 Comment(0)
S
8

Finally I found an answer to the questions 1 + 2

The kinit command bundled with the java distribution is a java application that authenticates the user into the realm/domain and saves the acquired ticket inside a ccache file. The kinit command code is available in the sun.security.krb5.internal.tools package of the OpenJDK. The main class is sun.security.krb5.internal.tools.Kinit. In order to acquire (authenticate) and persist the Kerberos tickets you can copy all the tool package into your application and invoke from Kinit class the method main(String[] arv) by providing the cli arguments. You can also, as I have done, change the Kinit class in order to integrate better with your code.

Kinit code is very useful in order to understand inner workings of internal private Kerberos code and in order to customize it. For example there is a KDCOptions instance that you can manually configure in order to ask for a renewable ticket and much more. Let's study it! ;-)

Please consider that:

  • there is not guarantee that interfaces of internal code will be left unchanged in the future JDK releases
  • there is not guarantee that interfaces of internal code are the same between different JDK vendors.

I can confirm that my code is working fine with OpenJDK and Oracle JDK both.

The big picture

At the moment my application uses Jaas in order to authenticate by looking at Krb credentials in the local ccache file, in case of failure it executes the kinit code as mentioned above. Then, it authenticates with Jaas from the updated ccache file.

The next step

I'm currently trying to persist the Kerberos Ticket to ccache directly from the Credentials in a Subject Object.
I'll try to use the sun.security.krb5.internal.ccache.FileCredentialCache class but it looks a low-level way to go. Let's look at the use of CredentialCache abstract class in the kinit code, may be useful. I'll update the thread in case of success.

Thanks

Thank you to Michael-O that showed me the sun.security.krb5.internal package where I finally found out the kinit code.

Regards

Scalenus answered 10/8, 2017 at 15:52 Comment(1)
sun.security.krb5.internal is no longer available in Java 9Deflate
M
4

You can't. Java does not support persisting your TGT or service tickets back to a file-based cache which can be used with MIT Kerberos or Heimdal. Oracle has some private classes to do this, but I wouldn't recommend doing this.

Miterwort answered 2/8, 2017 at 23:33 Comment(5)
Thank you Michael, I totally misunderstood the Oracle documentation. When reading I have thought it was a normal behaviour for an application but I was biased by my needs. In the case I'll found out an elegant solution I'll update this thread. RegardsScalenus
@FabianoTarlao Note also that if you even have service tickets in your file-based cache, JGSS won't use them and will request new ones.Miterwort
Michael, for the sake of curiosity, can you provide me any direction/hint/keyword about the Oracle private classes that could be used to persist kerberos tickets?Scalenus
Look in the package sun.security.krb5.internal.ccache.Miterwort
I'm sorry, I have changed the "answer flag" on your post because I have added what it looks a complete answer. Thank you for you precious tips.Scalenus
K
2

Unfortunately many of Java developers don't understand low-level of MIT Kerberos and they import build-in native library by default. So you must not use Java JAAS because it does not support Linux and Windows Kerberos. Credential Cache is strong standard for MIT and it is basis of HA and authentication optimization becuase SSO doesn't require to repeat authentication when the user or the web-gate service has been already authenticated and connected to target service during lifetime limit.

Oracle and OpenJDK has Sun GSSAPI classes for MIT and Heimdal since protocols appeared.

It is the simplest way to change authentication low-level algorithm you should only add in JAVA_OPTS or in java arguments: -Dsun.security.jgss.native=true -Djavax.security.auth.useSubjectCredsOnly=false

So this solution works for any OS and helps to store TGT and TGS to user credential cache file. For the target service that will use native jgss you must add to environment SPN keytab for decrypt user TGT by executing "export KRB5_KTNAME=/path-to-keytab". Also credential cache file should be saved to home directory of your client or server application owner for security reason because default /tmp/krb5cc_%uid% is not good idea sometimes.

Kaolinite answered 8/11, 2021 at 23:57 Comment(3)
Thanks @tribit, I red the official Oracle docs without finding the reference to this solution or I misunderstood completely. can you provide here also a reference to documentation? RegardsScalenus
docs.oracle.com/en/java/javase/11/security/…Kaolinite
All of OS use own GSS libraries. In Linux it is libgssapi.so. in Windows it is SSPI provider or gssapi32.dll. Oracle/Sun JGSS module delegate authentication to external GSS library that use credential cache. Native Java module cannot store to file cache or RAM cache (Keyring/KCM in Linux and MSLSA in Windows) but only stores tickets to JVM internally so there is no way to get valid and created tickets by any new thread or process or application inside user or service session so any new connection attacks KDC server for any action. Native JGSS solves this problem.Kaolinite
S
1

The other questions.

3 - just for curiosity, is the Java JaaS able to manage the linux KEYRINGs ? (At the moment Jaas was not able to automatically manage them)

No, the internal Java Krb classes only manages files not KEYRINGs.

4 - Is Java JaaS only able to manage/persist tickets for the Default principal in the cache? - Or how do I manage with JaaS a situation where I have tickets for a lot of principals in a single cache file?

I found no simple way to manage collections (it's quite a recent standard), my personal choice is to create one cache file per principal.

Scalenus answered 10/8, 2017 at 15:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.