4

I am having problems authenticating via SPNEGO from a Web Browser (Internet Explorer 11) to a Web Service offered by a custom Java Application Server.

I can successfully authenticate using SPNEGO to the same Application Server using a custom Java Client Application.

Implementation details of the custom Java Client and Application server can be found below.

I suspect that SPNEGO from a Web Browser is not working because:

a) Is the Token from Internet Explorer a valid SPNEGO Token?

The GSSAPI token provided by the Web Browser is different to that provided by my Java Client, and may not be a valid SPNEGO / Kerberos token. The Java Client provides an Authorization Header starting with “Negotiate YIMMQA...” (OK), whereas the Web Browser provides an Authorization Header starting with “Negotiate oYIMRz...” (Probably NOT OK).

and / or

b) Format of the Server Principal Name

For historical reasons, the Application Server is running using a Service Principal Name that is actually a Microsoft Active Directory User Principal (format = "user@DOMAIN"), whereas I strongly suspect that Web Browser SPNEGO implementations use the requested URL to build a Service Principal Name. Indeed this is exactly what my custom Java Client does when running on Linux against a Linux backend.

Implementation Details:

The Java Application Server runs on Windows Server 2012. The Kerberos / SPNEGO implementation is pure Java JAAS + GSSAPI.

The Java Client runs on Windows (7 / 10), and can be configured to use either Java SSPI (via Waffle), or JAAS + GSSAPI. Both implementations create GSS Tokens accepted by the Server.

The GSS / SPNEGO tokens generated are transported in the headers of Web Service requests (Client) and responses (Server).

The Server is using the Oids "1.3.6.1.5.5.2" (SPNEGO) and "1.2.840.113554.1.2.2" (Kerberos).

Testing using the Custom Java Client (OK):

The Server is able to authenticate the Java Client in a single handshake. The Java Client directly calls the Web Service with an Authorization Header starting with “Negotiate YIMMQA...” After Base64 decoding on the Server the gssapiData 3140 bytes long and the call to acceptSecContext() is successful.

If I convert the gssapiData from this call to a string, and search this for anything human readable, then towards the start I find “EXAMPLE.COM” and “user-DEV”. This looks like the SPN that the Server is using, an Active Directory User Principal (“[email protected]”).

Testing using Internet Explorer 11 (NOT OK):

The first call from the Browser has an empty Authorization Header. My Server prompts with “Negotiate” —> OK.

The second call from the Browser has an Authorization Header starting with “Negotiate YH4GBis...”. Once Base64 decoded, the gssapiData is 128 bytes long. Clearly this does not contain a service ticket.

If I convert the gssapiData to a string, in the middle I find the characters “NTLMSSP”. I guess the Browser is suggesting NTLM. My Server rejects this call.

The third call from the Browser has an Authorization Header starting with “Negotiate oYIMRz...” Once Base64 decoded, the gssapiData is 3147 bytes long (very close to the length of that from the Java Client).

However when my Server does a acceptSecContext() on this, It throws the error. “GSS Exception: Defective Token detected (Mechanism level: GSS Header did not find the right tag). —> NOT OK.

This suggests to me that the token is not valid, or I am using the wrong Oids to read it.

If I convert the gssapiData from this call to a string, then towards the start I find “HTTP” and “APPSERVER.example.com”. This looks like a Kerberos Service Principal Name (SPN) built using the URL as a basis. —> This suggests to me that my Application Server should be running with an SPN in a format something like “HTTP/APPSERVER.example.com” or “HTTP/[email protected]” (the second being the format my Linux / FreeIPA configuration uses).

As a side note: On the Windows platform that is the focus of this question I do not have the rights to create / change SPNs or aliases to the same, or to try different Web Browsers. On my Linux development environment I do, which may provide additional input . . .

4
  • Have you tried from any other browser? I know that Firefox allows to modify the GSSAPI settings it uses, which means you can install MIT Kerberos, request a ticket for the principal “[email protected]”, and use that ticket when accessing the service (instead of the "HTTP/_" one that browsers usually use) - meaning that you'd be connecting to the service with the same credential as your Java app uses. But I'm not aware of this being possible with IE Commented Jul 18, 2018 at 20:22
  • @AlexSavitsky: Re other browsers, not yet. On the Windows platform my hands are tied as to what I can install without jumping through enormous hoops, and IE is the Browser end users use 8-(. I do have a Linux VM + FreeIPA setup where I can try Firefox, Chrome etc, install Wireshark, change SPNs, create Aliases etc. Commented Jul 19, 2018 at 4:19
  • For Kerberos authentication I only use Firefox combined with MIT Kerberos. You can try it using a portable Firefox on Windows. Get a valid Kerberos ticket, configure FF with your company proxy, (about:config in the URL bar) add the domain you aim to reach to network.negociate-auth.delegation-uris and network.negociate-auth.trusted-uris and disable network.auth.use-sspi.
    – teikitel
    Commented Jul 19, 2018 at 11:53
  • For the reference, the following settings had to be modified in our company's Kerberos/Firefox install: network.negotiate-auth.trusted-uris=<domain of your app server> network.negotiate-auth.using-native-gsslib=true network.auth.use-sspi=false network.negotiate-auth.allow-non-fqdn = false Commented Jul 19, 2018 at 12:59

1 Answer 1

4

Quick Answer

Two fixes were required:

1) Internet Explorer (IE) builds the Service Principal Name (SPN) based on the URL. e.g. https://appserver.example.com/foo results in the SPN "HTTP/APPSERVER.example.com".

Therefore proper Service Principal Names had to be setup in Active Directory in the above format as aliases to the User Principal Name (UPN) used by the Application Server.

and

2) The Token from Internet Explorer is a valid SPNEGO token, but is not accepted by GSS API on the Server.

