changeset 6337:98935c514de4

8005447: default principal should act as anyone Reviewed-by: valeriep
author weijun
date Tue, 08 Jan 2013 14:54:56 +0800
parents 797e8a3dcd51
children d29a7ce28189
files src/share/classes/sun/security/jgss/krb5/InitSecContextToken.java src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java src/share/classes/sun/security/jgss/krb5/Krb5Context.java src/share/classes/sun/security/jgss/krb5/Krb5MechFactory.java src/share/classes/sun/security/jgss/krb5/Krb5Util.java src/share/classes/sun/security/jgss/krb5/ServiceCreds.java src/share/classes/sun/security/jgss/krb5/SubjectComber.java src/share/classes/sun/security/krb5/KrbApReq.java src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java test/sun/security/krb5/ServiceCredsCombination.java test/sun/security/krb5/auto/AcceptPermissions.java test/sun/security/krb5/auto/CleanState.java test/sun/security/krb5/auto/Context.java test/sun/security/krb5/auto/DiffNameSameKey.java test/sun/security/krb5/auto/DynamicKeytab.java test/sun/security/krb5/auto/KDC.java test/sun/security/krb5/auto/KeyTabCompat.java test/sun/security/krb5/auto/TwoOrThree.java
diffstat 19 files changed, 787 insertions(+), 169 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/security/jgss/krb5/InitSecContextToken.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/src/share/classes/sun/security/jgss/krb5/InitSecContextToken.java	Tue Jan 08 14:54:56 2013 +0800
@@ -86,7 +86,7 @@
      * For the context acceptor to call. It reads the bytes out of an
      * InputStream and constructs an InitSecContextToken with them.
      */
