1 /* 2 * Copyright (c) 2001, 2019, 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 /* 27 * 28 * (C) Copyright IBM Corp. 1999 All Rights Reserved. 29 * Copyright 1997 The Open Group Research Institute. All rights reserved. 30 */ 31 32 package sun.security.krb5.internal; 33 34 import sun.security.krb5.*; 35 import java.io.IOException; 36 import java.util.Arrays; 37 import java.util.LinkedList; 38 import java.util.List; 39 40 /** 41 * This class is a utility that contains much of the TGS-Exchange 42 * protocol. It is used by ../Credentials.java for service ticket 43 * acquisition in both the normal and the x-realm case. 44 */ 45 public class CredentialsUtil { 46 47 private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG; 48 49 /** 50 * Used by a middle server to acquire credentials on behalf of a 51 * client to itself using the S4U2self extension. 52 * @param client the client to impersonate 53 * @param ccreds the TGT of the middle service 54 * @return the new creds (cname=client, sname=middle) 55 */ 56 public static Credentials acquireS4U2selfCreds(PrincipalName client, 57 Credentials ccreds) throws KrbException, IOException { 58 String uRealm = client.getRealmString(); 59 String localRealm = ccreds.getClient().getRealmString(); 60 if (!uRealm.equals(localRealm)) { 61 // TODO: we do not support kerberos referral now 62 throw new KrbException("Cross realm impersonation not supported"); 63 } 64 if (!ccreds.isForwardable()) { 65 throw new KrbException("S4U2self needs a FORWARDABLE ticket"); 66 } 67 Credentials creds = serviceCreds(KDCOptions.with(KDCOptions.FORWARDABLE), 68 ccreds, ccreds.getClient(), ccreds.getClient(), null, 69 new PAData[] {new PAData(Krb5.PA_FOR_USER, 70 new PAForUserEnc(client, 71 ccreds.getSessionKey()).asn1Encode())}); 72 if (!creds.getClient().equals(client)) { 73 throw new KrbException("S4U2self request not honored by KDC"); 74 } 75 if (!creds.isForwardable()) { 76 throw new KrbException("S4U2self ticket must be FORWARDABLE"); 77 } 78 return creds; 79 } 80 81 /** 82 * Used by a middle server to acquire a service ticket to a backend 83 * server using the S4U2proxy extension. 84 * @param backend the name of the backend service 85 * @param second the client's service ticket to the middle server 86 * @param ccreds the TGT of the middle server 87 * @return the creds (cname=client, sname=backend) 88 */ 89 public static Credentials acquireS4U2proxyCreds( 90 String backend, Ticket second, 91 PrincipalName client, Credentials ccreds) 92 throws KrbException, IOException { 93 Credentials creds = serviceCreds(KDCOptions.with( 94 KDCOptions.CNAME_IN_ADDL_TKT, KDCOptions.FORWARDABLE), 95 ccreds, ccreds.getClient(), new PrincipalName(backend), 96 new Ticket[] {second}, null); 97 if (!creds.getClient().equals(client)) { 98 throw new KrbException("S4U2proxy request not honored by KDC"); 99 } 100 return creds; 101 } 102 103 /** 104 * Acquires credentials for a specified service using initial 105 * credential. When the service has a different realm from the initial 106 * credential, we do cross-realm authentication - first, we use the 107 * current credential to get a cross-realm credential from the local KDC, 108 * then use that cross-realm credential to request service credential 109 * from the foreign KDC. 110 * 111 * @param service the name of service principal 112 * @param ccreds client's initial credential 113 */ 114 public static Credentials acquireServiceCreds( 115 String service, Credentials ccreds) 116 throws KrbException, IOException { 117 PrincipalName sname = new PrincipalName(service, 118 PrincipalName.KRB_NT_SRV_HST); 119 return serviceCreds(sname, ccreds); 120 } 121 122 /** 123 * Gets a TGT to another realm 124 * @param localRealm this realm 125 * @param serviceRealm the other realm, cannot equals to localRealm 126 * @param ccreds TGT in this realm 127 * @param okAsDelegate an [out] argument to receive the okAsDelegate 128 * property. True only if all realms allow delegation. 129 * @return the TGT for the other realm, null if cannot find a path 130 * @throws KrbException if something goes wrong 131 */ 132 private static Credentials getTGTforRealm(String localRealm, 133 String serviceRealm, Credentials ccreds, boolean[] okAsDelegate) 134 throws KrbException { 135 136 // Get a list of realms to traverse 137 String[] realms = Realm.getRealmsList(localRealm, serviceRealm); 138 139 int i = 0, k = 0; 140 Credentials cTgt = null, newTgt = null, theTgt = null; 141 PrincipalName tempService = null; 142 String newTgtRealm = null; 143 144 okAsDelegate[0] = true; 145 for (cTgt = ccreds, i = 0; i < realms.length;) { 146 tempService = PrincipalName.tgsService(serviceRealm, realms[i]); 147 148 if (DEBUG) { 149 System.out.println( 150 ">>> Credentials acquireServiceCreds: main loop: [" 151 + i +"] tempService=" + tempService); 152 } 153 154 try { 155 newTgt = serviceCreds(tempService, cTgt); 156 } catch (Exception exc) { 157 newTgt = null; 158 } 159 160 if (newTgt == null) { 161 if (DEBUG) { 162 System.out.println(">>> Credentials acquireServiceCreds: " 163 + "no tgt; searching thru capath"); 164 } 165 166 /* 167 * No tgt found. Let's go thru the realms list one by one. 168 */ 169 for (newTgt = null, k = i+1; 170 newTgt == null && k < realms.length; k++) { 171 tempService = PrincipalName.tgsService(realms[k], realms[i]); 172 if (DEBUG) { 173 System.out.println( 174 ">>> Credentials acquireServiceCreds: " 175 + "inner loop: [" + k 176 + "] tempService=" + tempService); 177 } 178 try { 179 newTgt = serviceCreds(tempService, cTgt); 180 } catch (Exception exc) { 181 newTgt = null; 182 } 183 } 184 } // Ends 'if (newTgt == null)' 185 186 if (newTgt == null) { 187 if (DEBUG) { 188 System.out.println(">>> Credentials acquireServiceCreds: " 189 + "no tgt; cannot get creds"); 190 } 191 break; 192 } 193 194 /* 195 * We have a tgt. It may or may not be for the target. 196 * If it's for the target realm, we're done looking for a tgt. 197 */ 198 newTgtRealm = newTgt.getServer().getInstanceComponent(); 199 if (okAsDelegate[0] && !newTgt.checkDelegate()) { 200 if (DEBUG) { 201 System.out.println(">>> Credentials acquireServiceCreds: " + 202 "global OK-AS-DELEGATE turned off at " + 203 newTgt.getServer()); 204 } 205 okAsDelegate[0] = false; 206 } 207 208 if (DEBUG) { 209 System.out.println(">>> Credentials acquireServiceCreds: " 210 + "got tgt"); 211 } 212 213 if (newTgtRealm.equals(serviceRealm)) { 214 /* We got the right tgt */ 215 theTgt = newTgt; 216 break; 217 } 218 219 /* 220 * The new tgt is not for the target realm. 221 * See if the realm of the new tgt is in the list of realms 222 * and continue looking from there. 223 */ 224 for (k = i+1; k < realms.length; k++) { 225 if (newTgtRealm.equals(realms[k])) { 226 break; 227 } 228 } 229 230 if (k < realms.length) { 231 /* 232 * (re)set the counter so we start looking 233 * from the realm we just obtained a tgt for. 234 */ 235 i = k; 236 cTgt = newTgt; 237 238 if (DEBUG) { 239 System.out.println(">>> Credentials acquireServiceCreds: " 240 + "continuing with main loop counter reset to " + i); 241 } 242 continue; 243 } 244 else { 245 /* 246 * The new tgt's realm is not in the hierarchy of realms. 247 * It's probably not safe to get a tgt from 248 * a tgs that is outside the known list of realms. 249 * Give up now. 250 */ 251 break; 252 } 253 } // Ends outermost/main 'for' loop 254 255 return theTgt; 256 } 257 258 /* 259 * This method does the real job to request the service credential. 260 */ 261 private static Credentials serviceCreds( 262 PrincipalName service, Credentials ccreds) 263 throws KrbException, IOException { 264 return serviceCreds(new KDCOptions(), ccreds, 265 ccreds.getClient(), service, null, null); 266 } 267 268 private static Credentials serviceCreds( 269 KDCOptions options, Credentials asCreds, 270 PrincipalName cname, PrincipalName sname, 271 Ticket[] additionalTickets, PAData[] extraPAs) 272 throws KrbException, IOException { 273 if (!Config.DISABLE_REFERRALS) { 274 try { 275 return serviceCredsReferrals(options, asCreds, 276 cname, sname, additionalTickets, extraPAs); 277 } catch (KrbException e) { 278 // Server may raise an error if CANONICALIZE is true. 279 // Try CANONICALIZE false. 280 } 281 } 282 return serviceCredsSingle(options, asCreds, 283 cname, sname, additionalTickets, extraPAs); 284 } 285 286 private static Credentials serviceCredsReferrals( 287 KDCOptions options, Credentials asCreds, 288 PrincipalName cname, PrincipalName sname, 289 Ticket[] additionalTickets, PAData[] extraPAs) 290 throws KrbException, IOException { 291 options = new KDCOptions(options.toBooleanArray()); 292 options.set(KDCOptions.CANONICALIZE, true); 293 PrincipalName cSname = (PrincipalName) sname.clone(); 294 Credentials creds = null; 295 boolean isReferral = false; 296 List<String> referrals = new LinkedList<>(); 297 while (referrals.size() <= Config.MAX_REFERRALS) { 298 ReferralsCache.ReferralCacheEntry ref = 299 ReferralsCache.get(sname, cSname.getRealmString()); 300 String toRealm = null; 301 if (ref == null) { 302 creds = serviceCredsSingle(options, asCreds, 303 cname, cSname, additionalTickets, extraPAs); 304 PrincipalName server = creds.getServer(); 305 if (!cSname.equals(server)) { 306 String[] serverNameStrings = server.getNameStrings(); 307 if (serverNameStrings.length == 2 && 308 serverNameStrings[0].equals( 309 PrincipalName.TGS_DEFAULT_SRV_NAME) && 310 !cSname.getRealmAsString().equals(serverNameStrings[1])) { 311 // Server Name (sname) has the following format: 312 // krbtgt/TO-REALM.COM@FROM-REALM.COM 313 ReferralsCache.put(sname, server.getRealmString(), 314 serverNameStrings[1], creds); 315 toRealm = serverNameStrings[1]; 316 isReferral = true; 317 asCreds = creds; 318 } 319 } 320 } else { 321 toRealm = ref.getToRealm(); 322 asCreds = ref.getCreds(); 323 isReferral = true; 324 } 325 if (isReferral) { 326 if (referrals.contains(toRealm)) { 327 // Referrals loop detected 328 return null; 329 } 330 cSname = new PrincipalName(cSname.getNameString(), 331 cSname.getNameType(), toRealm); 332 referrals.add(toRealm); 333 isReferral = false; 334 continue; 335 } 336 break; 337 } 338 return creds; 339 } 340 341 private static Credentials serviceCredsSingle( 342 KDCOptions options, Credentials asCreds, 343 PrincipalName cname, PrincipalName sname, 344 Ticket[] additionalTickets, PAData[] extraPAs) 345 throws KrbException, IOException { 346 Credentials theCreds = null; 347 boolean[] okAsDelegate = new boolean[]{true}; 348 String[] serverAsCredsNames = asCreds.getServer().getNameStrings(); 349 if (serverAsCredsNames.length == 2 && 350 serverAsCredsNames[0].equals( 351 PrincipalName.TGS_DEFAULT_SRV_NAME)) { 352 String tgtRealm = serverAsCredsNames[1]; 353 String serviceRealm = sname.getRealmString(); 354 if (!serviceRealm.equals(tgtRealm)) { 355 // This is a cross-realm service request 356 if (DEBUG) { 357 System.out.println(">>> serviceCredsSingle:" + 358 " cross-realm authentication"); 359 System.out.println(">>> serviceCredsSingle:" + 360 " obtaining credentials from " + tgtRealm + 361 " to " + serviceRealm); 362 } 363 Credentials newTgt = getTGTforRealm(tgtRealm, serviceRealm, 364 asCreds, okAsDelegate); 365 if (newTgt == null) { 366 throw new KrbApErrException(Krb5.KRB_AP_ERR_GEN_CRED, 367 "No service creds"); 368 } 369 if (DEBUG) { 370 System.out.println(">>> Cross-realm TGT Credentials" + 371 " serviceCredsSingle: "); 372 Credentials.printDebug(newTgt); 373 } 374 asCreds = newTgt; 375 cname = asCreds.getClient(); 376 } else if (DEBUG) { 377 System.out.println(">>> Credentials serviceCredsSingle:" + 378 " same realm"); 379 } 380 } 381 KrbTgsReq req = new KrbTgsReq(options, asCreds, 382 cname, sname, additionalTickets, extraPAs); 383 theCreds = req.sendAndGetCreds(); 384 if (theCreds != null) { 385 if (DEBUG) { 386 System.out.println(">>> TGS credentials serviceCredsSingle:"); 387 Credentials.printDebug(theCreds); 388 } 389 if (!okAsDelegate[0]) { 390 theCreds.resetDelegate(); 391 } 392 } 393 return theCreds; 394 } 395 }