changeset 2461:ea8c57ec8409

6951366: kerberos login failure on win2008 with AD set to win2000 compat mode Reviewed-by: valeriep, xuelei
author weijun
date Fri, 04 Jun 2010 19:28:53 +0800
parents 1db252f307b6
children 489c1720757b
files src/share/classes/sun/security/krb5/Credentials.java src/share/classes/sun/security/krb5/EncryptionKey.java src/share/classes/sun/security/krb5/KrbAsReq.java src/windows/classes/sun/security/krb5/internal/tools/Kinit.java test/sun/security/krb5/auto/Context.java test/sun/security/krb5/auto/KDC.java test/sun/security/krb5/auto/W83.java
diffstat 7 files changed, 244 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/security/krb5/Credentials.java	Wed Jun 02 17:53:54 2010 -0700
+++ b/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/src/share/classes/sun/security/krb5/EncryptionKey.java	Wed Jun 02 17:53:54 2010 -0700
+++ b/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/src/share/classes/sun/security/krb5/KrbAsReq.java	Wed Jun 02 17:53:54 2010 -0700
+++ b/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/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java	Wed Jun 02 17:53:54 2010 -0700
+++ b/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/test/sun/security/krb5/auto/Context.java	Wed Jun 02 17:53:54 2010 -0700
+++ b/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/test/sun/security/krb5/auto/KDC.java	Wed Jun 02 17:53:54 2010 -0700
+++ b/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/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();
+    }
+}