What is the difference between GSS API and SSPI API when using Kerberos with delegation?

1.5k views Asked by At

What is the difference between GSS API and SSPI API when using Kerberos with delegation?

I have middleware running Java code in a Tomcat Server. The middleware authenticates the user with Kerberos (GSS API). If no Kerberos token is present in the Authorization header the middleware returns a 401 and attaches a WWW-Authenticate:Negotiate response header to initialize a SPNEGO Authentication.

The check of the incoming Service Ticket using GSSContext.acceptSecContext works fine.

However, I have some issues in the delegation case.

As the name “middleware” indicates, my java service must call a backend service using Kerberos authentication with the original user principal. For that, I implemented the Kerberos Java GSS API delegation mechanism. Also, the AD was configured properly, and the tomcat runs as a service with a specific service account.

To test this implementation, I wrote a Java test client utilizing the GSS API to get a ticket for the middleware. Running the Java test client with admin rights or getting a forwardable ticket using kinit -f the client and middleware combination works fine: The client gets a ticket, the middleware accepts the ticket, GSSContext.getCredDelegState() returns true, using GSSContext.getDelegCred() the middleware gets delegation credentials and the login in the backend works fine.

Also, I tested the middleware implementation with browsers and a small C# test client. Both uses SPNEGO. In this case the authorization works, too. I get the message that the authentication succeeded, and I get the user Principal. Using browsers or my C# test client I get the following debug print in the middleware:

    Debug is  true storeKey true useTicketCache false useKeyTab true doNotPrompt false ticketCache is null isInitiator false KeyTab is D:/app/Tomcat_9019_SSO/conf/tomcat.keytab refreshKrb5Config is true principal is HTTP/[email protected] tryFirstPass is false useFirstPass is false storePass is false clearPass is false
Refreshing Kerberos configuration
Java config name: C:\Windows\kerb5.ini
Loading krb5 profile at C:\Windows\kerb5.ini
Loaded from Java config
>>> KdcAccessibility: reset
principal is HTTP/[email protected]
Will use keytab
Commit Succeeded

2020-03-18 06:36:50.254  INFO .e.s.a.t.a.KerberosCheckAuthTicketAction [TC~3~c80e3d5b-3] : Starting check of incoming Kerberos service ticket.
Search Subject for SPNEGO ACCEPT cred (<<DEF>>, sun.security.jgss.spnego.SpNegoCredElement)
Search Subject for Kerberos V5 ACCEPT cred (<<DEF>>, sun.security.jgss.krb5.Krb5AcceptCredential)
Found KeyTab D:\app\Tomcat_9019_SSO\conf\tomcat.keytab for HTTP/[email protected]
Found KeyTab D:\app\Tomcat_9019_SSO\conf\tomcat.keytab for HTTP/[email protected]
Search Subject for Kerberos V5 ACCEPT cred (<<DEF>>, sun.security.jgss.krb5.Krb5AcceptCredential)
Found KeyTab D:\app\Tomcat_9019_SSO\conf\tomcat.keytab for HTTP/[email protected]
Found KeyTab D:\app\Tomcat_9019_SSO\conf\tomcat.keytab for HTTP/[email protected]
Entered Krb5Context.acceptSecContext with state=STATE_NEW
Looking for keys for: HTTP/[email protected]
Added key: 23version: 0
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
Using builtin default etypes for permitted_enctypes
default etypes for permitted_enctypes: 18 17 20 19 16 23.
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
MemoryCache: add 1584509810/000627/5EBDF35F49476E365F32DE53C3CAFA81C4730A13D881ECA15E9F43023F99A80B/[email protected] to [email protected]|HTTP/[email protected]
>>> KrbApReq: authenticate succeed.
Krb5Context setting peerSeqNumber to: 947381056
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
Krb5Context setting mySeqNumber to: 214468704
>>> Constrained deleg from GSSCaller{UNKNOWN}
Debug is  true storeKey true useTicketCache false useKeyTab true doNotPrompt false ticketCache is null isInitiator true KeyTab is D:/app/Tomcat_9019_SSO/conf/tomcat.keytab refreshKrb5Config is false principal is HTTP/[email protected] tryFirstPass is false useFirstPass is false storePass is false clearPass is false
Looking for keys for: HTTP/[email protected]
Added key: 23version: 0
Looking for keys for: HTTP/[email protected]
Added key: 23version: 0
default etypes for default_tkt_enctypes: 23 18 17.
>>> KrbAsReq creating message
>>> KrbKdcReq send: kdc=kb01.mydomain.net UDP:88, timeout=30000, number of retries =3, #bytes=174
>>> KDCCommunication: kdc=kb01.mydomain.net UDP:88, timeout=30000,Attempt =1, #bytes=174
>>> KrbKdcReq send: #bytes read=175
>>>Pre-Authentication Data:
         PA-DATA type = 11
         PA-ETYPE-INFO etype = 23, salt =

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

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