However with some simple string manipulation of the incoming token, a Kerbeos token can be extracted that GSS will accept and successfully authenticate.

Longer Answer

1) Service Principal Names ...

After posting this issue here, we set up 2 SPNs HTTP/APPSERVER.example.com and HTTP/APPSERVER as aliases for the Server's UPN [email protected].

The Server continues to run using the UPN [email protected]. (my initial assumption that the server had to run with one of the new SPNs was wrong.)

My Java client is now able to use the new SPNs to acquire Kerberos Service Tickets and to create tokens that are successfully authenticated by my Server.

However tokens from Internet Explorer continue to be be rejected.

2) The Tokens from Internet Explorer vs my Java Client...

The token from my Java Client starts like this:

Negotiate YIIMdwYGKwYBBQUCoI....

Base64 decoded, and represented as Hex bytes:

60 82 0C 77 06 06 2B 06 01 05 05 02 A0………

of which 06 06 2B 06 01 05 05 02 is the SPNEGO OID 1.3.6.1.5.5.2 .

The token from Internet Explorer starts like this:

Negotiate oYIMPjCCDDqgAwoBAaKCDDEEggwtYIIMKQYJKoZIhvcSAQIC

Base64 decoded, and represented as Hex bytes:

A1 82 0C 3E 30 82 0C 3A A0 03 0A 01 01 A2 82 0C 31 04 82 0C 2D 60 82 0C 29 06 09 2A 86 48 86 F7 12 01 02 02….

This is a Spnego NegTokenTarg as it starts with "A1". However the java class sun.security.jgss.GSSHeader will reject any GSS token that does not start with "60".

Examining the IE NegTokenTarg byte by byte shows that after the first 21 bytes I have a series of bytes very close to that of the token from my app:

60 82 0C 29 06 09 2A 86 48 86 F7 12 01 02 02....

of which 06 09 2A 86 48 86 F7 12 01 02 02 is the Kerberos OID 1.2.840.113554.1.2.2

If I extract this token by discarding the first 21 bytes of the original token (or provide gssContext.acceptSecContext(gssapiData, offset, gssapiData.length) with an offset of 21), then GSS API is able to read the new token, and extract the user principal and thus authenticate the request from Internet Explorer.

The code example below uses string manipulation of the base64 endcoded authorization string to achieve the same:

String auth =req.headers("Authorization");
if ( auth != null && auth.startsWith("Negotiate ")) {
    //smells like an SPNEGO request, so get the token from the http headers
    String authBody = auth.substring("Negotiate ".length());
    if (authBody.startsWith("oY")) {
        // This is a NegTokenTarg from IE, which GSS API does not properly handle.
        // However if we chop of the first (28) chars, we find a Kerberos Token starting with "60 82 0C" that GSS can handle.            
        authBody=authBody.substring(authBody.indexOf("YI", 2));
     }

     try {                 
         byte gssapiData[] = Base64.getDecoder().decode(authBody);               
         gssContext = initGSSContext(MyUtils.SPNEGOOID, MyUtils.KRB5OID);
         byte token[] = gssContext.acceptSecContext(gssapiData, offset, gssapiData.length);


         ..etc.

In conclusion I think we have either

a) A Java GSS API weakness: GSS does not directly accept a SPNEGO token that is a NegTokenTarg.

or

b) The interplay between the Internet Explorer and my Server, resulting in IE sending a NegTokenTarg, which is not expected by GSS API.

The IE - Server interplay is:

1) Request from IE (without Negotiate)

2) Reject from my Server, with Negotiate

3) 2nd Request from IE, with Negotiate Header + Token that looks like NTLM,not Kerberos. --> This may be the route cause of the problem.

4) Reject from my Server, with Negotiate + SPNEGO token

5) 3rd Request from IE, with Negotiate Header + SPNEGO NegTokenTarg

Background Info:

My Application Server uses Java JAAS + GSS for the Kerberos / Spnego functionality. My custom client can use either Java JAAS + GSS, or Microsoft SSPI + Waffle.

I found this Microsoft document very helpful to understand the format of a SPNEGO token.

https://msdn.microsoft.com/en-us/library/ms995330.aspx

and this blog to understand how to “handle” negative decimal bytes. (The numbers -128 to -1 translate as 128 to 255).

http://sketchytech.blogspot.com/2015/11/bytes-for-beginners-representation-of.html

As the corporate standard browser of my target end-users is Internet Explorer, with no realistic option to use something “better”, while googling I came across the SPN handling code of Chromium and Firefox. Links are below. The Chromium code has extensive comments and links to Knowledge Base articles and SME Blogs.

Chromiumn Code

https://cs.chromium.org/chromium/src/net/http/http_auth_handler_negotiate.cc?type=cs&l=142

FireFox Code

https://dxr.mozilla.org/mozilla-central/source/extensions/auth/nsAuthSSPI.cpp#98

2
  • strange, for spnego we did not need to do any "magic" with the header, the default Tomcat authenticator could handle that gusto77.wordpress.com/2015/09/02/… the only issue was that for users with many groups the token become too big and the default headers size in Tomcat had to be increased
    – gusto2
    Commented Aug 3, 2018 at 10:16
  • @gusto2 I am using JAAS GSS on the application server (and Spark + embedded Jetty for the Web Server). If a GSSContext is used, only application constructed tokens starting with 0x60 are accepted. If a SpnegoContext is used, only Spnego NegTokenInits starting with 0xa0 are accepted. I suspect that each Spnego implementation may handle things slightly differently, and some may be more flexible than others. In this case flexibility is good, especially if the "other end" is a party that one does not have under one's own control, or insight into the source code e.g. Internet Explorer. Commented Aug 4, 2018 at 11:39

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.