8001104: Unbound SASL service: the GSSAPI/krb5 mech
authorweijun
Sat, 09 Feb 2013 16:43:49 +0800
changeset 15649 f6bd3d34f844
parent 15648 6a38cf764825
child 15650 04ebec48c104
8001104: Unbound SASL service: the GSSAPI/krb5 mech Reviewed-by: valeriep
jdk/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java
jdk/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java
jdk/src/share/classes/javax/security/auth/kerberos/KeyTab.java
jdk/src/share/classes/sun/security/jgss/LoginConfigImpl.java
jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java
jdk/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java
jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java
jdk/src/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java
jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java
jdk/src/share/classes/sun/security/provider/ConfigSpiFile.java
jdk/test/sun/security/krb5/ServiceCredsCombination.java
jdk/test/sun/security/krb5/auto/AcceptPermissions.java
jdk/test/sun/security/krb5/auto/GSSUnbound.java
jdk/test/sun/security/krb5/auto/OneKDC.java
jdk/test/sun/security/krb5/auto/SaslUnbound.java
jdk/test/sun/security/krb5/auto/UnboundService.java
--- a/jdk/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/src/share/classes/com/sun/security/auth/module/Krb5LoginModule.java	Sat Feb 09 16:43:49 2013 +0800
@@ -52,16 +52,19 @@
  * principal set and private credentials set are updated only when
  * <code>commit</code> is called.
  * When <code>commit</code> is called, the <code>KerberosPrincipal</code>
- * is added to the  <code>Subject</code>'s
- * principal set and <code>KerberosTicket</code> is
+ * is added to the <code>Subject</code>'s principal set (unless the
+ * <code>principal</code> is specified as "*"). If <code>isInitiator</code>
+ * is true, the <code>KerberosTicket</code> is
  * added to the <code>Subject</code>'s private credentials.
  *
  * <p> If the configuration entry for <code>KerberosLoginModule</code>
  * has the option <code>storeKey</code> set to true, then
- * <code>KerberosKey</code> will also be added to the
+ * <code>KerberosKey</code> or <code>KeyTab</code> will also be added to the
  * subject's private credentials. <code>KerberosKey</code>, the principal's
- * key will be either obtained from the keytab or
- * derived from user's password.
+ * key(s) will be derived from user's password, and <code>KeyTab</code> is
+ * the keytab used when <code>useKeyTab</code> is set to true. The
+ * <code>KeyTab</code> object is restricted to be used by the specified
+ * principal unless the principal value is "*".
  *
  * <p> This <code>LoginModule</code> recognizes the <code>doNotPrompt</code>
  * option. If set to true the user will not be prompted for the password.
@@ -75,8 +78,8 @@
  *
  * <p> The principal name can be specified in the configuration entry
  * by using the option <code>principal</code>. The principal name
- * can either be a simple user name or a service name such as
- * <code>host/mission.eng.sun.com</code>. The principal can also
+ * can either be a simple user name, a service name such as
+ * <code>host/mission.eng.sun.com</code>, or "*". The principal can also
  * be set using the system property <code>sun.security.krb5.principal</code>.
  * This property is checked during login. If this property is not set, then
  * the principal name from the configuration is used. In the
@@ -87,11 +90,10 @@
  *
  * <p> The following is a list of configuration options supported
  * for <code>Krb5LoginModule</code>:
- * <dl>
- * <blockquote><dt><b><code>refreshKrb5Config</code></b>:</dt>
+ * <blockquote><dl>
+ * <dt><b><code>refreshKrb5Config</code></b>:</dt>
  * <dd> Set this to true, if you want the configuration
  * to be refreshed before the <code>login</code> method is called.</dd>
- * <P>
  * <dt><b><code>useTicketCache</code></b>:</dt>
  * <dd>Set this to true, if you want the
  * TGT to be obtained
@@ -112,19 +114,16 @@
  * <code>ticketCache</code>.
  * For Windows, if a ticket cannot be retrieved from the file ticket cache,
  * it will use Local Security Authority (LSA) API to get the TGT.
- * <P>
  * <dt><b><code>ticketCache</code></b>:</dt>
  * <dd>Set this to the name of the ticket
  * cache that  contains user's TGT.
  * If this is set,  <code>useTicketCache</code>
  * must also be set to true; Otherwise a configuration error will
  * be returned.</dd>
- *  <P>
  * <dt><b><code>renewTGT</code></b>:</dt>
  * <dd>Set this to true, if you want to renew
  * the TGT. If this is set, <code>useTicketCache</code> must also be
  * set to true; otherwise a configuration error will be returned.</dd>
- * <p>
  * <dt><b><code>doNotPrompt</code></b>:</dt>
  * <dd>Set this to true if you do not want to be
  * prompted for the password
@@ -132,7 +131,6 @@
  * or through shared state.(Default is false)
  * If set to true, credential must be obtained through cache, keytab,
  * or shared state. Otherwise, authentication will fail.</dd>
- * <P>
  * <dt><b><code>useKeyTab</code></b>:</dt>
  * <dd>Set this to true if you
  * want the module to get the principal's key from the
@@ -144,15 +142,15 @@
  * If it is not specified in the Kerberos configuration file
  * then it will look for the file
  * <code>{user.home}{file.separator}</code>krb5.keytab.</dd>
- * <P>
  * <dt><b><code>keyTab</code></b>:</dt>
  * <dd>Set this to the file name of the
  * keytab to get principal's secret key.</dd>
- * <P>
  * <dt><b><code>storeKey</code></b>:</dt>
