1 /* 2 * Copyright (c) 2001, 2021, 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 sun.security.util.DerValue; 36 37 import java.io.IOException; 38 import java.util.LinkedList; 39 import java.util.List; 40 41 /** 42 * This class is a utility that contains much of the TGS-Exchange 43 * protocol. It is used by ../Credentials.java for service ticket 44 * acquisition in both the normal and the x-realm case. 45 */ 46 public class CredentialsUtil { 47 48 private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG; 49 50 private static enum S4U2Type { 51 NONE, SELF, PROXY 52 } 53 54 /** 55 * Used by a middle server to acquire credentials on behalf of a 56 * user to itself using the S4U2self extension. 57 * @param user the user to impersonate 58 * @param ccreds the TGT of the middle service 59 * @return the new creds (cname=user, sname=middle) 60 */ acquireS4U2selfCreds(PrincipalName user, Credentials ccreds)61 public static Credentials acquireS4U2selfCreds(PrincipalName user, 62 Credentials ccreds) throws KrbException, IOException { 63 if (!ccreds.isForwardable()) { 64 throw new KrbException("S4U2self needs a FORWARDABLE ticket"); 65 } 66 PrincipalName sname = ccreds.getClient(); 67 String uRealm = user.getRealmString(); 68 String localRealm = ccreds.getClient().getRealmString(); 69 if (!uRealm.equals(localRealm)) { 70 // Referrals will be required because the middle service 71 // and the user impersonated are on different realms. 72 if (Config.DISABLE_REFERRALS) { 73 throw new KrbException("Cross-realm S4U2Self request not" + 74 " possible when referrals are disabled."); 75 } 76 if (ccreds.getClientAlias() != null) { 77 // If the name was canonicalized, the user pick 78 // has preference. This gives the possibility of 79 // using FQDNs that KDCs may use to return referrals. 80 // I.e.: a SVC/host.realm-2.com@REALM-1.COM name 81 // may be used by REALM-1.COM KDC to return a 82 // referral to REALM-2.COM. 83 sname = ccreds.getClientAlias(); 84 } 85 sname = new PrincipalName(sname.getNameType(), 86 sname.getNameStrings(), new Realm(uRealm)); 87 } 88 Credentials creds = serviceCreds( 89 KDCOptions.with(KDCOptions.FORWARDABLE), 90 ccreds, ccreds.getClient(), sname, user, 91 null, new PAData[] { 92 new PAData(Krb5.PA_FOR_USER, 93 new PAForUserEnc(user, 94 ccreds.getSessionKey()).asn1Encode()), 95 new PAData(Krb5.PA_PAC_OPTIONS, 96 new PaPacOptions() 97 .setResourceBasedConstrainedDelegation(true) 98 .setClaims(true) 99 .asn1Encode()) 100 }, S4U2Type.SELF); 101 if (!creds.getClient().equals(user)) { 102 throw new KrbException("S4U2self request not honored by KDC"); 103 } 104 if (!creds.isForwardable()) { 105 throw new KrbException("S4U2self ticket must be FORWARDABLE"); 106 } 107 return creds; 108 } 109 110 /** 111 * Used by a middle server to acquire a service ticket to a backend 112 * server using the S4U2proxy extension. 113 * @param backend the name of the backend service 114 * @param second the client's service ticket to the middle server 115 * @param ccreds the TGT of the middle server 116 * @return the creds (cname=client, sname=backend) 117 */ acquireS4U2proxyCreds( String backend, Ticket second, PrincipalName client, Credentials ccreds)118 public static Credentials acquireS4U2proxyCreds( 119 String backend, Ticket second, 120 PrincipalName client, Credentials ccreds) 121 throws KrbException, IOException { 122 PrincipalName backendPrincipal = new PrincipalName(backend); 123 String backendRealm = backendPrincipal.getRealmString(); 124 String localRealm = ccreds.getClient().getRealmString(); 125 if (!backendRealm.equals(localRealm)) { 126 // The middle service and the backend service are on 127 // different realms, so referrals will be required. 128 if (Config.DISABLE_REFERRALS) { 129 throw new KrbException("Cross-realm S4U2Proxy request not" + 130 " possible when referrals are disabled."); 131 } 132 backendPrincipal = new PrincipalName( 133 backendPrincipal.getNameType(), 134 backendPrincipal.getNameStrings(), 135 new Realm(localRealm)); 136 } 137 Credentials creds = serviceCreds(KDCOptions.with( 138 KDCOptions.CNAME_IN_ADDL_TKT, KDCOptions.FORWARDABLE), 139 ccreds, ccreds.getClient(), backendPrincipal, null, 140 new Ticket[] {second}, new PAData[] { 141 new PAData(Krb5.PA_PAC_OPTIONS, 142 new PaPacOptions() 143 .setResourceBasedConstrainedDelegation(true) 144 .setClaims(true) 145 .asn1Encode()) 146 }, S4U2Type.PROXY); 147 if (!creds.getClient().equals(client)) { 148 throw new KrbException("S4U2proxy request not honored by KDC"); 149 } 150 return creds; 151 } 152 153 /** 154 * Acquires credentials for a specified service using initial 155 * credential. When the service has a different realm from the initial 156 * credential, we do cross-realm authentication - first, we use the 157 * current credential to get a cross-realm credential from the local KDC, 158 * then use that cross-realm credential to request service credential 159 * from the foreign KDC. 160 * 161 * @param service the name of service principal 162 * @param ccreds client's initial credential 163 */ acquireServiceCreds( String service, Credentials ccreds)164 public static Credentials acquireServiceCreds( 165 String service, Credentials ccreds) 166 throws KrbException, IOException { 167 PrincipalName sname = new PrincipalName(service, 168 PrincipalName.KRB_NT_UNKNOWN); 169 return serviceCreds(sname, ccreds); 170 } 171 172 /** 173 * Gets a TGT to another realm 174 * @param localRealm this realm 175 * @param serviceRealm the other realm, cannot equals to localRealm 176 * @param ccreds TGT in this realm 177 * @param okAsDelegate an [out] argument to receive the okAsDelegate 178 * property. True only if all realms allow delegation. 179 * @return the TGT for the other realm, null if cannot find a path 180 * @throws KrbException if something goes wrong 181 */ getTGTforRealm(String localRealm, String serviceRealm, Credentials ccreds, boolean[] okAsDelegate)182 private static Credentials getTGTforRealm(String localRealm, 183 String serviceRealm, Credentials ccreds, boolean[] okAsDelegate) 184 throws KrbException { 185 186 // Get a list of realms to traverse 187 String[] realms = Realm.getRealmsList(localRealm, serviceRealm); 188 189 int i = 0, k = 0; 190 Credentials cTgt = null, newTgt = null, theTgt = null; 191 PrincipalName tempService = null; 192 String newTgtRealm = null; 193 194 okAsDelegate[0] = true; 195 for (cTgt = ccreds, i = 0; i < realms.length;) { 196 tempService = PrincipalName.tgsService(serviceRealm, realms[i]); 197 198 if (DEBUG) { 199 System.out.println( 200 ">>> Credentials acquireServiceCreds: main loop: [" 201 + i +"] tempService=" + tempService); 202 } 203 204 try { 205 newTgt = serviceCreds(tempService, cTgt); 206 } catch (Exception exc) { 207 newTgt = null; 208 } 209 210 if (newTgt == null) { 211 if (DEBUG) { 212 System.out.println(">>> Credentials acquireServiceCreds: " 213 + "no tgt; searching thru capath"); 214 } 215 216 /* 217 * No tgt found. Let's go thru the realms list one by one. 218 */ 219 for (newTgt = null, k = i+1; 220 newTgt == null && k < realms.length; k++) { 221 tempService = PrincipalName.tgsService(realms[k], realms[i]); 222 if (DEBUG) { 223 System.out.println( 224 ">>> Credentials acquireServiceCreds: " 225 + "inner loop: [" + k 226 + "] tempService=" + tempService); 227 } 228 try { 229 newTgt = serviceCreds(tempService, cTgt); 230 } catch (Exception exc) { 231 newTgt = null; 232 } 233 } 234 } // Ends 'if (newTgt == null)' 235 236 if (newTgt == null) { 237 if (DEBUG) { 238 System.out.println(">>> Credentials acquireServiceCreds: " 239 + "no tgt; cannot get creds"); 240 } 241 break; 242 } 243 244 /* 245 * We have a tgt. It may or may not be for the target. 246 * If it's for the target realm, we're done looking for a tgt. 247 */ 248 newTgtRealm = newTgt.getServer().getInstanceComponent(); 249 if (okAsDelegate[0] && !newTgt.checkDelegate()) { 250 if (DEBUG) { 251 System.out.println(">>> Credentials acquireServiceCreds: " + 252 "global OK-AS-DELEGATE turned off at " + 253 newTgt.getServer()); 254 } 255 okAsDelegate[0] = false; 256 } 257 258 if (DEBUG) { 259 System.out.println(">>> Credentials acquireServiceCreds: " 260 + "got tgt"); 261 } 262 263 if (newTgtRealm.equals(serviceRealm)) { 264 /* We got the right tgt */ 265 theTgt = newTgt; 266 break; 267 } 268 269 /* 270 * The new tgt is not for the target realm. 271 * See if the realm of the new tgt is in the list of realms 272 * and continue looking from there. 273 */ 274 for (k = i+1; k < realms.length; k++) { 275 if (newTgtRealm.equals(realms[k])) { 276 break; 277 } 278 } 279 280 if (k < realms.length) { 281 /* 282 * (re)set the counter so we start looking 283 * from the realm we just obtained a tgt for. 284 */ 285 i = k; 286 cTgt = newTgt; 287 288 if (DEBUG) { 289 System.out.println(">>> Credentials acquireServiceCreds: " 290 + "continuing with main loop counter reset to " + i); 291 } 292 continue; 293 } 294 else { 295 /* 296 * The new tgt's realm is not in the hierarchy of realms. 297 * It's probably not safe to get a tgt from 298 * a tgs that is outside the known list of realms. 299 * Give up now. 300 */ 301 break; 302 } 303 } // Ends outermost/main 'for' loop 304 305 return theTgt; 306 } 307 308 /* 309 * This method does the real job to request the service credential. 310 */ serviceCreds( PrincipalName service, Credentials ccreds)311 private static Credentials serviceCreds( 312 PrincipalName service, Credentials ccreds) 313 throws KrbException, IOException { 314 return serviceCreds(new KDCOptions(), ccreds, 315 ccreds.getClient(), service, null, null, 316 null, S4U2Type.NONE); 317 } 318 319 /* 320 * Obtains credentials for a service (TGS). 321 * Cross-realm referrals are handled if enabled. A fallback scheme 322 * without cross-realm referrals supports is used in case of server 323 * error to maintain backward compatibility. 324 */ serviceCreds( KDCOptions options, Credentials asCreds, PrincipalName cname, PrincipalName sname, PrincipalName user, Ticket[] additionalTickets, PAData[] extraPAs, S4U2Type s4u2Type)325 private static Credentials serviceCreds( 326 KDCOptions options, Credentials asCreds, 327 PrincipalName cname, PrincipalName sname, 328 PrincipalName user, Ticket[] additionalTickets, 329 PAData[] extraPAs, S4U2Type s4u2Type) 330 throws KrbException, IOException { 331 if (!Config.DISABLE_REFERRALS) { 332 try { 333 return serviceCredsReferrals(options, asCreds, cname, sname, 334 s4u2Type, user, additionalTickets, extraPAs); 335 } catch (KrbException e) { 336 // Server may raise an error if CANONICALIZE is true. 337 // Try CANONICALIZE false. 338 } 339 } 340 return serviceCredsSingle(options, asCreds, cname, 341 asCreds.getClientAlias(), sname, sname, s4u2Type, 342 user, additionalTickets, extraPAs); 343 } 344 345 /* 346 * Obtains credentials for a service (TGS). 347 * May handle and follow cross-realm referrals as defined by RFC 6806. 348 */ serviceCredsReferrals( KDCOptions options, Credentials asCreds, PrincipalName cname, PrincipalName sname, S4U2Type s4u2Type, PrincipalName user, Ticket[] additionalTickets, PAData[] extraPAs)349 private static Credentials serviceCredsReferrals( 350 KDCOptions options, Credentials asCreds, 351 PrincipalName cname, PrincipalName sname, 352 S4U2Type s4u2Type, PrincipalName user, 353 Ticket[] additionalTickets, PAData[] extraPAs) 354 throws KrbException, IOException { 355 options = new KDCOptions(options.toBooleanArray()); 356 options.set(KDCOptions.CANONICALIZE, true); 357 PrincipalName cSname = sname; 358 PrincipalName refSname = sname; // May change with referrals 359 Credentials creds = null; 360 boolean isReferral = false; 361 List<String> referrals = new LinkedList<>(); 362 PrincipalName clientAlias = asCreds.getClientAlias(); 363 while (referrals.size() <= Config.MAX_REFERRALS) { 364 ReferralsCache.ReferralCacheEntry ref = 365 ReferralsCache.get(cname, sname, user, 366 additionalTickets, refSname.getRealmString()); 367 String toRealm = null; 368 if (ref == null) { 369 creds = serviceCredsSingle(options, asCreds, cname, 370 clientAlias, refSname, cSname, s4u2Type, 371 user, additionalTickets, extraPAs); 372 PrincipalName server = creds.getServer(); 373 if (!refSname.equals(server)) { 374 String[] serverNameStrings = server.getNameStrings(); 375 if (serverNameStrings.length == 2 && 376 serverNameStrings[0].equals( 377 PrincipalName.TGS_DEFAULT_SRV_NAME) && 378 !refSname.getRealmAsString().equals( 379 serverNameStrings[1])) { 380 // Server Name (sname) has the following format: 381 // krbtgt/TO-REALM.COM@FROM-REALM.COM 382 ReferralsCache.put(cname, sname, user, 383 additionalTickets, server.getRealmString(), 384 serverNameStrings[1], creds); 385 toRealm = serverNameStrings[1]; 386 isReferral = true; 387 } 388 } 389 } else { 390 creds = ref.getCreds(); 391 toRealm = ref.getToRealm(); 392 isReferral = true; 393 } 394 if (isReferral) { 395 if (s4u2Type == S4U2Type.PROXY) { 396 Credentials[] credsInOut = 397 new Credentials[] {creds, null}; 398 toRealm = handleS4U2ProxyReferral(asCreds, 399 credsInOut, sname); 400 creds = credsInOut[0]; 401 if (additionalTickets == null || 402 additionalTickets.length == 0 || 403 credsInOut[1] == null) { 404 throw new KrbException("Additional tickets expected" + 405 " for S4U2Proxy."); 406 } 407 additionalTickets[0] = credsInOut[1].getTicket(); 408 } else if (s4u2Type == S4U2Type.SELF) { 409 handleS4U2SelfReferral(extraPAs, user, creds); 410 } 411 if (referrals.contains(toRealm)) { 412 // Referrals loop detected 413 return null; 414 } 415 asCreds = creds; 416 refSname = new PrincipalName(refSname.getNameString(), 417 refSname.getNameType(), toRealm); 418 referrals.add(toRealm); 419 isReferral = false; 420 continue; 421 } 422 break; 423 } 424 return creds; 425 } 426 427 /* 428 * Obtains credentials for a service (TGS). 429 * If the service realm is different than the one in the TGT, a new TGT for 430 * the service realm is obtained first (see getTGTforRealm call). This is 431 * not expected when following cross-realm referrals because the referral 432 * TGT realm matches the service realm. 433 */ serviceCredsSingle( KDCOptions options, Credentials asCreds, PrincipalName cname, PrincipalName clientAlias, PrincipalName refSname, PrincipalName sname, S4U2Type s4u2Type, PrincipalName user, Ticket[] additionalTickets, PAData[] extraPAs)434 private static Credentials serviceCredsSingle( 435 KDCOptions options, Credentials asCreds, 436 PrincipalName cname, PrincipalName clientAlias, 437 PrincipalName refSname, PrincipalName sname, 438 S4U2Type s4u2Type, PrincipalName user, 439 Ticket[] additionalTickets, PAData[] extraPAs) 440 throws KrbException, IOException { 441 Credentials theCreds = null; 442 boolean[] okAsDelegate = new boolean[]{true}; 443 String[] serverAsCredsNames = asCreds.getServer().getNameStrings(); 444 String tgtRealm = serverAsCredsNames[1]; 445 String serviceRealm = refSname.getRealmString(); 446 if (!serviceRealm.equals(tgtRealm)) { 447 // This is a cross-realm service request 448 if (DEBUG) { 449 System.out.println(">>> serviceCredsSingle:" + 450 " cross-realm authentication"); 451 System.out.println(">>> serviceCredsSingle:" + 452 " obtaining credentials from " + tgtRealm + 453 " to " + serviceRealm); 454 } 455 Credentials newTgt = getTGTforRealm(tgtRealm, serviceRealm, 456 asCreds, okAsDelegate); 457 if (newTgt == null) { 458 throw new KrbApErrException(Krb5.KRB_AP_ERR_GEN_CRED, 459 "No service creds"); 460 } 461 if (DEBUG) { 462 System.out.println(">>> Cross-realm TGT Credentials" + 463 " serviceCredsSingle: "); 464 Credentials.printDebug(newTgt); 465 } 466 if (s4u2Type == S4U2Type.SELF) { 467 handleS4U2SelfReferral(extraPAs, user, newTgt); 468 } 469 asCreds = newTgt; 470 cname = asCreds.getClient(); 471 } else if (DEBUG) { 472 System.out.println(">>> Credentials serviceCredsSingle:" + 473 " same realm"); 474 } 475 KrbTgsReq req = new KrbTgsReq(options, asCreds, cname, clientAlias, 476 refSname, sname, additionalTickets, extraPAs); 477 theCreds = req.sendAndGetCreds(); 478 if (theCreds != null) { 479 if (DEBUG) { 480 System.out.println(">>> TGS credentials serviceCredsSingle:"); 481 Credentials.printDebug(theCreds); 482 } 483 if (!okAsDelegate[0]) { 484 theCreds.resetDelegate(); 485 } 486 } 487 return theCreds; 488 } 489 490 /** 491 * PA-FOR-USER may need to be regenerated if credentials 492 * change. This may happen when obtaining a TGT for a 493 * different realm or when using a referral TGT. 494 */ handleS4U2SelfReferral(PAData[] pas, PrincipalName user, Credentials newCreds)495 private static void handleS4U2SelfReferral(PAData[] pas, 496 PrincipalName user, Credentials newCreds) 497 throws Asn1Exception, KrbException, IOException { 498 if (DEBUG) { 499 System.out.println(">>> Handling S4U2Self referral"); 500 } 501 for (int i = 0; i < pas.length; i++) { 502 PAData pa = pas[i]; 503 if (pa.getType() == Krb5.PA_FOR_USER) { 504 pas[i] = new PAData(Krb5.PA_FOR_USER, 505 new PAForUserEnc(user, 506 newCreds.getSessionKey()).asn1Encode()); 507 break; 508 } 509 } 510 } 511 512 /** 513 * This method is called after receiving the first realm referral for 514 * a S4U2Proxy request. The credentials and tickets needed for the 515 * final S4U2Proxy request (in the referrals chain) are returned. 516 * 517 * Referrals are handled as described by MS-SFU (section 3.1.5.2.2 518 * Receives Referral). 519 * 520 * @param asCreds middle service credentials used for the first S4U2Proxy 521 * request 522 * @param credsInOut (in/out parameter): 523 * * input: first S4U2Proxy referral TGT received, null 524 * * output: referral TGT for final S4U2Proxy service request, 525 * client referral TGT for final S4U2Proxy service request 526 * (to be sent as additional-ticket) 527 * @param sname the backend service name 528 * @param additionalTickets (out parameter): the additional ticket for the 529 * last S4U2Proxy request is returned 530 * @return the backend realm for the last S4U2Proxy request 531 */ handleS4U2ProxyReferral(Credentials asCreds, Credentials[] credsInOut, PrincipalName sname)532 private static String handleS4U2ProxyReferral(Credentials asCreds, 533 Credentials[] credsInOut, PrincipalName sname) 534 throws KrbException, IOException { 535 if (DEBUG) { 536 System.out.println(">>> Handling S4U2Proxy referral"); 537 } 538 Credentials refTGT = null; 539 // Get a credential for the middle service to the backend so we know 540 // the backend realm, as described in MS-SFU (section 3.1.5.2.2). 541 Credentials middleSvcCredsInBackendRealm = 542 serviceCreds(sname, asCreds); 543 String backendRealm = 544 middleSvcCredsInBackendRealm.getServer().getRealmString(); 545 String toRealm = credsInOut[0].getServer().getNameStrings()[1]; 546 if (!toRealm.equals(backendRealm)) { 547 // More than 1 hop. Follow the referrals chain and obtain a 548 // TGT for the backend realm. 549 refTGT = getTGTforRealm(toRealm, backendRealm, credsInOut[0], 550 new boolean[1]); 551 } else { 552 // There was only 1 hop. The referral TGT received is already 553 // for the backend realm. 554 refTGT = credsInOut[0]; 555 } 556 credsInOut[0] = getTGTforRealm(asCreds.getClient().getRealmString(), 557 backendRealm, asCreds, new boolean[1]); 558 credsInOut[1] = refTGT; 559 return backendRealm; 560 } 561 } 562