>>>Pre-Authentication Data:
         PA-DATA type = 15

>>> KdcAccessibility: remove kb01.mydomain.net
>>> KDCRep: init() encoding tag is 126 req type is 11
>>>KRBError:
         sTime is Wed Mar 18 06:36:50 CET 2020 1584509810000
         suSec is 765149
         error code is 25
         error Message is Additional pre-authentication required
         sname is krbtgt/[email protected]
         eData provided.
         msgType is 30
>>>Pre-Authentication Data:
         PA-DATA type = 11
         PA-ETYPE-INFO etype = 23, salt =

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

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

>>>Pre-Authentication Data:
         PA-DATA type = 15

KrbAsReqBuilder: PREAUTH FAILED/REQ, re-send AS-REQ
default etypes for default_tkt_enctypes: 23 18 17.
Looking for keys for: HTTP/[email protected]
Added key: 23version: 0
Looking for keys for: HTTP/[email protected]
Added key: 23version: 0
default etypes for default_tkt_enctypes: 23 18 17.
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbAsReq creating message
>>> KrbKdcReq send: kdc=kb01.mydomain.net UDP:88, timeout=30000, number of retries =3, #bytes=253
>>> KDCCommunication: kdc=kb01.mydomain.net UDP:88, timeout=30000,Attempt =1, #bytes=253
>>> KrbKdcReq send: #bytes read=90
>>> KrbKdcReq send: kdc=kb01.mydomain.net TCP:88, timeout=30000, number of retries =3, #bytes=253
>>> KDCCommunication: kdc=kb01.mydomain.net TCP:88, timeout=30000,Attempt =1, #bytes=253
>>>DEBUG: TCPClient reading 2154 bytes
>>> KrbKdcReq send: #bytes read=2154
>>> KdcAccessibility: remove kb01.mydomain.net
Looking for keys for: HTTP/[email protected]
Added key: 23version: 0
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
                [Krb5LoginModule] authentication failed
Message stream modified (41)

Using the Java client, I get this debug print in the middleware:

Debug is  true storeKey true useTicketCache false useKeyTab true doNotPrompt false ticketCache is null isInitiator false KeyTab is D:/app/Tomcat_9019_SSO/conf/tomcat.keytab refreshKrb5Config is true principal is HTTP/[email protected] tryFirstPass is false useFirstPass is false storePass is false clearPass is false
Refreshing Kerberos configuration
Java config name: C:\Windows\kerb5.ini
Loading krb5 profile at C:\Windows\kerb5.ini
Loaded from Java config
>>> KdcAccessibility: reset
principal is HTTP/[email protected]
Will use keytab
Commit Succeeded