- * <dd>Set this to true to if you want the
- * principal's key to be stored in the Subject's private credentials. </dd>
- * <p>
+ * <dd>Set this to true to if you want the keytab or the
+ * principal's key to be stored in the Subject's private credentials.
+ * For <code>isInitiator</code> being false, if <code>principal</code>
+ * is "*", the {@link KeyTab} stored can be used by anyone, otherwise,
+ * it's restricted to be used by the specified principal only.</dd>
  * <dt><b><code>principal</code></b>:</dt>
  * <dd>The name of the principal that should
  * be used. The principal can be a simple username such as
@@ -165,8 +163,13 @@
  * <code>sun.security.krb5.principal</code>. In addition, if this
  * system property is defined, then it will be used. If this property
  * is not set, then the principal name from the configuration will be
- * used.</dd>
- * <P>
+ * used.
+ * The principal name can be set to "*" when <code>isInitiator</code> is false.
+ * In this case, the acceptor is not bound to a single principal. It can
+ * act as any principal an initiator requests if keys for that principal
+ * can be found. When <code>isInitiator</code> is true, the principal name
+ * cannot be set to "*".
+ * </dd>
  * <dt><b><code>isInitiator</code></b>:</dt>
  * <dd>Set this to true, if initiator. Set this to false, if acceptor only.
  * (Default is true).
@@ -177,18 +180,20 @@
  * <code>Configuration</code>
  * options that enable you to share username and passwords across different
  * authentication modules:
- * <pre>
+ * <blockquote><dl>
  *
- *    useFirstPass   if, true, this LoginModule retrieves the
+ *    <dt><b><code>useFirstPass</code></b>:</dt>
+ *                   <dd>if, true, this LoginModule retrieves the
  *                   username and password from the module's shared state,
  *                   using "javax.security.auth.login.name" and
  *                   "javax.security.auth.login.password" as the respective
  *                   keys. The retrieved values are used for authentication.
  *                   If authentication fails, no attempt for a retry
  *                   is made, and the failure is reported back to the
- *                   calling application.
+ *                   calling application.</dd>
  *
- *    tryFirstPass   if, true, this LoginModule retrieves the
+ *    <dt><b><code>tryFirstPass</code></b>:</dt>
+ *                   <dd>if, true, this LoginModule retrieves the
  *                   the username and password from the module's shared
  *                   state using "javax.security.auth.login.name" and
  *                   "javax.security.auth.login.password" as the respective
@@ -198,26 +203,28 @@
  *                   CallbackHandler to retrieve a new username
  *                   and password, and another attempt to authenticate
  *                   is made. If the authentication fails,
- *                   the failure is reported back to the calling application
+ *                   the failure is reported back to the calling application</dd>
  *
- *    storePass      if, true, this LoginModule stores the username and
+ *    <dt><b><code>storePass</code></b>:</dt>
+ *                   <dd>if, true, this LoginModule stores the username and
  *                   password obtained from the CallbackHandler in the
  *                   modules shared state, using
  *                   "javax.security.auth.login.name" and
  *                   "javax.security.auth.login.password" as the respective
  *                   keys.  This is not performed if existing values already
  *                   exist for the username and password in the shared
- *                   state, or if authentication fails.
+ *                   state, or if authentication fails.</dd>
  *
- *    clearPass      if, true, this LoginModule clears the
+ *    <dt><b><code>clearPass</code></b>:</dt>
+ *                   <dd>if, true, this LoginModule clears the
  *                   username and password stored in the module's shared
  *                   state  after both phases of authentication
- *                   (login and commit) have completed.
- * </pre>
+ *                   (login and commit) have completed.</dd>
+ * </dl></blockquote>
  * <p>If the principal system property or key is already provided, the value of
  * "javax.security.auth.login.name" in the shared state is ignored.
  * <p>When multiple mechanisms to retrieve a ticket or key is provided, the
- * preference order looks like this:
+ * preference order is:
  * <ol>
  * <li>ticket cache
  * <li>keytab
@@ -225,7 +232,7 @@
  * <li>user prompt
  * </ol>
  * <p>Note that if any step fails, it will fallback to the next step.
- * There's only one exception, it the shared state step fails and
+ * There's only one exception, if the shared state step fails and
  * <code>useFirstPass</code>=true, no user prompt is made.
  * <p>Examples of some configuration values for Krb5LoginModule in
  * JAAS config file and the results are:
@@ -318,7 +325,7 @@
  * <p> <code>useKeyTab</code> = true
  * <code>keyTab</code>=&lt;keytabname&gt;
  * <code>storeKey</code>=true
- * <code>doNotPrompt</code>=true;
+ * <code>doNotPrompt</code>=false;
  *</ul>
  * <p>The user will be prompted for the service principal name.
  * If the principal's
@@ -328,6 +335,14 @@
  * If successful the TGT will be added to the
  * Subject's private credentials set. Otherwise the authentication will
  * fail.
+ * <ul>
+ * <p> <code>isInitiator</code> = false <code>useKeyTab</code> = true
+ * <code>keyTab</code>=&lt;keytabname&gt;
+ * <code>storeKey</code>=true
+ * <code>principal</code>=*;
+ *</ul>
+ * <p>The acceptor will be an unbound acceptor and it can act as any principal
+ * as long that principal has keys in the keytab.
  *<ul>
  * <p>
  * <code>useTicketCache</code>=true
@@ -409,6 +424,7 @@
     private KerberosTicket kerbTicket = null;
     private KerberosKey[] kerbKeys = null;
     private StringBuffer krb5PrincName = null;
+    private boolean unboundServer = false;
     private char[] password = null;
 
     private static final String NAME = "javax.security.auth.login.name";
