diff --git a/src/share/classes/sun/security/jgss/krb5/InitSecContextToken.java b/src/share/classes/sun/security/jgss/krb5/InitSecContextToken.java index edd4bcf7c4514e98e96e015ce65d2640f338add4..ff2154ffce015183683acadabd8768fbe771dbe7 100644 --- a/src/share/classes/sun/security/jgss/krb5/InitSecContextToken.java +++ b/src/share/classes/sun/security/jgss/krb5/InitSecContextToken.java @@ -86,7 +86,7 @@ class InitSecContextToken extends InitialToken { * 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 @@ class InitSecContextToken extends InitialToken { 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(); diff --git a/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java b/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java index 0f65137cecfacfb0056e97586e03e39a87037672..4a4d0fca89c94e23551207e4b817582d9cda6c43 100644 --- a/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java +++ b/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java @@ -45,13 +45,10 @@ import javax.security.auth.DestroyFailedException; 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 @@ public class Krb5AcceptCredential name.getKrb5PrincipalName().getName()); final AccessControlContext acc = AccessController.getContext(); - Krb5Util.ServiceCreds creds = null; + ServiceCreds creds = null; try { creds = AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public Krb5Util.ServiceCreds run() throws Exception { + new PrivilegedExceptionAction() { + public ServiceCreds run() throws Exception { return Krb5Util.getServiceCreds( caller == GSSCaller.CALLER_UNKNOWN ? GSSCaller.CALLER_ACCEPT: caller, serverPrinc, acc); @@ -92,8 +89,10 @@ public class Krb5AcceptCredential 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 @@ public class Krb5AcceptCredential return Krb5MechFactory.PROVIDER; } - EncryptionKey[] getKrb5EncryptionKeys() { - return screds.getEKeys(); + public EncryptionKey[] getKrb5EncryptionKeys(PrincipalName princ) { + return screds.getEKeys(princ); } /** diff --git a/src/share/classes/sun/security/jgss/krb5/Krb5Context.java b/src/share/classes/sun/security/jgss/krb5/Krb5Context.java index be8074097c9cc353ca28039aeb323bcc389c8d4b..309c1814bf3ae9e5a9a52c72bdd7fd65976d1946 100644 --- a/src/share/classes/sun/security/jgss/krb5/Krb5Context.java +++ b/src/share/classes/sun/security/jgss/krb5/Krb5Context.java @@ -818,16 +818,23 @@ class Krb5Context implements GSSContextSpi { } 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(); diff --git a/src/share/classes/sun/security/jgss/krb5/Krb5MechFactory.java b/src/share/classes/sun/security/jgss/krb5/Krb5MechFactory.java index d9552a1ec6aa4158707ea8b5dc56df2e083d4976..a1995293314384cb987cbd3f3033a41e8e1920a4 100644 --- a/src/share/classes/sun/security/jgss/krb5/Krb5MechFactory.java +++ b/src/share/classes/sun/security/jgss/krb5/Krb5MechFactory.java @@ -158,7 +158,7 @@ public final class Krb5MechFactory implements MechanismFactory { 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 { diff --git a/src/share/classes/sun/security/jgss/krb5/Krb5Util.java b/src/share/classes/sun/security/jgss/krb5/Krb5Util.java index ec5bc9ca43b30a6f0f35e20bec021d8771892b62..f0495eef872d3aa5a9293a53ab84339980f8a77e 100644 --- a/src/share/classes/sun/security/jgss/krb5/Krb5Util.java +++ b/src/share/classes/sun/security/jgss/krb5/Krb5Util.java @@ -186,114 +186,6 @@ public class Krb5Util { return subject; } - /** - * 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 ktabs; - private List 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 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 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 allPrincs; + + // All private credentials that can be used + private List ktabs; + private List 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 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 answer = (oneOnly ? null : new ArrayList()); - if (credClass == KeyTab.class) { // Principal un-related - // We are looking for credentials unrelated to serverPrincipal - Iterator 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 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 @@ class SubjectComber { 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)); } } diff --git a/src/share/classes/sun/security/krb5/KrbApReq.java b/src/share/classes/sun/security/krb5/KrbApReq.java index f4e52d35f7d810a488de6f25659e4ae29286c3ac..535bb0dc52f525e2a27d13bd62a5c0290640415f 100644 --- a/src/share/classes/sun/security/krb5/KrbApReq.java +++ b/src/share/classes/sun/security/krb5/KrbApReq.java @@ -34,6 +34,7 @@ package sun.security.krb5; 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 @@ public class KrbApReq { */ // 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 @@ public class KrbApReq { } } - 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) { diff --git a/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java b/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java index b2a71ce6693024463d98101f975c187bfc63fdf2..d1023de9de3f3b44b9ae61b705f46ecbfa4d758e 100644 --- a/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java +++ b/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java @@ -382,9 +382,15 @@ public class KeyTab implements KeyTabConstants { */ 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. diff --git a/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java b/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java index bc22a77a93b1535109badbbf49af4656c1a1c22d..7f9a7984b98f70155395d243e12bf288b843db93 100644 --- a/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java +++ b/src/share/classes/sun/security/ssl/krb5/Krb5ProxyImpl.java @@ -36,6 +36,7 @@ import javax.security.auth.login.LoginException; 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 @@ public class Krb5ProxyImpl implements Krb5Proxy { @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]; diff --git a/test/sun/security/krb5/ServiceCredsCombination.java b/test/sun/security/krb5/ServiceCredsCombination.java new file mode 100644 index 0000000000000000000000000000000000000000..3208560e788c6d00e5838f9b40f9a4618c946855 --- /dev/null +++ b/test/sun/security/krb5/ServiceCredsCombination.java @@ -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() { + @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(); + } +} diff --git a/test/sun/security/krb5/auto/AcceptPermissions.java b/test/sun/security/krb5/auto/AcceptPermissions.java new file mode 100644 index 0000000000000000000000000000000000000000..3a6d422842fa2de7671340cf339e0a0bad9773ad --- /dev/null +++ b/test/sun/security/krb5/auto/AcceptPermissions.java @@ -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 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 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(); + } +} diff --git a/test/sun/security/krb5/auto/CleanState.java b/test/sun/security/krb5/auto/CleanState.java index fbd8785cbab2f7a07fb986a1e3fc2f2c3e9be694..d87325456e610c99900610c8497075f60e805c1e 100644 --- a/test/sun/security/krb5/auto/CleanState.java +++ b/test/sun/security/krb5/auto/CleanState.java @@ -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 */ diff --git a/test/sun/security/krb5/auto/Context.java b/test/sun/security/krb5/auto/Context.java index 7eef77c61c83fe348c0137e0d4bbf9d3be037de1..aae14f46bd3a00b8c5d9b7fbc8dc308f90849795 100644 --- a/test/sun/security/krb5/auto/Context.java +++ b/test/sun/security/krb5/auto/Context.java @@ -131,21 +131,24 @@ public class Context { 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 map = new HashMap<>(); Map shared = new HashMap<>(); @@ -172,14 +175,23 @@ public class Context { } /** - * 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 map = new HashMap<>(); diff --git a/test/sun/security/krb5/auto/DiffNameSameKey.java b/test/sun/security/krb5/auto/DiffNameSameKey.java new file mode 100644 index 0000000000000000000000000000000000000000..92c487839c0f6d9c7dccda98ba3c3c64cc63a131 --- /dev/null +++ b/test/sun/security/krb5/auto/DiffNameSameKey.java @@ -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"; + } + } +} diff --git a/test/sun/security/krb5/auto/DynamicKeytab.java b/test/sun/security/krb5/auto/DynamicKeytab.java index 6525b8fb49f314b0ef7a1be1a3f481139a870b3b..9c48fe44332be44f31eb5723d4b43592f86898b1 100644 --- a/test/sun/security/krb5/auto/DynamicKeytab.java +++ b/test/sun/security/krb5/auto/DynamicKeytab.java @@ -24,6 +24,7 @@ /* * @test * @bug 6894072 + * @compile -XDignore.symbol.file DynamicKeytab.java * @run main/othervm DynamicKeytab * @summary always refresh keytab */ diff --git a/test/sun/security/krb5/auto/KDC.java b/test/sun/security/krb5/auto/KDC.java index 409f3131fb80249cc096a7da2ad3f80889423b88..83cafe73f8f313b9ecb1de6d772b195cd77781cc 100644 --- a/test/sun/security/krb5/auto/KDC.java +++ b/test/sun/security/krb5/auto/KDC.java @@ -285,10 +285,12 @@ public class KDC { 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 @@ public class KDC { 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 @@ public class KDC { * @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(); diff --git a/test/sun/security/krb5/auto/KeyTabCompat.java b/test/sun/security/krb5/auto/KeyTabCompat.java index 87a3e7e9c787a5032bebfff55bd0fb40e80b1c6a..ebc1c75c7e478cd1345bfd3316efcaced7acc382 100644 --- a/test/sun/security/krb5/auto/KeyTabCompat.java +++ b/test/sun/security/krb5/auto/KeyTabCompat.java @@ -38,7 +38,7 @@ import sun.security.jgss.GSSUtil; * * 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 { diff --git a/test/sun/security/krb5/auto/TwoOrThree.java b/test/sun/security/krb5/auto/TwoOrThree.java new file mode 100644 index 0000000000000000000000000000000000000000..77f5e21b440ebfd0ba78698076e1f3691347b55c --- /dev/null +++ b/test/sun/security/krb5/auto/TwoOrThree.java @@ -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(); + } +}