2020-03-18 06:47:41.029  INFO .e.s.a.t.a.KerberosCheckAuthTicketAction [TC~9~c80e3d5b-9] : Starting check of incoming Kerberos service ticket.
Search Subject for SPNEGO ACCEPT cred (<<DEF>>, sun.security.jgss.spnego.SpNegoCredElement)
Search Subject for Kerberos V5 ACCEPT cred (<<DEF>>, sun.security.jgss.krb5.Krb5AcceptCredential)
Found KeyTab D:\app\Tomcat_9019_SSO\conf\tomcat.keytab for HTTP/[email protected]
Found KeyTab D:\app\Tomcat_9019_SSO\conf\tomcat.keytab for HTTP/[email protected]
Search Subject for Kerberos V5 ACCEPT cred (<<DEF>>, sun.security.jgss.krb5.Krb5AcceptCredential)
Found KeyTab D:\app\Tomcat_9019_SSO\conf\tomcat.keytab for HTTP/[email protected]
Found KeyTab D:\app\Tomcat_9019_SSO\conf\tomcat.keytab for HTTP/[email protected]
Entered Krb5Context.acceptSecContext with state=STATE_NEW
Looking for keys for: HTTP/[email protected]
Added key: 23version: 0
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
Using builtin default etypes for permitted_enctypes
default etypes for permitted_enctypes: 18 17 20 19 16 23.
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
MemoryCache: add 1584510459/567826/FDE0027391B8BF26BF807FF04E5FD5F7CE38794A3264EB298BB36F736B2CF050/[email protected] to [email protected]|HTTP/[email protected]
>>> KrbApReq: authenticate succeed.
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>>Delegated Creds have [email protected] sname=krbtgt/[email protected] authtime=20200318054735Z starttime=20200318054739Z endtime=20200318154735ZrenewTill=null
Krb5Context setting peerSeqNumber to: 99984043
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
Krb5Context setting mySeqNumber to: 161819208

However, the main issue here is, that in case of the Java client the delegation works and in case of browsers and C# client the delegation does not work. Note, the browsers have been configured to whitelist the domain for delegation.

Additional Information: I configured constrained delegation. The Tomcat with the middleware is running on a Windows 2016 server as a Service with an AD service account.

I compared the service tickets which has been send to the middleware:

Java (forwardable), delegation works: 10980 byte

C# (delegation doesn’t work): 8572 byte

Browser (delegation doesn’t work): 8572 byte

For comparison I used kinit without -f option to get a tgt which is not forwardable and measured the size:

Java (not forwardable, delegation doesn’t work): 8174 bytes

Btw., this produces the same error.

2

There are 2 answers

0
Axel-R-D On BEST ANSWER

Here is the answer to my own question.

I realized that the domain – I am working in – has 3 domain controllers: In my middleware the krb5.ini had the first one configured:

[realms]
    MYDOMAIN.NET = {
            kdc = d01.mydomain.net
            admin_server = d01.mydomain.net
            default_domain = MYDOMAIN.NET
    } 

My Java test client had the following code line

System.setProperty("java.security.krb5.kdc", d01.mydomain.net);

My client computer, however, used the d02.mydomain.net what I saw using the “klist” command.

#2>     Client: CLIENTUSERD @ MYDOMAIN.NET
        Server: HTTP/SERVICE.MYDOMAIN.NET @ MYDOMAIN.NET
        KerbTicket Encryption Type: RSADSI RC4-HMAC(NT)
        Ticket Flags 0x40a00000 -> forwardable renewable pre_authent
        Start Time: 3/24/2020 5:33:39 (local)
        End Time:   3/24/2020 15:33:39 (local)
        Renew Time: 3/31/2020 5:33:39 (local)
        Session Key Type: RSADSI RC4-HMAC(NT)
        Cache Flags: 0
        Kdc Called: d02.mydomain.net

For some reasons – which are unclear for me right now – the delegation does not work if the wrong domain controllers must work together. If I change the domain controller to d03.mydomain.net in the middleware, the delegation works fine. However, the ticket acceptance works fine in any combination. Maybe the timing of the domain controllers are out of sync.

0
Steve On

How do you have delegation configured for the middleware service account in AD? Unconstrained or constrained?

Try grabbing a copy of the tickets sent by both java and C# clients and compare the ticket sizes. They should be approximately the same, however if one is significantly bigger then that bigger ones contains a delegated TGT.

Based on the logs it looks like the Java client is sending an unconstrained ticket (the delegated TGT, AKA forwarded) and is just using that.

We can't see what the C# ticket contains but the middleware is attempting to contact the KDC immediately after receiving which seems to indicate it may be attempting to exchange it for a delegated ticket, but fails because the middleware can't get it's own ticket first. That's a bit of a wild guess though because the logs don't indicate one way or another why it's kicking off the AS-REQ.

You might consider getting a network trace between the middleware and the KDC to see what it's sending in the C# case.