| /* | |
| * Copyright (c) 2012, 2022, 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.Collections; | |
| 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 | |
| } | |
| } | |
| Collections.addAll(keys, ktab.getKeys(princ)); | |
| } | |
| return keys.toArray(new KerberosKey[0]); | |
| } | |
| /** | |
| * 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(), | |
| 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; | |
| } | |
| } |