6951366: kerberos login failure on win2008 with AD set to win2000 compat mode
Reviewed-by: valeriep, xuelei
--- a/jdk/src/share/classes/sun/security/krb5/Credentials.java Wed Jun 02 17:53:54 2010 -0700
+++ b/jdk/src/share/classes/sun/security/krb5/Credentials.java Fri Jun 04 19:28:53 2010 +0800
@@ -356,6 +356,7 @@
* @param princ the client principal. This value cannot be null.
* @param secretKey the secret key of the client principal.This value
* cannot be null.
+ * @param password if null, caller is using a keytab
* @returns the TGT credentials
*/
public static Credentials acquireTGT(PrincipalName princ,
@@ -372,8 +373,18 @@
"Cannot have null secretKey to do AS-Exchange");
KrbAsRep asRep = null;
+
+ // The etype field to be placed in AS-REQ. If caller is using keytab,
+ // it must be limited to etypes in keytab. Otherwise, leave it null,
+ // and KrbAsReq will populate it with all supported etypes.
+
+ int[] eTypes = null;
+ if (password == null) {
+ eTypes = EncryptionKey.getETypes(secretKeys);
+ }
+
try {
- asRep = sendASRequest(princ, secretKeys, null);
+ asRep = sendASRequest(princ, secretKeys, eTypes, null);
} catch (KrbException ke) {
if ((ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) ||
(ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED)) {
@@ -396,7 +407,7 @@
princ.getSalt(), true,
error.getEType(), error.getParams());
}
- asRep = sendASRequest(princ, secretKeys, ke.getError());
+ asRep = sendASRequest(princ, secretKeys, eTypes, ke.getError());
} else {
throw ke;
}
@@ -406,17 +417,18 @@
/**
* Sends the AS-REQ
+ * @param eTypes not null if caller using keytab
*/
private static KrbAsRep sendASRequest(PrincipalName princ,
- EncryptionKey[] secretKeys, KRBError error)
+ EncryptionKey[] secretKeys, int[] eTypes, KRBError error)
throws KrbException, IOException {
// %%%
KrbAsReq asReq = null;
if (error == null) {
- asReq = new KrbAsReq(princ, secretKeys);
+ asReq = new KrbAsReq(princ, secretKeys, eTypes);
} else {
- asReq = new KrbAsReq(princ, secretKeys, true,
+ asReq = new KrbAsReq(princ, secretKeys, eTypes, true,
error.getEType(), error.getSalt(), error.getParams());
}
--- a/jdk/src/share/classes/sun/security/krb5/EncryptionKey.java Wed Jun 02 17:53:54 2010 -0700
+++ b/jdk/src/share/classes/sun/security/krb5/EncryptionKey.java Fri Jun 04 19:28:53 2010 +0800
@@ -76,6 +76,26 @@
private static final boolean DEBUG = Krb5.DEBUG;
+ public static int[] getETypes(EncryptionKey[] keys) {
+ int len = keys.length;
+ int[] result = new int[len];
+ int count = 0; // Number of elements in result. Might be less than
+ // len if there are keys having the same etype
+ loopi: for (int i=0; i<len; i++) {
+ int eType = keys[i].getEType();
+ for (int j=0; j<count; j++) {
+ if (result[j] == eType) {
+ continue loopi;
+ }
+ }
+ result[count++] = eType;
+ }
+ if (count != len) {
+ result = Arrays.copyOf(result, count);
+ }
+ return result;
+ }
+
public synchronized int getEType() {
return keyType;
}
--- a/jdk/src/share/classes/sun/security/krb5/KrbAsReq.java Wed Jun 02 17:53:54 2010 -0700
+++ b/jdk/src/share/classes/sun/security/krb5/KrbAsReq.java Fri Jun 04 19:28:53 2010 +0800
@@ -35,11 +35,9 @@
import sun.security.krb5.internal.crypto.EType;
import sun.security.krb5.internal.crypto.Nonce;
import sun.security.krb5.internal.crypto.KeyUsage;
-import sun.security.util.*;
import java.io.IOException;
-import java.io.ByteArrayInputStream;
import java.net.UnknownHostException;
-import java.util.StringTokenizer;
+import java.util.Arrays;
/**
* This class encapsulates the KRB-AS-REQ message that the client
@@ -64,11 +62,13 @@
/**
* Creates a KRB-AS-REQ to send to the default KDC
+ * @param eTypes not null when using a keytab, this can make sure the etypes
+ * in AS-REQ contains only those available on client
* @throws KrbException
* @throws IOException
*/
// Called by Credentials
- KrbAsReq(PrincipalName principal, EncryptionKey[] keys)
+ KrbAsReq(PrincipalName principal, EncryptionKey[] keys, int[] eTypes)
throws KrbException, IOException {
this(keys, // for pre-authentication
false, 0, null, null, // pre-auth values
@@ -78,7 +78,7 @@
null, // KerberosTime from
null, // KerberosTime till
null, // KerberosTime rtime
- null, // int[] eTypes
+ eTypes, // int[] eTypes
null, // HostAddresses addresses
null); // Ticket[] additionalTickets
}
@@ -86,8 +86,10 @@
/**
* Creates a KRB-AS-REQ to send to the default KDC
* with pre-authentication values
+ * @param eTypes not null when using a keytab, this can make sure the etypes
+ * in AS-REQ contains only those available on client
*/
- KrbAsReq(PrincipalName principal, EncryptionKey[] keys,
+ KrbAsReq(PrincipalName principal, EncryptionKey[] keys, int[] eTypes,
boolean pa_exists, int etype, String salt, byte[] s2kparams)
throws KrbException, IOException {
this(keys, // for pre-authentication
@@ -98,7 +100,7 @@
null, // KerberosTime from
null, // KerberosTime till
null, // KerberosTime rtime
- null, // int[] eTypes
+ eTypes, // int[] eTypes
null, // HostAddresses addresses
null); // Ticket[] additionalTickets
}
@@ -343,19 +345,25 @@
princName = cname;
- EncryptionKey key = null;
- int[] tktETypes = EType.getDefaults("default_tkt_enctypes");
- if (pa_exists && pa_etype != EncryptedData.ETYPE_NULL) {
- if (DEBUG) {
- System.out.println("Pre-Authenticaton: find key for etype = " + pa_etype);
- }
- key = EncryptionKey.findKey(pa_etype, keys);
- } else {
- key = EncryptionKey.findKey(tktETypes[0], keys);
- }
+ // keys might contain many etypes, or only one if in preauth mode,
+ // coz EncryptionKey.acquireSecretKeys() with pa returns only one key.
PAData[] paData = null;
if (PA_ENC_TIMESTAMP_REQUIRED) {
+ EncryptionKey key = null;
+ if (pa_etype != EncryptedData.ETYPE_NULL) {
+ if (DEBUG) {
+ System.out.println("Pre-Authenticaton: " +
+ "find key for etype = " + pa_etype);
+ }
+ key = EncryptionKey.findKey(pa_etype, keys);
+ } else {
+ int[] availableETypes =
+ EType.getDefaults("default_tkt_enctypes", keys);
+ if (availableETypes.length > 0) {
+ key = EncryptionKey.findKey(availableETypes[0], keys);
+ }
+ }
if (DEBUG) {
System.out.println("AS-REQ: Add PA_ENC_TIMESTAMP now");
}
@@ -376,7 +384,7 @@
}
if (eTypes == null) {
- eTypes = tktETypes;
+ eTypes = EType.getDefaults("default_tkt_enctypes");
}
// check to use addresses in tickets
--- a/jdk/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java Wed Jun 02 17:53:54 2010 -0700
+++ b/jdk/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java Fri Jun 04 19:28:53 2010 +0800
@@ -229,7 +229,9 @@
if (useKeytab) {
as_req = new KrbAsReq(skeys, opt,
principal, sname,
- null, null, null, null, addresses, null);
+ null, null, null,
+ EncryptionKey.getETypes(skeys),
+ addresses, null);
} else {
as_req = new KrbAsReq(psswd, opt,
principal, sname,
@@ -257,7 +259,9 @@
if (useKeytab) {
as_req = new KrbAsReq(skeys, true, etype, salt,
s2kparams, opt, principal, sname,
- null, null, null, null, addresses, null);
+ null, null, null,
+ EncryptionKey.getETypes(skeys),
+ addresses, null);
} else {
as_req = new KrbAsReq(psswd, true, etype, salt,
s2kparams, opt, principal, sname,
--- a/jdk/test/sun/security/krb5/auto/Context.java Wed Jun 02 17:53:54 2010 -0700
+++ b/jdk/test/sun/security/krb5/auto/Context.java Fri Jun 04 19:28:53 2010 +0800
@@ -42,6 +42,7 @@
import com.sun.security.jgss.ExtendedGSSContext;
import com.sun.security.jgss.InquireType;
import com.sun.security.jgss.AuthorizationDataEntry;
+import java.io.File;
/**
* Context of a JGSS subject, encapsulating Subject and GSSContext.
@@ -107,7 +108,8 @@
* Logins with a username and a password, using Krb5LoginModule directly
* @param storeKey true if key should be saved, used on acceptor side
*/
- public static Context fromUserPass(String user, char[] pass, boolean storeKey) throws Exception {
+ public static Context fromUserPass(String user, char[] pass, boolean storeKey)
+ throws Exception {
Context out = new Context();
out.name = user;
out.s = new Subject();
@@ -137,6 +139,33 @@
}
/**
+ * Logins with a username and a keytab, using Krb5LoginModule directly
+ * @param storeKey true if key should be saved, used on acceptor side
+ */
+ public static Context fromUserKtab(String user, String ktab, boolean storeKey)
+ throws Exception {
+ Context out = new Context();
+ out.name = user;
+ out.s = new Subject();
+ Krb5LoginModule krb5 = new Krb5LoginModule();
+ Map<String, String> map = new HashMap<String, String>();
+
+ map.put("doNotPrompt", "true");
+ map.put("useTicketCache", "false");
+ map.put("useKeyTab", "true");
+ map.put("keyTab", ktab);
+ map.put("principal", user);
+ if (storeKey) {
+ map.put("storeKey", "true");
+ }
+
+ krb5.initialize(out.s, null, null, map);
+ krb5.login();
+ krb5.commit();
+ return out;
+ }
+
+ /**
* Starts as a client
* @param target communication peer
* @param mech GSS mech
--- a/jdk/test/sun/security/krb5/auto/KDC.java Wed Jun 02 17:53:54 2010 -0700
+++ b/jdk/test/sun/security/krb5/auto/KDC.java Fri Jun 04 19:28:53 2010 +0800
@@ -35,6 +35,7 @@
import sun.security.krb5.*;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.ccache.CredentialsCache;
+import sun.security.krb5.internal.crypto.EType;
import sun.security.krb5.internal.crypto.KeyUsage;
import sun.security.krb5.internal.ktab.KeyTab;
import sun.security.util.DerInputStream;
@@ -153,6 +154,10 @@
* Whether pre-authentication is required. Default Boolean.TRUE
*/
PREAUTH_REQUIRED,
+ /**
+ * Onlyy issue TGT in RC4
+ */
+ ONLY_RC4_TGT,
};
static {
@@ -743,13 +748,25 @@
Field f = KDCReqBody.class.getDeclaredField("eType");
f.setAccessible(true);
eTypes = (int[])f.get(body);
- if (eTypes.length < 2) {
- throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
- }
int eType = eTypes[0];
EncryptionKey ckey = keyForUser(body.cname, eType, false);
EncryptionKey skey = keyForUser(body.sname, eType, true);
+
+ if (options.containsKey(KDC.Option.ONLY_RC4_TGT)) {
+ int tgtEType = EncryptedData.ETYPE_ARCFOUR_HMAC;
+ boolean found = false;
+ for (int i=0; i<eTypes.length; i++) {
+ if (eTypes[i] == tgtEType) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
+ }
+ skey = keyForUser(body.sname, tgtEType, true);
+ }
if (ckey == null) {
throw new KrbException(Krb5.KDC_ERR_ETYPE_NOSUPP);
}
@@ -799,7 +816,8 @@
Constructor<EncryptedData> ctor = EncryptedData.class.getDeclaredConstructor(DerValue.class);
ctor.setAccessible(true);
EncryptedData data = ctor.newInstance(new DerValue(pas[0].getValue()));
- data.decrypt(ckey, KeyUsage.KU_PA_ENC_TS);
+ EncryptionKey pakey = keyForUser(body.cname, data.getEType(), false);
+ data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS);
} catch (Exception e) {
throw new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED);
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/auto/W83.java Fri Jun 04 19:28:53 2010 +0800
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6951366
+ * @summary kerberos login failure on win2008 with AD set to win2000 compat mode
+ */
+import com.sun.security.auth.module.Krb5LoginModule;
+import java.io.File;
+import sun.security.krb5.Config;
+import sun.security.krb5.EncryptedData;
+import sun.security.krb5.PrincipalName;
+import sun.security.krb5.internal.crypto.EType;
+import sun.security.krb5.internal.ktab.KeyTab;
+
+public class W83 {
+ public static void main(String[] args) throws Exception {
+
+ W83 x = new W83();
+
+ // Cannot use OneKDC. kinit command cannot resolve
+ // hostname kdc.rabbit.hole
+ KDC kdc = new KDC(OneKDC.REALM, "127.0.0.1", 0, true);
+ kdc.addPrincipal(OneKDC.USER, OneKDC.PASS);
+ kdc.addPrincipalRandKey("krbtgt/" + OneKDC.REALM);
+ KDC.saveConfig(OneKDC.KRB5_CONF, kdc);
+ System.setProperty("java.security.krb5.conf", OneKDC.KRB5_CONF);
+ Config.refresh();
+
+ kdc.writeKtab(OneKDC.KTAB);
+ new File(OneKDC.KRB5_CONF).deleteOnExit();
+ new File(OneKDC.KTAB).deleteOnExit();
+
+ kdc.setOption(KDC.Option.ONLY_RC4_TGT, true);
+
+ KeyTab ktab = KeyTab.getInstance(OneKDC.KTAB);
+ for (int etype: EType.getBuiltInDefaults()) {
+ if (etype != EncryptedData.ETYPE_ARCFOUR_HMAC) {
+ ktab.deleteEntry(new PrincipalName(OneKDC.USER), etype);
+ }
+ }
+ ktab.save();
+ x.go();
+ }
+
+ void go() throws Exception {
+ Krb5LoginModule krb5 = new Krb5LoginModule();
+ StringBuffer error = new StringBuffer();
+ try {
+ Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+ } catch (Exception e) {
+ error.append("Krb5LoginModule password login error\n");
+ }
+ try {
+ Context.fromUserKtab(OneKDC.USER, OneKDC.KTAB, false);
+ } catch (Exception e) {
+ error.append("Krb5LoginModule keytab login error\n");
+ }
+ try {
+ Class.forName("sun.security.krb5.internal.tools.Kinit");
+ String cmd = System.getProperty("java.home") +
+ System.getProperty("file.separator") +
+ "bin" +
+ System.getProperty("file.separator") +
+ "kinit";
+
+ int p = execute(
+ cmd,
+ "-J-Djava.security.krb5.conf=" + OneKDC.KRB5_CONF,
+ "-c", "cache1",
+ OneKDC.USER,
+ new String(OneKDC.PASS));
+ if (p != 0) {
+ error.append("kinit password login error\n");
+ }
+ p = execute(
+ cmd,
+ "-J-Djava.security.krb5.conf=" + OneKDC.KRB5_CONF,
+ "-c", "cache2",
+ "-k", "-t", OneKDC.KTAB,
+ OneKDC.USER);
+ if (p != 0) {
+ error.append("kinit keytab login error\n");
+ }
+ } catch (ClassNotFoundException cnfe) {
+ System.out.println("No kinit, test ignored.");
+ // Ignore, not on windows
+ }
+ if (error.length() != 0) {
+ throw new Exception(error.toString());
+ }
+ }
+
+ private static int execute(String... args) throws Exception {
+ for (String arg: args) {
+ System.out.printf("%s ", arg);
+ }
+ System.out.println();
+ Process p = Runtime.getRuntime().exec(args);
+ return p.waitFor();
+ }
+}