/* * 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 { // 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(); } } } 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". 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"); } KerberosPrincipal one = kp; // named principal if (one == null && !allPrincs.isEmpty()) { // or, a known principal one = allPrincs.iterator().next(); } 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]; } } /** * Get kkeys for a principal, * @param princ the target name initiator requests. Not null. * @return keys for the princ, never null, might be empty */ public KerberosKey[] getKKeys(KerberosPrincipal princ) { if (destroyed) { throw new IllegalStateException("This object is destroyed"); } 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)) { keys.add(k); } } 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); } } 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) { // 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]; 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; } }