8270137: Kerberos Credential Retrieval from Cache not Working in Cross-Realm Setup

Reviewed-by: weijun
This commit is contained in:
Martin Balao 2021-08-10 16:28:10 +00:00
parent 35b399aca8
commit 67869b491a
3 changed files with 88 additions and 50 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -53,22 +53,22 @@ public class CredentialsUtil {
/** /**
* Used by a middle server to acquire credentials on behalf of a * Used by a middle server to acquire credentials on behalf of a
* client to itself using the S4U2self extension. * user to itself using the S4U2self extension.
* @param client the client to impersonate * @param user the user to impersonate
* @param ccreds the TGT of the middle service * @param ccreds the TGT of the middle service
* @return the new creds (cname=client, sname=middle) * @return the new creds (cname=user, sname=middle)
*/ */
public static Credentials acquireS4U2selfCreds(PrincipalName client, public static Credentials acquireS4U2selfCreds(PrincipalName user,
Credentials ccreds) throws KrbException, IOException { Credentials ccreds) throws KrbException, IOException {
if (!ccreds.isForwardable()) { if (!ccreds.isForwardable()) {
throw new KrbException("S4U2self needs a FORWARDABLE ticket"); throw new KrbException("S4U2self needs a FORWARDABLE ticket");
} }
PrincipalName sname = ccreds.getClient(); PrincipalName sname = ccreds.getClient();
String uRealm = client.getRealmString(); String uRealm = user.getRealmString();
String localRealm = ccreds.getClient().getRealmString(); String localRealm = ccreds.getClient().getRealmString();
if (!uRealm.equals(localRealm)) { if (!uRealm.equals(localRealm)) {
// Referrals will be required because the middle service // Referrals will be required because the middle service
// and the client impersonated are on different realms. // and the user impersonated are on different realms.
if (Config.DISABLE_REFERRALS) { if (Config.DISABLE_REFERRALS) {
throw new KrbException("Cross-realm S4U2Self request not" + throw new KrbException("Cross-realm S4U2Self request not" +
" possible when referrals are disabled."); " possible when referrals are disabled.");
@ -87,10 +87,10 @@ public class CredentialsUtil {
} }
Credentials creds = serviceCreds( Credentials creds = serviceCreds(
KDCOptions.with(KDCOptions.FORWARDABLE), KDCOptions.with(KDCOptions.FORWARDABLE),
ccreds, ccreds.getClient(), sname, null, ccreds, ccreds.getClient(), sname, user,
new PAData[] { null, new PAData[] {
new PAData(Krb5.PA_FOR_USER, new PAData(Krb5.PA_FOR_USER,
new PAForUserEnc(client, new PAForUserEnc(user,
ccreds.getSessionKey()).asn1Encode()), ccreds.getSessionKey()).asn1Encode()),
new PAData(Krb5.PA_PAC_OPTIONS, new PAData(Krb5.PA_PAC_OPTIONS,
new PaPacOptions() new PaPacOptions()
@ -98,7 +98,7 @@ public class CredentialsUtil {
.setClaims(true) .setClaims(true)
.asn1Encode()) .asn1Encode())
}, S4U2Type.SELF); }, S4U2Type.SELF);
if (!creds.getClient().equals(client)) { if (!creds.getClient().equals(user)) {
throw new KrbException("S4U2self request not honored by KDC"); throw new KrbException("S4U2self request not honored by KDC");
} }
if (!creds.isForwardable()) { if (!creds.isForwardable()) {
@ -136,7 +136,7 @@ public class CredentialsUtil {
} }
Credentials creds = serviceCreds(KDCOptions.with( Credentials creds = serviceCreds(KDCOptions.with(
KDCOptions.CNAME_IN_ADDL_TKT, KDCOptions.FORWARDABLE), KDCOptions.CNAME_IN_ADDL_TKT, KDCOptions.FORWARDABLE),
ccreds, ccreds.getClient(), backendPrincipal, ccreds, ccreds.getClient(), backendPrincipal, null,
new Ticket[] {second}, new PAData[] { new Ticket[] {second}, new PAData[] {
new PAData(Krb5.PA_PAC_OPTIONS, new PAData(Krb5.PA_PAC_OPTIONS,
new PaPacOptions() new PaPacOptions()
@ -313,7 +313,7 @@ public class CredentialsUtil {
throws KrbException, IOException { throws KrbException, IOException {
return serviceCreds(new KDCOptions(), ccreds, return serviceCreds(new KDCOptions(), ccreds,
ccreds.getClient(), service, null, null, ccreds.getClient(), service, null, null,
S4U2Type.NONE); null, S4U2Type.NONE);
} }
/* /*
@ -325,13 +325,13 @@ public class CredentialsUtil {
private static Credentials serviceCreds( private static Credentials serviceCreds(
KDCOptions options, Credentials asCreds, KDCOptions options, Credentials asCreds,
PrincipalName cname, PrincipalName sname, PrincipalName cname, PrincipalName sname,
Ticket[] additionalTickets, PAData[] extraPAs, PrincipalName user, Ticket[] additionalTickets,
S4U2Type s4u2Type) PAData[] extraPAs, S4U2Type s4u2Type)
throws KrbException, IOException { throws KrbException, IOException {
if (!Config.DISABLE_REFERRALS) { if (!Config.DISABLE_REFERRALS) {
try { try {
return serviceCredsReferrals(options, asCreds, cname, sname, return serviceCredsReferrals(options, asCreds, cname, sname,
s4u2Type, additionalTickets, extraPAs); s4u2Type, user, additionalTickets, extraPAs);
} catch (KrbException e) { } catch (KrbException e) {
// Server may raise an error if CANONICALIZE is true. // Server may raise an error if CANONICALIZE is true.
// Try CANONICALIZE false. // Try CANONICALIZE false.
@ -339,7 +339,7 @@ public class CredentialsUtil {
} }
return serviceCredsSingle(options, asCreds, cname, return serviceCredsSingle(options, asCreds, cname,
asCreds.getClientAlias(), sname, sname, s4u2Type, asCreds.getClientAlias(), sname, sname, s4u2Type,
additionalTickets, extraPAs); user, additionalTickets, extraPAs);
} }
/* /*
@ -349,8 +349,8 @@ public class CredentialsUtil {
private static Credentials serviceCredsReferrals( private static Credentials serviceCredsReferrals(
KDCOptions options, Credentials asCreds, KDCOptions options, Credentials asCreds,
PrincipalName cname, PrincipalName sname, PrincipalName cname, PrincipalName sname,
S4U2Type s4u2Type, Ticket[] additionalTickets, S4U2Type s4u2Type, PrincipalName user,
PAData[] extraPAs) Ticket[] additionalTickets, PAData[] extraPAs)
throws KrbException, IOException { throws KrbException, IOException {
options = new KDCOptions(options.toBooleanArray()); options = new KDCOptions(options.toBooleanArray());
options.set(KDCOptions.CANONICALIZE, true); options.set(KDCOptions.CANONICALIZE, true);
@ -362,12 +362,13 @@ public class CredentialsUtil {
PrincipalName clientAlias = asCreds.getClientAlias(); PrincipalName clientAlias = asCreds.getClientAlias();
while (referrals.size() <= Config.MAX_REFERRALS) { while (referrals.size() <= Config.MAX_REFERRALS) {
ReferralsCache.ReferralCacheEntry ref = ReferralsCache.ReferralCacheEntry ref =
ReferralsCache.get(cname, sname, refSname.getRealmString()); ReferralsCache.get(cname, sname, user,
additionalTickets, refSname.getRealmString());
String toRealm = null; String toRealm = null;
if (ref == null) { if (ref == null) {
creds = serviceCredsSingle(options, asCreds, cname, creds = serviceCredsSingle(options, asCreds, cname,
clientAlias, refSname, cSname, s4u2Type, clientAlias, refSname, cSname, s4u2Type,
additionalTickets, extraPAs); user, additionalTickets, extraPAs);
PrincipalName server = creds.getServer(); PrincipalName server = creds.getServer();
if (!refSname.equals(server)) { if (!refSname.equals(server)) {
String[] serverNameStrings = server.getNameStrings(); String[] serverNameStrings = server.getNameStrings();
@ -378,15 +379,9 @@ public class CredentialsUtil {
serverNameStrings[1])) { serverNameStrings[1])) {
// Server Name (sname) has the following format: // Server Name (sname) has the following format:
// krbtgt/TO-REALM.COM@FROM-REALM.COM // krbtgt/TO-REALM.COM@FROM-REALM.COM
if (s4u2Type == S4U2Type.NONE) { ReferralsCache.put(cname, sname, user,
// Do not store S4U2Self or S4U2Proxy referral additionalTickets, server.getRealmString(),
// TGTs in the cache. Caching such tickets is not serverNameStrings[1], creds);
// defined in MS-SFU and may cause unexpected
// results when using them in a different context.
ReferralsCache.put(cname, sname,
server.getRealmString(),
serverNameStrings[1], creds);
}
toRealm = serverNameStrings[1]; toRealm = serverNameStrings[1];
isReferral = true; isReferral = true;
} }
@ -411,7 +406,7 @@ public class CredentialsUtil {
} }
additionalTickets[0] = credsInOut[1].getTicket(); additionalTickets[0] = credsInOut[1].getTicket();
} else if (s4u2Type == S4U2Type.SELF) { } else if (s4u2Type == S4U2Type.SELF) {
handleS4U2SelfReferral(extraPAs, asCreds, creds); handleS4U2SelfReferral(extraPAs, user, creds);
} }
if (referrals.contains(toRealm)) { if (referrals.contains(toRealm)) {
// Referrals loop detected // Referrals loop detected
@ -440,8 +435,8 @@ public class CredentialsUtil {
KDCOptions options, Credentials asCreds, KDCOptions options, Credentials asCreds,
PrincipalName cname, PrincipalName clientAlias, PrincipalName cname, PrincipalName clientAlias,
PrincipalName refSname, PrincipalName sname, PrincipalName refSname, PrincipalName sname,
S4U2Type s4u2Type, Ticket[] additionalTickets, S4U2Type s4u2Type, PrincipalName user,
PAData[] extraPAs) Ticket[] additionalTickets, PAData[] extraPAs)
throws KrbException, IOException { throws KrbException, IOException {
Credentials theCreds = null; Credentials theCreds = null;
boolean[] okAsDelegate = new boolean[]{true}; boolean[] okAsDelegate = new boolean[]{true};
@ -469,7 +464,7 @@ public class CredentialsUtil {
Credentials.printDebug(newTgt); Credentials.printDebug(newTgt);
} }
if (s4u2Type == S4U2Type.SELF) { if (s4u2Type == S4U2Type.SELF) {
handleS4U2SelfReferral(extraPAs, asCreds, newTgt); handleS4U2SelfReferral(extraPAs, user, newTgt);
} }
asCreds = newTgt; asCreds = newTgt;
cname = asCreds.getClient(); cname = asCreds.getClient();
@ -498,7 +493,7 @@ public class CredentialsUtil {
* different realm or when using a referral TGT. * different realm or when using a referral TGT.
*/ */
private static void handleS4U2SelfReferral(PAData[] pas, private static void handleS4U2SelfReferral(PAData[] pas,
Credentials oldCeds, Credentials newCreds) PrincipalName user, Credentials newCreds)
throws Asn1Exception, KrbException, IOException { throws Asn1Exception, KrbException, IOException {
if (DEBUG) { if (DEBUG) {
System.out.println(">>> Handling S4U2Self referral"); System.out.println(">>> Handling S4U2Self referral");
@ -506,11 +501,8 @@ public class CredentialsUtil {
for (int i = 0; i < pas.length; i++) { for (int i = 0; i < pas.length; i++) {
PAData pa = pas[i]; PAData pa = pas[i];
if (pa.getType() == Krb5.PA_FOR_USER) { if (pa.getType() == Krb5.PA_FOR_USER) {
PAForUserEnc paForUser = new PAForUserEnc(
new DerValue(pa.getValue()),
oldCeds.getSessionKey());
pas[i] = new PAData(Krb5.PA_FOR_USER, pas[i] = new PAData(Krb5.PA_FOR_USER,
new PAForUserEnc(paForUser.getName(), new PAForUserEnc(user,
newCreds.getSessionKey()).asn1Encode()); newCreds.getSessionKey()).asn1Encode());
break; break;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019, Red Hat, Inc. * Copyright (c) 2019, 2021, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,12 +25,14 @@
package sun.security.krb5.internal; package sun.security.krb5.internal;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects;
import sun.security.krb5.Credentials; import sun.security.krb5.Credentials;
import sun.security.krb5.PrincipalName; import sun.security.krb5.PrincipalName;
@ -51,19 +53,33 @@ final class ReferralsCache {
private static final class ReferralCacheKey { private static final class ReferralCacheKey {
private PrincipalName cname; private PrincipalName cname;
private PrincipalName sname; private PrincipalName sname;
ReferralCacheKey (PrincipalName cname, PrincipalName sname) { private PrincipalName user; // S4U2Self only
private byte[] userSvcTicketEnc; // S4U2Proxy only
ReferralCacheKey (PrincipalName cname, PrincipalName sname,
PrincipalName user, Ticket userSvcTicket) {
this.cname = cname; this.cname = cname;
this.sname = sname; this.sname = sname;
this.user = user;
if (userSvcTicket != null && userSvcTicket.encPart != null) {
byte[] userSvcTicketEnc = userSvcTicket.encPart.getBytes();
if (userSvcTicketEnc.length > 0) {
this.userSvcTicketEnc = userSvcTicketEnc;
}
}
} }
public boolean equals(Object other) { public boolean equals(Object other) {
if (!(other instanceof ReferralCacheKey)) if (!(other instanceof ReferralCacheKey))
return false; return false;
ReferralCacheKey that = (ReferralCacheKey)other; ReferralCacheKey that = (ReferralCacheKey)other;
return cname.equals(that.cname) && return cname.equals(that.cname) &&
sname.equals(that.sname); sname.equals(that.sname) &&
Objects.equals(user, that.user) &&
Arrays.equals(userSvcTicketEnc, that.userSvcTicketEnc);
} }
public int hashCode() { public int hashCode() {
return cname.hashCode() + sname.hashCode(); return cname.hashCode() + sname.hashCode() +
Objects.hashCode(user) +
Arrays.hashCode(userSvcTicketEnc);
} }
} }
@ -84,7 +100,8 @@ final class ReferralsCache {
/* /*
* Add a new referral entry to the cache, including: client principal, * Add a new referral entry to the cache, including: client principal,
* service principal, source KDC realm, destination KDC realm and * service principal, user principal (S4U2Self only), client service
* ticket (S4U2Proxy only), source KDC realm, destination KDC realm and
* referral TGT. * referral TGT.
* *
* If a loop is generated when adding the new referral, the first hop is * If a loop is generated when adding the new referral, the first hop is
@ -94,8 +111,12 @@ final class ReferralsCache {
* REALM-1.COM -> REALM-2.COM referral entry is removed from the cache. * REALM-1.COM -> REALM-2.COM referral entry is removed from the cache.
*/ */
static synchronized void put(PrincipalName cname, PrincipalName service, static synchronized void put(PrincipalName cname, PrincipalName service,
String fromRealm, String toRealm, Credentials creds) { PrincipalName user, Ticket[] userSvcTickets, String fromRealm,
ReferralCacheKey k = new ReferralCacheKey(cname, service); String toRealm, Credentials creds) {
Ticket userSvcTicket = (userSvcTickets != null ?
userSvcTickets[0] : null);
ReferralCacheKey k = new ReferralCacheKey(cname, service,
user, userSvcTicket);
pruneExpired(k); pruneExpired(k);
if (creds.getEndTime().before(new Date())) { if (creds.getEndTime().before(new Date())) {
return; return;
@ -125,11 +146,16 @@ final class ReferralsCache {
/* /*
* Obtain a referral entry from the cache given a client principal, * Obtain a referral entry from the cache given a client principal,
* service principal and a source KDC realm. * a service principal, a user principal (S4U2Self only), a client
* service ticket (S4U2Proxy only) and a source KDC realm.
*/ */
static synchronized ReferralCacheEntry get(PrincipalName cname, static synchronized ReferralCacheEntry get(PrincipalName cname,
PrincipalName service, String fromRealm) { PrincipalName service, PrincipalName user,
ReferralCacheKey k = new ReferralCacheKey(cname, service); Ticket[] userSvcTickets, String fromRealm) {
Ticket userSvcTicket = (userSvcTickets != null ?
userSvcTickets[0] : null);
ReferralCacheKey k = new ReferralCacheKey(cname, service,
user, userSvcTicket);
pruneExpired(k); pruneExpired(k);
Map<String, ReferralCacheEntry> entries = referralsMap.get(k); Map<String, ReferralCacheEntry> entries = referralsMap.get(k);
if (entries != null) { if (entries != null) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019, 2020, Red Hat, Inc. * Copyright (c) 2019, 2021, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -287,6 +287,16 @@ public class ReferralsTest {
* on different realms. * on different realms.
*/ */
private static void testImpersonation() throws Exception { private static void testImpersonation() throws Exception {
testImpersonationSingle();
// Try a second time to force the use of the Referrals Cache.
// During this execution, the referral ticket from RABBIT.HOLE
// to DEV.RABBIT.HOLE (upon the initial S4U2Self message) will
// be obtained from the Cache.
testImpersonationSingle();
}
private static void testImpersonationSingle() throws Exception {
Context s = Context.fromUserPass(serviceKDC2Name, password, true); Context s = Context.fromUserPass(serviceKDC2Name, password, true);
s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
GSSName impName = s.impersonate(userKDC1Name).cred().getName(); GSSName impName = s.impersonate(userKDC1Name).cred().getName();
@ -306,6 +316,16 @@ public class ReferralsTest {
* because the server and the backend are on different realms. * because the server and the backend are on different realms.
*/ */
private static void testDelegationWithReferrals() throws Exception { private static void testDelegationWithReferrals() throws Exception {
testDelegationWithReferralsSingle();
// Try a second time to force the use of the Referrals Cache.
// During this execution, the referral ticket from RABBIT.HOLE
// to DEV.RABBIT.HOLE (upon the initial S4U2Proxy message) will
// be obtained from the Cache.
testDelegationWithReferralsSingle();
}
private static void testDelegationWithReferralsSingle() throws Exception {
Context c = Context.fromUserPass(userKDC1Name, password, false); Context c = Context.fromUserPass(userKDC1Name, password, false);
c.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID); c.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID);
Context s = Context.fromUserPass(serviceKDC2Name, password, true); Context s = Context.fromUserPass(serviceKDC2Name, password, true);