From 5e28e87f581629724ff8a0f6d24c3ebc591a00bb Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Wed, 13 May 2015 13:15:21 +0100 Subject: [PATCH] SPNEGO auth fails if client proposes MS krb5 OID - Tweak incoming oid order in spnego token to workaround jdk regression. - Fixes #34 --- .../sun/SunJaasKerberosTicketValidator.java | 67 ++++++++++++++- .../SunJaasKerberosTicketValidatorTests.java | 82 +++++++++++++++++++ 2 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 spring-security-kerberos-core/src/test/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidatorTests.java diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java index fcc6240..ba9903b 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java @@ -32,6 +32,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; import org.springframework.beans.factory.InitializingBean; @@ -158,11 +159,20 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, @Override public KerberosTicketValidation run() throws Exception { + byte[] responseToken = new byte[0]; + GSSName gssName = null; GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null); - byte[] responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length); - GSSName gssName = context.getSrcName(); - if (gssName == null) { - throw new BadCredentialsException("GSSContext name of the context initiator is null"); + boolean first = true; + while (!context.isEstablished()) { + if (first) { + kerberosTicket = tweakJdkRegression(kerberosTicket); + } + responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length); + gssName = context.getSrcName(); + if (gssName == null) { + throw new BadCredentialsException("GSSContext name of the context initiator is null"); + } + first = false; } if (!holdOnToGSSContext) { context.dispose(); @@ -205,4 +215,53 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, } + private static byte[] tweakJdkRegression(byte[] token) throws GSSException { + +// Due to regression in 8u40/8u45 described in +// https://bugs.openjdk.java.net/browse/JDK-8078439 +// try to tweak token package if it looks like it has +// OID's in wrong order +// +// 0000: 60 82 06 5C 06 06 2B 06 01 05 05 02 A0 82 06 50 +// 0010: 30 82 06 4C A0 30 30 2E |06 09 2A 86 48 82 F7 12 +// 0020: 01 02 02|06 09 2A 86 48 86 F7 12 01 02 02 06|0A +// 0030: 2B 06 01 04 01 82 37 02 02 1E 06 0A 2B 06 01 04 +// 0040: 01 82 37 02 02 0A A2 82 06 16 04 82 06 12 60 82 +// +// In above package first token is in position 24 and second +// in 35 with both having size 11. +// +// We simple check if we have these two in this order and swap +// +// Below code would create two arrays, lets just create that +// manually because it doesn't change +// Oid GSS_KRB5_MECH_OID = new Oid("1.2.840.113554.1.2.2"); +// Oid MS_KRB5_MECH_OID = new Oid("1.2.840.48018.1.2.2"); +// byte[] der1 = GSS_KRB5_MECH_OID.getDER(); +// byte[] der2 = MS_KRB5_MECH_OID.getDER(); + +// 0000: 06 09 2A 86 48 86 F7 12 01 02 02 +// 0000: 06 09 2A 86 48 82 F7 12 01 02 02 + + if (token == null || token.length < 48) { + return token; + } + + int[] toCheck = new int[] { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x82, 0xF7, 0x12, 0x01, 0x02, 0x02, 0x06, 0x09, 0x2A, + 0x86, 0x48, 0x86, 0xF7, 0x12, 0x01, 0x02, 0x02 }; + + for (int i = 0; i < 22; i++) { + if ((byte) toCheck[i] != token[i + 24]) { + return token; + } + } + + byte[] nt = new byte[token.length]; + System.arraycopy(token, 0, nt, 0, 24); + System.arraycopy(token, 35, nt, 24, 11); + System.arraycopy(token, 24, nt, 35, 11); + System.arraycopy(token, 46, nt, 46, token.length - 24 - 11 - 11); + return nt; + } + } diff --git a/spring-security-kerberos-core/src/test/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidatorTests.java b/spring-security-kerberos-core/src/test/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidatorTests.java new file mode 100644 index 0000000..7af14d0 --- /dev/null +++ b/spring-security-kerberos-core/src/test/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidatorTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.kerberos.authentication.sun; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.crypto.codec.Base64; + +public class SunJaasKerberosTicketValidatorTests { + + // copy of token taken from a test where windows host + // is trying to authenticate with spnego. nothing sensitive here + private static String header = "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" + + "eL2PHBfvkne/FgxC"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testJdkMsKrb5OIDRegressionTweak() throws Exception { + thrown.expect(BadCredentialsException.class); + thrown.expectMessage(not(containsString("GSSContext name of the context initiator is null"))); + thrown.expectMessage(containsString("Kerberos validation not succesful")); + SunJaasKerberosTicketValidator validator = new SunJaasKerberosTicketValidator(); + byte[] kerberosTicket = Base64.decode(header.getBytes()); + validator.validateTicket(kerberosTicket); + } + +}