Path: blob/master/src/java.security.jgss/share/classes/sun/security/krb5/internal/CredentialsUtil.java
41161 views
/*1* Copyright (c) 2001, 2019, 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* client to itself using the S4U2self extension.56* @param client the client to impersonate57* @param ccreds the TGT of the middle service58* @return the new creds (cname=client, sname=middle)59*/60public static Credentials acquireS4U2selfCreds(PrincipalName client,61Credentials ccreds) throws KrbException, IOException {62if (!ccreds.isForwardable()) {63throw new KrbException("S4U2self needs a FORWARDABLE ticket");64}65PrincipalName sname = ccreds.getClient();66String uRealm = client.getRealmString();67String localRealm = ccreds.getClient().getRealmString();68if (!uRealm.equals(localRealm)) {69// Referrals will be required because the middle service70// and the client 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, null,90new PAData[] {91new PAData(Krb5.PA_FOR_USER,92new PAForUserEnc(client,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(client)) {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,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,315S4U2Type.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,327Ticket[] additionalTickets, PAData[] extraPAs,328S4U2Type s4u2Type)329throws KrbException, IOException {330if (!Config.DISABLE_REFERRALS) {331try {332return serviceCredsReferrals(options, asCreds, cname, sname,333s4u2Type, 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,341additionalTickets, 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, Ticket[] additionalTickets,352PAData[] 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, refSname.getRealmString());365String toRealm = null;366if (ref == null) {367creds = serviceCredsSingle(options, asCreds, cname,368clientAlias, refSname, cSname, s4u2Type,369additionalTickets, extraPAs);370PrincipalName server = creds.getServer();371if (!refSname.equals(server)) {372String[] serverNameStrings = server.getNameStrings();373if (serverNameStrings.length == 2 &&374serverNameStrings[0].equals(375PrincipalName.TGS_DEFAULT_SRV_NAME) &&376!refSname.getRealmAsString().equals(377serverNameStrings[1])) {378// Server Name (sname) has the following format:379// krbtgt/[email protected]380if (s4u2Type == S4U2Type.NONE) {381// Do not store S4U2Self or S4U2Proxy referral382// TGTs in the cache. Caching such tickets is not383// defined in MS-SFU and may cause unexpected384// results when using them in a different context.385ReferralsCache.put(cname, sname,386server.getRealmString(),387serverNameStrings[1], creds);388}389toRealm = serverNameStrings[1];390isReferral = true;391}392}393} else {394creds = ref.getCreds();395toRealm = ref.getToRealm();396isReferral = true;397}398if (isReferral) {399if (s4u2Type == S4U2Type.PROXY) {400Credentials[] credsInOut =401new Credentials[] {creds, null};402toRealm = handleS4U2ProxyReferral(asCreds,403credsInOut, sname);404creds = credsInOut[0];405if (additionalTickets == null ||406additionalTickets.length == 0 ||407credsInOut[1] == null) {408throw new KrbException("Additional tickets expected" +409" for S4U2Proxy.");410}411additionalTickets[0] = credsInOut[1].getTicket();412} else if (s4u2Type == S4U2Type.SELF) {413handleS4U2SelfReferral(extraPAs, asCreds, creds);414}415if (referrals.contains(toRealm)) {416// Referrals loop detected417return null;418}419asCreds = creds;420refSname = new PrincipalName(refSname.getNameString(),421refSname.getNameType(), toRealm);422referrals.add(toRealm);423isReferral = false;424continue;425}426break;427}428return creds;429}430431/*432* Obtains credentials for a service (TGS).433* If the service realm is different than the one in the TGT, a new TGT for434* the service realm is obtained first (see getTGTforRealm call). This is435* not expected when following cross-realm referrals because the referral436* TGT realm matches the service realm.437*/438private static Credentials serviceCredsSingle(439KDCOptions options, Credentials asCreds,440PrincipalName cname, PrincipalName clientAlias,441PrincipalName refSname, PrincipalName sname,442S4U2Type s4u2Type, Ticket[] additionalTickets,443PAData[] extraPAs)444throws KrbException, IOException {445Credentials theCreds = null;446boolean[] okAsDelegate = new boolean[]{true};447String[] serverAsCredsNames = asCreds.getServer().getNameStrings();448String tgtRealm = serverAsCredsNames[1];449String serviceRealm = refSname.getRealmString();450if (!serviceRealm.equals(tgtRealm)) {451// This is a cross-realm service request452if (DEBUG) {453System.out.println(">>> serviceCredsSingle:" +454" cross-realm authentication");455System.out.println(">>> serviceCredsSingle:" +456" obtaining credentials from " + tgtRealm +457" to " + serviceRealm);458}459Credentials newTgt = getTGTforRealm(tgtRealm, serviceRealm,460asCreds, okAsDelegate);461if (newTgt == null) {462throw new KrbApErrException(Krb5.KRB_AP_ERR_GEN_CRED,463"No service creds");464}465if (DEBUG) {466System.out.println(">>> Cross-realm TGT Credentials" +467" serviceCredsSingle: ");468Credentials.printDebug(newTgt);469}470if (s4u2Type == S4U2Type.SELF) {471handleS4U2SelfReferral(extraPAs, asCreds, newTgt);472}473asCreds = newTgt;474cname = asCreds.getClient();475} else if (DEBUG) {476System.out.println(">>> Credentials serviceCredsSingle:" +477" same realm");478}479KrbTgsReq req = new KrbTgsReq(options, asCreds, cname, clientAlias,480refSname, sname, additionalTickets, extraPAs);481theCreds = req.sendAndGetCreds();482if (theCreds != null) {483if (DEBUG) {484System.out.println(">>> TGS credentials serviceCredsSingle:");485Credentials.printDebug(theCreds);486}487if (!okAsDelegate[0]) {488theCreds.resetDelegate();489}490}491return theCreds;492}493494/**495* PA-FOR-USER may need to be regenerated if credentials496* change. This may happen when obtaining a TGT for a497* different realm or when using a referral TGT.498*/499private static void handleS4U2SelfReferral(PAData[] pas,500Credentials oldCeds, Credentials newCreds)501throws Asn1Exception, KrbException, IOException {502if (DEBUG) {503System.out.println(">>> Handling S4U2Self referral");504}505for (int i = 0; i < pas.length; i++) {506PAData pa = pas[i];507if (pa.getType() == Krb5.PA_FOR_USER) {508PAForUserEnc paForUser = new PAForUserEnc(509new DerValue(pa.getValue()),510oldCeds.getSessionKey());511pas[i] = new PAData(Krb5.PA_FOR_USER,512new PAForUserEnc(paForUser.getName(),513newCreds.getSessionKey()).asn1Encode());514break;515}516}517}518519/**520* This method is called after receiving the first realm referral for521* a S4U2Proxy request. The credentials and tickets needed for the522* final S4U2Proxy request (in the referrals chain) are returned.523*524* Referrals are handled as described by MS-SFU (section 3.1.5.2.2525* Receives Referral).526*527* @param asCreds middle service credentials used for the first S4U2Proxy528* request529* @param credsInOut (in/out parameter):530* * input: first S4U2Proxy referral TGT received, null531* * output: referral TGT for final S4U2Proxy service request,532* client referral TGT for final S4U2Proxy service request533* (to be sent as additional-ticket)534* @param sname the backend service name535* @param additionalTickets (out parameter): the additional ticket for the536* last S4U2Proxy request is returned537* @return the backend realm for the last S4U2Proxy request538*/539private static String handleS4U2ProxyReferral(Credentials asCreds,540Credentials[] credsInOut, PrincipalName sname)541throws KrbException, IOException {542if (DEBUG) {543System.out.println(">>> Handling S4U2Proxy referral");544}545Credentials refTGT = null;546// Get a credential for the middle service to the backend so we know547// the backend realm, as described in MS-SFU (section 3.1.5.2.2).548Credentials middleSvcCredsInBackendRealm =549serviceCreds(sname, asCreds);550String backendRealm =551middleSvcCredsInBackendRealm.getServer().getRealmString();552String toRealm = credsInOut[0].getServer().getNameStrings()[1];553if (!toRealm.equals(backendRealm)) {554// More than 1 hop. Follow the referrals chain and obtain a555// TGT for the backend realm.556refTGT = getTGTforRealm(toRealm, backendRealm, credsInOut[0],557new boolean[1]);558} else {559// There was only 1 hop. The referral TGT received is already560// for the backend realm.561refTGT = credsInOut[0];562}563credsInOut[0] = getTGTforRealm(asCreds.getClient().getRealmString(),564backendRealm, asCreds, new boolean[1]);565credsInOut[1] = refTGT;566return backendRealm;567}568}569570571