changeset 4280:401ef8c488e0

Merge
author vinnie
date Thu, 21 Apr 2011 14:25:46 +0100
parents 7cd0403492b6 (current diff) 7fd31e477313 (diff)
children e9ec52c63a9f b626f78c57e1 cec45f3353be
files
diffstat 46 files changed, 1644 insertions(+), 522 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2011, 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
@@ -395,7 +395,13 @@
     private boolean succeeded = false;
     private boolean commitSucceeded = false;
     private String username;
+
+    // Encryption keys calculated from password. Assigned when storekey == true
+    // and useKeyTab == false (or true but not found)
     private EncryptionKey[] encKeys = null;
+
+    KeyTab ktab = null;
+
     private Credentials cred = null;
 
     private PrincipalName principal = null;
@@ -663,28 +669,49 @@
                         (krb5PrincName.toString(),
                          PrincipalName.KRB_NT_PRINCIPAL);
                 }
+
+                /*
+                 * Before dynamic KeyTab support (6894072), here we check if
+                 * the keytab contains keys for the principal. If no, keytab
+                 * will not be used and password is prompted for.
+                 *
+                 * After 6894072, we normally don't check it, and expect the
+                 * keys can be populated until a real connection is made. The
+                 * check is still done when isInitiator == true, where the keys
+                 * will be used right now.
+                 *
+                 * Probably tricky relations:
+                 *
+                 * useKeyTab is config flag, but when it's true but the ktab
+                 * does not contains keys for principal, we would use password
+                 * and keep the flag unchanged (for reuse?). In this method,
+                 * we use (ktab != null) to check whether keytab is used.
+                 * After this method (and when storeKey == true), we use
+                 * (encKeys == null) to check.
+                 */
                 if (useKeyTab) {
-                    encKeys =
-                        EncryptionKey.acquireSecretKeys(principal, keyTabName);
-
-                    if (debug) {
-                        if (encKeys != null)
-                            System.out.println
-                                ("principal's key obtained from the keytab");
-                        else
-                            System.out.println
-                                ("Key for the principal " +
-                                 principal  +
-                                 " not available in " +
-                                 ((keyTabName == null) ?
-                                  "default key tab" : keyTabName));
+                    ktab = (keyTabName == null)
+                                ? KeyTab.getInstance()
+                                : KeyTab.getInstance(new File(keyTabName));
+                    if (isInitiator) {
+                        if (Krb5Util.keysFromJavaxKeyTab(ktab, principal).length
+                                == 0) {
+                            ktab = null;
+                            if (debug) {
+                                System.out.println
+                                    ("Key for the principal " +
+                                     principal  +
+                                     " not available in " +
+                                     ((keyTabName == null) ?
+                                      "default key tab" : keyTabName));
+                            }
+                        }
                     }
-
                 }
 
                 KrbAsReqBuilder builder;
-                // We can't get the key from the keytab so prompt
-                if (encKeys == null) {
+
+                if (ktab == null) {
                     promptForPass(getPasswdFromSharedState);
                     builder = new KrbAsReqBuilder(principal, password);
                     if (isInitiator) {
@@ -693,9 +720,13 @@
                         // updated with PA info
                         cred = builder.action().getCreds();
                     }
-                    encKeys = builder.getKeys();
+                    if (storeKey) {
+                        encKeys = builder.getKeys();
+                        // When encKeys is empty, the login actually fails.
+                        // For compatibility, exception is thrown in commit().
+                    }
                 } else {
-                    builder = new KrbAsReqBuilder(principal, encKeys);
+                    builder = new KrbAsReqBuilder(principal, ktab);
                     if (isInitiator) {
                         cred = builder.action().getCreds();
                     }
@@ -705,10 +736,15 @@
                 if (debug) {
                     System.out.println("principal is " + principal);
                     HexDumpEncoder hd = new HexDumpEncoder();
-                    for (int i = 0; i < encKeys.length; i++) {
-                        System.out.println("EncryptionKey: keyType=" +
-                            encKeys[i].getEType() + " keyBytes (hex dump)=" +
-                            hd.encodeBuffer(encKeys[i].getBytes()));
+                    if (ktab != null) {
+                        System.out.println("Will use keytab");
+                    } else if (storeKey) {
+                        for (int i = 0; i < encKeys.length; i++) {
+                            System.out.println("EncryptionKey: keyType=" +
+                                encKeys[i].getEType() +
+                                " keyBytes (hex dump)=" +
+                                hd.encodeBuffer(encKeys[i].getBytes()));
+                        }
                     }
                 }
 
@@ -989,8 +1025,8 @@
                 kerbTicket = Krb5Util.credsToTicket(cred);
             }
 
-            if (storeKey) {
-                if (encKeys == null || encKeys.length <= 0) {
+            if (storeKey && encKeys != null) {
+                if (encKeys.length == 0) {
                     succeeded = false;
                     throw new LoginException("Null Server Key ");
                 }
@@ -1006,10 +1042,11 @@
                 }
 
             }
-            // Let us add the kerbClientPrinc,kerbTicket and kerbKey (if
+            // Let us add the kerbClientPrinc,kerbTicket and KeyTab/KerbKey (if
             // storeKey is true)
-            if (!princSet.contains(kerbClientPrinc))
+            if (!princSet.contains(kerbClientPrinc)) {
                 princSet.add(kerbClientPrinc);
+            }
 
             // add the TGT
             if (kerbTicket != null) {
@@ -1018,19 +1055,29 @@
             }
 
             if (storeKey) {
-                for (int i = 0; i < kerbKeys.length; i++) {
-                    if (!privCredSet.contains(kerbKeys[i])) {
-                        privCredSet.add(kerbKeys[i]);
+                if (encKeys == null) {
+                    if (!privCredSet.contains(ktab)) {
+                        privCredSet.add(ktab);
+                        // Compatibility; also add keys to privCredSet
+                        for (KerberosKey key: ktab.getKeys(kerbClientPrinc)) {
+                            privCredSet.add(new Krb5Util.KeysFromKeyTab(key));
+                        }
                     }
-                    encKeys[i].destroy();
-                    encKeys[i] = null;
-                    if (debug) {
-                        System.out.println("Added server's key"
-                                        + kerbKeys[i]);
-                        System.out.println("\t\t[Krb5LoginModule] " +
-                                       "added Krb5Principal  " +
-                                       kerbClientPrinc.toString()
-                                       + " to Subject");
+                } else {
+                    for (int i = 0; i < kerbKeys.length; i ++) {
+                        if (!privCredSet.contains(kerbKeys[i])) {
+                            privCredSet.add(kerbKeys[i]);
+                        }
+                        encKeys[i].destroy();
+                        encKeys[i] = null;
+                        if (debug) {
+                            System.out.println("Added server's key"
+                                            + kerbKeys[i]);
+                            System.out.println("\t\t[Krb5LoginModule] " +
+                                           "added Krb5Principal  " +
+                                           kerbClientPrinc.toString()
+                                           + " to Subject");
+                        }
                     }
                 }
             }
@@ -1106,7 +1153,8 @@
         while (it.hasNext()) {
             Object o = it.next();
             if (o instanceof KerberosTicket ||
-                o instanceof KerberosKey) {
+                    o instanceof KerberosKey ||
+                    o instanceof KeyTab) {
                 it.remove();
             }
         }
@@ -1161,6 +1209,7 @@
         } else {
             // remove temp results for the next try
             encKeys = null;
+            ktab = null;
             principal = null;
         }
         username = null;
--- a/src/share/classes/java/lang/ProcessBuilder.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/lang/ProcessBuilder.java	Thu Apr 21 14:25:46 2011 +0100
@@ -938,6 +938,11 @@
      * but at the very least the command must be a non-empty list of
      * non-null strings.
      *
+     * <p>A minimal set of system dependent environment variables may
+     * be required to start a process on some operating systems.
+     * As a result, the subprocess may inherit additional environment variable
+     * settings beyond those in the process builder's {@link #environment()}.
+     *
      * <p>If there is a security manager, its
      * {@link SecurityManager#checkExec checkExec}
      * method is called with the first component of this object's
--- a/src/share/classes/java/lang/Runtime.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/lang/Runtime.java	Thu Apr 21 14:25:46 2011 +0100
@@ -544,6 +544,11 @@
      * <p>If <tt>envp</tt> is <tt>null</tt>, the subprocess inherits the
      * environment settings of the current process.
      *
+     * <p>A minimal set of system dependent environment variables may
+     * be required to start a process on some operating systems.
+     * As a result, the subprocess may inherit additional environment variable
+     * settings beyond those in the specified environment.
+     *
      * <p>{@link ProcessBuilder#start()} is now the preferred way to
      * start a process with a modified environment.
      *
--- a/src/share/classes/java/util/AbstractSet.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/AbstractSet.java	Thu Apr 21 14:25:46 2011 +0100
@@ -156,9 +156,11 @@
      * @throws UnsupportedOperationException if the <tt>removeAll</tt> operation
      *         is not supported by this set
      * @throws ClassCastException if the class of an element of this set
-     *         is incompatible with the specified collection (optional)
+     *         is incompatible with the specified collection
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this set contains a null element and the
-     *         specified collection does not permit null elements (optional),
+     *         specified collection does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>),
      *         or if the specified collection is null
      * @see #remove(Object)
      * @see #contains(Object)
--- a/src/share/classes/java/util/ArrayList.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/ArrayList.java	Thu Apr 21 14:25:46 2011 +0100
@@ -628,9 +628,11 @@
      * @param c collection containing elements to be removed from this list
      * @return {@code true} if this list changed as a result of the call
      * @throws ClassCastException if the class of an element of this list
-     *         is incompatible with the specified collection (optional)
+     *         is incompatible with the specified collection
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this list contains a null element and the
-     *         specified collection does not permit null elements (optional),
+     *         specified collection does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>),
      *         or if the specified collection is null
      * @see Collection#contains(Object)
      */
@@ -646,9 +648,11 @@
      * @param c collection containing elements to be retained in this list
      * @return {@code true} if this list changed as a result of the call
      * @throws ClassCastException if the class of an element of this list
-     *         is incompatible with the specified collection (optional)
+     *         is incompatible with the specified collection
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this list contains a null element and the
-     *         specified collection does not permit null elements (optional),
+     *         specified collection does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>),
      *         or if the specified collection is null
      * @see Collection#contains(Object)
      */
--- a/src/share/classes/java/util/Collection.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/Collection.java	Thu Apr 21 14:25:46 2011 +0100
@@ -60,7 +60,8 @@
  * but is not required to, throw the exception if the collection to be added
  * is empty.
  *
- * <p>Some collection implementations have restrictions on the elements that
+ * <p><a name="optional-restrictions"/>
+ * Some collection implementations have restrictions on the elements that
  * they may contain.  For example, some implementations prohibit null elements,
  * and some have restrictions on the types of their elements.  Attempting to
  * add an ineligible element throws an unchecked exception, typically
@@ -152,9 +153,11 @@
      * @return <tt>true</tt> if this collection contains the specified
      *         element
      * @throws ClassCastException if the type of the specified element
-     *         is incompatible with this collection (optional)
+     *         is incompatible with this collection
+     *         (<a href="#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         collection does not permit null elements (optional)
+     *         collection does not permit null elements
+     *         (<a href="#optional-restrictions">optional</a>)
      */
     boolean contains(Object o);
 
@@ -279,9 +282,11 @@
      * @param o element to be removed from this collection, if present
      * @return <tt>true</tt> if an element was removed as a result of this call
      * @throws ClassCastException if the type of the specified element
-     *         is incompatible with this collection (optional)
+     *         is incompatible with this collection
+     *         (<a href="#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         collection does not permit null elements (optional)
+     *         collection does not permit null elements
+     *         (<a href="#optional-restrictions">optional</a>)
      * @throws UnsupportedOperationException if the <tt>remove</tt> operation
      *         is not supported by this collection
      */
@@ -299,10 +304,13 @@
      *         in the specified collection
      * @throws ClassCastException if the types of one or more elements
      *         in the specified collection are incompatible with this
-     *         collection (optional)
+     *         collection
+     *         (<a href="#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified collection contains one
      *         or more null elements and this collection does not permit null
-     *         elements (optional), or if the specified collection is null
+     *         elements
+     *         (<a href="#optional-restrictions">optional</a>),
+     *         or if the specified collection is null.
      * @see    #contains(Object)
      */
     boolean containsAll(Collection<?> c);
@@ -346,10 +354,13 @@
      *         is not supported by this collection
      * @throws ClassCastException if the types of one or more elements
      *         in this collection are incompatible with the specified
-     *         collection (optional)
+     *         collection
+     *         (<a href="#optional-restrictions">optional</a>)
      * @throws NullPointerException if this collection contains one or more
      *         null elements and the specified collection does not support
-     *         null elements (optional), or if the specified collection is null
+     *         null elements
+     *         (<a href="#optional-restrictions">optional</a>),
+     *         or if the specified collection is null
      * @see #remove(Object)
      * @see #contains(Object)
      */
@@ -367,10 +378,13 @@
      *         is not supported by this collection
      * @throws ClassCastException if the types of one or more elements
      *         in this collection are incompatible with the specified
-     *         collection (optional)
+     *         collection
+     *         (<a href="#optional-restrictions">optional</a>)
      * @throws NullPointerException if this collection contains one or more
      *         null elements and the specified collection does not permit null
-     *         elements (optional), or if the specified collection is null
+     *         elements
+     *         (<a href="#optional-restrictions">optional</a>),
+     *         or if the specified collection is null
      * @see #remove(Object)
      * @see #contains(Object)
      */
--- a/src/share/classes/java/util/Collections.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/Collections.java	Thu Apr 21 14:25:46 2011 +0100
@@ -3746,9 +3746,10 @@
      * @throws NullPointerException if either collection is {@code null}.
      * @throws NullPointerException if one collection contains a {@code null}
      * element and {@code null} is not an eligible element for the other collection.
-     * (optional)
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws ClassCastException if one collection contains an element that is
-     * of a type which is ineligible for the other collection. (optional)
+     * of a type which is ineligible for the other collection.
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @since 1.5
      */
     public static boolean disjoint(Collection<?> c1, Collection<?> c2) {
--- a/src/share/classes/java/util/Deque.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/Deque.java	Thu Apr 21 14:25:46 2011 +0100
@@ -351,9 +351,11 @@
      * @param o element to be removed from this deque, if present
      * @return <tt>true</tt> if an element was removed as a result of this call
      * @throws ClassCastException if the class of the specified element
-     *         is incompatible with this deque (optional)
+     *         is incompatible with this deque
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         deque does not permit null elements (optional)
+     *         deque does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     boolean removeFirstOccurrence(Object o);
 
@@ -369,9 +371,11 @@
      * @param o element to be removed from this deque, if present
      * @return <tt>true</tt> if an element was removed as a result of this call
      * @throws ClassCastException if the class of the specified element
-     *         is incompatible with this deque (optional)
+     *         is incompatible with this deque
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         deque does not permit null elements (optional)
+     *         deque does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     boolean removeLastOccurrence(Object o);
 
@@ -527,9 +531,11 @@
      * @param o element to be removed from this deque, if present
      * @return <tt>true</tt> if an element was removed as a result of this call
      * @throws ClassCastException if the class of the specified element
-     *         is incompatible with this deque (optional)
+     *         is incompatible with this deque
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         deque does not permit null elements (optional)
+     *         deque does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     boolean remove(Object o);
 
@@ -542,9 +548,11 @@
      * @param o element whose presence in this deque is to be tested
      * @return <tt>true</tt> if this deque contains the specified element
      * @throws ClassCastException if the type of the specified element
-     *         is incompatible with this deque (optional)
+     *         is incompatible with this deque
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         deque does not permit null elements (optional)
+     *         deque does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     boolean contains(Object o);
 
--- a/src/share/classes/java/util/List.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/List.java	Thu Apr 21 14:25:46 2011 +0100
@@ -134,9 +134,11 @@
      * @param o element whose presence in this list is to be tested
      * @return <tt>true</tt> if this list contains the specified element
      * @throws ClassCastException if the type of the specified element
-     *         is incompatible with this list (optional)
+     *         is incompatible with this list
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         list does not permit null elements (optional)
+     *         list does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     boolean contains(Object o);
 
@@ -245,9 +247,11 @@
      * @param o element to be removed from this list, if present
      * @return <tt>true</tt> if this list contained the specified element
      * @throws ClassCastException if the type of the specified element
-     *         is incompatible with this list (optional)
+     *         is incompatible with this list
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         list does not permit null elements (optional)
+     *         list does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws UnsupportedOperationException if the <tt>remove</tt> operation
      *         is not supported by this list
      */
@@ -265,10 +269,13 @@
      *         specified collection
      * @throws ClassCastException if the types of one or more elements
      *         in the specified collection are incompatible with this
-     *         list (optional)
+     *         list
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified collection contains one
      *         or more null elements and this list does not permit null
-     *         elements (optional), or if the specified collection is null
+     *         elements
+     *         (<a href="Collection.html#optional-restrictions">optional</a>),
+     *         or if the specified collection is null
      * @see #contains(Object)
      */
     boolean containsAll(Collection<?> c);
@@ -334,9 +341,11 @@
      * @throws UnsupportedOperationException if the <tt>removeAll</tt> operation
      *         is not supported by this list
      * @throws ClassCastException if the class of an element of this list
-     *         is incompatible with the specified collection (optional)
+     *         is incompatible with the specified collection
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this list contains a null element and the
-     *         specified collection does not permit null elements (optional),
+     *         specified collection does not permit null elements
+     *         (<a href="Collection.html#optional-restrictions">optional</a>),
      *         or if the specified collection is null
      * @see #remove(Object)
      * @see #contains(Object)
@@ -354,9 +363,11 @@
      * @throws UnsupportedOperationException if the <tt>retainAll</tt> operation
      *         is not supported by this list
      * @throws ClassCastException if the class of an element of this list
-     *         is incompatible with the specified collection (optional)
+     *         is incompatible with the specified collection
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this list contains a null element and the
-     *         specified collection does not permit null elements (optional),
+     *         specified collection does not permit null elements
+     *         (<a href="Collection.html#optional-restrictions">optional</a>),
      *         or if the specified collection is null
      * @see #remove(Object)
      * @see #contains(Object)
@@ -493,9 +504,11 @@
      * @return the index of the first occurrence of the specified element in
      *         this list, or -1 if this list does not contain the element
      * @throws ClassCastException if the type of the specified element
-     *         is incompatible with this list (optional)
+     *         is incompatible with this list
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         list does not permit null elements (optional)
+     *         list does not permit null elements
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     int indexOf(Object o);
 
@@ -510,9 +523,11 @@
      * @return the index of the last occurrence of the specified element in
      *         this list, or -1 if this list does not contain the element
      * @throws ClassCastException if the type of the specified element
-     *         is incompatible with this list (optional)
+     *         is incompatible with this list
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         list does not permit null elements (optional)
+     *         list does not permit null elements
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     int lastIndexOf(Object o);
 
--- a/src/share/classes/java/util/Map.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/Map.java	Thu Apr 21 14:25:46 2011 +0100
@@ -144,9 +144,11 @@
      * @return <tt>true</tt> if this map contains a mapping for the specified
      *         key
      * @throws ClassCastException if the key is of an inappropriate type for
-     *         this map (optional)
+     *         this map
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified key is null and this map
-     *         does not permit null keys (optional)
+     *         does not permit null keys
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     boolean containsKey(Object key);
 
@@ -162,9 +164,11 @@
      * @return <tt>true</tt> if this map maps one or more keys to the
      *         specified value
      * @throws ClassCastException if the value is of an inappropriate type for
-     *         this map (optional)
+     *         this map
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified value is null and this
-     *         map does not permit null values (optional)
+     *         map does not permit null values
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     boolean containsValue(Object value);
 
@@ -187,9 +191,11 @@
      * @return the value to which the specified key is mapped, or
      *         {@code null} if this map contains no mapping for the key
      * @throws ClassCastException if the key is of an inappropriate type for
-     *         this map (optional)
+     *         this map
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified key is null and this map
-     *         does not permit null keys (optional)
+     *         does not permit null keys
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     V get(Object key);
 
@@ -245,9 +251,11 @@
      * @throws UnsupportedOperationException if the <tt>remove</tt> operation
      *         is not supported by this map
      * @throws ClassCastException if the key is of an inappropriate type for
-     *         this map (optional)
+     *         this map
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified key is null and this
-     *         map does not permit null keys (optional)
+     *         map does not permit null keys
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     V remove(Object key);
 
@@ -466,4 +474,5 @@
      * @see #equals(Object)
      */
     int hashCode();
+
 }
--- a/src/share/classes/java/util/Set.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/Set.java	Thu Apr 21 14:25:46 2011 +0100
@@ -110,9 +110,11 @@
      * @param o element whose presence in this set is to be tested
      * @return <tt>true</tt> if this set contains the specified element
      * @throws ClassCastException if the type of the specified element
-     *         is incompatible with this set (optional)
+     *         is incompatible with this set
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         set does not permit null elements (optional)
+     *         set does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      */
     boolean contains(Object o);
 
@@ -236,9 +238,11 @@
      * @param o object to be removed from this set, if present
      * @return <tt>true</tt> if this set contained the specified element
      * @throws ClassCastException if the type of the specified element
-     *         is incompatible with this set (optional)
+     *         is incompatible with this set
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified element is null and this
-     *         set does not permit null elements (optional)
+     *         set does not permit null elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws UnsupportedOperationException if the <tt>remove</tt> operation
      *         is not supported by this set
      */
@@ -257,10 +261,13 @@
      *         specified collection
      * @throws ClassCastException if the types of one or more elements
      *         in the specified collection are incompatible with this
-     *         set (optional)
+     *         set
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified collection contains one
      *         or more null elements and this set does not permit null
-     *         elements (optional), or if the specified collection is null
+     *         elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>),
+     *         or if the specified collection is null
      * @see    #contains(Object)
      */
     boolean containsAll(Collection<?> c);
@@ -302,9 +309,11 @@
      * @throws UnsupportedOperationException if the <tt>retainAll</tt> operation
      *         is not supported by this set
      * @throws ClassCastException if the class of an element of this set
-     *         is incompatible with the specified collection (optional)
+     *         is incompatible with the specified collection
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this set contains a null element and the
-     *         specified collection does not permit null elements (optional),
+     *         specified collection does not permit null elements
+     *         (<a href="Collection.html#optional-restrictions">optional</a>),
      *         or if the specified collection is null
      * @see #remove(Object)
      */
@@ -322,9 +331,11 @@
      * @throws UnsupportedOperationException if the <tt>removeAll</tt> operation
      *         is not supported by this set
      * @throws ClassCastException if the class of an element of this set
-     *         is incompatible with the specified collection (optional)
+     *         is incompatible with the specified collection
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this set contains a null element and the
-     *         specified collection does not permit null elements (optional),
+     *         specified collection does not permit null elements
+     *         (<a href="Collection.html#optional-restrictions">optional</a>),
      *         or if the specified collection is null
      * @see #remove(Object)
      * @see #contains(Object)
--- a/src/share/classes/java/util/Vector.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/Vector.java	Thu Apr 21 14:25:46 2011 +0100
@@ -893,10 +893,13 @@
      * @return true if this Vector changed as a result of the call
      * @throws ClassCastException if the types of one or more elements
      *         in this vector are incompatible with the specified
-     *         collection (optional)
+     *         collection
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this vector contains one or more null
      *         elements and the specified collection does not support null
-     *         elements (optional), or if the specified collection is null
+     *         elements
+     * (<a href="Collection.html#optional-restrictions">optional</a>),
+     *         or if the specified collection is null
      * @since 1.2
      */
     public synchronized boolean removeAll(Collection<?> c) {
@@ -913,10 +916,13 @@
      * @return true if this Vector changed as a result of the call
      * @throws ClassCastException if the types of one or more elements
      *         in this vector are incompatible with the specified
-     *         collection (optional)
+     *         collection
+     * (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this vector contains one or more null
      *         elements and the specified collection does not support null
-     *         elements (optional), or if the specified collection is null
+     *         elements
+     *         (<a href="Collection.html#optional-restrictions">optional</a>),
+     *         or if the specified collection is null
      * @since 1.2
      */
     public synchronized boolean retainAll(Collection<?> c) {
--- a/src/share/classes/java/util/concurrent/BlockingDeque.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/concurrent/BlockingDeque.java	Thu Apr 21 14:25:46 2011 +0100
@@ -400,8 +400,10 @@
      * @param o element to be removed from this deque, if present
      * @return <tt>true</tt> if an element was removed as a result of this call
      * @throws ClassCastException if the class of the specified element
-     *         is incompatible with this deque (optional)
-     * @throws NullPointerException if the specified element is null (optional)
+     *         is incompatible with this deque
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
+     * @throws NullPointerException if the specified element is null
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
      */
     boolean removeFirstOccurrence(Object o);
 
@@ -416,8 +418,10 @@
      * @param o element to be removed from this deque, if present
      * @return <tt>true</tt> if an element was removed as a result of this call
      * @throws ClassCastException if the class of the specified element
-     *         is incompatible with this deque (optional)
-     * @throws NullPointerException if the specified element is null (optional)
+     *         is incompatible with this deque
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
+     * @throws NullPointerException if the specified element is null
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
      */
     boolean removeLastOccurrence(Object o);
 
@@ -591,8 +595,10 @@
      * @param o element to be removed from this deque, if present
      * @return <tt>true</tt> if this deque changed as a result of the call
      * @throws ClassCastException if the class of the specified element
-     *         is incompatible with this deque (optional)
-     * @throws NullPointerException if the specified element is null (optional)
+     *         is incompatible with this deque
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
+     * @throws NullPointerException if the specified element is null
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
      */
     boolean remove(Object o);
 
@@ -604,8 +610,10 @@
      * @param o object to be checked for containment in this deque
      * @return <tt>true</tt> if this deque contains the specified element
      * @throws ClassCastException if the class of the specified element
-     *         is incompatible with this deque (optional)
-     * @throws NullPointerException if the specified element is null (optional)
+     *         is incompatible with this deque
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
+     * @throws NullPointerException if the specified element is null
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
      */
     public boolean contains(Object o);
 
--- a/src/share/classes/java/util/concurrent/BlockingQueue.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/concurrent/BlockingQueue.java	Thu Apr 21 14:25:46 2011 +0100
@@ -303,8 +303,10 @@
      * @param o element to be removed from this queue, if present
      * @return <tt>true</tt> if this queue changed as a result of the call
      * @throws ClassCastException if the class of the specified element
-     *         is incompatible with this queue (optional)
-     * @throws NullPointerException if the specified element is null (optional)
+     *         is incompatible with this queue
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
+     * @throws NullPointerException if the specified element is null
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
      */
     boolean remove(Object o);
 
@@ -316,8 +318,10 @@
      * @param o object to be checked for containment in this queue
      * @return <tt>true</tt> if this queue contains the specified element
      * @throws ClassCastException if the class of the specified element
-     *         is incompatible with this queue (optional)
-     * @throws NullPointerException if the specified element is null (optional)
+     *         is incompatible with this queue
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
+     * @throws NullPointerException if the specified element is null
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
      */
     public boolean contains(Object o);
 
--- a/src/share/classes/java/util/concurrent/ConcurrentMap.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/concurrent/ConcurrentMap.java	Thu Apr 21 14:25:46 2011 +0100
@@ -103,9 +103,11 @@
      * @throws UnsupportedOperationException if the <tt>remove</tt> operation
      *         is not supported by this map
      * @throws ClassCastException if the key or value is of an inappropriate
-     *         type for this map (optional)
+     *         type for this map
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified key or value is null,
-     *         and this map does not permit null keys or values (optional)
+     *         and this map does not permit null keys or values
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
      */
     boolean remove(Object key, Object value);
 
--- a/src/share/classes/java/util/concurrent/CopyOnWriteArrayList.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/java/util/concurrent/CopyOnWriteArrayList.java	Thu Apr 21 14:25:46 2011 +0100
@@ -631,9 +631,11 @@
      * @param c collection containing elements to be removed from this list
      * @return <tt>true</tt> if this list changed as a result of the call
      * @throws ClassCastException if the class of an element of this list
-     *         is incompatible with the specified collection (optional)
+     *         is incompatible with the specified collection
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this list contains a null element and the
-     *         specified collection does not permit null elements (optional),
+     *         specified collection does not permit null elements
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>),
      *         or if the specified collection is null
      * @see #remove(Object)
      */
@@ -671,9 +673,11 @@
      * @param c collection containing elements to be retained in this list
      * @return <tt>true</tt> if this list changed as a result of the call
      * @throws ClassCastException if the class of an element of this list
-     *         is incompatible with the specified collection (optional)
+     *         is incompatible with the specified collection
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if this list contains a null element and the
-     *         specified collection does not permit null elements (optional),
+     *         specified collection does not permit null elements
+     *         (<a href="../Collection.html#optional-restrictions">optional</a>),
      *         or if the specified collection is null
      * @see #remove(Object)
      */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java	Thu Apr 21 14:25:46 2011 +0100
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2011, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package javax.security.auth.kerberos;
+
+import sun.misc.JavaxSecurityAuthKerberosAccess;
+import sun.security.krb5.EncryptionKey;
+import sun.security.krb5.PrincipalName;
+
+class JavaxSecurityAuthKerberosAccessImpl
+        implements JavaxSecurityAuthKerberosAccess {
+    public EncryptionKey[] keyTabGetEncryptionKeys(
+            KeyTab ktab, PrincipalName principal) {
+        return ktab.getEncryptionKeys(principal);
+    }
+}
--- a/src/share/classes/javax/security/auth/kerberos/KerberosKey.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/javax/security/auth/kerberos/KerberosKey.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2011, 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
@@ -35,14 +35,16 @@
  * principal.<p>
  *
  * All Kerberos JAAS login modules that obtain a principal's password and
- * generate the secret key from it should use this class. Where available,
- * the login module might even read this secret key directly from a
- * Kerberos "keytab". Sometimes, such as when authenticating a server in
+ * generate the secret key from it should use this class.
+ * Sometimes, such as when authenticating a server in
  * the absence of user-to-user authentication, the login module will store
  * an instance of this class in the private credential set of a
  * {@link javax.security.auth.Subject Subject} during the commit phase of the
  * authentication process.<p>
  *
+ * A Kerberos service using a keytab to read secret keys should use
+ * the {@link KeyTab} class, where latest keys can be read when needed.<p>
+ *
  * It might be necessary for the application to be granted a
  * {@link javax.security.auth.PrivateCredentialPermission
  * PrivateCredentialPermission} if it needs to access the KerberosKey
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/javax/security/auth/kerberos/KeyTab.java	Thu Apr 21 14:25:46 2011 +0100
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2011, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package javax.security.auth.kerberos;
+
+import java.io.File;
+import java.util.Objects;
+import sun.misc.SharedSecrets;
+import sun.security.krb5.EncryptionKey;
+import sun.security.krb5.PrincipalName;
+import sun.security.krb5.RealmException;
+
+/**
+ * This class encapsulates a keytab file.
+ * <p>
+ * A Kerberos JAAS login module that obtains long term secret keys from a
+ * keytab file should use this class. The login module will store
+ * an instance of this class in the private credential set of a
+ * {@link javax.security.auth.Subject Subject} during the commit phase of the
+ * authentication process.
+ * <p>
+ * It might be necessary for the application to be granted a
+ * {@link javax.security.auth.PrivateCredentialPermission
+ * PrivateCredentialPermission} if it needs to access the KeyTab
+ * instance from a Subject. This permission is not needed when the
+ * application depends on the default JGSS Kerberos mechanism to access the
+ * KeyTab. In that case, however, the application will need an appropriate
+ * {@link javax.security.auth.kerberos.ServicePermission ServicePermission}.
+ * <p>
+ * The keytab file format is described at
+ * <a href="http://www.ioplex.com/utilities/keytab.txt">
+ * http://www.ioplex.com/utilities/keytab.txt</a>.
+ *
+ * @since 1.7
+ */
+public final class KeyTab {
+
+    /*
+     * Impl notes:
+     *
+     * This class is only a name, a permanent link to the keytab source
+     * (can be missing). Itself has no content. In order to read content,
+     * take a snapshot and read from it.
+     *
+     * The snapshot is of type sun.security.krb5.internal.ktab.KeyTab, which
+     * contains the content of the keytab file when the snapshot is taken.
+     * Itself has no refresh function and mostly an immutable class (except
+     * for the create/add/save methods only used by the ktab command).
+     */
+
+    // Source, null if using the default one. Note that the default name
+    // is maintained in snapshot, this field is never "resolved".
+    private final File file;
+
+    // Set up JavaxSecurityAuthKerberosAccess in SharedSecrets
+    static {
+        SharedSecrets.setJavaxSecurityAuthKerberosAccess(
+                new JavaxSecurityAuthKerberosAccessImpl());
+    }
+
+    private KeyTab(File file) {
+        this.file = file;
+    }
+
+    /**
+     * Returns a {@code KeyTab} instance from a {@code File} object.
+     * <p>
+     * The result of this method is never null. This method only associates
+     * the returned {@code KeyTab} object with the file and does not read it.
+     * @param file the keytab {@code File} object, must not be null
+     * @return the keytab instance
+     * @throws NullPointerException if the {@code file} argument is null
+     */
+    public static KeyTab getInstance(File file) {
+        if (file == null) {
+            throw new NullPointerException("file must be non null");
+        }
+        return new KeyTab(file);
+    }
+
+    /**
+     * Returns the default {@code KeyTab} instance.
+     * <p>
+     * The result of this method is never null. This method only associates
+     * the returned {@code KeyTab} object with the default keytab file and
+     * does not read it.
+     * @return the default keytab instance.
+     */
+    public static KeyTab getInstance() {
+        return new KeyTab(null);
+    }
+
+    //Takes a snapshot of the keytab content
+    private sun.security.krb5.internal.ktab.KeyTab takeSnapshot() {
+        return sun.security.krb5.internal.ktab.KeyTab.getInstance(file);
+    }
+
+    /**
+     * Returns fresh keys for the given Kerberos principal.
+     * <p>
+     * Implementation of this method should make sure the returned keys match
+     * the latest content of the keytab file. The result is a newly created
+     * copy that can be modified by the caller without modifying the keytab
+     * object. The caller should {@link KerberosKey#destroy() destroy} the
+     * result keys after they are used.
+     * <p>
+     * Please note that the keytab file can be created after the
+     * {@code KeyTab} object is instantiated and its content may change over
+     * time. Therefore, an application should call this method only when it
+     * needs to use the keys. Any previous result from an earlier invocation
+     * could potentially be expired.
+     * <p>
+     * If there is any error (say, I/O error or format error)
+     * during the reading process of the KeyTab file, a saved result should be
+     * returned. If there is no saved result (say, this is the first time this
+     * method is called, or, all previous read attempts failed), an empty array
+     * should be returned. This can make sure the result is not drastically
+     * changed during the (probably slow) update of the keytab file.
+     * <p>
+     * Each time this method is called and the reading of the file succeeds
+     * with no exception (say, I/O error or file format error),
+     * the result should be saved for {@code principal}. The implementation can
+     * also save keys for other principals having keys in the same keytab object
+     * if convenient.
+     * <p>
+     * Any unsupported key read from the keytab is ignored and not included
+     * in the result.
+     *
+     * @param principal the Kerberos principal, must not be null.
+     * @return the keys (never null, may be empty)
+     * @throws NullPointerException if the {@code principal}
+     * argument is null
+     * @throws SecurityException if a security manager exists and the read
+     * access to the keytab file is not permitted
+     */
+    public KerberosKey[] getKeys(KerberosPrincipal principal) {
+        try {
+            EncryptionKey[] keys = takeSnapshot().readServiceKeys(
+                    new PrincipalName(principal.getName()));
+            KerberosKey[] kks = new KerberosKey[keys.length];
+            for (int i=0; i<kks.length; i++) {
+                Integer tmp = keys[i].getKeyVersionNumber();
+                kks[i] = new KerberosKey(
+                        principal,
+                        keys[i].getBytes(),
+                        keys[i].getEType(),
+                        tmp == null ? 0 : tmp.intValue());
+                keys[i].destroy();
+            }
+            return kks;
+        } catch (RealmException re) {
+            return new KerberosKey[0];
+        }
+    }
+
+    EncryptionKey[] getEncryptionKeys(PrincipalName principal) {
+        return takeSnapshot().readServiceKeys(principal);
+    }
+
+    /**
+     * Checks if the keytab file exists. Implementation of this method
+     * should make sure that the result matches the latest status of the
+     * keytab file.
+     * <p>
+     * The caller can use the result to determine if it should fallback to
+     * another mechanism to read the keys.
+     * @return true if the keytab file exists; false otherwise.
+     * @throws SecurityException if a security manager exists and the read
+     * access to the keytab file is not permitted
+     */
+    public boolean exists() {
+        return !takeSnapshot().isMissing();
+    }
+
+    public String toString() {
+        return file == null ? "Default keytab" : file.toString();
+    }
+
+    /**
+     * Returns a hashcode for this KeyTab.
+     *
+     * @return a hashCode() for the <code>KeyTab</code>
+     */
+    public int hashCode() {
+        return Objects.hash(file);
+    }
+
+    /**
+     * Compares the specified Object with this KeyTab for equality.
+     * Returns true if the given object is also a
+     * <code>KeyTab</code> and the two
+     * <code>KeyTab</code> instances are equivalent.
+     *
+     * @param other the Object to compare to
+     * @return true if the specified object is equal to this KeyTab
+     */
+    public boolean equals(Object other) {
+        if (other == this)
+            return true;
+
+        if (! (other instanceof KeyTab)) {
+            return false;
+        }
+
+        KeyTab otherKtab = (KeyTab) other;
+        return Objects.equals(otherKtab.file, file);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/misc/JavaxSecurityAuthKerberosAccess.java	Thu Apr 21 14:25:46 2011 +0100
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2011, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package sun.misc;
+
+import javax.security.auth.kerberos.KeyTab;
+import sun.security.krb5.EncryptionKey;
+import sun.security.krb5.PrincipalName;
+
+/**
+ * An unsafe tunnel to get non-public access to classes in the
+ * javax.security.auth.kerberos package.
+ */
+public interface JavaxSecurityAuthKerberosAccess {
+    /**
+     * Returns keys for a principal in a keytab.
+     * @return the keys, never null, can be empty.
+     */
+    public EncryptionKey[] keyTabGetEncryptionKeys(
+            KeyTab ktab, PrincipalName principal);
+}
--- a/src/share/classes/sun/misc/SharedSecrets.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/misc/SharedSecrets.java	Thu Apr 21 14:25:46 2011 +0100
@@ -29,6 +29,7 @@
 import java.io.Console;
 import java.io.FileDescriptor;
 import java.security.ProtectionDomain;
+import javax.security.auth.kerberos.KeyTab;
 
 import java.security.AccessController;
 
@@ -51,6 +52,7 @@
     private static JavaIOFileDescriptorAccess javaIOFileDescriptorAccess;
     private static JavaSecurityProtectionDomainAccess javaSecurityProtectionDomainAccess;
     private static JavaSecurityAccess javaSecurityAccess;
+    private static JavaxSecurityAuthKerberosAccess javaxSecurityAuthKerberosAccess;
 
     public static JavaUtilJarAccess javaUtilJarAccess() {
         if (javaUtilJarAccess == null) {
@@ -139,4 +141,16 @@
         }
         return javaSecurityAccess;
     }
+
+    public static void setJavaxSecurityAuthKerberosAccess
+            (JavaxSecurityAuthKerberosAccess jsaka) {
+        javaxSecurityAuthKerberosAccess = jsaka;
+    }
+
+    public static JavaxSecurityAuthKerberosAccess
+            getJavaxSecurityAuthKerberosAccess() {
+        if (javaxSecurityAuthKerberosAccess == null)
+            unsafe.ensureClassInitialized(KeyTab.class);
+        return javaxSecurityAuthKerberosAccess;
+    }
 }
--- a/src/share/classes/sun/rmi/log/ReliableLog.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/rmi/log/ReliableLog.java	Thu Apr 21 14:25:46 2011 +0100
@@ -380,9 +380,7 @@
             } catch (IOException e) {
                 throw e;
             } catch (Exception e) {
-                throw new IOException("snapshot failed with exception of type: " +
-                                      e.getClass().getName() +
-                                      ", message was: " + e.getMessage());
+                throw new IOException("snapshot failed", e);
             }
             lastSnapshot = System.currentTimeMillis();
         } finally {
--- a/src/share/classes/sun/rmi/server/Activation.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/rmi/server/Activation.java	Thu Apr 21 14:25:46 2011 +0100
@@ -30,6 +30,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.ObjectInputStream;
 import java.io.OutputStream;
 import java.io.PrintStream;
 import java.io.PrintWriter;
@@ -98,6 +99,7 @@
 import java.util.Properties;
 import java.util.ResourceBundle;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import sun.rmi.log.LogHandler;
 import sun.rmi.log.ReliableLog;
 import sun.rmi.registry.RegistryImpl;
@@ -147,10 +149,10 @@
 
     /** maps activation id to its respective group id */
     private Map<ActivationID,ActivationGroupID> idTable =
-        new HashMap<ActivationID,ActivationGroupID>();
+        new ConcurrentHashMap<>();
     /** maps group id to its GroupEntry groups */
     private Map<ActivationGroupID,GroupEntry> groupTable =
-        new HashMap<ActivationGroupID,GroupEntry>();
+        new ConcurrentHashMap<>();
 
     private byte majorVersion = MAJOR_VERSION;
     private byte minorVersion = MINOR_VERSION;
@@ -236,9 +238,11 @@
         groupSemaphore = getInt("sun.rmi.activation.groupThrottle", 3);
         groupCounter = 0;
         Runtime.getRuntime().addShutdownHook(shutdownHook);
+
+        // Use array size of 0, since the value from calling size()
+        // may be out of date by the time toArray() is called.
         ActivationGroupID[] gids =
-            groupTable.keySet().toArray(
-                new ActivationGroupID[groupTable.size()]);
+            groupTable.keySet().toArray(new ActivationGroupID[0]);
 
         synchronized (startupLock = new Object()) {
             // all the remote methods briefly synchronize on startupLock
@@ -274,6 +278,23 @@
         }
     }
 
+    /**
+     * Previous versions used HashMap instead of ConcurrentHashMap.
+     * Replace any HashMaps found during deserialization with
+     * ConcurrentHashMaps.
+     */
+    private void readObject(ObjectInputStream ois)
+        throws IOException, ClassNotFoundException
+    {
+        ois.defaultReadObject();
+        if (! (groupTable instanceof ConcurrentHashMap)) {
+            groupTable = new ConcurrentHashMap<>(groupTable);
+        }
+        if (! (idTable instanceof ConcurrentHashMap)) {
+            idTable = new ConcurrentHashMap<>(idTable);
+        }
+    }
+
     private static class SystemRegistryImpl extends RegistryImpl {
 
         private static final String NAME = ActivationSystem.class.getName();
@@ -488,9 +509,7 @@
             ActivationGroupID id = new ActivationGroupID(systemStub);
             GroupEntry entry = new GroupEntry(id, desc);
             // table insertion must take place before log update
-            synchronized (groupTable) {
-                groupTable.put(id, entry);
-            }
+            groupTable.put(id, entry);
             addLogRecord(new LogRegisterGroup(id, desc));
             return id;
         }
@@ -515,11 +534,7 @@
 
             // remove entry before unregister so state is updated before
             // logged
-            synchronized (groupTable) {
-                GroupEntry entry = getGroupEntry(id);
-                groupTable.remove(id);
-                entry.unregisterGroup(true);
-            }
+            removeGroupEntry(id).unregisterGroup(true);
         }
 
         public ActivationDesc setActivationDesc(ActivationID id,
@@ -637,12 +652,7 @@
                 unexport(system);
 
                 // destroy all child processes (groups)
-                GroupEntry[] groupEntries;
-                synchronized (groupTable) {
-                    groupEntries = groupTable.values().
-                        toArray(new GroupEntry[groupTable.size()]);
-                }
-                for (GroupEntry groupEntry : groupEntries) {
+                for (GroupEntry groupEntry : groupTable.values()) {
                     groupEntry.shutdown();
                 }
 
@@ -693,10 +703,8 @@
             }
 
             // destroy all child processes (groups) quickly
-            synchronized (groupTable) {
-                for (GroupEntry groupEntry : groupTable.values()) {
-                    groupEntry.shutdownFast();
-                }
+            for (GroupEntry groupEntry : groupTable.values()) {
+                groupEntry.shutdownFast();
             }
         }
     }
@@ -708,13 +716,32 @@
     private ActivationGroupID getGroupID(ActivationID id)
         throws UnknownObjectException
     {
-        synchronized (idTable) {
-            ActivationGroupID groupID = idTable.get(id);
-            if (groupID != null) {
-                return groupID;
+        ActivationGroupID groupID = idTable.get(id);
+        if (groupID != null) {
+            return groupID;
+        }
+        throw new UnknownObjectException("unknown object: " + id);
+    }
+
+    /**
+     * Returns the group entry for the group id, optionally removing it.
+     * Throws UnknownGroupException if the group is not registered.
+     */
+    private GroupEntry getGroupEntry(ActivationGroupID id, boolean rm)
+        throws UnknownGroupException
+    {
+        if (id.getClass() == ActivationGroupID.class) {
+            GroupEntry entry;
+            if (rm) {
+                entry = groupTable.remove(id);
+            } else {
+                entry = groupTable.get(id);
+            }
+            if (entry != null && !entry.removed) {
+                return entry;
             }
         }
-        throw new UnknownObjectException("unknown object: " + id);
+        throw new UnknownGroupException("group unknown");
     }
 
     /**
@@ -724,15 +751,17 @@
     private GroupEntry getGroupEntry(ActivationGroupID id)
         throws UnknownGroupException
     {
-        if (id.getClass() == ActivationGroupID.class) {
-            synchronized (groupTable) {
-                GroupEntry entry = groupTable.get(id);
-                if (entry != null && !entry.removed) {
-                    return entry;
-                }
-            }
-        }
-        throw new UnknownGroupException("group unknown");
+        return getGroupEntry(id, false);
+    }
+
+    /**
+     * Removes and returns the group entry for the group id. Throws
+     * UnknownGroupException if the group is not registered.
+     */
+    private GroupEntry removeGroupEntry(ActivationGroupID id)
+        throws UnknownGroupException
+    {
+        return getGroupEntry(id, true);
     }
 
     /**
@@ -744,11 +773,9 @@
         throws UnknownObjectException
     {
         ActivationGroupID gid = getGroupID(id);
-        synchronized (groupTable) {
-            GroupEntry entry = groupTable.get(gid);
-            if (entry != null) {
-                return entry;
-            }
+        GroupEntry entry = groupTable.get(gid);
+        if (entry != null && !entry.removed) {
+            return entry;
         }
         throw new UnknownObjectException("object's group removed");
     }
@@ -882,9 +909,7 @@
             }
 
             // table insertion must take place before log update
-            synchronized (idTable) {
-                idTable.put(id, groupID);
-            }
+            idTable.put(id, groupID);
 
             if (addRecord) {
                 addLogRecord(new LogRegisterObject(id, desc));
@@ -901,10 +926,8 @@
                 restartSet.remove(id);
             }
 
-            // table insertion must take place before log update
-            synchronized (idTable) {
-                idTable.remove(id);
-            }
+            // table removal must take place before log update
+            idTable.remove(id);
             if (addRecord) {
                 addLogRecord(new LogUnregisterObject(id));
             }
@@ -919,9 +942,7 @@
                      objects.entrySet())
             {
                 ActivationID id = entry.getKey();
-                synchronized (idTable) {
-                    idTable.remove(id);
-                }
+                idTable.remove(id);
                 ObjectEntry objEntry = entry.getValue();
                 objEntry.removed = true;
             }
--- a/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2011, 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
@@ -29,7 +29,6 @@
 import sun.security.jgss.GSSCaller;
 import sun.security.jgss.spi.*;
 import sun.security.krb5.*;
-import javax.security.auth.kerberos.*;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.security.AccessController;
@@ -43,40 +42,23 @@
  * @since 1.4
  */
 public class Krb5AcceptCredential
-    extends KerberosKey
     implements Krb5CredElement {
 
     private static final long serialVersionUID = 7714332137352567952L;
 
     private Krb5NameElement name;
 
-    /**
-     * We cache an EncryptionKey representation of this key because many
-     * Krb5 operation require a key in that form. At some point we might do
-     * away with EncryptionKey altogether and use the base class
-     * KerberosKey everywhere.
-     */
-    private EncryptionKey[] krb5EncryptionKeys;
+    private Krb5Util.ServiceCreds screds;
 
-    private Krb5AcceptCredential(Krb5NameElement name, KerberosKey[] keys) {
+    private Krb5AcceptCredential(Krb5NameElement name, Krb5Util.ServiceCreds creds) {
         /*
          * Initialize this instance with the data from the acquired
          * KerberosKey. This class needs to be a KerberosKey too
          * hence we can't just store a reference.
          */
-        super(keys[0].getPrincipal(),
-              keys[0].getEncoded(),
-              keys[0].getKeyType(),
-              keys[0].getVersionNumber());
 
         this.name = name;
-        // Cache this for later use by the sun.security.krb5 package.
-        krb5EncryptionKeys = new EncryptionKey[keys.length];
-        for (int i = 0; i < keys.length; i++) {
-            krb5EncryptionKeys[i] = new EncryptionKey(keys[i].getEncoded(),
-                                    keys[i].getKeyType(),
-                                    new Integer(keys[i].getVersionNumber()));
-        }
+        this.screds = creds;
     }
 
     static Krb5AcceptCredential getInstance(final GSSCaller caller, Krb5NameElement name)
@@ -86,12 +68,12 @@
             name.getKrb5PrincipalName().getName());
         final AccessControlContext acc = AccessController.getContext();
 
-        KerberosKey[] keys;
+        Krb5Util.ServiceCreds creds = null;
         try {
-            keys = AccessController.doPrivileged(
-                        new PrivilegedExceptionAction<KerberosKey[]>() {
-                public KerberosKey[] run() throws Exception {
-                    return Krb5Util.getKeys(
+            creds = AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Krb5Util.ServiceCreds>() {
+                public Krb5Util.ServiceCreds run() throws Exception {
+                    return Krb5Util.getServiceCreds(
                         caller == GSSCaller.CALLER_UNKNOWN ? GSSCaller.CALLER_ACCEPT: caller,
                         serverPrinc, acc);
                 }});
@@ -103,17 +85,17 @@
             throw ge;
         }
 
-        if (keys == null || keys.length == 0)
+        if (creds == null)
             throw new GSSException(GSSException.NO_CRED, -1,
-                                   "Failed to find any Kerberos Key");
+                                   "Failed to find any Kerberos credentails");
 
         if (name == null) {
-            String fullName = keys[0].getPrincipal().getName();
+            String fullName = creds.getName();
             name = Krb5NameElement.getInstance(fullName,
                                        Krb5MechFactory.NT_GSS_KRB5_PRINCIPAL);
         }
 
-        return new Krb5AcceptCredential(name, keys);
+        return new Krb5AcceptCredential(name, creds);
     }
 
     /**
@@ -171,7 +153,7 @@
     }
 
     EncryptionKey[] getKrb5EncryptionKeys() {
-        return krb5EncryptionKeys;
+        return screds.getEKeys();
     }
 
     /**
@@ -193,13 +175,6 @@
      * destroy in the base class.
      */
     public void destroy() throws DestroyFailedException {
-        if (krb5EncryptionKeys != null) {
-            for (int i = 0; i < krb5EncryptionKeys.length; i++) {
-                krb5EncryptionKeys[i].destroy();
-            }
-            krb5EncryptionKeys = null;
-        }
-
-        super.destroy();
+        screds.destroy();
     }
 }
--- a/src/share/classes/sun/security/jgss/krb5/Krb5Util.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5Util.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2011, 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
@@ -28,6 +28,7 @@
 import javax.security.auth.kerberos.KerberosTicket;
 import javax.security.auth.kerberos.KerberosKey;
 import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.kerberos.KeyTab;
 import javax.security.auth.Subject;
 import javax.security.auth.login.LoginException;
 import java.security.AccessControlContext;
@@ -38,7 +39,13 @@
 import sun.security.krb5.EncryptionKey;
 import sun.security.krb5.KrbException;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import sun.misc.SharedSecrets;
+import sun.security.krb5.PrincipalName;
 /**
  * Utilities for obtaining and converting Kerberos tickets.
  *
@@ -75,7 +82,7 @@
 
         // 1. Try to find service ticket in acc subject
         Subject accSubj = Subject.getSubject(acc);
-        KerberosTicket ticket = (KerberosTicket) SubjectComber.find(accSubj,
+        KerberosTicket ticket = SubjectComber.find(accSubj,
             serverPrincipal, clientPrincipal, KerberosTicket.class);
 
         if (ticket != null) {
@@ -87,7 +94,7 @@
             // 2. Try to get ticket from login
             try {
                 loginSubj = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
-                ticket = (KerberosTicket) SubjectComber.find(loginSubj,
+                ticket = SubjectComber.find(loginSubj,
                     serverPrincipal, clientPrincipal, KerberosTicket.class);
                 if (ticket != null) {
                     return ticket; // found it
@@ -102,13 +109,13 @@
         // Try to get TGT to acquire service ticket
 
         // 3. Try to get TGT from acc subject
-        KerberosTicket tgt = (KerberosTicket) SubjectComber.find(accSubj,
+        KerberosTicket tgt = SubjectComber.find(accSubj,
             tgsPrincipal, clientPrincipal, KerberosTicket.class);
 
         boolean fromAcc;
         if (tgt == null && loginSubj != null) {
             // 4. Try to get TGT from login subject
-            tgt = (KerberosTicket) SubjectComber.find(loginSubj,
+            tgt = SubjectComber.find(loginSubj,
                 tgsPrincipal, clientPrincipal, KerberosTicket.class);
             fromAcc = false;
         } else {
@@ -145,14 +152,14 @@
 
         // Try to get ticket from acc's Subject
         Subject accSubj = Subject.getSubject(acc);
-        KerberosTicket ticket = (KerberosTicket)
+        KerberosTicket ticket =
             SubjectComber.find(accSubj, serverPrincipal, clientPrincipal,
                   KerberosTicket.class);
 
         // Try to get ticket from Subject obtained from GSSUtil
         if (ticket == null && !GSSUtil.useSubjectCredsOnly(caller)) {
             Subject subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
-            ticket = (KerberosTicket) SubjectComber.find(subject,
+            ticket = SubjectComber.find(subject,
                 serverPrincipal, clientPrincipal, KerberosTicket.class);
         }
         return ticket;
@@ -182,37 +189,152 @@
         return subject;
     }
 
+    // A special KerberosKey, used as keys read from a KeyTab object.
+    // Each time new keys are read from KeyTab objects in the private
+    // credentials set, old ones are removed and new ones added.
+    public static class KeysFromKeyTab extends KerberosKey {
+        public KeysFromKeyTab(KerberosKey key) {
+            super(key.getPrincipal(), key.getEncoded(),
+                    key.getKeyType(), key.getVersionNumber());
+        }
+    }
+
     /**
-     * Retrieves the keys for the specified server principal from
-     * the Subject in the specified AccessControlContext.
-     * If the ticket can not be found in the Subject, and if
-     * useSubjectCredsOnly is false, then obtain keys from
-     * a LoginContext.
+     * Credentials of a service, the private secret to authenticate its
+     * identity, which can be:
+     *   1. Some KerberosKeys (generated from password)
+     *   2. A KeyTab (for a typical service)
+     *   3. A TGT (for a user2user service. Not supported yet)
      *
-     * NOTE: This method is used by JSSE Kerberos Cipher Suites
+     * Note that some creds can coexist. For example, a user2user service
+     * can use its keytab (or keys) if the client can successfully obtain a
+     * normal service ticket, otherwise, it can uses the TGT (actually, the
+     * session key of the TGT) if the client can only acquire a service ticket
+     * of ENC-TKT-IN-SKEY style.
      */
-    public static KerberosKey[] getKeys(GSSCaller caller,
+    public static class ServiceCreds {
+        private KerberosPrincipal kp;
+        private List<KeyTab> ktabs;
+        private List<KerberosKey> kk;
+        private Subject subj;
+        //private KerberosTicket tgt;   // user2user, not supported yet
+
+        private static ServiceCreds getInstance(
+                Subject subj, String serverPrincipal) {
+
+            ServiceCreds sc = new ServiceCreds();
+            sc.subj = subj;
+
+            for (KerberosPrincipal p: subj.getPrincipals(KerberosPrincipal.class)) {
+                if (serverPrincipal == null ||
+                        p.getName().equals(serverPrincipal)) {
+                    sc.kp = p;
+                    serverPrincipal = p.getName();
+                    break;
+                }
+            }
+            if (sc.kp == null) {
+                // Compatibility with old behavior: even when there is no
+                // KerberosPrincipal, we can find one from KerberosKeys
+                List<KerberosKey> keys = SubjectComber.findMany(
+                        subj, null, null, KerberosKey.class);
+                if (!keys.isEmpty()) {
+                    sc.kp = keys.get(0).getPrincipal();
+                    serverPrincipal = sc.kp.getName();
+                    if (DEBUG) {
+                        System.out.println(">>> ServiceCreds: no kp?"
+                                + " find one from kk: " + serverPrincipal);
+                    }
+                } else {
+                    return null;
+                }
+            }
+            sc.ktabs = SubjectComber.findMany(
+                        subj, null, null, KeyTab.class);
+            sc.kk = SubjectComber.findMany(
+                        subj, serverPrincipal, null, KerberosKey.class);
+            if (sc.ktabs.isEmpty() && sc.kk.isEmpty()) {
+                return null;
+            }
+            return sc;
+        }
+
+        public String getName() {
+            return kp.getName();
+        }
+
+        public KerberosKey[] getKKeys() {
+            if (ktabs.isEmpty()) {
+                return kk.toArray(new KerberosKey[kk.size()]);
+            } else {
+                List<KerberosKey> keys = new ArrayList<>();
+                for (KeyTab ktab: ktabs) {
+                    for (KerberosKey k: ktab.getKeys(kp)) {
+                        keys.add(k);
+                    }
+                }
+                // Compatibility: also add keys to privCredSet. Remove old
+                // ones first, only remove those from keytab.
+                if (!subj.isReadOnly()) {
+                    Set<Object> pcs = subj.getPrivateCredentials();
+                    synchronized (pcs) {
+                        Iterator<Object> iterator = pcs.iterator();
+                        while (iterator.hasNext()) {
+                            Object obj = iterator.next();
+                            if (obj instanceof KeysFromKeyTab) {
+                                KerberosKey key = (KerberosKey)obj;
+                                if (Objects.equals(key.getPrincipal(), kp)) {
+                                    iterator.remove();
+                                }
+                            }
+                        }
+                    }
+                    for (KerberosKey key: keys) {
+                        subj.getPrivateCredentials().add(new KeysFromKeyTab(key));
+                    }
+                }
+                return keys.toArray(new KerberosKey[keys.size()]);
+            }
+        }
+
+        public EncryptionKey[] getEKeys() {
+            KerberosKey[] kkeys = getKKeys();
+            EncryptionKey[] ekeys = new EncryptionKey[kkeys.length];
+            for (int i=0; i<ekeys.length; i++) {
+                ekeys[i] =  new EncryptionKey(
+                            kkeys[i].getEncoded(), kkeys[i].getKeyType(),
+                            new Integer(kkeys[i].getVersionNumber()));
+            }
+            return ekeys;
+        }
+
+        public void destroy() {
+            kp = null;
+            ktabs = null;
+            kk = null;
+        }
+    }
+    /**
+     * Retrieves the ServiceCreds for the specified server principal from
+     * the Subject in the specified AccessControlContext. If not found, and if
+     * useSubjectCredsOnly is false, then obtain from a LoginContext.
+     *
+     * NOTE: This method is also used by JSSE Kerberos Cipher Suites
+     */
+    public static ServiceCreds getServiceCreds(GSSCaller caller,
         String serverPrincipal, AccessControlContext acc)
                 throws LoginException {
 
         Subject accSubj = Subject.getSubject(acc);
-        List<KerberosKey> kkeys = (List<KerberosKey>)SubjectComber.findMany(
-                accSubj, serverPrincipal, null, KerberosKey.class);
-
-        if (kkeys == null && !GSSUtil.useSubjectCredsOnly(caller)) {
-            Subject subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
-            kkeys = (List<KerberosKey>) SubjectComber.findMany(subject,
-                serverPrincipal, null, KerberosKey.class);
+        ServiceCreds sc = null;
+        if (accSubj != null) {
+            sc = ServiceCreds.getInstance(accSubj, serverPrincipal);
         }
-
-        int len;
-        if (kkeys != null && (len = kkeys.size()) > 0) {
-            KerberosKey[] keys = new KerberosKey[len];
-            kkeys.toArray(keys);
-            return keys;
-        } else {
-            return null;
+        if (sc == null && !GSSUtil.useSubjectCredsOnly(caller)) {
+            Subject subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
+            sc = ServiceCreds.getInstance(subject, serverPrincipal);
         }
+        return sc;
     }
 
     public static KerberosTicket credsToTicket(Credentials serviceCreds) {
@@ -247,4 +369,17 @@
             kerbTicket.getRenewTill(),
             kerbTicket.getClientAddresses());
     }
+
+    /**
+     * A helper method to get EncryptionKeys from a javax..KeyTab
+     * @param ktab the javax..KeyTab class
+     * @param cname the PrincipalName
+     * @return the EKeys, never null, might be empty
+     */
+    public static EncryptionKey[] keysFromJavaxKeyTab(
+            KeyTab ktab, PrincipalName cname) {
+        return SharedSecrets.getJavaxSecurityAuthKerberosAccess().
+                keyTabGetEncryptionKeys(ktab, cname);
+    }
+
 }
--- a/src/share/classes/sun/security/jgss/krb5/SubjectComber.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/security/jgss/krb5/SubjectComber.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2011, 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
@@ -33,10 +33,11 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import javax.security.auth.kerberos.KeyTab;
 
 /**
- * This utility looks through the current Subject and retrieves a ticket or key
- * for the desired client/server principals.
+ * This utility looks through the current Subject and retrieves private
+ * credentials for the desired client/server principals.
  *
  * @author Ram Marti
  * @since 1.4.2
@@ -52,58 +53,70 @@
     private SubjectComber() {  // Cannot create one of these
     }
 
-    static Object find(Subject subject, String serverPrincipal,
-        String clientPrincipal, Class credClass) {
+    static <T> T find(Subject subject, String serverPrincipal,
+        String clientPrincipal, Class<T> credClass) {
 
-        return findAux(subject, serverPrincipal, clientPrincipal, credClass,
+        return (T)findAux(subject, serverPrincipal, clientPrincipal, credClass,
             true);
     }
 
-    static Object findMany(Subject subject, String serverPrincipal,
-        String clientPrincipal, Class credClass) {
+    static <T> List<T> findMany(Subject subject, String serverPrincipal,
+        String clientPrincipal, Class<T> credClass) {
 
-        return findAux(subject, serverPrincipal, clientPrincipal, credClass,
+        return (List<T>)findAux(subject, serverPrincipal, clientPrincipal, credClass,
             false);
     }
 
     /**
-     * Find the ticket or key for the specified client/server principals
+     * Find private credentials for the specified client/server principals
      * in the subject. Returns null if the subject is null.
      *
-     * @return the ticket or key
+     * @return the private credentials
      */
-    private static Object findAux(Subject subject, String serverPrincipal,
-        String clientPrincipal, Class credClass, boolean oneOnly) {
+    private static <T> Object findAux(Subject subject, String serverPrincipal,
+        String clientPrincipal, Class<T> credClass, boolean oneOnly) {
 
         if (subject == null) {
             return null;
         } else {
-            List<Object> answer = (oneOnly ? null : new ArrayList<Object>());
+            List<T> answer = (oneOnly ? null : new ArrayList<T>());
 
-            if (credClass == KerberosKey.class) {
-                // We are looking for a KerberosKey credentials for the
-                // serverPrincipal
-                Iterator<KerberosKey> iterator =
-                    subject.getPrivateCredentials(KerberosKey.class).iterator();
+            if (credClass == KeyTab.class) {    // Principal un-related
+                // We are looking for credentials unrelated to serverPrincipal
+                Iterator<T> iterator =
+                    subject.getPrivateCredentials(credClass).iterator();
                 while (iterator.hasNext()) {
-                    KerberosKey key = iterator.next();
-                    if (serverPrincipal == null ||
-                        serverPrincipal.equals(key.getPrincipal().getName())) {
+                    T t = iterator.next();
+                    if (DEBUG) {
+                        System.out.println("Found " + credClass.getSimpleName());
+                    }
+                    if (oneOnly) {
+                        return t;
+                    } else {
+                        answer.add(t);
+                    }
+                }
+            } else if (credClass == KerberosKey.class) {
+                // We are looking for credentials for the serverPrincipal
+                Iterator<T> iterator =
+                    subject.getPrivateCredentials(credClass).iterator();
+                while (iterator.hasNext()) {
+                    T t = iterator.next();
+                    String name = ((KerberosKey)t).getPrincipal().getName();
+                    if (serverPrincipal == null || serverPrincipal.equals(name)) {
                          if (DEBUG) {
-                             System.out.println("Found key for "
-                                 + key.getPrincipal() + "(" +
-                                 key.getKeyType() + ")");
+                             System.out.println("Found " +
+                                     credClass.getSimpleName() + " for " + name);
                          }
                          if (oneOnly) {
-                             return key;
+                             return t;
                          } else {
                              if (serverPrincipal == null) {
                                  // Record name so that keys returned will all
                                  // belong to the same principal
-                                 serverPrincipal =
-                                     key.getPrincipal().getName();
+                                 serverPrincipal = name;
                              }
-                             answer.add(key);
+                             answer.add(t);
                          }
                     }
                 }
@@ -167,7 +180,7 @@
                                                 serverPrincipal =
                                                 ticket.getServer().getName();
                                             }
-                                            answer.add(ticket);
+                                            answer.add((T)ticket);
                                         }
                                     }
                                 }
--- a/src/share/classes/sun/security/krb5/Config.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/security/krb5/Config.java	Thu Apr 21 14:25:46 2011 +0100
@@ -110,7 +110,6 @@
 
     public static synchronized void refresh() throws KrbException {
         singleton = new Config();
-        KeyTab.refresh();
         KdcComm.initStatic();
     }
 
--- a/src/share/classes/sun/security/krb5/EncryptionKey.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/security/krb5/EncryptionKey.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2011, 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
@@ -138,8 +138,7 @@
      * @returns an array of secret keys or null if none were found.
      */
     public static EncryptionKey[] acquireSecretKeys(PrincipalName princ,
-                                                    String keytab)
-        throws KrbException, IOException {
+                                                    String keytab) {
 
         if (princ == null)
             throw new IllegalArgumentException(
@@ -148,11 +147,6 @@
         // KeyTab getInstance(keytab) will call KeyTab.getInstance()
         // if keytab is null
         KeyTab ktab = KeyTab.getInstance(keytab);
-
-        if (ktab == null) {
-            return null;
-        }
-
         return ktab.readServiceKeys(princ);
     }
 
--- a/src/share/classes/sun/security/krb5/KrbAsRep.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/security/krb5/KrbAsRep.java	Thu Apr 21 14:25:46 2011 +0100
@@ -37,6 +37,8 @@
 import sun.security.util.*;
 import java.io.IOException;
 import java.util.Objects;
+import javax.security.auth.kerberos.KeyTab;
+import sun.security.jgss.krb5.Krb5Util;
 
 /**
  * This class encapsulates a AS-REP message that the KDC sends to the
@@ -90,29 +92,32 @@
     }
 
     /**
-     * Called by KrbAsReqBuilder to resolve a AS-REP message using keys.
-     * @param keys user provided keys, not null
+     * Called by KrbAsReqBuilder to resolve a AS-REP message using a keytab.
+     * @param ktab the keytab, not null
      * @param asReq the original AS-REQ sent, used to validate AS-REP
+     * @param cname the user principal name, used to locate keys in ktab
      */
-    void decryptUsingKeys(EncryptionKey[] keys, KrbAsReq asReq)
+    void decryptUsingKeyTab(KeyTab ktab, KrbAsReq asReq, PrincipalName cname)
             throws KrbException, Asn1Exception, IOException {
         EncryptionKey dkey = null;
         int encPartKeyType = rep.encPart.getEType();
         Integer encPartKvno = rep.encPart.kvno;
-        try {
-            dkey = EncryptionKey.findKey(encPartKeyType, encPartKvno, keys);
-        } catch (KrbException ke) {
-            if (ke.returnCode() == Krb5.KRB_AP_ERR_BADKEYVER) {
-                // Fallback to no kvno. In some cases, keytab is generated
-                // not by sysadmin but Java's ktab command
-                dkey = EncryptionKey.findKey(encPartKeyType, keys);
+            try {
+                dkey = EncryptionKey.findKey(encPartKeyType, encPartKvno,
+                        Krb5Util.keysFromJavaxKeyTab(ktab, cname));
+            } catch (KrbException ke) {
+                if (ke.returnCode() == Krb5.KRB_AP_ERR_BADKEYVER) {
+                    // Fallback to no kvno. In some cases, keytab is generated
+                    // not by sysadmin but Java's ktab command
+                    dkey = EncryptionKey.findKey(encPartKeyType,
+                            Krb5Util.keysFromJavaxKeyTab(ktab, cname));
+                }
             }
-        }
-        if (dkey == null) {
-            throw new KrbException(Krb5.API_INVALID_ARG,
-                "Cannot find key for type/kvno to decrypt AS REP - " +
-                EType.toString(encPartKeyType) + "/" + encPartKvno);
-        }
+            if (dkey == null) {
+                throw new KrbException(Krb5.API_INVALID_ARG,
+                    "Cannot find key for type/kvno to decrypt AS REP - " +
+                    EType.toString(encPartKeyType) + "/" + encPartKvno);
+            }
         decrypt(dkey, asReq);
     }
 
--- a/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2011, 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
@@ -27,6 +27,8 @@
 
 import java.io.IOException;
 import java.util.Arrays;
+import javax.security.auth.kerberos.KeyTab;
+import sun.security.jgss.krb5.Krb5Util;
 import sun.security.krb5.internal.HostAddresses;
 import sun.security.krb5.internal.KDCOptions;
 import sun.security.krb5.internal.KRBError;
@@ -42,13 +44,16 @@
  * 1. Gather information to create AS-REQ
  * 2. Create and send AS-REQ
  * 3. Receive AS-REP and KRB-ERROR (-KRB_ERR_RESPONSE_TOO_BIG) and parse them
- * 4. Emit credentials and secret keys (for JAAS storeKey=true)
+ * 4. Emit credentials and secret keys (for JAAS storeKey=true with password)
  *
  * This class does not:
  * 1. Deal with real communications (KdcComm does it, and TGS-REQ)
  *    a. Name of KDCs for a realm
  *    b. Server availability, timeout, UDP or TCP
  *    d. KRB_ERR_RESPONSE_TOO_BIG
+ * 2. Stores its own copy of password, this means:
+ *    a. Do not change/wipe it before Builder finish
+ *    b. Builder will not wipe it for you
  *
  * With this class:
  * 1. KrbAsReq has only one constructor
@@ -70,19 +75,17 @@
     private HostAddresses addresses;
 
     // Secret source: can't be changed once assigned, only one (of the two
-    // sources) can be set and should be non-null
-    private EncryptionKey[] keys;
-    private char[] password;
+    // sources) can be set to non-null
+    private final char[] password;
+    private final KeyTab ktab;
 
     // Used to create a ENC-TIMESTAMP in the 2nd AS-REQ
-    private EncryptionKey pakey;
     private PAData[] paList;        // PA-DATA from both KRB-ERROR and AS-REP.
                                     // Used by getKeys() only.
                                     // Only AS-REP should be enough per RFC,
                                     // combined in case etypes are different.
 
     // The generated and received:
-    int[] eTypes;
     private KrbAsReq req;
     private KrbAsRep rep;
 
@@ -94,7 +97,7 @@
     private State state;
 
     // Called by other constructors
-    private KrbAsReqBuilder(PrincipalName cname)
+    private void init(PrincipalName cname)
             throws KrbException {
         if (cname.getRealm() == null) {
             cname.setRealm(Config.getInstance().getDefaultRealm());
@@ -114,14 +117,11 @@
      * This argument will neither be modified nor stored by the method.
      * @throws KrbException
      */
-    public KrbAsReqBuilder(PrincipalName cname, EncryptionKey[] keys)
+    public KrbAsReqBuilder(PrincipalName cname, KeyTab ktab)
             throws KrbException {
-        this(cname);
-        this.keys = new EncryptionKey[keys.length];
-        for (int i=0; i<keys.length; i++) {
-            this.keys[i] = (EncryptionKey)keys[i].clone();
-        }
-        eTypes = EType.getDefaults("default_tkt_enctypes", keys);
+        init(cname);
+        this.ktab = ktab;
+        this.password = null;
     }
 
     /**
@@ -137,30 +137,24 @@
      */
     public KrbAsReqBuilder(PrincipalName cname, char[] pass)
             throws KrbException {
-        this(cname);
+        init(cname);
         this.password = pass.clone();
-        eTypes = EType.getDefaults("default_tkt_enctypes");
+        this.ktab = null;
     }
 
     /**
-     * Retrieves an array of secret keys for the client. This is useful if
+     * Retrieves an array of secret keys for the client. This is used when
      * the client supplies password but need keys to act as an acceptor
      * (in JAAS words, isInitiator=true and storeKey=true)
-     * @return original keys if initiated with keys, or generated keys if
-     * password. In latter case, PA-DATA from server might be used to
-     * generate keys. All "default_tkt_enctypes" keys will be generated,
-     * Never null.
+     * @return generated keys from password. PA-DATA from server might be used.
+     * All "default_tkt_enctypes" keys will be generated, Never null.
+     * @throws IllegalStateException if not constructed from a password
      * @throws KrbException
      */
     public EncryptionKey[] getKeys() throws KrbException {
         checkState(State.REQ_OK, "Cannot get keys");
-        if (keys != null) {
-            EncryptionKey[] result = new EncryptionKey[keys.length];
-            for (int i=0; i<keys.length; i++) {
-                result[i] = (EncryptionKey)keys[i].clone();
-            }
-            return result;
-        } else {
+        if (password != null) {
+            int[] eTypes = EType.getDefaults("default_tkt_enctypes");
             EncryptionKey[] result = new EncryptionKey[eTypes.length];
 
             /*
@@ -205,6 +199,8 @@
                 }
             }
             return result;
+        } else {
+            throw new IllegalStateException("Required password not provided");
         }
     }
 
@@ -241,12 +237,22 @@
     /**
      * Build a KrbAsReq object from all info fed above. Normally this method
      * will be called twice: initial AS-REQ and second with pakey
+     * @param key null (initial AS-REQ) or pakey (with preauth)
      * @return the KrbAsReq object
      * @throws KrbException
      * @throws IOException
      */
-    private KrbAsReq build() throws KrbException, IOException {
-        return new KrbAsReq(pakey,
+    private KrbAsReq build(EncryptionKey key) throws KrbException, IOException {
+        int[] eTypes;
+        if (password != null) {
+            eTypes = EType.getDefaults("default_tkt_enctypes");
+        } else {
+            EncryptionKey[] ks = Krb5Util.keysFromJavaxKeyTab(ktab, cname);
+            eTypes = EType.getDefaults("default_tkt_enctypes",
+                    ks);
+            for (EncryptionKey k: ks) k.destroy();
+        }
+        return new KrbAsReq(key,
             options,
             cname,
             sname,
@@ -263,9 +269,10 @@
      * @throws Asn1Exception
      * @throws IOException
      */
-    private KrbAsReqBuilder resolve() throws KrbException, Asn1Exception, IOException {
-        if (keys != null) {
-            rep.decryptUsingKeys(keys, req);
+    private KrbAsReqBuilder resolve()
+            throws KrbException, Asn1Exception, IOException {
+        if (ktab != null) {
+            rep.decryptUsingKeyTab(ktab, req, cname);
         } else {
             rep.decryptUsingPassword(password, req, cname);
         }
@@ -292,9 +299,10 @@
     private KrbAsReqBuilder send() throws KrbException, IOException {
         boolean preAuthFailedOnce = false;
         KdcComm comm = new KdcComm(cname.getRealmAsString());
+        EncryptionKey pakey = null;
         while (true) {
             try {
-                req = build();
+                req = build(pakey);
                 rep = new KrbAsRep(comm.send(req.encoding()));
                 return this;
             } catch (KrbException ke) {
@@ -308,7 +316,10 @@
                     preAuthFailedOnce = true;
                     KRBError kerr = ke.getError();
                     if (password == null) {
-                        pakey = EncryptionKey.findKey(kerr.getEType(), keys);
+                        EncryptionKey[] ks = Krb5Util.keysFromJavaxKeyTab(ktab, cname);
+                        pakey = EncryptionKey.findKey(kerr.getEType(), ks);
+                        if (pakey != null) pakey = (EncryptionKey)pakey.clone();
+                        for (EncryptionKey k: ks) k.destroy();
                     } else {
                         PAData.SaltAndParams snp = PAData.getSaltAndParams(
                                 kerr.getEType(), kerr.getPA());
@@ -317,7 +328,7 @@
                             // does not recommend this
                             pakey = EncryptionKey.acquireSecretKey(password,
                                     snp.salt == null ? cname.getSalt() : snp.salt,
-                                    eTypes[0],
+                                    EType.getDefaults("default_tkt_enctypes")[0],
                                     null);
                         } else {
                             pakey = EncryptionKey.acquireSecretKey(password,
@@ -369,15 +380,8 @@
      */
     public void destroy() {
         state = State.DESTROYED;
-        if (keys != null) {
-            for (EncryptionKey k: keys) {
-                k.destroy();
-            }
-            keys = null;
-        }
         if (password != null) {
             Arrays.fill(password, (char)0);
-            password = null;
         }
     }
 
--- a/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java	Thu Apr 21 14:25:46 2011 +0100
@@ -40,6 +40,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
@@ -50,92 +51,138 @@
  * This class represents key table. The key table functions deal with storing
  * and retrieving service keys for use in authentication exchanges.
  *
+ * A KeyTab object is always constructed, if the file specified does not
+ * exist, it's still valid but empty. If there is an I/O error or file format
+ * error, it's invalid.
+ *
+ * The class is immutable on the read side (the write side is only used by
+ * the ktab tool).
+ *
  * @author Yanni Zhang
  */
 public class KeyTab implements KeyTabConstants {
-    int kt_vno;
-    private static KeyTab singleton = null;
+
     private static final boolean DEBUG = Krb5.DEBUG;
-    private static String name;
+    private static String defaultTabName = null;
+
+    // Attention: Currently there is no way to remove a keytab from this map,
+    // this might lead to a memory leak.
+    private static Map<String,KeyTab> map = new HashMap<>();
+
+    // KeyTab file does not exist. Note: a missing keytab is still valid
+    private boolean isMissing = false;
+
+    // KeyTab file is invalid, possibly an I/O error or a file format error.
+    private boolean isValid = true;
+
+    private final String tabName;
+    private long lastModified;
+    private int kt_vno;
+
     private Vector<KeyTabEntry> entries = new Vector<>();
 
-    private KeyTab(String filename) throws IOException, RealmException {
-        init(filename);
-    }
-
-    public static KeyTab getInstance(String s) {
-        name = parse(s);
-        if (name == null) {
-            return getInstance();
+    /**
+     * Constructs a KeyTab object.
+     *
+     * If there is any I/O error or format errot during the loading, the
+     * isValid flag is set to false, and all half-read entries are dismissed.
+     * @param filename path name for the keytab file, must not be null
+     */
+    private KeyTab(String filename) {
+        tabName = filename;
+        try {
+            lastModified = new File(tabName).lastModified();
+            KeyTabInputStream kis =
+                new KeyTabInputStream(new FileInputStream(filename));
+            load(kis);
+            kis.close();
+        } catch (FileNotFoundException e) {
+            entries.clear();
+            isMissing = true;
+        } catch (Exception ioe) {
+            entries.clear();
+            isValid = false;
         }
-        return getInstance(new File(name));
     }
 
     /**
-     * Gets the single instance of KeyTab class.
-     * @param file the key tab file.
-     * @return single instance of KeyTab;
-     *  return null if error occurs while reading data out of the file.
+     * Read a keytab file. Returns a new object and save it into cache when
+     * new content (modified since last read) is available. If keytab file is
+     * invalid, the old object will be returned. This is a safeguard for
+     * partial-written keytab files or non-stable network. Please note that
+     * a missing keytab is valid, which is equivalent to an empty keytab.
+     *
+     * @param s file name of keytab, must not be null
+     * @return the keytab object, can be invalid, but never null.
      */
-    public static KeyTab getInstance(File file) {
-        try {
-            if (!(file.exists())) {
-                singleton = null;
-            } else {
-                String fname = file.getAbsolutePath();
-                // Since this class deals with file I/O operations,
-                // we want only one class instance existing.
-                if (singleton != null) {
-                    File kfile = new File(singleton.name);
-                    String kname = kfile.getAbsolutePath();
-                    if (kname.equalsIgnoreCase(fname)) {
-                       if (DEBUG) {
-                          System.out.println("KeyTab instance already exists");
-                       }
-                    }
-                } else {
-                    singleton = new KeyTab(fname);
-                }
-            }
-        } catch (Exception e) {
-            singleton = null;
-            if (DEBUG) {
-                System.out.println("Could not obtain an instance of KeyTab" +
-                                   e.getMessage());
-            }
+    private synchronized static KeyTab getInstance0(String s) {
+        long lm = new File(s).lastModified();
+        KeyTab old = map.get(s);
+        if (old != null && old.isValid() && old.lastModified == lm) {
+            return old;
         }
-        return singleton;
+        KeyTab ktab = new KeyTab(s);
+        if (ktab.isValid()) {               // A valid new keytab
+            map.put(s, ktab);
+            return ktab;
+        } else if (old != null) {           // An existing old one
+            return old;
+        } else {
+            return ktab;                    // first read is invalid
+        }
     }
 
     /**
-     * Gets the single instance of KeyTab class.
-     * @return single instance of KeyTab; return null if default keytab file
-     *  does not exist, or error occurs while reading data from the file.
+     * Gets a KeyTab object.
+     * @param s the key tab file name.
+     * @return the KeyTab object, never null.
+     */
+    public static KeyTab getInstance(String s) {
+        if (s == null) {
+            return getInstance();
+        } else {
+            return getInstance0(s);
+        }
+    }
+
+    /**
+     * Gets a KeyTab object.
+     * @param file the key tab file.
+     * @return the KeyTab object, never null.
+     */
+    public static KeyTab getInstance(File file) {
+        if (file == null) {
+            return getInstance();
+        } else {
+            return getInstance0(file.getPath());
+        }
+    }
+
+    /**
+     * Gets the default KeyTab object.
+     * @return the KeyTab object, never null.
      */
     public static KeyTab getInstance() {
-        try {
-            name = getDefaultKeyTab();
-            if (name != null) {
-                singleton = getInstance(new File(name));
-            }
-        } catch (Exception e) {
-            singleton = null;
-            if (DEBUG) {
-                System.out.println("Could not obtain an instance of KeyTab" +
-                                   e.getMessage());
-            }
-        }
-        return singleton;
+        return getInstance(getDefaultTabName());
+    }
+
+    public boolean isMissing() {
+        return isMissing;
+    }
+
+    public boolean isValid() {
+        return isValid;
     }
 
     /**
      * The location of keytab file will be read from the configuration file
      * If it is not specified, consider user.home as the keytab file's
      * default location.
+     * @return never null
      */
-    private static String getDefaultKeyTab() {
-        if (name != null) {
-            return name;
+    private static String getDefaultTabName() {
+        if (defaultTabName != null) {
+            return defaultTabName;
         } else {
             String kname = null;
             try {
@@ -145,7 +192,7 @@
                     StringTokenizer st = new StringTokenizer(keytab_names, " ");
                     while (st.hasMoreTokens()) {
                         kname = parse(st.nextToken());
-                        if (kname != null) {
+                        if (new File(kname).exists()) {
                             break;
                         }
                     }
@@ -165,19 +212,20 @@
                         new sun.security.action.GetPropertyAction("user.dir"));
                 }
 
-                if (user_home != null) {
-                    kname = user_home + File.separator  + "krb5.keytab";
-                }
+                kname = user_home + File.separator  + "krb5.keytab";
             }
+            defaultTabName = kname;
             return kname;
         }
     }
 
+    /**
+     * Parses some common keytab name formats
+     * @param name never null
+     * @return never null
+     */
     private static String parse(String name) {
-        String kname = null;
-        if (name == null) {
-            return null;
-        }
+        String kname;
         if ((name.length() >= 5) &&
             (name.substring(0, 5).equalsIgnoreCase("FILE:"))) {
             kname = name.substring(5);
@@ -194,18 +242,6 @@
         return kname;
     }
 
-    private synchronized void init(String filename)
-        throws IOException, RealmException {
-
-        if (filename != null) {
-            KeyTabInputStream kis =
-                new KeyTabInputStream(new FileInputStream(filename));
-            load(kis);
-            kis.close();
-            name = filename;
-        }
-    }
-
     private void load(KeyTabInputStream kis)
         throws IOException, RealmException {
 
@@ -234,14 +270,13 @@
      * etypes that have been configured for use. If there are multiple
      * keys with same etype, the one with the highest kvno is returned.
      * @param service the PrincipalName of the requested service
-     * @return an array containing all the service keys
+     * @return an array containing all the service keys, never null
      */
     public EncryptionKey[] readServiceKeys(PrincipalName service) {
         KeyTabEntry entry;
         EncryptionKey key;
         int size = entries.size();
         ArrayList<EncryptionKey> keys = new ArrayList<>(size);
-
         for (int i = size-1; i >= 0; i--) {
             entry = entries.elementAt(i);
             if (entry.service.match(service)) {
@@ -260,10 +295,7 @@
                 }
             }
         }
-
         size = keys.size();
-        if (size == 0)
-            return null;
         EncryptionKey[] retVal = keys.toArray(new EncryptionKey[size]);
 
         // Sort keys according to default_tkt_enctypes
@@ -328,10 +360,13 @@
         return false;
     }
 
-    public static String tabName() {
-        return name;
+    public String tabName() {
+        return tabName;
     }
 
+    /////////////////// THE WRITE SIDE ///////////////////////
+    /////////////// only used by ktab tool //////////////////
+
     /**
      * Adds a new entry in the key table.
      * @param service the service which will have a new entry in the key table.
@@ -394,7 +429,7 @@
      */
     public synchronized static KeyTab create()
         throws IOException, RealmException {
-        String dname = getDefaultKeyTab();
+        String dname = getDefaultTabName();
         return create(dname);
     }
 
@@ -408,8 +443,7 @@
                 new KeyTabOutputStream(new FileOutputStream(name));
         kos.writeVersion(KRB5_KT_VNO);
         kos.close();
-        singleton = new KeyTab(name);
-        return singleton;
+        return new KeyTab(name);
     }
 
     /**
@@ -417,7 +451,7 @@
      */
     public synchronized void save() throws IOException {
         KeyTabOutputStream kos =
-                new KeyTabOutputStream(new FileOutputStream(name));
+                new KeyTabOutputStream(new FileOutputStream(tabName));
         kos.writeVersion(kt_vno);
         for (int i = 0; i < entries.size(); i++) {
             kos.writeEntry(entries.elementAt(i));
@@ -490,13 +524,4 @@
         kos.write16(KRB5_KT_VNO);
         kos.close();
     }
-
-    public static void refresh() {
-        if (singleton != null) {
-            if (DEBUG) {
-                System.out.println("Refreshing Keytab");
-            }
-            singleton = null;
-        }
-    }
 }
--- a/src/share/classes/sun/security/ssl/ServerHandshaker.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/security/ssl/ServerHandshaker.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2011, 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
@@ -1285,11 +1285,12 @@
 
             // check permission to access and use the secret key of the
             // Kerberized "host" service
-            if (kerberosKeys != null) {
-
+            if (kerberosKeys != null && kerberosKeys.length > 0) {
                 if (debug != null && Debug.isOn("handshake")) {
-                    System.out.println("Using Kerberos key: " +
-                        kerberosKeys[0]);
+                    for (SecretKey k: kerberosKeys) {
+                        System.out.println("Using Kerberos key: " +
+                            k);
+                    }
                 }
 
                 String serverPrincipal =
--- a/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2011, 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
@@ -40,7 +40,7 @@
 import sun.security.ssl.Krb5Proxy;
 
 /**
- * An implementatin of Krb5Proxy that simply delegates to the appropriate
+ * An implementation of Krb5Proxy that simply delegates to the appropriate
  * Kerberos APIs.
  */
 public class Krb5ProxyImpl implements Krb5Proxy {
@@ -62,7 +62,7 @@
     @Override
     public SecretKey[] getServerKeys(AccessControlContext acc)
             throws LoginException {
-        return Krb5Util.getKeys(GSSCaller.CALLER_SSL_SERVER, null, acc);
+        return Krb5Util.getServiceCreds(GSSCaller.CALLER_SSL_SERVER, null, acc).getKKeys();
     }
 
     @Override
--- a/src/windows/classes/java/lang/ProcessEnvironment.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/windows/classes/java/lang/ProcessEnvironment.java	Thu Apr 21 14:25:46 2011 +0100
@@ -143,7 +143,7 @@
                 public void remove() { i.remove();}
             };
         }
-        private static Map.Entry<String,String> checkedEntry (Object o) {
+        private static Map.Entry<String,String> checkedEntry(Object o) {
             Map.Entry<String,String> e = (Map.Entry<String,String>) o;
             nonNullString(e.getKey());
             nonNullString(e.getValue());
@@ -285,7 +285,7 @@
         return (Map<String,String>) theEnvironment.clone();
     }
 
-    // Only for use by Runtime.exec(...String[]envp...)
+    // Only for use by ProcessBuilder.environment(String[] envp)
     static Map<String,String> emptyEnvironment(int capacity) {
         return new ProcessEnvironment(capacity);
     }
@@ -299,19 +299,46 @@
         Collections.sort(list, entryComparator);
 
         StringBuilder sb = new StringBuilder(size()*30);
-        for (Map.Entry<String,String> e : list)
-            sb.append(e.getKey())
-              .append('=')
-              .append(e.getValue())
-              .append('\u0000');
-        // Ensure double NUL termination,
-        // even if environment is empty.
-        if (sb.length() == 0)
+        int cmp = -1;
+
+        // Some versions of MSVCRT.DLL require SystemRoot to be set.
+        // So, we make sure that it is always set, even if not provided
+        // by the caller.
+        final String SYSTEMROOT = "SystemRoot";
+
+        for (Map.Entry<String,String> e : list) {
+            String key = e.getKey();
+            String value = e.getValue();
+            if (cmp < 0 && (cmp = nameComparator.compare(key, SYSTEMROOT)) > 0) {
+                // Not set, so add it here
+                addToEnvIfSet(sb, SYSTEMROOT);
+            }
+            addToEnv(sb, key, value);
+        }
+        if (cmp < 0) {
+            // Got to end of list and still not found
+            addToEnvIfSet(sb, SYSTEMROOT);
+        }
+        if (sb.length() == 0) {
+            // Environment was empty and SystemRoot not set in parent
             sb.append('\u0000');
+        }
+        // Block is double NUL terminated
         sb.append('\u0000');
         return sb.toString();
     }
 
+    // add the environment variable to the child, if it exists in parent
+    private static void addToEnvIfSet(StringBuilder sb, String name) {
+        String s = getenv(name);
+        if (s != null)
+            addToEnv(sb, name, s);
+    }
+
+    private static void addToEnv(StringBuilder sb, String name, String val) {
+        sb.append(name).append('=').append(val).append('\u0000');
+    }
+
     static String toEnvironmentBlock(Map<String,String> map) {
         return map == null ? null :
             ((ProcessEnvironment)map).toEnvironmentBlock();
--- a/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/windows/classes/sun/security/krb5/internal/tools/Kinit.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2011, 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
@@ -30,12 +30,15 @@
 
 package sun.security.krb5.internal.tools;
 
+import java.io.File;
 import sun.security.krb5.*;
 import sun.security.krb5.internal.*;
 import sun.security.krb5.internal.ccache.*;
 import java.io.IOException;
 import java.util.Arrays;
+import javax.security.auth.kerberos.KerberosPrincipal;
 import sun.security.util.Password;
+import javax.security.auth.kerberos.KeyTab;
 
 /**
  * Kinit tool for obtaining Kerberos v5 tickets.
@@ -153,7 +156,6 @@
             System.out.println("Principal is " + principal);
         }
         char[] psswd = options.password;
-        EncryptionKey[] skeys = null;
         boolean useKeytab = options.useKeytabFile();
         if (!useKeytab) {
             if (princName == null) {
@@ -186,17 +188,9 @@
                 }
             }
 
-            // assert princName and principal are nonnull
-            skeys = EncryptionKey.acquireSecretKeys(principal, ktabName);
-
-            if (skeys == null || skeys.length == 0) {
-                String msg = "No supported key found in keytab";
-                if (princName != null) {
-                    msg += " for principal " + princName;
-                }
-                throw new KrbException(msg);
-            }
-            builder = new KrbAsReqBuilder(principal, skeys);
+            builder = new KrbAsReqBuilder(principal, ktabName == null
+                    ? KeyTab.getInstance()
+                    : KeyTab.getInstance(new File(ktabName)));
         }
 
         KDCOptions opt = new KDCOptions();
--- a/src/windows/classes/sun/security/krb5/internal/tools/Klist.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/windows/classes/sun/security/krb5/internal/tools/Klist.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2011, 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
@@ -95,16 +95,15 @@
             }
             break;
         case 'k':
-            if (klist.name == null) {
-                klist.target = KeyTab.getInstance();
-                klist.name = KeyTab.tabName();
-            } else klist.target = KeyTab.getInstance(klist.name);
-            if (klist.target != null) {
-                klist.displayTab();
-            } else {
+            try {
+                KeyTab ktab = KeyTab.getInstance(klist.name);
+                klist.target = ktab;
+                klist.name = ktab.tabName();
+            } catch (Exception e) {
                 klist.displayMessage("KeyTab");
                 System.exit(-1);
             }
+            klist.displayTab();
             break;
         default:
             if (klist.name != null) {
@@ -295,9 +294,10 @@
 
     void displayMessage(String target) {
         if (name == null) {
-            name = "";
+            System.out.println("Default " + target + " not found.");
+        } else {
+            System.out.println(target + " " + name + " not found.");
         }
-        System.out.println(target + " " + name + " not found.");
     }
     /**
      * Reformats the date from the form -
--- a/src/windows/classes/sun/security/krb5/internal/tools/Ktab.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/src/windows/classes/sun/security/krb5/internal/tools/Ktab.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2011, 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
@@ -321,7 +321,7 @@
      * Lists key table name and entries in it.
      */
     void listKt() {
-        System.out.println("Keytab name: " + KeyTab.tabName());
+        System.out.println("Keytab name: " + table.tabName());
         KeyTabEntry[] entries = table.getEntries();
         if ((entries != null) && (entries.length > 0)) {
             String[][] output = new String[entries.length+1][showTime?3:2];
--- a/test/java/lang/ProcessBuilder/Basic.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/test/java/lang/ProcessBuilder/Basic.java	Thu Apr 21 14:25:46 2011 +0100
@@ -26,7 +26,7 @@
  * @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689
  *      5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313
  *      6464154 6523983 6206031 4960438 6631352 6631966 6850957 6850958
- *      4947220 7018606
+ *      4947220 7018606 7034570
  * @summary Basic tests for Process and Environment Variable code
  * @run main/othervm/timeout=300 Basic
  * @author Martin Buchholz
@@ -1440,11 +1440,12 @@
         // Check for sort order of environment variables on Windows.
         //----------------------------------------------------------------
         try {
+            String systemRoot = "SystemRoot=" + System.getenv("SystemRoot");
             // '+' < 'A' < 'Z' < '_' < 'a' < 'z' < '~'
             String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=",
-                            "+=+", "_=_", "~=~"};
+                            "+=+", "_=_", "~=~", systemRoot};
             String output = nativeEnv(envp);
-            String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n_=_\n~=~\n";
+            String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n";
             // On Windows, Java must keep the environment sorted.
             // Order is random on Unix, so this test does the sort.
             if (! Windows.is())
@@ -1453,6 +1454,21 @@
         } catch (Throwable t) { unexpected(t); }
 
         //----------------------------------------------------------------
+        // Test Runtime.exec(...envp...)
+        // and check SystemRoot gets set automatically on Windows
+        //----------------------------------------------------------------
+        try {
+            if (Windows.is()) {
+                String systemRoot = "SystemRoot=" + System.getenv("SystemRoot");
+                String[]envp = {"FOO=BAR","BAZ=GORP","QUUX=",
+                                "+=+", "_=_", "~=~"};
+                String output = nativeEnv(envp);
+                String expected = "+=+\nBAZ=GORP\nFOO=BAR\nQUUX=\n"+systemRoot+"\n_=_\n~=~\n";
+                equal(output, expected);
+            }
+        } catch (Throwable t) { unexpected(t); }
+
+        //----------------------------------------------------------------
         // System.getenv() must be consistent with System.getenv(String)
         //----------------------------------------------------------------
         try {
--- a/test/sun/security/krb5/auto/Context.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/test/sun/security/krb5/auto/Context.java	Thu Apr 21 14:25:46 2011 +0100
@@ -44,6 +44,7 @@
 import com.sun.security.jgss.AuthorizationDataEntry;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import javax.security.auth.kerberos.KeyTab;
 
 /**
  * Context of a JGSS subject, encapsulating Subject and GSSContext.
@@ -107,15 +108,19 @@
         return out;
     }
 
+    public static Context fromUserPass(
+            String user, char[] pass, boolean storeKey) throws Exception {
+        return fromUserPass(null, user, pass, storeKey);
+    }
     /**
      * 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(Subject s,
+            String user, char[] pass, boolean storeKey) throws Exception {
         Context out = new Context();
         out.name = user;
-        out.s = new Subject();
+        out.s = s == null ? new Subject() : s;
         Krb5LoginModule krb5 = new Krb5LoginModule();
         Map<String, String> map = new HashMap<>();
         Map<String, Object> shared = new HashMap<>();
@@ -198,12 +203,25 @@
      * @throws java.lang.Exception
      */
     public void startAsServer(final Oid mech) throws Exception {
+        startAsServer(null, mech);
+    }
+
+    /**
+     * Starts as a server with the specified service name
+     * @param name the service name
+     * @param mech GSS mech
+     * @throws java.lang.Exception
+     */
+    public void startAsServer(final String name, final Oid mech) throws Exception {
         doAs(new Action() {
             @Override
             public byte[] run(Context me, byte[] dummy) throws Exception {
                 GSSManager m = GSSManager.getInstance();
                 me.x = (ExtendedGSSContext)m.createContext(m.createCredential(
-                        null,
+                        name == null ? null :
+                          (name.indexOf('@') < 0 ?
+                            m.createName(name, null) :
+                            m.createName(name, GSSName.NT_HOSTBASED_SERVICE)),
                         GSSCredential.INDEFINITE_LIFETIME,
                         mech,
                         GSSCredential.ACCEPT_ONLY));
@@ -230,6 +248,14 @@
     }
 
     /**
+     * Accesses the internal subject.
+     * @return the subject
+     */
+    public Subject s() {
+        return s;
+    }
+
+    /**
      * Disposes the GSSContext within
      * @throws org.ietf.jgss.GSSException
      */
@@ -297,7 +323,7 @@
         } catch (Exception e) {
             ;// Don't care
         }
-        System.out.println("=====================================");
+        System.out.println("====== Private Credentials Set ======");
         for (Object o : s.getPrivateCredentials()) {
             System.out.println("    " + o.getClass());
             if (o instanceof KerberosTicket) {
@@ -315,6 +341,8 @@
                 for (Object k : map.keySet()) {
                     System.out.println("        " + k + ": " + map.get(k));
                 }
+            } else {
+                System.out.println("        " + o);
             }
         }
         if (x != null && x instanceof ExtendedGSSContext) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/DynamicKeytab.java	Thu Apr 21 14:25:46 2011 +0100
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2011, 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 6894072
+ * @run main/othervm DynamicKeytab
+ * @summary always refresh keytab
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import org.ietf.jgss.GSSException;
+import sun.security.jgss.GSSUtil;
+import sun.security.krb5.KrbException;
+import sun.security.krb5.internal.Krb5;
+
+public class DynamicKeytab {
+
+    Context c, s;
+    public static void main(String[] args)
+            throws Exception {
+        new DynamicKeytab().go();
+    }
+
+    void go() throws Exception {
+        OneKDC k = new OneKDC(null);
+        k.writeJAASConf();
+
+        new File(OneKDC.KTAB).delete();
+
+
+        // Starts with no keytab
+        c = Context.fromJAAS("client");
+        s = Context.fromJAAS("com.sun.security.jgss.krb5.accept");
+
+        // Test 1: read new key 1 from keytab
+        k.addPrincipal(OneKDC.SERVER, "pass1".toCharArray());
+        k.writeKtab(OneKDC.KTAB);
+        connect();
+
+        // Test 2: service key cached, find 1 in keytab (now contains 1 and 2)
+        k.addPrincipal(OneKDC.SERVER, "pass2".toCharArray());
+        k.appendKtab(OneKDC.KTAB);
+        connect();
+
+        // Test 3: re-login. Now find 2 in keytab
+        c = Context.fromJAAS("client");
+        connect();
+
+        // Test 4: re-login, KDC use 3 this time.
+        c = Context.fromJAAS("client");
+        // Put 3 and 4 into keytab but keep the real key back to 3.
+        k.addPrincipal(OneKDC.SERVER, "pass3".toCharArray());
+        k.appendKtab(OneKDC.KTAB);
+        k.addPrincipal(OneKDC.SERVER, "pass4".toCharArray());
+        k.appendKtab(OneKDC.KTAB);
+        k.addPrincipal(OneKDC.SERVER, "pass3".toCharArray());
+        connect();
+
+        // Test 5: invalid keytab file, should ignore
+        new FileOutputStream(OneKDC.KTAB).write("BADBADBAD".getBytes());
+        connect();
+
+        // Test 6: delete keytab file, identical to revoke all
+        new File(OneKDC.KTAB).delete();
+        try {
+            connect();
+            throw new Exception("Should not success");
+        } catch (GSSException gsse) {
+            System.out.println(gsse);
+            KrbException ke = (KrbException)gsse.getCause();
+            // KrbApReq.authenticate(*) if (dkey == null)...
+            // This should have been Krb5.KRB_AP_ERR_NOKEY
+            if (ke.returnCode() != Krb5.API_INVALID_ARG) {
+                throw new Exception("Not expected failure code: " +
+                        ke.returnCode());
+            }
+        }
+
+        // Test 7: 3 revoked, should fail (now contains only 5)
+        k.addPrincipal(OneKDC.SERVER, "pass5".toCharArray());
+        k.writeKtab(OneKDC.KTAB);   // overwrite keytab, which means
+                                    // old key is revoked
+        try {
+            connect();
+            throw new Exception("Should not success");
+        } catch (GSSException gsse) {
+            System.out.println(gsse);
+            KrbException ke = (KrbException)gsse.getCause();
+            if (ke.returnCode() != Krb5.KRB_AP_ERR_BADKEYVER) {
+                throw new Exception("Not expected failure code: " +
+                        ke.returnCode());
+            }
+        }
+
+        // Test 8: an empty KDC means revoke all
+        KDC.create("EMPTY.REALM").writeKtab(OneKDC.KTAB);
+        try {
+            connect();
+            throw new Exception("Should not success");
+        } catch (GSSException gsse) {
+            System.out.println(gsse);
+            KrbException ke = (KrbException)gsse.getCause();
+            // KrbApReq.authenticate(*) if (dkey == null)...
+            // This should have been Krb5.KRB_AP_ERR_NOKEY
+            if (ke.returnCode() != Krb5.API_INVALID_ARG) {
+                throw new Exception("Not expected failure code: " +
+                        ke.returnCode());
+            }
+        }
+    }
+
+    void connect() throws Exception {
+        Thread.sleep(2000);     // make sure ktab timestamp is different
+        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+        Context.handshake(c, s);
+    }
+}
--- a/test/sun/security/krb5/auto/KDC.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/test/sun/security/krb5/auto/KDC.java	Thu Apr 21 14:25:46 2011 +0100
@@ -228,7 +228,33 @@
     }
 
     /**
-     * Write all principals' keys from multiple KDCsinto one keytab file.
+     * Writes or appends KDC keys into a keytab. See doc for writeMultiKtab.
+     * @param append true if append, otherwise, overwrite.
+     */
+    private static void writeKtab0(String tab, boolean append, KDC... kdcs)
+            throws IOException, KrbException {
+        KeyTab ktab = append ? KeyTab.getInstance(tab) : KeyTab.create(tab);
+        for (KDC kdc: kdcs) {
+            for (String name : kdc.passwords.keySet()) {
+                char[] pass = kdc.passwords.get(name);
+                int kvno = 0;
+                if (Character.isDigit(pass[pass.length-1])) {
+                    kvno = pass[pass.length-1] - '0';
+                }
+                ktab.addEntry(new PrincipalName(name,
+                        name.indexOf('/') < 0 ?
+                            PrincipalName.KRB_NT_UNKNOWN :
+                            PrincipalName.KRB_NT_SRV_HST),
+                            pass,
+                            kvno,
+                            true);
+            }
+        }
+        ktab.save();
+    }
+
+    /**
+     * Writes all principals' keys from multiple KDCs into one keytab file.
      * Note that the keys for the krbtgt principals will not be written.
      * <p>
      * Attention: This method references krb5.conf settings. If you need to
@@ -252,17 +278,16 @@
      */
     public static void writeMultiKtab(String tab, KDC... kdcs)
             throws IOException, KrbException {
-        KeyTab ktab = KeyTab.create(tab);
-        for (KDC kdc: kdcs) {
-            for (String name : kdc.passwords.keySet()) {
-                ktab.addEntry(new PrincipalName(name,
-                        name.indexOf('/') < 0 ?
-                            PrincipalName.KRB_NT_UNKNOWN :
-                            PrincipalName.KRB_NT_SRV_HST),
-                            kdc.passwords.get(name), -1, true);
-            }
-        }
-        ktab.save();
+        writeKtab0(tab, false, kdcs);
+    }
+
+    /**
+     * Appends all principals' keys from multiple KDCs to one keytab file.
+     * See writeMultiKtab for details.
+     */
+    public static void appendMultiKtab(String tab, KDC... kdcs)
+            throws IOException, KrbException {
+        writeKtab0(tab, true, kdcs);
     }
 
     /**
@@ -273,6 +298,13 @@
     }
 
     /**
+     * Appends keys in this KDC to a ktab.
+     */
+    public void appendKtab(String tab) throws IOException, KrbException {
+        KDC.appendMultiKtab(tab, this);
+    }
+
+    /**
      * Adds a new principal to this realm with a given password.
      * @param user the principal's name. For a service principal, use the
      *        form of host/f.q.d.n
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/KeyTabCompat.java	Thu Apr 21 14:25:46 2011 +0100
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2011, 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 6894072
+ * @compile -XDignore.symbol.file KeyTabCompat.java
+ * @run main/othervm KeyTabCompat
+ * @summary always refresh keytab
+ */
+
+import javax.security.auth.kerberos.KerberosKey;
+import sun.security.jgss.GSSUtil;
+
+/*
+ * There are 2 compat issues to check:
+ *
+ * 1. If there is only KerberosKeys in private credential set and no
+ *    KerberosPrincipal. JAAS login should go on.
+ * 2. Even if KeyTab is used, user can still get KerberosKeys from
+ *    private credentials set.
+ */
+public class KeyTabCompat {
+
+    public static void main(String[] args)
+            throws Exception {
+        OneKDC kdc = new OneKDC("aes128-cts");
+        kdc.writeJAASConf();
+        kdc.addPrincipal(OneKDC.SERVER, "pass1".toCharArray());
+        kdc.writeKtab(OneKDC.KTAB);
+
+        Context c, s;
+
+        // Part 1
+        c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+        s = Context.fromUserPass(OneKDC.USER2, OneKDC.PASS2, true);
+
+        s.s().getPrincipals().clear();
+
+        c.startAsClient(OneKDC.USER2, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+
+        Context.handshake(c, s);
+
+        // Part 2
+        c = Context.fromJAAS("client");
+        s = Context.fromJAAS("server");
+
+        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+        s.status();
+
+        if (s.s().getPrivateCredentials(KerberosKey.class).size() != 1) {
+            throw new Exception("There should be one KerberosKey");
+        }
+
+        Thread.sleep(2000);     // make sure ktab timestamp is different
+
+        kdc.addPrincipal(OneKDC.SERVER, "pass2".toCharArray());
+        kdc.writeKtab(OneKDC.KTAB);
+
+        Context.handshake(c, s);
+        s.status();
+
+        if (s.s().getPrivateCredentials(KerberosKey.class).size() != 1) {
+            throw new Exception("There should be only one KerberosKey");
+        }
+
+    }
+}
--- a/test/sun/security/krb5/auto/LoginModuleOptions.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/test/sun/security/krb5/auto/LoginModuleOptions.java	Thu Apr 21 14:25:46 2011 +0100
@@ -28,7 +28,6 @@
  * @summary Krb5LoginModule a little too restrictive, and the doc is not clear.
  */
 import com.sun.security.auth.module.Krb5LoginModule;
-import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 import javax.security.auth.Subject;
@@ -36,7 +35,6 @@
 import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.callback.NameCallback;
 import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
 
 public class LoginModuleOptions {
 
@@ -61,10 +59,12 @@
         // 1. ccache -> keytab
         login(null, "useTicketCache", "true", "ticketCache", "krbcc_non_exists",
                 "useKeyTab", "true", "principal", "dummy");
+
         // 2. keytab -> shared
         login(null, "useKeyTab", "true", "principal", "dummy",
                 "keyTab", "ktab_non_exist",
                 "tryFirstPass", "true", NAME, OneKDC.USER, PWD, OneKDC.PASS);
+
         // 3. shared -> callback
         // 3.1. useFirstPass, no callback
         boolean failed = false;
--- a/test/sun/security/krb5/auto/SSL.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/test/sun/security/krb5/auto/SSL.java	Thu Apr 21 14:25:46 2011 +0100
@@ -48,7 +48,7 @@
 public class SSL {
 
     private static String krb5Cipher;
-    private static final int LOOP_LIMIT = 1;
+    private static final int LOOP_LIMIT = 3;
     private static int loopCount = 0;
     private static volatile String server;
     private static volatile int port;
@@ -98,13 +98,13 @@
         fos.close();
         f.deleteOnExit();
 
-        final Context c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+        Context c;
         final Context s = Context.fromJAAS("ssl");
 
-        c.startAsClient("host/" + server, GSSUtil.GSS_KRB5_MECH_OID);
+        // There's no keytab file when server starts.
         s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
 
-        new Thread(new Runnable() {
+        Thread server = new Thread(new Runnable() {
             public void run() {
                 try {
                     s.doAs(new JsseServerAction(), null);
@@ -112,12 +112,57 @@
                     e.printStackTrace();
                 }
             }
-        }).start();
+        });
+        server.setDaemon(true);
+        server.start();
 
         // Warm the server
         Thread.sleep(2000);
 
+        // Now create the keytab
+
+        /*
+        // Add 3 versions of keys into keytab
+        KeyTab ktab = KeyTab.create(OneKDC.KTAB);
+        PrincipalName service = new PrincipalName(
+                "host/" + server, PrincipalName.KRB_NT_SRV_HST);
+        ktab.addEntry(service, "pass1".toCharArray(), 1);
+        ktab.addEntry(service, "pass2".toCharArray(), 2);
+        ktab.addEntry(service, "pass3".toCharArray(), 3);
+        ktab.save();
+
+        // and use the middle one as the  real key
+        kdc.addPrincipal("host/" + server, "pass2".toCharArray());
+         */
+        c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+        c.startAsClient("host/" + server, GSSUtil.GSS_KRB5_MECH_OID);
         c.doAs(new JsseClientAction(), null);
+
+        // Add another version of key, make sure it can be loaded
+        Thread.sleep(2000);
+        ktab = KeyTab.getInstance(OneKDC.KTAB);
+        ktab.addEntry(service, "pass4".toCharArray(), 4, true);
+        ktab.save();
+        kdc.addPrincipal("host/" + server, "pass4".toCharArray());
+
+        c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+        c.startAsClient("host/" + server, GSSUtil.GSS_KRB5_MECH_OID);
+        c.doAs(new JsseClientAction(), null);
+
+        // Revoke the old key
+        /*Thread.sleep(2000);
+        ktab = KeyTab.create(OneKDC.KTAB);
+        ktab.addEntry(service, "pass5".toCharArray(), 5, false);
+        ktab.save();
+
+        c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+        c.startAsClient("host/" + server, GSSUtil.GSS_KRB5_MECH_OID);
+        try {
+            c.doAs(new JsseClientAction(), null);
+            throw new Exception("Should fail this time.");
+        } catch (SSLException e) {
+            // Correct behavior.
+        }*/
     }
 
     // Following codes copied from
@@ -126,6 +171,7 @@
         public byte[] run(Context s, byte[] input) throws Exception {
             SSLSocketFactory sslsf =
                 (SSLSocketFactory) SSLSocketFactory.getDefault();
+            System.out.println("Connecting " + server + ":" + port);
             SSLSocket sslSocket = (SSLSocket) sslsf.createSocket(server, port);
 
             // Enable only a KRB5 cipher suite.
@@ -154,6 +200,9 @@
             System.out.println("Server is: " + peer.toString());
 
             sslSocket.close();
+            // This line should not be needed. It's the server's duty to
+            // forget the old key
+            //sslSocket.getSession().invalidate();
             return null;
         }
     }
@@ -165,6 +214,7 @@
             SSLServerSocket sslServerSocket =
                 (SSLServerSocket) sslssf.createServerSocket(0); // any port
             port = sslServerSocket.getLocalPort();
+            System.out.println("Listening on " + port);
 
             // Enable only a KRB5 cipher suite.
             String enabledSuites[] = {krb5Cipher};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/TwoPrinces.java	Thu Apr 21 14:25:46 2011 +0100
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2011, 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 6894072
+ * @compile -XDignore.symbol.file TwoPrinces.java
+ * @run main/othervm TwoPrinces
+ * @summary always refresh keytab
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import sun.security.jgss.GSSUtil;
+import sun.security.krb5.Config;
+
+public class TwoPrinces {
+
+    public static void main(String[] args)
+            throws Exception {
+
+        KDC k1 = KDC.create("R1");
+        k1.addPrincipal("u1", "hello".toCharArray());
+        k1.addPrincipalRandKey("krbtgt/R1");
+        k1.addPrincipalRandKey("host/same.host");
+
+        KDC k2 = KDC.create("R2");
+        k2.addPrincipal("u2", "hello".toCharArray());
+        k2.addPrincipalRandKey("krbtgt/R2");
+        k2.addPrincipalRandKey("host/same.host");
+
+        System.setProperty("java.security.krb5.conf", "krb5.conf");
+
+        // R1 is the default realm now
+        KDC.saveConfig("krb5.conf", k1, k2);
+        Config.refresh();
+
+        k1.writeKtab("ktab1");
+        k2.writeKtab("ktab2");
+
+        // A JAAS config file with 2 Krb5LoginModules, after commit, the
+        // subject with have principals and keytabs from both sides
+        System.setProperty("java.security.auth.login.config", "jaas.conf");
+        File f = new File("jaas.conf");
+        FileOutputStream fos = new FileOutputStream(f);
+        fos.write((
+                "me {\n"
+                + "  com.sun.security.auth.module.Krb5LoginModule required"
+                + "    isInitiator=true principal=\"host/same.host@R1\""
+                + "    useKeyTab=true keyTab=ktab1 storeKey=true;\n"
+                + "  com.sun.security.auth.module.Krb5LoginModule required"
+                + "    isInitiator=true principal=\"host/same.host@R2\""
+                + "    useKeyTab=true keyTab=ktab2 storeKey=true;\n"
+                + "};\n"
+                ).getBytes());
+        fos.close();
+
+        /*
+         * This server side context will be able to act as services in both
+         * realms. Please note that we still don't support a single instance
+         * of server to accept connections from two realms at the same time.
+         * Therefore, we must call startAsServer in a given realm to start
+         * working there. The same Subject never changes anyway.
+         */
+        Context s = Context.fromJAAS("me");
+
+        // Default realm still R1
+        s.startAsServer("host@same.host", GSSUtil.GSS_KRB5_MECH_OID);
+        Context c1 = Context.fromUserPass("u1", "hello".toCharArray(), false);
+        c1.startAsClient("host@same.host", GSSUtil.GSS_KRB5_MECH_OID);
+        Context.handshake(c1, s);
+
+        KDC.saveConfig("krb5.conf", k2, k1);
+        Config.refresh();
+
+        // Default realm now R2
+        s.startAsServer("host@same.host", GSSUtil.GSS_KRB5_MECH_OID);
+        Context c2 = Context.fromUserPass("u2", "hello".toCharArray(), false);
+        c2.startAsClient("host@same.host", GSSUtil.GSS_KRB5_MECH_OID);
+        Context.handshake(c2, s);
+    }
+}
--- a/test/sun/security/krb5/ktab/KeyTabIndex.java	Thu Apr 21 14:23:57 2011 +0100
+++ b/test/sun/security/krb5/ktab/KeyTabIndex.java	Thu Apr 21 14:25:46 2011 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2011, 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
@@ -44,7 +44,6 @@
                 KeyTab.getInstance("ktab").getClass();
             }
         };
-        KeyTab.refresh();
         for (int i=0; i<10; i++) {
             new Thread(t).start();
         }