1 /* 2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.jgss.krb5; 27 28 import javax.security.auth.kerberos.KerberosTicket; 29 import javax.security.auth.kerberos.KerberosKey; 30 import javax.security.auth.kerberos.KerberosPrincipal; 31 import javax.security.auth.kerberos.KeyTab; 32 import javax.security.auth.Subject; 33 34 import sun.security.krb5.Credentials; 35 import sun.security.krb5.EncryptionKey; 36 import sun.security.krb5.KrbException; 37 import java.io.IOException; 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.Set; 41 import sun.security.krb5.*; 42 import sun.security.krb5.internal.Krb5; 43 44 /** 45 * Credentials of a kerberos acceptor. A KerberosPrincipal object (kp) is 46 * the principal. It can be specified as the serverPrincipal argument 47 * in the getInstance() method, or uses only KerberosPrincipal in the subject. 48 * Otherwise, the creds object is unbound and kp is null. 49 * 50 * The class also encapsulates various secrets, which can be: 51 * 52 * 1. Some KerberosKeys (generated from password) 53 * 2. Some KeyTabs (for a typical service based on keytabs) 54 * 3. A TGT (for S4U2proxy extension or user2user) 55 * 56 * Note that some secrets can coexist. For example, a user2user service 57 * can use its keytab (or keys) if the client can successfully obtain a 58 * normal service ticket, or it can use the TGT (actually, the session key 59 * of the TGT) if the client can only acquire a service ticket 60 * of ENC-TKT-IN-SKEY style. 61 * 62 * @since 1.8 63 */ 64 public final class ServiceCreds { 65 // The principal, or null if unbound 66 private KerberosPrincipal kp; 67 68 // All principals in the subject's princ set 69 private Set<KerberosPrincipal> allPrincs; 70 71 // All private credentials that can be used 72 private List<KeyTab> ktabs; 73 private List<KerberosKey> kk; 74 private KerberosTicket tgt; 75 76 private boolean destroyed; 77 ServiceCreds()78 private ServiceCreds() { 79 // Make sure this class cannot be instantiated externally. 80 } 81 82 /** 83 * Creates a ServiceCreds object based on info in a Subject for 84 * a given principal name (if specified). 85 * @return the object, or null if there is no private creds for it 86 */ getInstance( Subject subj, String serverPrincipal)87 public static ServiceCreds getInstance( 88 Subject subj, String serverPrincipal) { 89 90 ServiceCreds sc = new ServiceCreds(); 91 92 sc.allPrincs = 93 subj.getPrincipals(KerberosPrincipal.class); 94 95 // Compatibility. A key implies its own principal 96 for (KerberosKey key: SubjectComber.findMany( 97 subj, serverPrincipal, null, KerberosKey.class)) { 98 sc.allPrincs.add(key.getPrincipal()); 99 } 100 101 if (serverPrincipal != null) { // A named principal 102 sc.kp = new KerberosPrincipal(serverPrincipal); 103 } else { 104 // For compatibility reason, we set the name of default principal 105 // to the "only possible" name it can take, which means there is 106 // only one KerberosPrincipal and there is no unbound keytabs 107 if (sc.allPrincs.size() == 1) { 108 boolean hasUnbound = false; 109 for (KeyTab ktab: SubjectComber.findMany( 110 subj, null, null, KeyTab.class)) { 111 if (!ktab.isBound()) { 112 hasUnbound = true; 113 break; 114 } 115 } 116 if (!hasUnbound) { 117 sc.kp = sc.allPrincs.iterator().next(); 118 serverPrincipal = sc.kp.getName(); 119 } 120 } 121 } 122 123 sc.ktabs = SubjectComber.findMany( 124 subj, serverPrincipal, null, KeyTab.class); 125 sc.kk = SubjectComber.findMany( 126 subj, serverPrincipal, null, KerberosKey.class); 127 sc.tgt = SubjectComber.find( 128 subj, null, serverPrincipal, KerberosTicket.class); 129 if (sc.ktabs.isEmpty() && sc.kk.isEmpty() && sc.tgt == null) { 130 return null; 131 } 132 133 sc.destroyed = false; 134 135 return sc; 136 } 137 138 // can be null getName()139 public String getName() { 140 if (destroyed) { 141 throw new IllegalStateException("This object is destroyed"); 142 } 143 return kp == null ? null : kp.getName(); 144 } 145 146 /** 147 * Gets keys for "someone". Used in 2 cases: 148 * 1. By TLS because it needs to get keys before client comes in. 149 * 2. As a fallback in getEKeys() below. 150 * This method can still return an empty array. 151 */ getKKeys()152 public KerberosKey[] getKKeys() { 153 if (destroyed) { 154 throw new IllegalStateException("This object is destroyed"); 155 } 156 KerberosPrincipal one = kp; // named principal 157 if (one == null && !allPrincs.isEmpty()) { // or, a known principal 158 one = allPrincs.iterator().next(); 159 } 160 if (one == null) { // Or, some random one 161 for (KeyTab ktab: ktabs) { 162 // Must be unbound keytab, otherwise, allPrincs is not empty 163 PrincipalName pn = 164 Krb5Util.snapshotFromJavaxKeyTab(ktab).getOneName(); 165 if (pn != null) { 166 one = new KerberosPrincipal(pn.getName()); 167 break; 168 } 169 } 170 } 171 if (one != null) { 172 return getKKeys(one); 173 } else { 174 return new KerberosKey[0]; 175 } 176 } 177 178 /** 179 * Get kkeys for a principal, 180 * @param princ the target name initiator requests. Not null. 181 * @return keys for the princ, never null, might be empty 182 */ getKKeys(KerberosPrincipal princ)183 public KerberosKey[] getKKeys(KerberosPrincipal princ) { 184 if (destroyed) { 185 throw new IllegalStateException("This object is destroyed"); 186 } 187 ArrayList<KerberosKey> keys = new ArrayList<>(); 188 if (kp != null && !princ.equals(kp)) { // named principal 189 return new KerberosKey[0]; 190 } 191 for (KerberosKey k: kk) { 192 if (k.getPrincipal().equals(princ)) { 193 keys.add(k); 194 } 195 } 196 for (KeyTab ktab: ktabs) { 197 if (ktab.getPrincipal() == null && ktab.isBound()) { 198 // legacy bound keytab. although we don't know who 199 // the bound principal is, it must be in allPrincs 200 if (!allPrincs.contains(princ)) { 201 continue; // skip this legacy bound keytab 202 } 203 } 204 for (KerberosKey k: ktab.getKeys(princ)) { 205 keys.add(k); 206 } 207 } 208 return keys.toArray(new KerberosKey[keys.size()]); 209 } 210 211 /** 212 * Gets EKeys for a principal. 213 * @param princ the target name initiator requests. Not null. 214 * @return keys for the princ, never null, might be empty 215 */ getEKeys(PrincipalName princ)216 public EncryptionKey[] getEKeys(PrincipalName princ) { 217 if (destroyed) { 218 throw new IllegalStateException("This object is destroyed"); 219 } 220 KerberosKey[] kkeys = getKKeys(new KerberosPrincipal(princ.getName())); 221 if (kkeys.length == 0) { 222 // Fallback: old JDK does not perform real name checking. If the 223 // acceptor has host.sun.com but initiator requests for host, 224 // as long as their keys match (i.e. keys for one can decrypt 225 // the other's service ticket), the authentication is OK. 226 // There are real customers depending on this to use different 227 // names for a single service. 228 kkeys = getKKeys(); 229 } 230 EncryptionKey[] ekeys = new EncryptionKey[kkeys.length]; 231 for (int i=0; i<ekeys.length; i++) { 232 ekeys[i] = new EncryptionKey( 233 kkeys[i].getEncoded(), kkeys[i].getKeyType(), 234 kkeys[i].getVersionNumber()); 235 } 236 return ekeys; 237 } 238 getInitCred()239 public Credentials getInitCred() { 240 if (destroyed) { 241 throw new IllegalStateException("This object is destroyed"); 242 } 243 if (tgt == null) { 244 return null; 245 } 246 try { 247 return Krb5Util.ticketToCreds(tgt); 248 } catch (KrbException | IOException e) { 249 return null; 250 } 251 } 252 destroy()253 public void destroy() { 254 // Do not wipe out real keys because they are references to the 255 // priv creds in subject. Just make it useless. 256 destroyed = true; 257 kp = null; 258 ktabs.clear(); 259 kk.clear(); 260 tgt = null; 261 } 262 } 263