-    InitSecContextToken(Krb5Context context, EncryptionKey[] keys,
+    InitSecContextToken(Krb5Context context, Krb5AcceptCredential cred,
                                InputStream is)
         throws IOException, GSSException, KrbException  {
 
@@ -105,7 +105,7 @@
         if (context.getChannelBinding() != null) {
             addr = context.getChannelBinding().getInitiatorAddress();
         }
-        apReq = new KrbApReq(apReqBytes, keys, addr);
+        apReq = new KrbApReq(apReqBytes, cred, addr);
         //debug("\nReceived AP-REQ and authenticated it.\n");
 
         EncryptionKey sessionKey = apReq.getCreds().getSessionKey();
--- a/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java	Tue Jan 08 14:54:56 2013 +0800
@@ -45,13 +45,10 @@
 public class Krb5AcceptCredential
     implements Krb5CredElement {
 
-    private static final long serialVersionUID = 7714332137352567952L;
+    private final Krb5NameElement name;
+    private final ServiceCreds screds;
 
-    private Krb5NameElement name;
-
-    private Krb5Util.ServiceCreds screds;
-
-    private Krb5AcceptCredential(Krb5NameElement name, Krb5Util.ServiceCreds creds) {
+    private Krb5AcceptCredential(Krb5NameElement name, ServiceCreds creds) {
         /*
          * Initialize this instance with the data from the acquired
          * KerberosKey. This class needs to be a KerberosKey too
@@ -69,11 +66,11 @@
             name.getKrb5PrincipalName().getName());
         final AccessControlContext acc = AccessController.getContext();
 
-        Krb5Util.ServiceCreds creds = null;
+        ServiceCreds creds = null;
         try {
             creds = AccessController.doPrivileged(
-                        new PrivilegedExceptionAction<Krb5Util.ServiceCreds>() {
-                public Krb5Util.ServiceCreds run() throws Exception {
+                        new PrivilegedExceptionAction<ServiceCreds>() {
+                public ServiceCreds run() throws Exception {
                     return Krb5Util.getServiceCreds(
                         caller == GSSCaller.CALLER_UNKNOWN ? GSSCaller.CALLER_ACCEPT: caller,
                         serverPrinc, acc);
@@ -92,8 +89,10 @@
 
         if (name == null) {
             String fullName = creds.getName();
-            name = Krb5NameElement.getInstance(fullName,
+            if (fullName != null) {
+                name = Krb5NameElement.getInstance(fullName,
                                        Krb5MechFactory.NT_GSS_KRB5_PRINCIPAL);
+            }
         }
 
         return new Krb5AcceptCredential(name, creds);
@@ -153,8 +152,8 @@
         return Krb5MechFactory.PROVIDER;
     }
 
-    EncryptionKey[] getKrb5EncryptionKeys() {
-        return screds.getEKeys();
+    public EncryptionKey[] getKrb5EncryptionKeys(PrincipalName princ) {
+        return screds.getEKeys(princ);
     }
 
     /**
--- a/src/share/classes/sun/security/jgss/krb5/Krb5Context.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5Context.java	Tue Jan 08 14:54:56 2013 +0800
@@ -818,16 +818,23 @@
                 }
                 myName = (Krb5NameElement) myCred.getName();
 
-                checkPermission(myName.getKrb5PrincipalName().getName(),
-                                "accept");
-
-                EncryptionKey[] secretKeys =
-                 ((Krb5AcceptCredential) myCred).getKrb5EncryptionKeys();
+                // If there is already a bound name, check now
+                if (myName != null) {
+                    Krb5MechFactory.checkAcceptCredPermission(myName, myName);
+                }
 
                 InitSecContextToken token = new InitSecContextToken(this,
-                                                    secretKeys, is);
+                                                    (Krb5AcceptCredential) myCred, is);
                 PrincipalName clientName = token.getKrbApReq().getClient();
                 peerName = Krb5NameElement.getInstance(clientName);
+
+                // If unbound, check after the bound name is found
+                if (myName == null) {
+                    myName = Krb5NameElement.getInstance(
+                        token.getKrbApReq().getCreds().getServer());
+                    Krb5MechFactory.checkAcceptCredPermission(myName, myName);
+                }
+
                 if (getMutualAuthState()) {
                         retVal = new AcceptSecContextToken(this,
                                           token.getKrbApReq()).encode();
--- a/src/share/classes/sun/security/jgss/krb5/Krb5MechFactory.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5MechFactory.java	Tue Jan 08 14:54:56 2013 +0800
@@ -158,7 +158,7 @@
     public static void checkAcceptCredPermission(Krb5NameElement name,
                                            GSSNameSpi originalName) {
         SecurityManager sm = System.getSecurityManager();
-        if (sm != null) {
+        if (sm != null && name != null) {
             ServicePermission perm = new ServicePermission
                 (name.getKrb5PrincipalName().getName(), "accept");
             try {
--- a/src/share/classes/sun/security/jgss/krb5/Krb5Util.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5Util.java	Tue Jan 08 14:54:56 2013 +0800
@@ -187,114 +187,6 @@
     }
 
     /**
-     * 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 S4U2proxy extension)
-     *
-     * 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 class ServiceCreds {
-        private KerberosPrincipal kp;
-        private List<KeyTab> ktabs;
-        private List<KerberosKey> kk;
-        private Subject subj;
-        private KerberosTicket tgt;
-
-        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, serverPrincipal, 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);
-            sc.tgt = SubjectComber.find(
-                    subj, null, serverPrincipal, KerberosTicket.class);
-            if (sc.ktabs.isEmpty() && sc.kk.isEmpty() && sc.tgt == null) {
-                return null;
-            }
-            return sc;
-        }
-
-        public String getName() {
-            return kp.getName();
-        }
-
-        public KerberosKey[] getKKeys() {
-            List<KerberosKey> keys = new ArrayList<>();
-            for (KerberosKey k: kk) {
-                keys.add(k);
-            }
-            for (KeyTab ktab: ktabs) {
-                for (KerberosKey k: ktab.getKeys(kp)) {
-                    keys.add(k);
-                }
-            }
-            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 Credentials getInitCred() {
-            if (tgt == null) {
-                return null;
-            }
-            try {
-                return ticketToCreds(tgt);
-            } catch (KrbException | IOException e) {
-                return null;
-            }
-        }
-
-        public void destroy() {
-            kp = null;
-            ktabs = null;
-            kk = null;
-            tgt = 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.
@@ -361,5 +253,4 @@
         return KerberosSecrets.getJavaxSecurityAuthKerberosAccess().
                 keyTabGetEncryptionKeys(ktab, cname);
     }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/jgss/krb5/ServiceCreds.java	Tue Jan 08 14:54:56 2013 +0800
@@ -0,0 +1,229 @@
+/*
+ * 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.  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.security.jgss.krb5;
+
+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 sun.security.krb5.Credentials;
+import sun.security.krb5.EncryptionKey;
+import sun.security.krb5.KrbException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import sun.security.krb5.*;
+import sun.security.krb5.internal.Krb5;
+
+/**
+ * Credentials of a kerberos acceptor. A KerberosPrincipal object (kp) is
+ * the principal. It can be specified as the serverPrincipal argument
+ * in the getInstance() method, or uses only KerberosPrincipal in the subject.
+ * Otherwise, the creds object is unbound and kp is null.
+ *
+ * The class also encapsulates various secrets, which can be:
+ *
+ *   1. Some KerberosKeys (generated from password)
+ *   2. Some KeyTabs (for a typical service based on keytabs)
+ *   3. A TGT (for S4U2proxy extension or user2user)
+ *
+ * Note that some secrets can coexist. For example, a user2user service
+ * can use its keytab (or keys) if the client can successfully obtain a
+ * normal service ticket, or it can use the TGT (actually, the session key
+ * of the TGT) if the client can only acquire a service ticket
+ * of ENC-TKT-IN-SKEY style.
+ *
+ * @since 1.8
+ */
+public final class ServiceCreds {
+    // The principal, or null if unbound
+    private KerberosPrincipal kp;
+
+    // All principals in the subject's princ set
+    private Set<KerberosPrincipal> allPrincs;
+
+    // All private credentials that can be used
+    private List<KeyTab> ktabs;
+    private List<KerberosKey> kk;
+    private KerberosTicket tgt;
+
+    private boolean destroyed;
+
+    private ServiceCreds() {
+        // Make sure this class cannot be instantiated externally.
+    }
+
+    /**
+     * Creates a ServiceCreds object based on info in a Subject for
+     * a given principal name (if specified).
+     * @return the object, or null if there is no private creds for it
+     */
+    public static ServiceCreds getInstance(
+            Subject subj, String serverPrincipal) {
+
+        ServiceCreds sc = new ServiceCreds();
+
+        sc.allPrincs =
+                subj.getPrincipals(KerberosPrincipal.class);
+
+        // Compatibility. A key implies its own principal
+        for (KerberosKey key: SubjectComber.findMany(
+                subj, serverPrincipal, null, KerberosKey.class)) {
+            sc.allPrincs.add(key.getPrincipal());
+        }
+
+        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();
+            }
+        }
+
+        sc.ktabs = SubjectComber.findMany(
+                    subj, serverPrincipal, null, KeyTab.class);
+        sc.kk = SubjectComber.findMany(
+                    subj, serverPrincipal, null, KerberosKey.class);
+        sc.tgt = SubjectComber.find(
+                subj, null, serverPrincipal, KerberosTicket.class);
+        if (sc.ktabs.isEmpty() && sc.kk.isEmpty() && sc.tgt == null) {
+            return null;
+        }
+
+        sc.destroyed = false;
+
+        return sc;
+    }
+
+    // can be null
+    public String getName() {
+        if (destroyed) {
+            throw new IllegalStateException("This object is destroyed");
+        }
+        return kp == null ? null : kp.getName();
+    }
+
+    /**
+     * Gets keys for someone unknown.
+     * Used by TLS or as a fallback in getEKeys(). 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());
+        }
+        return new KerberosKey[0];
+    }
+
+    /**
+     * Get kkeys for a principal,
+     * @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
+        }
+        if (!allPrincs.contains(princ)) {
+            return new KerberosKey[0];      // Not someone I know, This check
+                                            // is necessary but a KeyTab has
+                                            // no principal name recorded.
+        }
+        for (KerberosKey k: kk) {
+            if (k.getPrincipal().equals(princ)) {
+                keys.add(k);
+            }
+        }
+        for (KeyTab ktab: ktabs) {
+            for (KerberosKey k: ktab.getKeys(princ)) {
+                keys.add(k);
+            }
+        }
+        return keys.toArray(new KerberosKey[keys.size()]);
+    }
+
+    /**
+     * Gets EKeys for a principal.
+     * @param princ the target name initiator requests. Not null.
+     * @return keys for the princ, never null, might be empty
+     */
+    public EncryptionKey[] getEKeys(PrincipalName princ) {
+        if (destroyed) {
+            throw new IllegalStateException("This object is destroyed");
+        }
+        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.
+            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 Credentials getInitCred() {
+        if (destroyed) {
+            throw new IllegalStateException("This object is destroyed");
+        }
+        if (tgt == null) {
+            return null;
+        }
+        try {
+            return Krb5Util.ticketToCreds(tgt);
+        } catch (KrbException | IOException e) {
+            return null;
+        }
+    }
+
+    public void destroy() {
+        // Do not wipe out real keys because they are references to the
+        // priv creds in subject. Just make it useless.
+        destroyed = true;
+        kp = null;
+        ktabs.clear();
+        kk.clear();
+        tgt = null;
+    }
+}
--- a/src/share/classes/sun/security/jgss/krb5/SubjectComber.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/src/share/classes/sun/security/jgss/krb5/SubjectComber.java	Tue Jan 08 14:54:56 2013 +0800
@@ -33,6 +33,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import javax.security.auth.kerberos.KerberosPrincipal;
 import javax.security.auth.kerberos.KeyTab;
 
 /**
@@ -84,19 +85,37 @@
         } else {
             List<T> answer = (oneOnly ? null : new ArrayList<T>());
 
-            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()) {
-                    T t = iterator.next();
-                    if (DEBUG) {
-                        System.out.println("Found " + credClass.getSimpleName());
+            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;
+                        }
                     }
-                    if (oneOnly) {
-                        return t;
-                    } else {
-                        answer.add(t);
+                } 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));
+                        }
                     }
                 }
             } else if (credClass == KerberosKey.class) {
@@ -114,11 +133,6 @@
                          if (oneOnly) {
                              return t;
                          } else {
-                             if (serverPrincipal == null) {
-                                 // Record name so that keys returned will all
-                                 // belong to the same principal
-                                 serverPrincipal = name;
-                             }
                              answer.add(credClass.cast(t));
                          }
                     }
--- a/src/share/classes/sun/security/krb5/KrbApReq.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/src/share/classes/sun/security/krb5/KrbApReq.java	Tue Jan 08 14:54:56 2013 +0800
@@ -34,6 +34,7 @@
 import sun.security.krb5.internal.*;
 import sun.security.krb5.internal.crypto.*;
 import sun.security.krb5.internal.rcache.*;
+import sun.security.jgss.krb5.Krb5AcceptCredential;
 import java.net.InetAddress;
 import sun.security.util.*;
 import java.io.IOException;
@@ -135,13 +136,13 @@
      */
      // Used in InitSecContextToken (for AP_REQ and not TGS REQ)
     public KrbApReq(byte[] message,
-                    EncryptionKey[] keys,
+                    Krb5AcceptCredential cred,
                     InetAddress initiator)
         throws KrbException, IOException {
         obuf = message;
         if (apReqMessg == null)
             decode();
-        authenticate(keys, initiator);
+        authenticate(cred, initiator);
     }
 
     /**
@@ -260,10 +261,11 @@
         }
     }
 
-    private void authenticate(EncryptionKey[] keys, InetAddress initiator)
+    private void authenticate(Krb5AcceptCredential cred, InetAddress initiator)
         throws KrbException, IOException {
         int encPartKeyType = apReqMessg.ticket.encPart.getEType();
         Integer kvno = apReqMessg.ticket.encPart.getKeyVersionNumber();
+        EncryptionKey[] keys = cred.getKrb5EncryptionKeys(apReqMessg.ticket.sname);
         EncryptionKey dkey = EncryptionKey.findKey(encPartKeyType, kvno, keys);
 
         if (dkey == null) {
--- a/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java	Tue Jan 08 14:54:56 2013 +0800
@@ -382,9 +382,15 @@
      */
     public void addEntry(PrincipalName service, char[] psswd,
             int kvno, boolean append) throws KrbException {
+        addEntry(service, service.getSalt(), psswd, kvno, append);
+    }
+
+    // Called by KDC test
+    public void addEntry(PrincipalName service, String salt, char[] psswd,
+            int kvno, boolean append) throws KrbException {
 
         EncryptionKey[] encKeys = EncryptionKey.acquireSecretKeys(
-            psswd, service.getSalt());
+            psswd, salt);
 
         // There should be only one maximum KVNO value for all etypes, so that
         // all added keys can have the same KVNO.
--- a/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java	Tue Jan 08 14:54:56 2013 +0800
@@ -36,6 +36,7 @@
 
 import sun.security.jgss.GSSCaller;
 import sun.security.jgss.krb5.Krb5Util;
+import sun.security.jgss.krb5.ServiceCreds;
 import sun.security.krb5.PrincipalName;
 import sun.security.ssl.Krb5Proxy;
 
@@ -62,7 +63,7 @@
     @Override
     public SecretKey[] getServerKeys(AccessControlContext acc)
             throws LoginException {
-        Krb5Util.ServiceCreds serviceCreds =
+        ServiceCreds serviceCreds =
             Krb5Util.getServiceCreds(GSSCaller.CALLER_SSL_SERVER, null, acc);
         return serviceCreds != null ? serviceCreds.getKKeys() :
                                         new KerberosKey[0];
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/ServiceCredsCombination.java	Tue Jan 08 14:54:56 2013 +0800
@@ -0,0 +1,133 @@
+/*
+ * 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 8005447
+ * @compile -XDignore.symbol.file ServiceCredsCombination.java
+ * @run main ServiceCredsCombination
+ * @summary default principal can act as anyone
+ */
+
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Objects;
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosKey;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.kerberos.KeyTab;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import sun.security.jgss.GSSUtil;
+
+public class ServiceCredsCombination {
+
+    public static void main(String[] args) throws Exception {
+        // pass
+        check("a", "a", princ("a"), key("a"));
+        check(null, "a", princ("a"), key("a"));
+        check("x", "NOCRED", princ("a"), key("a"));
+        // two pass
+        check("a", "a", princ("a"), key("a"), princ("b"), key("b"));
+        check("b", "b", princ("a"), key("a"), princ("b"), key("b"));
+        check(null, null, princ("a"), key("a"), princ("b"), key("b"));
+        check("x", "NOCRED", princ("a"), key("a"), princ("b"), key("b"));
+        // old ktab
+        check("b", "b", princ("b"), oldktab());
+        check("x", "NOCRED", princ("b"), oldktab());
+        check(null, "b", princ("b"), oldktab());
+        // Two old ktab
+        check("a", "a", princ("a"), princ("b"), oldktab(), oldktab());
+        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());
+        // 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());
+        // Compatibility, automatically add princ for keys
+        check(null, "a", key("a"));
+        check("x", "NOCRED", key("a"));
+        check(null, "a", key("a"), oldktab());
+        check("x", "NOCRED", key("a"), oldktab());
+        // Limitation, "a" has no key, but we don't know oldktab() is for "b"
+        check("a", "a", princ("a"), princ("b"), oldktab());
+    }
+
+    /**
+     * Checks the correct bound
+     * @param a get a creds for this principal, null for default one
+     * @param b expected name, null for still unbound, "NOCRED" for no creds
+     * @param objs princs, keys and keytabs in the subject
+     */
+    private static void check(final String a, String b, Object... objs)
+            throws Exception {
+        Subject subj = new Subject();
+        for (Object obj: objs) {
+            if (obj instanceof KerberosPrincipal) {
+                subj.getPrincipals().add((KerberosPrincipal)obj);
+            } else if (obj instanceof KerberosKey || obj instanceof KeyTab) {
+                subj.getPrivateCredentials().add(obj);
+            }
+        }
+        final GSSManager man = GSSManager.getInstance();
+        try {
+            String result = Subject.doAs(
+                    subj, new PrivilegedExceptionAction<String>() {
+                @Override
+                public String run() throws GSSException {
+                    GSSCredential cred = man.createCredential(
+                            a == null ? null : man.createName(r(a), null),
+                            GSSCredential.INDEFINITE_LIFETIME,
+                            GSSUtil.GSS_KRB5_MECH_OID,
+                            GSSCredential.ACCEPT_ONLY);
+                    GSSName name = cred.getName();
+                    return name == null ? null : name.toString();
+                }
+            });
+            if (!Objects.equals(result, r(b))) {
+                throw new Exception("Check failed: getInstance(" + a
+                        + ") has name " + result + ", not " + b);
+            }
+        } catch (PrivilegedActionException e) {
+            if (!"NOCRED".equals(b)) {
+                throw new Exception("Check failed: getInstance(" + a
+                        + ") is null " + ", but not one with name " + b);
+            }
+        }
+    }
+    private static String r(String s) {
+        return s == null ? null : (s+"@REALM");
+    }
+    private static KerberosPrincipal princ(String s) {
+        return new KerberosPrincipal(r(s));
+    }
+    private static KerberosKey key(String s) {
+        return new KerberosKey(princ(s), new byte[0], 0, 0);
+    }
+    private static KeyTab oldktab() {
+        return KeyTab.getInstance();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/AcceptPermissions.java	Tue Jan 08 14:54:56 2013 +0800
@@ -0,0 +1,147 @@
+/*
+ * 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 9999999
+ * @summary default principal can act as anyone
+ * @compile -XDignore.symbol.file AcceptPermissions.java
+ * @run main/othervm AcceptPermissions
+ */
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.security.Permission;
+import javax.security.auth.kerberos.ServicePermission;
+import sun.security.jgss.GSSUtil;
+import java.util.*;
+
+public class AcceptPermissions extends SecurityManager {
+
+    private static Map<Permission,String> perms = new HashMap<>();
+    @Override
+    public void checkPermission(Permission perm) {
+        if (!(perm instanceof ServicePermission)) {
+            return;
+        }
+        ServicePermission sp = (ServicePermission)perm;
+        if (!sp.getActions().equals("accept")) {
+            return;
+        }
+        // We only care about accept ServicePermission in this test
+        try {
+            super.checkPermission(sp);
+        } catch (SecurityException se) {
+            if (perms.containsKey(sp)) {
+                perms.put(sp, "checked");
+            } else {
+                throw se;   // We didn't expect this is needed
+            }
+        }
+    }
+
+    // Fills in permissions we are expecting
+    private static void initPerms(String... names) {
+        perms.clear();
+        for (String name: names) {
+            perms.put(new ServicePermission(
+                    name + "@" + OneKDC.REALM, "accept"), "expected");
+        }
+    }
+
+    // Checks if they are all checked
+    private static void checkPerms() {
+        for (Map.Entry<Permission,String> entry: perms.entrySet()) {
+            if (entry.getValue().equals("expected")) {
+                throw new RuntimeException(
+                        "Expected but not used: " + entry.getKey());
+            }
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        System.setSecurityManager(new AcceptPermissions());
+        new OneKDC(null).writeJAASConf();
+        String two = "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";
+        Files.write(Paths.get(OneKDC.JAAS_CONF), two.getBytes(),
+                StandardOpenOption.APPEND);
+
+        Context c, s;
+
+        // In all cases, a ServicePermission on the acceptor name is needed
+        // for a handshake. For default principal with no predictable name,
+        // permission not needed (yet) for credentials creation.
+
+        // Named principal
+        initPerms(OneKDC.SERVER);
+        c = Context.fromJAAS("client");
+        s = Context.fromJAAS("server");
+        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        checkPerms();
+        initPerms(OneKDC.SERVER);
+        Context.handshake(c, s);
+        checkPerms();
+
+        // Named principal (even if there are 2 JAAS modules)
+        initPerms(OneKDC.SERVER);
+        c = Context.fromJAAS("client");
+        s = Context.fromJAAS("two");
+        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        checkPerms();
+        initPerms(OneKDC.SERVER);
+        Context.handshake(c, s);
+        checkPerms();
+
+        // Default principal with a predictable name
+        initPerms(OneKDC.SERVER);
+        c = Context.fromJAAS("client");
+        s = Context.fromJAAS("server");
+        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+        checkPerms();
+        initPerms(OneKDC.SERVER);
+        Context.handshake(c, s);
+        checkPerms();
+
+        // Default principal with no predictable name
+        initPerms();    // permission not needed for cred !!!
+        c = Context.fromJAAS("client");
+        s = Context.fromJAAS("two");
+        c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+        checkPerms();
+        initPerms(OneKDC.SERVER);   // still needed for handshake !!!
+        Context.handshake(c, s);
+        checkPerms();
+    }
+}
--- a/test/sun/security/krb5/auto/CleanState.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/test/sun/security/krb5/auto/CleanState.java	Tue Jan 08 14:54:56 2013 +0800
@@ -24,6 +24,7 @@
 /*
  * @test
  * @bug 6716534
+ * @compile -XDignore.symbol.file CleanState.java
  * @run main/othervm CleanState
  * @summary Krb5LoginModule has not cleaned temp info between authentication attempts
  */
--- a/test/sun/security/krb5/auto/Context.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/test/sun/security/krb5/auto/Context.java	Tue Jan 08 14:54:56 2013 +0800
@@ -131,21 +131,24 @@
         return out;
     }
 
+    /**
+     * Logins with username/password as a new Subject
+     */
     public static Context fromUserPass(
             String user, char[] pass, boolean storeKey) throws Exception {
-        return fromUserPass(null, user, pass, storeKey);
+        return fromUserPass(new Subject(), user, pass, storeKey);
     }
 
     /**
-     * Logins with a username and a password, using Krb5LoginModule directly
-     * @param s existing subject, test multiple princ & creds for single subj
-     * @param storeKey true if key should be saved, used on acceptor side
+     * Logins with username/password as an existing Subject. The
+     * same subject can be used multiple times to simulate multiple logins.
+     * @param s existing subject
      */
     public static Context fromUserPass(Subject s,
             String user, char[] pass, boolean storeKey) throws Exception {
         Context out = new Context();
         out.name = user;
-        out.s = s == null ? new Subject() : s;
+        out.s = s;
         Krb5LoginModule krb5 = new Krb5LoginModule();
         Map<String, String> map = new HashMap<>();
         Map<String, Object> shared = new HashMap<>();
@@ -172,14 +175,23 @@
     }
 
     /**
-     * Logins with a username and a keytab, using Krb5LoginModule directly
-     * @param storeKey true if key should be saved, used on acceptor side
+     * Logins with username/keytab as an existing Subject. The
+     * same subject can be used multiple times to simulate multiple logins.
+     * @param s existing subject
      */
-    public static Context fromUserKtab(String user, String ktab, boolean storeKey)
-            throws Exception {
+    public static Context fromUserKtab(
+            String user, String ktab, boolean storeKey) throws Exception {
+        return fromUserKtab(new Subject(), user, ktab, storeKey);
+    }
+
+    /**
+     * Logins with username/keytab as a new subject,
+     */
+    public static Context fromUserKtab(Subject s,
+            String user, String ktab, boolean storeKey) throws Exception {
         Context out = new Context();
         out.name = user;
-        out.s = new Subject();
+        out.s = s;
         Krb5LoginModule krb5 = new Krb5LoginModule();
         Map<String, String> map = new HashMap<>();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/DiffNameSameKey.java	Tue Jan 08 14:54:56 2013 +0800
@@ -0,0 +1,91 @@
+/*
+ * 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 8005447
+ * @summary default principal can act as anyone
+ * @compile -XDignore.symbol.file DiffNameSameKey.java
+ * @run main/othervm/fail DiffNameSameKey a
+ * @run main/othervm DiffNameSameKey b
+ */
+
+import sun.security.jgss.GSSUtil;
+import sun.security.krb5.PrincipalName;
+
+/**
+ * This test confirms the compatibility codes described in
+ * ServiceCreds.getEKeys(). If the acceptor starts as x.us.oracle.com
+ * but client requests for x.us, as long as the KDC supports both names
+ * and the keys are the same, the auth should succeed.
+ */
+public class DiffNameSameKey {
+
+    static final String SERVER2 = "x" + OneKDC.SERVER;
+
+    public static void main(String[] args) throws Exception {
+
+        OneKDC kdc = new KDC2();
+        kdc.addPrincipal(SERVER2, "samepass".toCharArray());
+        kdc.addPrincipal(OneKDC.SERVER, "samepass".toCharArray());
+        kdc.writeJAASConf();
+        kdc.writeKtab(OneKDC.KTAB);
+
+        Context c, s;
+        c = Context.fromJAAS("client");
+        s = Context.fromJAAS("server");
+
+        switch (args[0]) {
+            case "a":   // If server starts as another service, should fail
+                c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_SPNEGO_MECH_OID);
+                s.startAsServer(SERVER2.replace('/', '@'),
+                        GSSUtil.GSS_SPNEGO_MECH_OID);
+                break;
+            case "b":   // If client requests another server with the same keys,
+                        // succeed to be compatible
+                c.startAsClient(SERVER2, GSSUtil.GSS_SPNEGO_MECH_OID);
+                s.startAsServer(OneKDC.SERVER.replace('/', '@'),
+                        GSSUtil.GSS_SPNEGO_MECH_OID);
+                break;
+        }
+
+        Context.handshake(c, s);
+
+        s.dispose();
+        c.dispose();
+    }
+
+    /**
+     * This KDC returns the same salt for all principals. This means same
+     * passwords generate same keys.
+     */
+    static class KDC2 extends OneKDC {
+        KDC2() throws Exception {
+            super(null);
+        }
+        @Override
+        public String getSalt(PrincipalName pn) {
+            return "SAME";
+        }
+    }
+}
--- a/test/sun/security/krb5/auto/DynamicKeytab.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/test/sun/security/krb5/auto/DynamicKeytab.java	Tue Jan 08 14:54:56 2013 +0800
@@ -24,6 +24,7 @@
 /*
  * @test
  * @bug 6894072
+ * @compile -XDignore.symbol.file DynamicKeytab.java
  * @run main/othervm DynamicKeytab
  * @summary always refresh keytab
  */
--- a/test/sun/security/krb5/auto/KDC.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/test/sun/security/krb5/auto/KDC.java	Tue Jan 08 14:54:56 2013 +0800
@@ -285,10 +285,12 @@
             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),
+            PrincipalName pn = new PrincipalName(name,
+                        name.indexOf('/') < 0 ?
+                            PrincipalName.KRB_NT_UNKNOWN :
+                            PrincipalName.KRB_NT_SRV_HST);
+            ktab.addEntry(pn,
+                        getSalt(pn),
                         pass,
                         kvno,
                         true);
@@ -534,7 +536,7 @@
         if (pass == null) {
             throw new KrbException(server?
                 Krb5.KDC_ERR_S_PRINCIPAL_UNKNOWN:
-                Krb5.KDC_ERR_C_PRINCIPAL_UNKNOWN);
+                Krb5.KDC_ERR_C_PRINCIPAL_UNKNOWN, pn.toString());
         }
         return pass;
     }
@@ -544,7 +546,7 @@
      * @param p principal
      * @return the salt
      */
-    private String getSalt(PrincipalName p) {
+    protected String getSalt(PrincipalName p) {
         String pn = p.toString();
         if (p.getRealmString() == null) {
             pn = pn + "@" + getRealm();
--- a/test/sun/security/krb5/auto/KeyTabCompat.java	Mon Jan 07 18:09:07 2013 -0800
+++ b/test/sun/security/krb5/auto/KeyTabCompat.java	Tue Jan 08 14:54:56 2013 +0800
@@ -38,7 +38,7 @@
  *
  * 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
+ * 2. If KeyTab is used, user won't get KerberosKeys from
  *    private credentials set.
  */
 public class KeyTabCompat {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/TwoOrThree.java	Tue Jan 08 14:54:56 2013 +0800
@@ -0,0 +1,82 @@
+/*
+ * 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 8005447
+ * @summary default principal can act as anyone
+ * @compile -XDignore.symbol.file TwoOrThree.java
+ * @run main/othervm TwoOrThree first first
+ * @run main/othervm/fail TwoOrThree first second
+ * @run main/othervm TwoOrThree - first
+ * @run main/othervm TwoOrThree - second
+ * @run main/othervm/fail TwoOrThree - third
+ */
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import javax.security.auth.Subject;
+import sun.security.jgss.GSSUtil;
+
+/*
+ * The JAAS login has two krb5 modules
+ *   1. principal is A
+ *   2. principal is B
+ * A named principal can only accept itself. The default principal can accept
+ * either, but not any other service even if the keytab also include its keys.
+ */
+public class TwoOrThree {
+
+    public static void main(String[] args) throws Exception {
+
+        String server = args[0].equals("-") ? null : args[0];
+        String target = args[1];
+        OneKDC kdc = new OneKDC(null);
+        kdc.addPrincipal("first", "first".toCharArray());
+        kdc.addPrincipal("second", "second".toCharArray());
+        kdc.addPrincipal("third", "third".toCharArray());
+        kdc.writeKtab(OneKDC.KTAB);
+
+        Context c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
+
+        // Using keytabs
+        Subject sub4s = new Subject();
+        Context.fromUserKtab(sub4s, "first", OneKDC.KTAB, true);
+        Context s = Context.fromUserKtab(sub4s, "second", OneKDC.KTAB, true);
+        c.startAsClient(target, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(server, GSSUtil.GSS_KRB5_MECH_OID);
+        Context.handshake(c, s);
+
+        // Using keys
+        sub4s = new Subject();
+        Context.fromUserPass(sub4s, "first", "first".toCharArray(), true);
+        s = Context.fromUserPass(sub4s, "second", "second".toCharArray(), true);
+        c.startAsClient(target, GSSUtil.GSS_KRB5_MECH_OID);
+        s.startAsServer(server, GSSUtil.GSS_KRB5_MECH_OID);
+        Context.handshake(c, s);
+
+        s.dispose();
+        c.dispose();
+    }
+}