@@ -520,8 +536,6 @@
      */
     public boolean login() throws LoginException {
 
-        int len;
-        validateConfiguration();
         if (refreshKrb5Config) {
             try {
                 if (debug) {
@@ -544,6 +558,12 @@
             }
         }
 
+        validateConfiguration();
+
+        if (krb5PrincName != null && krb5PrincName.toString().equals("*")) {
+            unboundServer = true;
+        }
+
         if (tryFirstPass) {
             try {
                 attemptAuthentication(true);
@@ -698,9 +718,17 @@
                  * (encKeys == null) to check.
                  */
                 if (useKeyTab) {
-                    ktab = (keyTabName == null)
-                                ? KeyTab.getInstance()
-                                : KeyTab.getInstance(new File(keyTabName));
+                    if (!unboundServer) {
+                        KerberosPrincipal kp =
+                                new KerberosPrincipal(principal.getName());
+                        ktab = (keyTabName == null)
+                                ? KeyTab.getInstance(kp)
+                                : KeyTab.getInstance(kp, new File(keyTabName));
+                    } else {
+                        ktab = (keyTabName == null)
+                                ? KeyTab.getUnboundInstance()
+                                : KeyTab.getUnboundInstance(new File(keyTabName));
+                    }
                     if (isInitiator) {
                         if (Krb5Util.keysFromJavaxKeyTab(ktab, principal).length
                                 == 0) {
@@ -939,6 +967,13 @@
                 ("Configuration Error"
                  + " - either useTicketCache should be "
                  + " true or renewTGT should be false");
+        if (krb5PrincName != null && krb5PrincName.toString().equals("*")) {
+            if (isInitiator) {
+                throw new LoginException
+                    ("Configuration Error"
+                    + " - principal cannot be * when isInitiator is true");
+            }
+        }
     }
 
     private boolean isCurrent(Credentials creds)
@@ -1052,7 +1087,10 @@
             }
             // Let us add the kerbClientPrinc,kerbTicket and KeyTab/KerbKey (if
             // storeKey is true)
-            if (!princSet.contains(kerbClientPrinc)) {
+
+            // We won't add "*" as a KerberosPrincipal
+            if (!unboundServer &&
+                    !princSet.contains(kerbClientPrinc)) {
                 princSet.add(kerbClientPrinc);
             }
 
--- a/jdk/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/src/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java	Sat Feb 09 16:43:49 2013 +0800
@@ -31,8 +31,8 @@
 
 class JavaxSecurityAuthKerberosAccessImpl
         implements JavaxSecurityAuthKerberosAccess {
-    public EncryptionKey[] keyTabGetEncryptionKeys(
-            KeyTab ktab, PrincipalName principal) {
-        return ktab.getEncryptionKeys(principal);
+    public sun.security.krb5.internal.ktab.KeyTab keyTabTakeSnapshot(
+            KeyTab ktab) {
+        return ktab.takeSnapshot();
     }
 }
--- a/jdk/src/share/classes/javax/security/auth/kerberos/KeyTab.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/src/share/classes/javax/security/auth/kerberos/KeyTab.java	Sat Feb 09 16:43:49 2013 +0800
@@ -41,6 +41,20 @@
  * {@link javax.security.auth.Subject Subject} during the commit phase of the
  * authentication process.
  * <p>
+ * If a {@code KeyTab} object is obtained from {@link #getUnboundInstance()}
+ * or {@link #getUnboundInstance(java.io.File)}, it is unbound and thus can be
+ * used by any service principal. Otherwise, if it's obtained from
+ * {@link #getInstance(KerberosPrincipal)} or
+ * {@link #getInstance(KerberosPrincipal, java.io.File)}, it is bound to the
+ * specific service principal and can only be used by it.
+ * <p>
+ * Please note the constructors {@link #getInstance()} and
+ * {@link #getInstance(java.io.File)} were created when there was no support
+ * for unbound keytabs. These methods should not be used anymore. An object
+ * created with either of these methods are considered to be bound to an
+ * unknown principal, which means, its {@link #isBound()} returns true and
+ * {@link #getPrincipal()} returns null.
+ * <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
@@ -52,7 +66,7 @@
  * The keytab file format is described at
  * <a href="http://www.ioplex.com/utilities/keytab.txt">
  * http://www.ioplex.com/utilities/keytab.txt</a>.
- *
+ * <p>
  * @since 1.7
  */
 public final class KeyTab {
@@ -74,21 +88,33 @@
     // is maintained in snapshot, this field is never "resolved".
     private final File file;
 
+    // Bound user: normally from the "principal" value in a JAAS krb5
+    // login conf. Will be null if it's "*".
+    private final KerberosPrincipal princ;
+
+    private final boolean bound;
+
     // Set up JavaxSecurityAuthKerberosAccess in KerberosSecrets
     static {
         KerberosSecrets.setJavaxSecurityAuthKerberosAccess(
                 new JavaxSecurityAuthKerberosAccessImpl());
     }
 
-    private KeyTab(File file) {
+    private KeyTab(KerberosPrincipal princ, File file, boolean bound) {
+        this.princ = princ;
         this.file = file;
+        this.bound = bound;
     }
 
     /**
-     * Returns a {@code KeyTab} instance from a {@code File} object.
+     * Returns a {@code KeyTab} instance from a {@code File} object
+     * that is bound to an unknown service principal.
      * <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.
+     * <p>
+     * Developers should call {@link #getInstance(KerberosPrincipal,File)}
+     * when the bound service principal is known.
      * @param file the keytab {@code File} object, must not be null
      * @return the keytab instance
      * @throws NullPointerException if the {@code file} argument is null
@@ -97,23 +123,99 @@
         if (file == null) {
             throw new NullPointerException("file must be non null");
         }
-        return new KeyTab(file);
+        return new KeyTab(null, file, true);
+    }
+
+    /**
+     * Returns an unbound {@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 file argument is null
+     * @since 1.8
+     */
+    public static KeyTab getUnboundInstance(File file) {
+        if (file == null) {
+            throw new NullPointerException("file must be non null");
+        }
+        return new KeyTab(null, file, false);
     }
 
     /**
-     * Returns the default {@code KeyTab} instance.
+     * Returns a {@code KeyTab} instance from a {@code File} object
+     * that is bound to the specified service principal.
+     * <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 princ the bound service principal, must not be null
+     * @param file the keytab {@code File} object, must not be null
+     * @return the keytab instance
+     * @throws NullPointerException if either of the arguments is null
+     * @since 1.8
+     */
+    public static KeyTab getInstance(KerberosPrincipal princ, File file) {
+        if (princ == null) {
+            throw new NullPointerException("princ must be non null");
+        }
+        if (file == null) {
+            throw new NullPointerException("file must be non null");
+        }
+        return new KeyTab(princ, file, true);
+    }
+
+    /**
+     * Returns the default {@code KeyTab} instance that is bound
+     * to an unknown service principal.
      * <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.
+     * <p>
+     * Developers should call {@link #getInstance(KerberosPrincipal)}
+     * when the bound service principal is known.
      * @return the default keytab instance.
      */
     public static KeyTab getInstance() {
-        return new KeyTab(null);
+        return new KeyTab(null, null, true);
+    }
+
+    /**
+     * Returns the default unbound {@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
+     * @since 1.8
+     */
+    public static KeyTab getUnboundInstance() {
+        return new KeyTab(null, null, false);
+    }
+
+    /**
+     * Returns the default {@code KeyTab} instance that is bound
+     * to the specified service principal.
+     * <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.
+     * @param princ the bound service principal, must not be null
+     * @return the default keytab instance
+     * @throws NullPointerException if {@code princ} is null
+     * @since 1.8
+     */
+    public static KeyTab getInstance(KerberosPrincipal princ) {
+        if (princ == null) {
+            throw new NullPointerException("princ must be non null");
+        }
+        return new KeyTab(princ, null, true);
     }
 
     //Takes a snapshot of the keytab content
-    private sun.security.krb5.internal.ktab.KeyTab takeSnapshot() {
+    sun.security.krb5.internal.ktab.KeyTab takeSnapshot() {
         return sun.security.krb5.internal.ktab.KeyTab.getInstance(file);
     }
 
@@ -147,6 +249,9 @@
      * <p>
      * Any unsupported key read from the keytab is ignored and not included
      * in the result.
+     * <p>
+     * If this keytab is bound to a specific principal, calling this method on
+     * another principal will return an empty array.
      *
      * @param principal the Kerberos principal, must not be null.
      * @return the keys (never null, may be empty)
@@ -157,8 +262,11 @@
      */
     public KerberosKey[] getKeys(KerberosPrincipal principal) {
         try {
-            EncryptionKey[] keys = takeSnapshot().readServiceKeys(
-                    new PrincipalName(principal.getName()));
+            if (princ != null && !principal.equals(princ)) {
+                return new KerberosKey[0];
+            }
+            PrincipalName pn = new PrincipalName(principal.getName());
+            EncryptionKey[] keys = takeSnapshot().readServiceKeys(pn);
             KerberosKey[] kks = new KerberosKey[keys.length];
             for (int i=0; i<kks.length; i++) {
                 Integer tmp = keys[i].getKeyVersionNumber();
@@ -195,7 +303,10 @@
     }
 
     public String toString() {
-        return file == null ? "Default keytab" : file.toString();
+        String s = (file == null) ? "Default keytab" : file.toString();
+        if (!bound) return s;
+        else if (princ == null) return s + " for someone";
+        else return s + " for " + princ;
     }
 
     /**
@@ -204,7 +315,7 @@
      * @return a hashCode() for the <code>KeyTab</code>
      */
     public int hashCode() {
-        return Objects.hash(file);
+        return Objects.hash(file, princ, bound);
     }
 
     /**
@@ -225,6 +336,31 @@
         }
 
         KeyTab otherKtab = (KeyTab) other;
-        return Objects.equals(otherKtab.file, file);
+        return Objects.equals(otherKtab.princ, princ) &&
+                Objects.equals(otherKtab.file, file) &&
+                bound == otherKtab.bound;
+    }
+
+    /**
+     * Returns the service principal this {@code KeyTab} object
+     * is bound to. Returns {@code null} if it's not bound.
+     * <p>
+     * Please note the deprecated constructors create a KeyTab object bound for
+     * some unknown principal. In this case, this method also returns null.
+     * User can call {@link #isBound()} to verify this case.
+     * @return the service principal
+     * @since 1.8
+     */
+    public KerberosPrincipal getPrincipal() {
+        return princ;
+    }
+
+    /**
+     * Returns if the keytab is bound to a principal
+     * @return if the keytab is bound to a principal
+     * @since 1.8
+     */
+    public boolean isBound() {
+        return bound;
     }
 }
--- a/jdk/src/share/classes/sun/security/jgss/LoginConfigImpl.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/src/share/classes/sun/security/jgss/LoginConfigImpl.java	Sat Feb 09 16:43:49 2013 +0800
@@ -175,6 +175,7 @@
                 options.put("useKeyTab", "true");
                 options.put("storeKey", "true");
                 options.put("doNotPrompt", "true");
+                options.put("principal", "*");
                 options.put("isInitiator", "false");
             } else {
                 options.put("useTicketCache", "true");
--- a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java	Sat Feb 09 16:43:49 2013 +0800
@@ -243,14 +243,24 @@
     }
 
     /**
+     * A helper method to get a sun..KeyTab from a javax..KeyTab
+     * @param ktab the javax..KeyTab object
+     * @return the sun..KeyTab object
+     */
+    public static sun.security.krb5.internal.ktab.KeyTab
+            snapshotFromJavaxKeyTab(KeyTab ktab) {
+        return KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
+                .keyTabTakeSnapshot(ktab);
+    }
+
+    /**
      * A helper method to get EncryptionKeys from a javax..KeyTab
-     * @param ktab the javax..KeyTab class
+     * @param ktab the javax..KeyTab object
      * @param cname the PrincipalName
      * @return the EKeys, never null, might be empty
      */
     public static EncryptionKey[] keysFromJavaxKeyTab(
             KeyTab ktab, PrincipalName cname) {
-        return KerberosSecrets.getJavaxSecurityAuthKerberosAccess().
-                keyTabGetEncryptionKeys(ktab, cname);
+        return snapshotFromJavaxKeyTab(ktab).readServiceKeys(cname);
     }
 }
--- a/jdk/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java	Sat Feb 09 16:43:49 2013 +0800
@@ -101,9 +101,22 @@
         if (serverPrincipal != null) {      // A named principal
             sc.kp = new KerberosPrincipal(serverPrincipal);
         } else {
-            if (sc.allPrincs.size() == 1) { // choose the only one
-                sc.kp = sc.allPrincs.iterator().next();
-                serverPrincipal = sc.kp.getName();
+            // For compatibility reason, we set the name of default principal
+            // to the "only possible" name it can take, which means there is
+            // only one KerberosPrincipal and there is no unbound keytabs
+            if (sc.allPrincs.size() == 1) {
+                boolean hasUnbound = false;
+                for (KeyTab ktab: SubjectComber.findMany(
+                        subj, null, null, KeyTab.class)) {
+                    if (!ktab.isBound()) {
+                        hasUnbound = true;
+                        break;
+                    }
+                }
+                if (!hasUnbound) {
+                    sc.kp = sc.allPrincs.iterator().next();
+                    serverPrincipal = sc.kp.getName();
+                }
             }
         }
 
@@ -131,20 +144,35 @@
     }
 
     /**
-     * Gets keys for someone unknown.
-     * Used by TLS or as a fallback in getEKeys(). Can still return an
-     * empty array.
+     * Gets keys for "someone". Used in 2 cases:
+     * 1. By TLS because it needs to get keys before client comes in.
+     * 2. As a fallback in getEKeys() below.
+     * This method can still return an empty array.
      */
     public KerberosKey[] getKKeys() {
         if (destroyed) {
             throw new IllegalStateException("This object is destroyed");
         }
-        if (kp != null) {
-            return getKKeys(kp);
-        } else if (!allPrincs.isEmpty()) {
-            return getKKeys(allPrincs.iterator().next());
+        KerberosPrincipal one = kp;                 // named principal
+        if (one == null && !allPrincs.isEmpty()) {  // or, a known principal
+            one = allPrincs.iterator().next();
         }
-        return new KerberosKey[0];
+        if (one == null) {                          // Or, some random one
+            for (KeyTab ktab: ktabs) {
+                // Must be unbound keytab, otherwise, allPrincs is not empty
+                PrincipalName pn =
+                        Krb5Util.snapshotFromJavaxKeyTab(ktab).getOneName();
+                if (pn != null) {
+                    one = new KerberosPrincipal(pn.getName());
+                    break;
+                }
+            }
+        }
+        if (one != null) {
+            return getKKeys(one);
+        } else {
+            return new KerberosKey[0];
+        }
     }
 
     /**
@@ -152,15 +180,13 @@
      * @param princ the target name initiator requests. Not null.
      * @return keys for the princ, never null, might be empty
      */
-    private KerberosKey[] getKKeys(KerberosPrincipal princ) {
-        ArrayList<KerberosKey> keys = new ArrayList<>();
-        if (kp != null && !princ.equals(kp)) {
-            return new KerberosKey[0];      // Not me
+    public KerberosKey[] getKKeys(KerberosPrincipal princ) {
+        if (destroyed) {
+            throw new IllegalStateException("This object is destroyed");
         }
-        if (!allPrincs.contains(princ)) {
-            return new KerberosKey[0];      // Not someone I know, This check
-                                            // is necessary but a KeyTab has
-                                            // no principal name recorded.
+        ArrayList<KerberosKey> keys = new ArrayList<>();
+        if (kp != null && !princ.equals(kp)) {      // named principal
+            return new KerberosKey[0];
         }
         for (KerberosKey k: kk) {
             if (k.getPrincipal().equals(princ)) {
@@ -168,6 +194,13 @@
             }
         }
         for (KeyTab ktab: ktabs) {
+            if (ktab.getPrincipal() == null && ktab.isBound()) {
+                // legacy bound keytab. although we don't know who
+                // the bound principal is, it must be in allPrincs
+                if (!allPrincs.contains(princ)) {
+                    continue;   // skip this legacy bound keytab
+                }
+            }
             for (KerberosKey k: ktab.getKeys(princ)) {
                 keys.add(k);
             }
@@ -186,12 +219,12 @@
         }
         KerberosKey[] kkeys = getKKeys(new KerberosPrincipal(princ.getName()));
         if (kkeys.length == 0) {
-            // Note: old JDK does not perform real name checking. If the
-            // acceptor starts by name A but initiator requests for B,
-            // as long as their keys match (i.e. A's keys can decrypt B's
-            // service ticket), the authentication is OK. There are real
-            // customers depending on this to use different names for a
-            // single service.
+            // Fallback: old JDK does not perform real name checking. If the
+            // acceptor has host.sun.com but initiator requests for host,
+            // as long as their keys match (i.e. keys for one can decrypt
+            // the other's service ticket), the authentication is OK.
+            // There are real customers depending on this to use different
+            // names for a single service.
             kkeys = getKKeys();
         }
         EncryptionKey[] ekeys = new EncryptionKey[kkeys.length];
--- a/jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/src/share/classes/sun/security/jgss/krb5/SubjectComber.java	Sat Feb 09 16:43:49 2013 +0800
@@ -86,36 +86,39 @@
             List<T> answer = (oneOnly ? null : new ArrayList<T>());
 
             if (credClass == KeyTab.class) {
-                // TODO: There is currently no good way to filter out keytabs
-                // not for serverPrincipal. We can only check the principal
-                // set. If the server is not there, we can be sure none of the
-                // keytabs should be used, otherwise, use all for safety.
-                boolean useAll = false;
-                if (serverPrincipal != null) {
-                    for (KerberosPrincipal princ:
-                            subject.getPrincipals(KerberosPrincipal.class)) {
-                        if (princ.getName().equals(serverPrincipal)) {
-                            useAll = true;
-                            break;
+                Iterator<KeyTab> iterator =
+                    subject.getPrivateCredentials(KeyTab.class).iterator();
+                while (iterator.hasNext()) {
+                    KeyTab t = iterator.next();
+                    if (serverPrincipal != null && t.isBound()) {
+                        KerberosPrincipal name = t.getPrincipal();
+                        if (name != null) {
+                            if (!serverPrincipal.equals(name.getName())) {
+                                continue;
+                            }
+                        } else {
+                            // legacy bound keytab. although we don't know who
+                            // the bound principal is, it must be in allPrincs
+                            boolean found = false;
+                            for (KerberosPrincipal princ:
+                                    subject.getPrincipals(KerberosPrincipal.class)) {
+                                if (princ.getName().equals(serverPrincipal)) {
+                                    found = true;
+                                    break;
+                                }
+                            }
+                            if (!found) continue;
                         }
                     }
-                } else {
-                    useAll = true;
-                }
-                if (useAll) {
-                    Iterator<KeyTab> iterator =
-                        subject.getPrivateCredentials(KeyTab.class).iterator();
-                    while (iterator.hasNext()) {
-                        KeyTab t = iterator.next();
-                        if (DEBUG) {
-                            System.out.println("Found " + credClass.getSimpleName()
-                                    + " " + t);
-                        }
-                        if (oneOnly) {
-                            return t;
-                        } else {
-                            answer.add(credClass.cast(t));
-                        }
+                    // Check passed, we can add now
+                    if (DEBUG) {
+                        System.out.println("Found " + credClass.getSimpleName()
+                                + " " + t);
+                    }
+                    if (oneOnly) {
+                        return t;
+                    } else {
+                        answer.add(credClass.cast(t));
                     }
                 }
             } else if (credClass == KerberosKey.class) {
--- a/jdk/src/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/src/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java	Sat Feb 09 16:43:49 2013 +0800
@@ -35,9 +35,8 @@
  */
 public interface JavaxSecurityAuthKerberosAccess {
     /**
-     * Returns keys for a principal in a keytab.
-     * @return the keys, never null, can be empty.
+     * Returns a snapshot to the backing keytab
      */
-    public EncryptionKey[] keyTabGetEncryptionKeys(
-            KeyTab ktab, PrincipalName principal);
+    public sun.security.krb5.internal.ktab.KeyTab keyTabTakeSnapshot(
+            KeyTab ktab);
 }
--- a/jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java	Sat Feb 09 16:43:49 2013 +0800
@@ -46,6 +46,7 @@
 import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.Vector;
+import sun.security.jgss.krb5.ServiceCreds;
 
 /**
  * This class represents key table. The key table functions deal with storing
@@ -268,6 +269,15 @@
     }
 
     /**
+     * Returns a principal name in this keytab. Used by
+     * {@link ServiceCreds#getKKeys()}.
+     */
+    public PrincipalName getOneName() {
+        int size = entries.size();
+        return size > 0 ? entries.elementAt(size-1).service : null;
+    }
+
+    /**
      * Reads all keys for a service from the keytab file that have
      * etypes that have been configured for use. If there are multiple
      * keys with same etype, the one with the highest kvno is returned.
--- a/jdk/src/share/classes/sun/security/provider/ConfigSpiFile.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/src/share/classes/sun/security/provider/ConfigSpiFile.java	Sat Feb 09 16:43:49 2013 +0800
@@ -404,6 +404,7 @@
         st.wordChars('$', '$');
         st.wordChars('_', '_');
         st.wordChars('-', '-');
+        st.wordChars('*', '*');
         st.lowerCaseMode(false);
         st.slashSlashComments(true);
         st.slashStarComments(true);
--- a/jdk/test/sun/security/krb5/ServiceCredsCombination.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/test/sun/security/krb5/ServiceCredsCombination.java	Sat Feb 09 16:43:49 2013 +0800
@@ -62,11 +62,38 @@
         check("b", "b", princ("a"), princ("b"), oldktab(), oldktab());
         check(null, null, princ("a"), princ("b"), oldktab(), oldktab());
         check("x", "NOCRED", princ("a"), princ("b"), oldktab(), oldktab());
+        // bound ktab
+        check("c", "c", princ("c"), ktab("c"));
+        check(null, "c", princ("c"), ktab("c"));
+        // unbound ktab
+        check("x", "x", ktab());
+        check(null, null, ktab());
+        // Two bound ktab
+        check("c1", "c1", princ("c1"), princ("c2"), ktab("c1"), ktab("c2"));
+        check("c2", "c2", princ("c1"), princ("c2"), ktab("c1"), ktab("c2"));
+        check("x", "NOCRED", princ("c1"), princ("c2"), ktab("c1"), ktab("c2"));
+        check(null, null, princ("c1"), princ("c2"), ktab("c1"), ktab("c2"));
+        // One bound, one unbound
+        check("c1", "c1", princ("c1"), ktab("c1"), ktab());
+        check("x", "x", princ("c1"), ktab("c1"), ktab());
+        check(null, null, princ("c1"), ktab("c1"), ktab());
+        // Two unbound ktab
+        check("x", "x", ktab(), ktab());
+        check(null, null, ktab(), ktab());
         // pass + old ktab
         check("a", "a", princ("a"), princ("b"), key("a"), oldktab());
         check("b", "b", princ("a"), princ("b"), key("a"), oldktab());
         check(null, null, princ("a"), princ("b"), key("a"), oldktab());
         check("x", "NOCRED", princ("a"), princ("b"), key("a"), oldktab());
+        // pass + bound ktab
+        check("a", "a", princ("a"), princ("c"), key("a"), ktab("c"));
+        check("c", "c", princ("a"), princ("c"), key("a"), ktab("c"));
+        check("x", "NOCRED", princ("a"), princ("c"), key("a"), ktab("c"));
+        check(null, null, princ("a"), princ("c"), key("a"), ktab("c"));
+        // pass + unbound ktab
+        check("a", "a", princ("a"), key("a"), ktab());
+        check("x", "x", princ("a"), key("a"), ktab());
+        check(null, null, princ("a"), key("a"), ktab());
         // Compatibility, automatically add princ for keys
         check(null, "a", key("a"));
         check("x", "NOCRED", key("a"));
@@ -130,4 +157,10 @@
     private static KeyTab oldktab() {
         return KeyTab.getInstance();
     }
+    static KeyTab ktab(String s) {
+        return KeyTab.getInstance(princ(s));
+    }
+    static KeyTab ktab() {
+        return KeyTab.getUnboundInstance();
+    }
 }
--- a/jdk/test/sun/security/krb5/auto/AcceptPermissions.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/test/sun/security/krb5/auto/AcceptPermissions.java	Sat Feb 09 16:43:49 2013 +0800
@@ -26,7 +26,8 @@
  * @bug 9999999
  * @summary default principal can act as anyone
  * @compile -XDignore.symbol.file AcceptPermissions.java
- * @run main/othervm AcceptPermissions
+ * @run main/othervm AcceptPermissions two
+ * @run main/othervm AcceptPermissions unbound
  */
 
 import java.nio.file.Files;
@@ -83,15 +84,20 @@
     public static void main(String[] args) throws Exception {
         System.setSecurityManager(new AcceptPermissions());
         new OneKDC(null).writeJAASConf();
-        String two = "two {\n"
+        String moreEntries = "two {\n"
                 + " com.sun.security.auth.module.Krb5LoginModule required"
                 + "     principal=\"" + OneKDC.SERVER + "\" useKeyTab=true"
                 + "     isInitiator=false storeKey=true;\n"
                 + " com.sun.security.auth.module.Krb5LoginModule required"
                 + "     principal=\"" + OneKDC.BACKEND + "\" useKeyTab=true"
                 + "     isInitiator=false storeKey=true;\n"
+                + "};\n"
+                + "unbound {"
+                + " com.sun.security.auth.module.Krb5LoginModule required"
+                + "     principal=* useKeyTab=true"
+                + "     isInitiator=false storeKey=true;\n"
                 + "};\n";
-        Files.write(Paths.get(OneKDC.JAAS_CONF), two.getBytes(),
+        Files.write(Paths.get(OneKDC.JAAS_CONF), moreEntries.getBytes(),
                 StandardOpenOption.APPEND);
 
         Context c, s;
@@ -114,7 +120,7 @@
         // Named principal (even if there are 2 JAAS modules)
         initPerms(OneKDC.SERVER);
         c = Context.fromJAAS("client");
-        s = Context.fromJAAS("two");
+        s = Context.fromJAAS(args[0]);
         c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
         s.startAsServer(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
         checkPerms();
@@ -136,7 +142,7 @@
         // Default principal with no predictable name
         initPerms();    // permission not needed for cred !!!
         c = Context.fromJAAS("client");
-        s = Context.fromJAAS("two");
+        s = Context.fromJAAS(args[0]);
         c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
         s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
         checkPerms();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/auto/GSSUnbound.java	Sat Feb 09 16:43:49 2013 +0800
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2012, 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 8001104
+ * @summary Unbound SASL service: the GSSAPI/krb5 mech
+ * @compile -XDignore.symbol.file GSSUnbound.java
+ * @run main/othervm GSSUnbound
+ */
+
+import java.security.Security;
+import sun.security.jgss.GSSUtil;
+
+// Testing JGSS without JAAS
+public class GSSUnbound {
+
+    public static void main(String[] args) throws Exception {
+
+        new OneKDC(null);
+
+        Context c, s;
+        c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+        s = Context.fromThinAir();
+
+        // This is the only setting needed for JGSS without JAAS. The default
+        // JAAS config entries are already created by OneKDC.
+        System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
+
+        c.startAsClient(OneKDC.BACKEND, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+
+        Context.handshake(c, s);
+
+        Context.transmit("i say high --", c, s);
+        Context.transmit("   you say low", s, c);
+
+        s.dispose();
+        c.dispose();
+    }
+}
--- a/jdk/test/sun/security/krb5/auto/OneKDC.java	Sat Feb 09 08:35:57 2013 +0000
+++ b/jdk/test/sun/security/krb5/auto/OneKDC.java	Sat Feb 09 16:43:49 2013 +0800
@@ -76,6 +76,8 @@
         Config.refresh();
 
         writeKtab(KTAB);
+        Security.setProperty("auth.login.defaultCallbackHandler",
+                "OneKDC$CallbackForClient");
     }
 
     /**
@@ -93,7 +95,7 @@
                 "    com.sun.security.auth.module.Krb5LoginModule required;\n};\n" +
                 "com.sun.security.jgss.krb5.accept {\n" +
                 "    com.sun.security.auth.module.Krb5LoginModule required\n" +
-                "    principal=\"" + SERVER + "\"\n" +
+                "    principal=\"*\"\n" +
                 "    useKeyTab=true\n" +
                 "    isInitiator=false\n" +
                 "    storeKey=true;\n};\n" +
@@ -112,7 +114,6 @@
                 "    isInitiator=false;\n};\n"
                 ).getBytes());
         fos.close();
-        Security.setProperty("auth.login.defaultCallbackHandler", "OneKDC$CallbackForClient");
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/auto/SaslUnbound.java	Sat Feb 09 16:43:49 2013 +0800
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2012, 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 8001104
+ * @summary Unbound SASL service: the GSSAPI/krb5 mech
+ * @compile -XDignore.symbol.file SaslUnbound.java
+ * @run main/othervm SaslUnbound 0
+ * @run main/othervm/fail SaslUnbound 1
+ * @run main/othervm/fail SaslUnbound 2
+ * @run main/othervm/fail SaslUnbound 3
+ * @run main/othervm/fail SaslUnbound 4
+ */
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.*;
+
+public class SaslUnbound {
+
+    public static void main(String[] args) throws Exception {
+
+        String serverProtocol, serverName;
+        switch (args[0].charAt(0)) {
+            case '1':       // Using another protocol, should fail
+                serverProtocol = "serv";
+                serverName = null;
+                break;
+            case '2':       // Using another protocol, should fail
+                serverProtocol = "otherwise";
+                serverName = null;
+                break;
+            case '3':       // Using another protocol, should fail
+                serverProtocol = "otherwise";
+                serverName = "host." + OneKDC.REALM;
+                break;
+            case '4':       // Bound to another serverName, should fail.
+                serverProtocol = "server";
+                serverName = "host2." + OneKDC.REALM;
+                break;
+            default:        // Good unbound server
+                serverProtocol = "server";
+                serverName = null;
+                break;
+        }
+        new OneKDC(null).writeJAASConf();
+        System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
+
+        HashMap clntprops = new HashMap();
+        clntprops.put(Sasl.QOP, "auth-conf");
+        SaslClient sc = Sasl.createSaslClient(
+                new String[]{"GSSAPI"}, null, "server",
+                "host." + OneKDC.REALM, clntprops, null);
+
+        final HashMap srvprops = new HashMap();
+        srvprops.put(Sasl.QOP, "auth,auth-int,auth-conf");
+        SaslServer ss = Sasl.createSaslServer("GSSAPI", serverProtocol,
+                serverName, srvprops,
+                new CallbackHandler() {
+                    public void handle(Callback[] callbacks)
+                            throws IOException, UnsupportedCallbackException {
+                        for (Callback cb : callbacks) {
+                            if (cb instanceof RealmCallback) {
+                                ((RealmCallback) cb).setText(OneKDC.REALM);
+                            } else if (cb instanceof AuthorizeCallback) {
+                                ((AuthorizeCallback) cb).setAuthorized(true);
+                            }
+                        }
+                    }
+                });
+
+        byte[] token = new byte[0];
+        while (!sc.isComplete() || !ss.isComplete()) {
+            if (!sc.isComplete()) {
+                token = sc.evaluateChallenge(token);
+            }
+            if (!ss.isComplete()) {
+                token = ss.evaluateResponse(token);
+            }
+        }
+        System.out.println(ss.getNegotiatedProperty(Sasl.BOUND_SERVER_NAME));
+        byte[] hello = "hello".getBytes();
+        token = sc.wrap(hello, 0, hello.length);
+        token = ss.unwrap(token, 0, token.length);
+        if (!Arrays.equals(hello, token)) {
+            throw new Exception("Message altered");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/auto/UnboundService.java	Sat Feb 09 16:43:49 2013 +0800
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2012, 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 8001104
+ * @summary Unbound SASL service: the GSSAPI/krb5 mech
+ * @compile -XDignore.symbol.file UnboundService.java
+ * @run main/othervm UnboundService null null
+ * @run main/othervm UnboundService server/host.rabbit.hole null
+ * @run main/othervm UnboundService server/host.rabbit.hole@RABBIT.HOLE null
+ * @run main/othervm/fail UnboundService backend/host.rabbit.hole null
+ * @run main/othervm UnboundService null server@host.rabbit.hole
+ * @run main/othervm UnboundService server/host.rabbit.hole server@host.rabbit.hole
+ * @run main/othervm UnboundService server/host.rabbit.hole@RABBIT.HOLE server@host.rabbit.hole
+ * @run main/othervm/fail UnboundService backend/host.rabbit.hole server@host.rabbit.hole
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import sun.security.jgss.GSSUtil;
+
+public class UnboundService {
+
+    /**
+     * @param args JAAS config pricipal and GSSCredential creation name
+     */
+    public static void main(String[] args) throws Exception {
+
+        String principal = args[0];
+        if (principal.equals("null")) principal = null;
+
+        String server = args[1];
+        if (server.equals("null")) server = null;
+
+        new OneKDC(null).writeJAASConf();
+        File f = new File(OneKDC.JAAS_CONF);
+        try (FileOutputStream fos = new FileOutputStream(f)) {
+            fos.write((
+                "client {\n" +
+                "    com.sun.security.auth.module.Krb5LoginModule required;\n};\n" +
+                "unbound {\n" +
+                "    com.sun.security.auth.module.Krb5LoginModule required\n" +
+                "    useKeyTab=true\n" +
+                "    principal=" +
+                    (principal==null? "*" :("\"" + principal + "\"")) + "\n" +
+                "    doNotPrompt=true\n" +
+                "    isInitiator=false\n" +
+                "    storeKey=true;\n};\n"
+                ).getBytes());
+        }
+
+        Context c, s;
+        c = Context.fromJAAS("client");
+        s = Context.fromJAAS("unbound");
+
+        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(server, GSSUtil.GSS_KRB5_MECH_OID);
+
+        Context.handshake(c, s);
+
+        s.dispose();
+        c.dispose();
+    }
+}