From 6c52fbd7b348700efa6fbc49aedf96336fd625ac Mon Sep 17 00:00:00 2001 From: weijun Date: Thu, 28 Oct 2010 21:14:44 +0800 Subject: [PATCH] 6950546: "ktab -d name etype" to "ktab -d name [-e etype] [kvno | all | old]" 6984764: kerberos fails if service side keytab is generated using JDK ktab Reviewed-by: valeriep --- .../security/krb5/internal/ktab/KeyTab.java | 206 ++++++------ .../security/krb5/internal/tools/Ktab.java | 303 +++++++++++------- test/sun/security/krb5/auto/KDC.java | 2 +- test/sun/security/krb5/tools/KtabCheck.java | 59 ++++ test/sun/security/krb5/tools/ktcheck.sh | 94 ++++++ test/sun/security/krb5/tools/onlythree.conf | 9 + 6 files changed, 441 insertions(+), 232 deletions(-) create mode 100644 test/sun/security/krb5/tools/KtabCheck.java create mode 100644 test/sun/security/krb5/tools/ktcheck.sh create mode 100644 test/sun/security/krb5/tools/onlythree.conf diff --git a/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java b/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java index 623d6252b..e0624e4db 100644 --- a/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java +++ b/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java @@ -34,7 +34,6 @@ package sun.security.krb5.internal.ktab; import sun.security.krb5.*; import sun.security.krb5.internal.*; import sun.security.krb5.internal.crypto.*; -import java.util.Vector; import java.util.ArrayList; import java.util.Arrays; import java.io.IOException; @@ -42,7 +41,10 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.File; import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; import java.util.StringTokenizer; +import java.util.Vector; /** * This class represents key table. The key table functions deal with storing @@ -239,23 +241,22 @@ public class KeyTab implements KeyTabConstants { EncryptionKey key; int size = entries.size(); ArrayList keys = new ArrayList (size); - if (entries != null) { - for (int i = size-1; i >= 0; i--) { - entry = entries.elementAt(i); - if (entry.service.match(service)) { - if (EType.isSupported(entry.keyType)) { - key = new EncryptionKey(entry.keyblock, - entry.keyType, - new Integer(entry.keyVersion)); - keys.add(key); - if (DEBUG) { - System.out.println("Added key: " + entry.keyType + - "version: " + entry.keyVersion); - } - } else if (DEBUG) { - System.out.println("Found unsupported keytype (" + - entry.keyType + ") for " + service); + + for (int i = size-1; i >= 0; i--) { + entry = entries.elementAt(i); + if (entry.service.match(service)) { + if (EType.isSupported(entry.keyType)) { + key = new EncryptionKey(entry.keyblock, + entry.keyType, + new Integer(entry.keyVersion)); + keys.add(key); + if (DEBUG) { + System.out.println("Added key: " + entry.keyType + + "version: " + entry.keyVersion); } + } else if (DEBUG) { + System.out.println("Found unsupported keytype (" + + entry.keyType + ") for " + service); } } } @@ -313,16 +314,14 @@ public class KeyTab implements KeyTabConstants { */ public boolean findServiceEntry(PrincipalName service) { KeyTabEntry entry; - if (entries != null) { - for (int i = 0; i < entries.size(); i++) { - entry = entries.elementAt(i); - if (entry.service.match(service)) { - if (EType.isSupported(entry.keyType)) { - return true; - } else if (DEBUG) { - System.out.println("Found unsupported keytype (" + - entry.keyType + ") for " + service); - } + for (int i = 0; i < entries.size(); i++) { + entry = entries.elementAt(i); + if (entry.service.match(service)) { + if (EType.isSupported(entry.keyType)) { + return true; + } else if (DEBUG) { + System.out.println("Found unsupported keytype (" + + entry.keyType + ") for " + service); } } } @@ -337,94 +336,57 @@ public class KeyTab implements KeyTabConstants { * Adds a new entry in the key table. * @param service the service which will have a new entry in the key table. * @param psswd the password which generates the key. + * @param kvno the kvno to use, -1 means automatic increasing + * @param append false if entries with old kvno would be removed. + * Note: if kvno is not -1, entries with the same kvno are always removed */ - public void addEntry(PrincipalName service, char[] psswd) - throws KrbException { + public void addEntry(PrincipalName service, char[] psswd, + int kvno, boolean append) throws KrbException { EncryptionKey[] encKeys = EncryptionKey.acquireSecretKeys( psswd, service.getSalt()); - for (int i = 0; encKeys != null && i < encKeys.length; i++) { - int keyType = encKeys[i].getEType(); - byte[] keyValue = encKeys[i].getBytes(); - int result = retrieveEntry(service, keyType); - int kvno = 1; - if (result != -1) { - KeyTabEntry oldEntry = entries.elementAt(result); - kvno = oldEntry.keyVersion; - entries.removeElementAt(result); - kvno += 1; - } else - kvno = 1; + // There should be only one maximum KVNO value for all etypes, so that + // all added keys can have the same KVNO. - KeyTabEntry newEntry = new KeyTabEntry(service, - service.getRealm(), - new KerberosTime(System.currentTimeMillis()), - kvno, keyType, keyValue); - if (entries == null) - entries = new Vector (); - entries.addElement(newEntry); + int maxKvno = 0; // only useful when kvno == -1 + for (int i = entries.size()-1; i >= 0; i--) { + KeyTabEntry e = entries.get(i); + if (e.service.match(service)) { + if (e.keyVersion > maxKvno) { + maxKvno = e.keyVersion; + } + if (!append || e.keyVersion == kvno) { + entries.removeElementAt(i); + } + } + } + if (kvno == -1) { + kvno = maxKvno + 1; } - } - - /** - * Only used by KDC test. This method can specify kvno and does not - * remove any old keys. - */ - public void addEntry(PrincipalName service, char[] psswd, int kvno) - throws KrbException { - - EncryptionKey[] encKeys = EncryptionKey.acquireSecretKeys( - psswd, service.getSalt()); for (int i = 0; encKeys != null && i < encKeys.length; i++) { int keyType = encKeys[i].getEType(); byte[] keyValue = encKeys[i].getBytes(); + KeyTabEntry newEntry = new KeyTabEntry(service, service.getRealm(), new KerberosTime(System.currentTimeMillis()), kvno, keyType, keyValue); - if (entries == null) - entries = new Vector (); entries.addElement(newEntry); } } - /** - * Retrieves the key table entry with the specified service name. - * @param service the service which may have an entry in the key table. - * @param keyType the etype to match, returns the 1st one if -1 provided - * @return -1 if the entry is not found, else return the entry index - * in the list. - */ - private int retrieveEntry(PrincipalName service, int keyType) { - KeyTabEntry e; - if (entries != null) { - for (int i = 0; i < entries.size(); i++) { - e = entries.elementAt(i); - if (service.match(e.getService()) && - (keyType == -1 || e.keyType == keyType)) { - return i; - } - } - } - return -1; - } - /** * Gets the list of service entries in key table. * @return array of KeyTabEntry. */ public KeyTabEntry[] getEntries() { - if (entries != null) { - KeyTabEntry[] kentries = new KeyTabEntry[entries.size()]; - for (int i = 0; i < kentries.length; i++) { - kentries[i] = entries.elementAt(i); - } - return kentries; - } else { - return null; + KeyTabEntry[] kentries = new KeyTabEntry[entries.size()]; + for (int i = 0; i < kentries.length; i++) { + kentries[i] = entries.elementAt(i); } + return kentries; } /** @@ -464,29 +426,55 @@ public class KeyTab implements KeyTabConstants { } /** - * Removes an entry from the key table. + * Removes entries from the key table. * @param service the service PrincipalName. - * @param etype the etype to match, first one if -1 provided - * @return 1 if removed successfully, 0 otherwise + * @param etype the etype to match, remove all if -1 + * @param kvno what kvno to remove, -1 for all, -2 for old + * @return the number of entries deleted */ - public int deleteEntry(PrincipalName service, int etype) { - int result = retrieveEntry(service, etype); - if (result != -1) { - entries.removeElementAt(result); - return 1; + public int deleteEntries(PrincipalName service, int etype, int kvno) { + int count = 0; + + // Remember the highest KVNO for each etype. Used for kvno == -2 + Map highest = new HashMap<>(); + + for (int i = entries.size()-1; i >= 0; i--) { + KeyTabEntry e = entries.get(i); + if (service.match(e.getService())) { + if (etype == -1 || e.keyType == etype) { + if (kvno == -2) { + // Two rounds for kvno == -2. In the first round (here), + // only find out highest KVNO for each etype + if (highest.containsKey(e.keyType)) { + int n = highest.get(e.keyType); + if (e.keyVersion > n) { + highest.put(e.keyType, e.keyVersion); + } + } else { + highest.put(e.keyType, e.keyVersion); + } + } else if (kvno == -1 || e.keyVersion == kvno) { + entries.removeElementAt(i); + count++; + } + } + } } - return 0; - } - /** - * Removes an entry from the key table. - * @param service the service PrincipalName. - * @return number of entries removed - */ - public int deleteEntry(PrincipalName service) { - int count = 0; - while (deleteEntry(service, -1) > 0) { - count++; + // Second round for kvno == -2, remove old entries + if (kvno == -2) { + for (int i = entries.size()-1; i >= 0; i--) { + KeyTabEntry e = entries.get(i); + if (service.match(e.getService())) { + if (etype == -1 || e.keyType == etype) { + int n = highest.get(e.keyType); + if (e.keyVersion != n) { + entries.removeElementAt(i); + count++; + } + } + } + } } return count; } diff --git a/src/windows/classes/sun/security/krb5/internal/tools/Ktab.java b/src/windows/classes/sun/security/krb5/internal/tools/Ktab.java index a92cb1b0e..9bcc935a1 100644 --- a/src/windows/classes/sun/security/krb5/internal/tools/Ktab.java +++ b/src/windows/classes/sun/security/krb5/internal/tools/Ktab.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -39,10 +39,11 @@ import java.io.File; import java.text.DateFormat; import java.util.Arrays; import java.util.Date; +import java.util.Locale; import sun.security.krb5.internal.crypto.EType; /** * This class can execute as a command-line tool to help the user manage - * entires in the key table. + * entries in the key table. * Available functions include list/add/update/delete service key(s). * * @author Yanni Zhang @@ -60,30 +61,20 @@ public class Ktab { int etype = -1; char[] password = null; + boolean forced = false; // true if delete without prompt. Default false + boolean append = false; // true if new keys are appended. Default false + int vDel = -1; // kvno to delete, -1 all, -2 old. Default -1 + int vAdd = -1; // kvno to add. Default -1, means auto incremented + /** * The main program that can be invoked at command line. - *
Usage: ktab - *
available options to Ktab: - *
    - *
  • -l [-e] [-t] list the keytab name and entries, -e show - * encryption etypes, -t show timestamps. - *
  • -a <principal name> - * (<password>) add an entry to the keytab. - * The entry is added only to the keytab. No changes are made to the - * Kerberos database. - *
  • -d <principal name> [<etype>] - * delete an entry from the keytab. - * The entry is deleted only from the keytab. No changes are made to the - * Kerberos database. - *
  • -k <keytab name > - * specify keytab name and path with prefix FILE: - *
  • -help display instructions. + * See {@link #printHelp} for usages. */ public static void main(String[] args) { Ktab ktab = new Ktab(); if ((args.length == 1) && (args[0].equalsIgnoreCase("-help"))) { ktab.printHelp(); - System.exit(0); + return; } else if ((args == null) || (args.length == 0)) { ktab.action = 'l'; } else { @@ -139,7 +130,6 @@ public class Ktab { break; default: ktab.printHelp(); - System.exit(-1); } } @@ -147,84 +137,129 @@ public class Ktab { * Parses the command line arguments. */ void processArgs(String[] args) { - Character arg = null; + + // Commands (should appear before options): + // -l + // -a + // -d + // Options: + // -e (for -d) + // -e (for -l) + // -n + // -k + // -t + // -f + // -append + // Optional extra arguments: + // password for -a + // [kvno|all|old] for -d + + boolean argAlreadyAppeared = false; for (int i = 0; i < args.length; i++) { - if ((args[i].length() == 2) && (args[i].startsWith("-"))) { - arg = new Character(args[i].charAt(1)); - } else { - printHelp(); - System.exit(-1); - } - switch (arg.charValue()) { - case 'l': - case 'L': - action = 'l'; // list keytab location, name and entries - break; - case 'a': - case 'A': - action = 'a'; // add a new entry to keytab. - i++; - if ((i < args.length) && (!args[i].startsWith("-"))) { - principal = args[i]; - } else { - System.out.println("Please specify the principal name"+ - " after -a option."); - printHelp(); - System.exit(-1); + if (args[i].startsWith("-")) { + switch (args[i].toLowerCase(Locale.US)) { + + // Commands + case "-l": // list + action = 'l'; + break; + case "-a": // add a new entry to keytab. + action = 'a'; + if (++i >= args.length || args[i].startsWith("-")) { + error("A principal name must be specified after -a"); + } + principal = args[i]; + break; + case "-d": // delete entries + action = 'd'; + if (++i >= args.length || args[i].startsWith("-")) { + error("A principal name must be specified after -d"); + } + principal = args[i]; + break; + + // Options + case "-e": + if (action == 'l') { // list etypes + showEType = true; + } else if (action == 'd') { // delete etypes + if (++i >= args.length || args[i].startsWith("-")) { + error("An etype must be specified after -e"); + } + try { + etype = Integer.parseInt(args[i]); + if (etype <= 0) { + throw new NumberFormatException(); + } + } catch (NumberFormatException nfe) { + error(args[i] + " is not a valid etype"); + } + } else { + error(args[i] + " is not valid after -" + action); + } + break; + case "-n": // kvno for -a + if (++i >= args.length || args[i].startsWith("-")) { + error("A KVNO must be specified after -n"); + } + try { + vAdd = Integer.parseInt(args[i]); + if (vAdd < 0) { + throw new NumberFormatException(); + } + } catch (NumberFormatException nfe) { + error(args[i] + " is not a valid KVNO"); + } + break; + case "-k": // specify keytab to use + if (++i >= args.length || args[i].startsWith("-")) { + error("A keytab name must be specified after -k"); + } + if (args[i].length() >= 5 && + args[i].substring(0, 5).equalsIgnoreCase("FILE:")) { + name = args[i].substring(5); + } else { + name = args[i]; + } + break; + case "-t": // list timestamps + showTime = true; + break; + case "-f": // force delete, no prompt + forced = true; + break; + case "-append": // -a, new keys append to file + append = true; + break; + default: + printHelp(); + break; } - if ((i + 1 < args.length) && - (!args[i + 1].startsWith("-"))) { - password = args[i + 1].toCharArray(); - i++; - } else { - password = null; // prompt user for password later. + } else { // optional standalone arguments + if (argAlreadyAppeared) { + error("Useless extra argument " + args[i]); } - break; - case 'd': - case 'D': - action = 'd'; // delete an entry. - i++; - if ((i < args.length) && (!args[i].startsWith("-"))) { - principal = args[i]; - int j = i + 1; - if ((j < args.length) && (!args[j].startsWith("-"))) { - etype = Integer.parseInt(args[j]); - i = j; + if (action == 'a') { + password = args[i].toCharArray(); + } else if (action == 'd') { + switch (args[i]) { + case "all": vDel = -1; break; + case "old": vDel = -2; break; + default: { + try { + vDel = Integer.parseInt(args[i]); + if (vDel < 0) { + throw new NumberFormatException(); + } + } catch (NumberFormatException nfe) { + error(args[i] + " is not a valid KVNO"); + } + } } } else { - System.out.println("Please specify the principal" + - "name of the entry you want to " + - " delete after -d option."); - printHelp(); - System.exit(-1); + error("Useless extra argument " + args[i]); } - break; - case 'k': - case 'K': - i++; - if ((i < args.length) && (!args[i].startsWith("-"))) { - if (args[i].length() >= 5 && - args[i].substring(0, 5).equalsIgnoreCase("FILE:")) { - name = args[i].substring(5); - } else - name = args[i]; - } else { - System.out.println("Please specify the keytab "+ - "file name and location " + - "after -k option"); - printHelp(); - System.exit(-1); - } - break; - case 'e': - showEType = true; - break; - case 't': - showTime = true; - break; - default: - printHelp(); - System.exit(-1); + argAlreadyAppeared = true; } } } @@ -263,7 +298,7 @@ public class Ktab { } try { // admin.addEntry(pname, password); - table.addEntry(pname, password); + table.addEntry(pname, password, vAdd, append); Arrays.fill(password, '0'); // clear password // admin.save(); table.save(); @@ -350,23 +385,25 @@ public class Ktab { if (pname.getRealm() == null) { pname.setRealm(Config.getInstance().getDefaultRealm()); } - String answer; - BufferedReader cis = - new BufferedReader(new InputStreamReader(System.in)); - System.out.print("Are you sure you want to"+ - " delete service key for " + pname.toString() + - " (" + (etype==-1?"all etypes":("etype = "+etype)) + - ") in " + table.tabName() + "?(Y/N): "); + if (!forced) { + String answer; + BufferedReader cis = + new BufferedReader(new InputStreamReader(System.in)); + System.out.print("Are you sure you want to delete "+ + "service key(s) for " + pname.toString() + + " (" + (etype==-1?"all etypes":("etype="+etype)) + ", " + + (vDel==-1?"all kvno":(vDel==-2?"old kvno":("kvno=" + vDel))) + + ") in " + table.tabName() + "? (Y/[N]): "); - System.out.flush(); - answer = cis.readLine(); - if (answer.equalsIgnoreCase("Y") || - answer.equalsIgnoreCase("Yes")); - else { - // no error, the user did not want to delete the entry - System.exit(0); + System.out.flush(); + answer = cis.readLine(); + if (answer.equalsIgnoreCase("Y") || + answer.equalsIgnoreCase("Yes")); + else { + // no error, the user did not want to delete the entry + System.exit(0); + } } - } catch (KrbException e) { System.err.println("Error occured while deleting the entry. "+ "Deletion failed."); @@ -379,9 +416,7 @@ public class Ktab { System.exit(-1); } - int count; - if (etype == -1) count = table.deleteEntry(pname); - else count = table.deleteEntry(pname, etype); + int count = table.deleteEntries(pname, etype, vDel); if (count == 0) { System.err.println("No matched entry in the keytab. " + @@ -396,23 +431,47 @@ public class Ktab { e.printStackTrace(); System.exit(-1); } - System.out.println("Done!"); + System.out.println("Done! " + count + " entries removed."); } } + void error(String... errors) { + for (String error: errors) { + System.out.println("Error: " + error + "."); + } + printHelp(); + System.exit(-1); + } /** * Prints out the help information. */ void printHelp() { - System.out.println("\nUsage: ktab " + - ""); - System.out.println("available options to Ktab:"); - System.out.println("-l [-e] [-t]\t\t\tlist the keytab name and entries,\n\t\t\t\t-e with etype, -t with timestamp"); - System.out.println("-a ()add an entry " + - "to the keytab"); - System.out.println("-d []\tdelete an "+ - "entry from the keytab"); - System.out.println("-k \t\tspecify keytab name and "+ - "path with prefix FILE:"); + System.out.println("\nUsage: ktab "); + System.out.println(); + System.out.println("Available commands:"); + System.out.println(); + System.out.println("-l [-e] [-t]\n" + + " list the keytab name and entries. -e with etype, -t with timestamp."); + System.out.println("-a [] [-n ] [-append]\n" + + " add new key entries to the keytab for the given principal name with\n" + + " optional . If a is specified, new keys' Key Version\n" + + " Numbers equal to the value, otherwise, automatically incrementing\n" + + " the Key Version Numbers. If -append is specified, new keys are\n" + + " appended to the keytab, otherwise, old keys for the\n" + + " same principal are removed."); + System.out.println("-d [-f] [-e ] [ | all | old]\n" + + " delete key entries from the keytab for the specified principal. If\n" + + " is specified, delete keys whose Key Version Numbers match\n" + + " kvno. If \"all\" is specified, delete all keys. If \"old\" is specified,\n" + + " delete all keys except those with the highest kvno. Default action\n" + + " is \"all\". If is specified, only keys of this encryption type\n" + + " are deleted. should be specified as the numberic value etype\n" + + " defined in RFC 3961, section 8. A prompt to confirm the deletion is\n" + + " displayed unless -f is specified."); + System.out.println(); + System.out.println("Common option(s):"); + System.out.println(); + System.out.println("-k \n" + + " specify keytab name and path with prefix FILE:"); } } diff --git a/test/sun/security/krb5/auto/KDC.java b/test/sun/security/krb5/auto/KDC.java index 6aca26c7f..8b56ed0f3 100644 --- a/test/sun/security/krb5/auto/KDC.java +++ b/test/sun/security/krb5/auto/KDC.java @@ -245,7 +245,7 @@ public class KDC { name.indexOf('/') < 0 ? PrincipalName.KRB_NT_UNKNOWN : PrincipalName.KRB_NT_SRV_HST), - kdc.passwords.get(name)); + kdc.passwords.get(name), -1, true); } } ktab.save(); diff --git a/test/sun/security/krb5/tools/KtabCheck.java b/test/sun/security/krb5/tools/KtabCheck.java new file mode 100644 index 000000000..0fe8b7831 --- /dev/null +++ b/test/sun/security/krb5/tools/KtabCheck.java @@ -0,0 +1,59 @@ +/* + * 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. + */ + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import sun.security.krb5.internal.ktab.KeyTab; +import sun.security.krb5.internal.ktab.KeyTabEntry; + +/** + * This class is called by the test ktcheck.sh and is not meant to run + * by itself. + */ +public class KtabCheck { + /** + * Checks if a keytab contains exactly the keys (kvno and etype) + * @param args keytabname kvno etype... + */ + public static void main(String[] args) throws Exception { + System.out.println("Checking " + Arrays.toString(args)); + KeyTab ktab = KeyTab.getInstance(args[0]); + Set expected = new HashSet(); + for (int i=1; i