Hi Daniel
Sorry for the trouble.
On 4/23/2015 7:25 PM, Daniel Jones wrote:
Hi all,
Thanks to everyone taking the time to look into this.
Before I get into the detail of the technical issue, can anyone
postulate as to *how quickly fixes tend to make it into releases of
OpenJDK*? Are we talking days, weeks, or months? I'm just trying to
advise my client on the best mitigation strategy until the issue is
resolved.
Oracle does not releases binaries for OpenJDK, you only get the source
codes on http://hg.openjdk.java.net. Once we made a fix, the changeset
will be there. So if you build your own JDK, that's quite fast.
If you use Oracle JDK, it has a release schedule at
https://www.java.com/en/download/faq/release_dates.xml
So it's months.
The Spring code in question actually changed this morning to throw a
more useful error:
https://github.com/spring-projects/spring-security-kerberos/commit/f046bd7c69d6dad74eb06a7651cd68060b31ff6f
On the other hand, your program has
context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
String user = context.getSrcName().toString();
which is not standard. The acceptSecContext call should be in a loop
until a context is established, and then you can call getSrcName().
If you look at the class spec in
http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/file/debb4ce14251/src/share/classes/org/ietf/jgss/GSSContext.java
the formal context establishment should be like
* while (!context.isEstablished()) {
* inToken = readToken();
* outToken = context.acceptSecContext(inToken, 0,
* inToken.length);
* // send output token if generated
* if (outToken != null)
* sendToken(outToken);
* }
In your case, before 8048194, it just happens that the while loop only
runs once.
What is the client here? A browser?
On the other hand, I am looking at your code and see if there is a
workaround. Can you capture some packets and send to me? Especially I'd
like the content of "kerberosTicket" in
byte[] responseToken = context.acceptSecContext(kerberosTicket, 0,
kerberosTicket.length);
In my experiment, the first 48 bytes look like this:
0000: 60 82 02 2D 06 06 2B 06 01 05 05 02 A0 82 02 21
0010: 30 82 02 1D A0 18 30 16 06 09 2A 86 48 82 F7 12
0020: 01 02 02 06 09 2A 86 48 86 F7 12 01 02 02 A1 04
Here, the OIDs are the 11 bytes from 0x18 and 0x23 starting with 06 09.
You can see the only difference between the 2 OIDs are 82 (at 0x1D) and
86 (at 0x28). If you swap them, Java will happily accept it.
Please note this is only a workaround and the real fix should be inside
JDK. If your packet is bigger the 2nd byte might be 83 and you need to
look a bit further for the OIDs (still starting with 06 09).
Thanks
Max
I'm not sure that this would work. I'm not at all familiar with what is
best practice in handling Kerberos tickets, but let me explain what
happens at present in u40:
1. Spring's SunJaasKerberosTicketValidator gets a GSSContextImpl, and
calls acceptSecContext on it.
2. The GSSContextImpl calls an overloaded accetSecContext method, which
successfully creates a SpNegoContext and assigns it as the mechCtxt
member.
3. GSSContextImpl#acceptSecContext then calls
SpNegoContext#acceptSecContext
4. In SpNegoContext#acceptSecContext we have the new functionality that
only looks at the top item of the list of OIDs from the service ticket.
5. The inner mechContext of the SpNegoContext is not set.
6. We return back to the Spring code, with a GSSContextImpl wrapping a
SpNegoContext
7. Spring calls GSSContextImpl#getSrcName(), which delegates to
SpNegoContext#getSrcName(), which returns null as its mechContext
member is null.
Yes.
Spring passes the whole ticket into GSSContextImpl, and doesn't know
about OIDs and the list of acceptable mechanisms. It seems like it's a
responsibility of either SpNegoContext or GSSContextImpl to know about
this list and iterate over it.
Yes. Unfortunately Java does not understand that 1st OID now.
If the Spring code were to attempt a repeat, how should it know that the
inner context was not set? What action should it perform next? It's
passed in the ticket it knows about, and got back a populated byte[],
without any exceptions. What would it use to determine that one of the
side affects of GSSContextImpl#acceptSecContext hasn't succeeded?
Like what I quoted above, loop until context.isEstablished() is true. Of
course, this means the client side must also coded formally to loop on
its side. I don't know what the client is and not sure if its
application protocol has this loop defined.
Does the above make sense? Please do get in touch if I an provide any
other assistance in helping with the issue, and thanks again to everyone
looking into it.
Thanks.
On Thu, Apr 23, 2015 at 1:58 AM, Weijun Wang <[email protected]
<mailto:[email protected]>> wrote:
Hi Daniel
I've read more about your bug report and know what's happening.
You are proposing 2 OIDs, [1.2.840.48018.1.2.2,
1.2.840.113554.1.2.2], 1st one being Microsoft's own krb5 OID and
2nd the standard one. Java only understands the 2nd one but before
that changeset it blindly accepts the mechToken without looking at
the OID. Since it's also krb5, the mechToken is processed correctly
and everything goes on. After the changeset, it does not recognize
the OID anymore and asks the client to send another mechToken with
1.2.840.113554.1.2.2.
I believe a lot of people is using the Microsoft OID. I will see if
it's possible to recognize both OIDs.
On the other hand, your program has
context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
String user = context.getSrcName().toString();
which is not standard. The acceptSecContext call should be in a loop
until a context is established, and then you can call getSrcName().
Can you try that? Hopefully after the client sees the server request
for a 1.2.840.113554.1.2.2 mechToken it can send one and the server
can go on.
Thanks
Max
On 4/23/2015 7:22 AM, Weijun Wang wrote:
Hi Daniel
Thanks for the report.
In fact, the bug behind the changeset you mentioned -- 8048194
-- was
just meant to make your case work. Before that, the server blindly
accepts the mechToken and process it no matter if the OID is
supported.
Now it first looks at the OID and accept the token if it
supports the
OID; otherwise, only the negotiated result (its supported OID)
is sent
back, and waits for the client sending the correct mechToken in
the next
round.
It seems the logic above is not implemented correctly, can you
show me
the full stack of your NullPointerException? If it includes any
sensitive info you can write me privately.
Thanks
Max
On 4/23/2015 12:21 AM, Rob McKenna wrote:
Hi Daniel,
Thanks for the report, I'm cc'ing the security-dev alias.
-Rob
On 22/04/15 13:10, Daniel Jones wrote:
Hi all,
Apologies if this is the wrong mailing list - please
direct me to the
correct one if so.
I believe I've found a bug in OpenJDK 1.8.0_40,
introduced in commit
d777e2918a77:
http://hg.openjdk.java.net/jdk8u/jdk8u40/jdk/file/d777e2918a77/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java
The change introduced on line 548 means that an
authentication
mechanism is
only accepted if the OID of the mechanism desired is the
*first* in the
list of mechanisms specified as acceptable in the
incoming ticket.
In the case of my current client their service tickets
are specifying 4
acceptable mechanism OIDs, but the only available
mechanism's OID
appears
second on that list. So whilst the server *can *satisfy
the ticket, the
code change on line 548 prevents this from happening.
Using the same server code, the same Kerberos KDC, and
OpenJDK 1.8.0_31,
everything works. Changing only the JDK results in the
mechContext not
being properly populated, which in turn causes a
NullPointerException
from
some Spring Security Kerberos code.
Has anyone else experienced this?
--
Regards,
Daniel Jones
EngineerBetter.com