Path: blob/master/src/java.security.jgss/share/classes/sun/security/krb5/internal/CredentialsUtil.java
67760 views
/*1* Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26*27* (C) Copyright IBM Corp. 1999 All Rights Reserved.28* Copyright 1997 The Open Group Research Institute. All rights reserved.29*/3031package sun.security.krb5.internal;3233import sun.security.krb5.*;34import sun.security.util.DerValue;3536import java.io.IOException;37import java.util.LinkedList;38import java.util.List;3940/**41* This class is a utility that contains much of the TGS-Exchange42* protocol. It is used by ../Credentials.java for service ticket43* acquisition in both the normal and the x-realm case.44*/45public class CredentialsUtil {4647private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG;4849private static enum S4U2Type {50NONE, SELF, PROXY51}5253/**54* Used by a middle server to acquire credentials on behalf of a55* user to itself using the S4U2self extension.56* @param user the user to impersonate57* @param ccreds the TGT of the middle service58* @return the new creds (cname=user, sname=middle)59*/60public static Credentials acquireS4U2selfCreds(PrincipalName user,61Credentials ccreds) throws KrbException, IOException {62if (!ccreds.isForwardable()) {63throw new KrbException("S4U2self needs a FORWARDABLE ticket");64}65PrincipalName sname = ccreds.getClient();66String uRealm = user.getRealmString();67String localRealm = ccreds.getClient().getRealmString();68if (!uRealm.equals(localRealm)) {69// Referrals will be required because the middle service70// and the user impersonated are on different realms.71if (Config.DISABLE_REFERRALS) {72throw new KrbException("Cross-realm S4U2Self request not" +73" possible when referrals are disabled.");74}75if (ccreds.getClientAlias() != null) {76// If the name was canonicalized, the user pick77// has preference. This gives the possibility of78// using FQDNs that KDCs may use to return referrals.79// I.e.: a SVC/[email protected] name80// may be used by REALM-1.COM KDC to return a81// referral to REALM-2.COM.82sname = ccreds.getClientAlias();83}84sname = new PrincipalName(sname.getNameType(),85sname.getNameStrings(), new Realm(uRealm));86}87Credentials creds = serviceCreds(88KDCOptions.with(KDCOptions.FORWARDABLE),89ccreds, ccreds.getClient(), sname, user,90null, new PAData[] {91new PAData(Krb5.PA_FOR_USER,92new PAForUserEnc(user,93ccreds.getSessionKey()).asn1Encode()),94new PAData(Krb5.PA_PAC_OPTIONS,95new PaPacOptions()96.setResourceBasedConstrainedDelegation(true)97.setClaims(true)98.asn1Encode())99}, S4U2Type.SELF);100if (!creds.getClient().equals(user)) {101throw new KrbException("S4U2self request not honored by KDC");102}103if (!creds.isForwardable()) {104throw new KrbException("S4U2self ticket must be FORWARDABLE");105}106return creds;107}108109/**110* Used by a middle server to acquire a service ticket to a backend111* server using the S4U2proxy extension.112* @param backend the name of the backend service113* @param second the client's service ticket to the middle server114* @param ccreds the TGT of the middle server115* @return the creds (cname=client, sname=backend)116*/117public static Credentials acquireS4U2proxyCreds(118String backend, Ticket second,119PrincipalName client, Credentials ccreds)120throws KrbException, IOException {121PrincipalName backendPrincipal = new PrincipalName(backend);122String backendRealm = backendPrincipal.getRealmString();123String localRealm = ccreds.getClient().getRealmString();124if (!backendRealm.equals(localRealm)) {125// The middle service and the backend service are on126// different realms, so referrals will be required.127if (Config.DISABLE_REFERRALS) {128throw new KrbException("Cross-realm S4U2Proxy request not" +129" possible when referrals are disabled.");130}131backendPrincipal = new PrincipalName(132backendPrincipal.getNameType(),133backendPrincipal.getNameStrings(),134new Realm(localRealm));135}136Credentials creds = serviceCreds(KDCOptions.with(137KDCOptions.CNAME_IN_ADDL_TKT, KDCOptions.FORWARDABLE),138ccreds, ccreds.getClient(), backendPrincipal, null,139new Ticket[] {second}, new PAData[] {140new PAData(Krb5.PA_PAC_OPTIONS,141new PaPacOptions()142.setResourceBasedConstrainedDelegation(true)143.setClaims(true)144.asn1Encode())145}, S4U2Type.PROXY);146if (!creds.getClient().equals(client)) {147throw new KrbException("S4U2proxy request not honored by KDC");148}149return creds;150}151152/**153* Acquires credentials for a specified service using initial154* credential. When the service has a different realm from the initial155* credential, we do cross-realm authentication - first, we use the156* current credential to get a cross-realm credential from the local KDC,157* then use that cross-realm credential to request service credential158* from the foreign KDC.159*160* @param service the name of service principal161* @param ccreds client's initial credential162*/163public static Credentials acquireServiceCreds(164String service, Credentials ccreds)165throws KrbException, IOException {166PrincipalName sname = new PrincipalName(service,167PrincipalName.KRB_NT_UNKNOWN);168return serviceCreds(sname, ccreds);169}170171/**172* Gets a TGT to another realm173* @param localRealm this realm174* @param serviceRealm the other realm, cannot equals to localRealm175* @param ccreds TGT in this realm176* @param okAsDelegate an [out] argument to receive the okAsDelegate177* property. True only if all realms allow delegation.178* @return the TGT for the other realm, null if cannot find a path179* @throws KrbException if something goes wrong180*/181private static Credentials getTGTforRealm(String localRealm,182String serviceRealm, Credentials ccreds, boolean[] okAsDelegate)183throws KrbException {184185// Get a list of realms to traverse186String[] realms = Realm.getRealmsList(localRealm, serviceRealm);187188int i = 0, k = 0;189Credentials cTgt = null, newTgt = null, theTgt = null;190PrincipalName tempService = null;191String newTgtRealm = null;192193okAsDelegate[0] = true;194for (cTgt = ccreds, i = 0; i < realms.length;) {195tempService = PrincipalName.tgsService(serviceRealm, realms[i]);196197if (DEBUG) {198System.out.println(199">>> Credentials acquireServiceCreds: main loop: ["200+ i +"] tempService=" + tempService);201}202203try {204newTgt = serviceCreds(tempService, cTgt);205} catch (Exception exc) {206newTgt = null;207}208209if (newTgt == null) {210if (DEBUG) {211System.out.println(">>> Credentials acquireServiceCreds: "212+ "no tgt; searching thru capath");213}214215/*216* No tgt found. Let's go thru the realms list one by one.217*/218for (newTgt = null, k = i+1;219newTgt == null && k < realms.length; k++) {220tempService = PrincipalName.tgsService(realms[k], realms[i]);221if (DEBUG) {222System.out.println(223">>> Credentials acquireServiceCreds: "224+ "inner loop: [" + k225+ "] tempService=" + tempService);226}227try {228newTgt = serviceCreds(tempService, cTgt);229} catch (Exception exc) {230newTgt = null;231}232}233} // Ends 'if (newTgt == null)'234235if (newTgt == null) {236if (DEBUG) {237System.out.println(">>> Credentials acquireServiceCreds: "238+ "no tgt; cannot get creds");239}240break;241}242243/*244* We have a tgt. It may or may not be for the target.245* If it's for the target realm, we're done looking for a tgt.246*/247newTgtRealm = newTgt.getServer().getInstanceComponent();248if (okAsDelegate[0] && !newTgt.checkDelegate()) {249if (DEBUG) {250System.out.println(">>> Credentials acquireServiceCreds: " +251"global OK-AS-DELEGATE turned off at " +252newTgt.getServer());253}254okAsDelegate[0] = false;255}256257if (DEBUG) {258System.out.println(">>> Credentials acquireServiceCreds: "259+ "got tgt");260}261262if (newTgtRealm.equals(serviceRealm)) {263/* We got the right tgt */264theTgt = newTgt;265break;266}267268/*269* The new tgt is not for the target realm.270* See if the realm of the new tgt is in the list of realms271* and continue looking from there.272*/273for (k = i+1; k < realms.length; k++) {274if (newTgtRealm.equals(realms[k])) {275break;276}277}278279if (k < realms.length) {280/*281* (re)set the counter so we start looking282* from the realm we just obtained a tgt for.283*/284i = k;285cTgt = newTgt;286287if (DEBUG) {288System.out.println(">>> Credentials acquireServiceCreds: "289+ "continuing with main loop counter reset to " + i);290}291continue;292}293else {294/*295* The new tgt's realm is not in the hierarchy of realms.296* It's probably not safe to get a tgt from297* a tgs that is outside the known list of realms.298* Give up now.299*/300break;301}302} // Ends outermost/main 'for' loop303304return theTgt;305}306307/*308* This method does the real job to request the service credential.309*/310private static Credentials serviceCreds(311PrincipalName service, Credentials ccreds)312throws KrbException, IOException {313return serviceCreds(new KDCOptions(), ccreds,314ccreds.getClient(), service, null, null,315null, S4U2Type.NONE);316}317318/*319* Obtains credentials for a service (TGS).320* Cross-realm referrals are handled if enabled. A fallback scheme321* without cross-realm referrals supports is used in case of server322* error to maintain backward compatibility.323*/324private static Credentials serviceCreds(325KDCOptions options, Credentials asCreds,326PrincipalName cname, PrincipalName sname,327PrincipalName user, Ticket[] additionalTickets,328PAData[] extraPAs, S4U2Type s4u2Type)329throws KrbException, IOException {330if (!Config.DISABLE_REFERRALS) {331try {332return serviceCredsReferrals(options, asCreds, cname, sname,333s4u2Type, user, additionalTickets, extraPAs);334} catch (KrbException e) {335// Server may raise an error if CANONICALIZE is true.336// Try CANONICALIZE false.337}338}339return serviceCredsSingle(options, asCreds, cname,340asCreds.getClientAlias(), sname, sname, s4u2Type,341user, additionalTickets, extraPAs);342}343344/*345* Obtains credentials for a service (TGS).346* May handle and follow cross-realm referrals as defined by RFC 6806.347*/348private static Credentials serviceCredsReferrals(349KDCOptions options, Credentials asCreds,350PrincipalName cname, PrincipalName sname,351S4U2Type s4u2Type, PrincipalName user,352Ticket[] additionalTickets, PAData[] extraPAs)353throws KrbException, IOException {354options = new KDCOptions(options.toBooleanArray());355options.set(KDCOptions.CANONICALIZE, true);356PrincipalName cSname = sname;357PrincipalName refSname = sname; // May change with referrals358Credentials creds = null;359boolean isReferral = false;360List<String> referrals = new LinkedList<>();361PrincipalName clientAlias = asCreds.getClientAlias();362while (referrals.size() <= Config.MAX_REFERRALS) {363ReferralsCache.ReferralCacheEntry ref =364ReferralsCache.get(cname, sname, user,365additionalTickets, refSname.getRealmString());366String toRealm = null;367if (ref == null) {368creds = serviceCredsSingle(options, asCreds, cname,369clientAlias, refSname, cSname, s4u2Type,370user, additionalTickets, extraPAs);371PrincipalName server = creds.getServer();372if (!refSname.equals(server)) {373String[] serverNameStrings = server.getNameStrings();374if (serverNameStrings.length == 2 &&375serverNameStrings[0].equals(376PrincipalName.TGS_DEFAULT_SRV_NAME) &&377!refSname.getRealmAsString().equals(378serverNameStrings[1])) {379// Server Name (sname) has the following format:380// krbtgt/[email protected]381ReferralsCache.put(cname, sname, user,382additionalTickets, server.getRealmString(),383serverNameStrings[1], creds);384toRealm = serverNameStrings[1];385isReferral = true;386}387}388} else {389creds = ref.getCreds();390toRealm = ref.getToRealm();391isReferral = true;392}393if (isReferral) {394if (s4u2Type == S4U2Type.PROXY) {395Credentials[] credsInOut =396new Credentials[] {creds, null};397toRealm = handleS4U2ProxyReferral(asCreds,398credsInOut, sname);399creds = credsInOut[0];400if (additionalTickets == null ||401additionalTickets.length == 0 ||402credsInOut[1] == null) {403throw new KrbException("Additional tickets expected" +404" for S4U2Proxy.");405}406additionalTickets[0] = credsInOut[1].getTicket();407} else if (s4u2Type == S4U2Type.SELF) {408handleS4U2SelfReferral(extraPAs, user, creds);409}410if (referrals.contains(toRealm)) {411// Referrals loop detected412return null;413}414asCreds = creds;415refSname = new PrincipalName(refSname.getNameString(),416refSname.getNameType(), toRealm);417referrals.add(toRealm);418isReferral = false;419continue;420}421break;422}423return creds;424}425426/*427* Obtains credentials for a service (TGS).428* If the service realm is different than the one in the TGT, a new TGT for429* the service realm is obtained first (see getTGTforRealm call). This is430* not expected when following cross-realm referrals because the referral431* TGT realm matches the service realm.432*/433private static Credentials serviceCredsSingle(434KDCOptions options, Credentials asCreds,435PrincipalName cname, PrincipalName clientAlias,436PrincipalName refSname, PrincipalName sname,437S4U2Type s4u2Type, PrincipalName user,438Ticket[] additionalTickets, PAData[] extraPAs)439throws KrbException, IOException {440Credentials theCreds = null;441boolean[] okAsDelegate = new boolean[]{true};442String[] serverAsCredsNames = asCreds.getServer().getNameStrings();443String tgtRealm = serverAsCredsNames[1];444String serviceRealm = refSname.getRealmString();445if (!serviceRealm.equals(tgtRealm)) {446// This is a cross-realm service request447if (DEBUG) {448System.out.println(">>> serviceCredsSingle:" +449" cross-realm authentication");450System.out.println(">>> serviceCredsSingle:" +451" obtaining credentials from " + tgtRealm +452" to " + serviceRealm);453}454Credentials newTgt = getTGTforRealm(tgtRealm, serviceRealm,455asCreds, okAsDelegate);456if (newTgt == null) {457throw new KrbApErrException(Krb5.KRB_AP_ERR_GEN_CRED,458"No service creds");459}460if (DEBUG) {461System.out.println(">>> Cross-realm TGT Credentials" +462" serviceCredsSingle: ");463Credentials.printDebug(newTgt);464}465if (s4u2Type == S4U2Type.SELF) {466handleS4U2SelfReferral(extraPAs, user, newTgt);467}468asCreds = newTgt;469cname = asCreds.getClient();470} else if (DEBUG) {471System.out.println(">>> Credentials serviceCredsSingle:" +472" same realm");473}474KrbTgsReq req = new KrbTgsReq(options, asCreds, cname, clientAlias,475refSname, sname, additionalTickets, extraPAs);476theCreds = req.sendAndGetCreds();477if (theCreds != null) {478if (DEBUG) {479System.out.println(">>> TGS credentials serviceCredsSingle:");480Credentials.printDebug(theCreds);481}482if (!okAsDelegate[0]) {483theCreds.resetDelegate();484}485}486return theCreds;487}488489/**490* PA-FOR-USER may need to be regenerated if credentials491* change. This may happen when obtaining a TGT for a492* different realm or when using a referral TGT.493*/494private static void handleS4U2SelfReferral(PAData[] pas,495PrincipalName user, Credentials newCreds)496throws Asn1Exception, KrbException, IOException {497if (DEBUG) {498System.out.println(">>> Handling S4U2Self referral");499}500for (int i = 0; i < pas.length; i++) {501PAData pa = pas[i];502if (pa.getType() == Krb5.PA_FOR_USER) {503pas[i] = new PAData(Krb5.PA_FOR_USER,504new PAForUserEnc(user,505newCreds.getSessionKey()).asn1Encode());506break;507}508}509}510511/**512* This method is called after receiving the first realm referral for513* a S4U2Proxy request. The credentials and tickets needed for the514* final S4U2Proxy request (in the referrals chain) are returned.515*516* Referrals are handled as described by MS-SFU (section 3.1.5.2.2517* Receives Referral).518*519* @param asCreds middle service credentials used for the first S4U2Proxy520* request521* @param credsInOut (in/out parameter):522* * input: first S4U2Proxy referral TGT received, null523* * output: referral TGT for final S4U2Proxy service request,524* client referral TGT for final S4U2Proxy service request525* (to be sent as additional-ticket)526* @param sname the backend service name527* @param additionalTickets (out parameter): the additional ticket for the528* last S4U2Proxy request is returned529* @return the backend realm for the last S4U2Proxy request530*/531private static String handleS4U2ProxyReferral(Credentials asCreds,532Credentials[] credsInOut, PrincipalName sname)533throws KrbException, IOException {534if (DEBUG) {535System.out.println(">>> Handling S4U2Proxy referral");536}537Credentials refTGT = null;538// Get a credential for the middle service to the backend so we know539// the backend realm, as described in MS-SFU (section 3.1.5.2.2).540Credentials middleSvcCredsInBackendRealm =541serviceCreds(sname, asCreds);542String backendRealm =543middleSvcCredsInBackendRealm.getServer().getRealmString();544String toRealm = credsInOut[0].getServer().getNameStrings()[1];545if (!toRealm.equals(backendRealm)) {546// More than 1 hop. Follow the referrals chain and obtain a547// TGT for the backend realm.548refTGT = getTGTforRealm(toRealm, backendRealm, credsInOut[0],549new boolean[1]);550} else {551// There was only 1 hop. The referral TGT received is already552// for the backend realm.553refTGT = credsInOut[0];554}555credsInOut[0] = getTGTforRealm(asCreds.getClient().getRealmString(),556backendRealm, asCreds, new boolean[1]);557credsInOut[1] = refTGT;558return backendRealm;559}560}561562563