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.
*
* 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
* client to itself using the S4U2self extension.
* @param client the client to impersonate
* user to itself using the S4U2self extension.
* @param user the user to impersonate
* @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 {
if (!ccreds.isForwardable()) {
throw new KrbException("S4U2self needs a FORWARDABLE ticket");
}
PrincipalName sname = ccreds.getClient();
String uRealm = client.getRealmString();
String uRealm = user.getRealmString();
String localRealm = ccreds.getClient().getRealmString();
if (!uRealm.equals(localRealm)) {
// 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) {
throw new KrbException("Cross-realm S4U2Self request not" +
" possible when referrals are disabled.");
@ -87,10 +87,10 @@ public class CredentialsUtil {
}
Credentials creds = serviceCreds(
KDCOptions.with(KDCOptions.FORWARDABLE),
ccreds, ccreds.getClient(), sname, null,
new PAData[] {
ccreds, ccreds.getClient(), sname, user,
null, new PAData[] {
new PAData(Krb5.PA_FOR_USER,
new PAForUserEnc(client,
new PAForUserEnc(user,
ccreds.getSessionKey()).asn1Encode()),
new PAData(Krb5.PA_PAC_OPTIONS,
new PaPacOptions()
@ -98,7 +98,7 @@ public class CredentialsUtil {
.setClaims(true)
.asn1Encode())
}, S4U2Type.SELF);
if (!creds.getClient().equals(client)) {
if (!creds.getClient().equals(user)) {
throw new KrbException("S4U2self request not honored by KDC");
}
if (!creds.isForwardable()) {
@ -136,7 +136,7 @@ public class CredentialsUtil {
}
Credentials creds = serviceCreds(KDCOptions.with(
KDCOptions.CNAME_IN_ADDL_TKT, KDCOptions.FORWARDABLE),
ccreds, ccreds.getClient(), backendPrincipal,
ccreds, ccreds.getClient(), backendPrincipal, null,
new Ticket[] {second}, new PAData[] {
new PAData(Krb5.PA_PAC_OPTIONS,
new PaPacOptions()
@ -313,7 +313,7 @@ public class CredentialsUtil {
throws KrbException, IOException {
return serviceCreds(new KDCOptions(), ccreds,
ccreds.getClient(), service, null, null,
S4U2Type.NONE);
null, S4U2Type.NONE);
}
/*
@ -325,13 +325,13 @@ public class CredentialsUtil {
private static Credentials serviceCreds(
KDCOptions options, Credentials asCreds,
PrincipalName cname, PrincipalName sname,
Ticket[] additionalTickets, PAData[] extraPAs,
S4U2Type s4u2Type)
PrincipalName user, Ticket[] additionalTickets,
PAData[] extraPAs, S4U2Type s4u2Type)
throws KrbException, IOException {
if (!Config.DISABLE_REFERRALS) {
try {
return serviceCredsReferrals(options, asCreds, cname, sname,
s4u2Type, additionalTickets, extraPAs);
s4u2Type, user, additionalTickets, extraPAs);
} catch (KrbException e) {
// Server may raise an error if CANONICALIZE is true.
// Try CANONICALIZE false.
@ -339,7 +339,7 @@ public class CredentialsUtil {
}
return serviceCredsSingle(options, asCreds, cname,
asCreds.getClientAlias(), sname, sname, s4u2Type,
additionalTickets, extraPAs);
user, additionalTickets, extraPAs);
}
/*
@ -349,8 +349,8 @@ public class CredentialsUtil {
private static Credentials serviceCredsReferrals(
KDCOptions options, Credentials asCreds,
PrincipalName cname, PrincipalName sname,
S4U2Type s4u2Type, Ticket[] additionalTickets,
PAData[] extraPAs)
S4U2Type s4u2Type, PrincipalName user,
Ticket[] additionalTickets, PAData[] extraPAs)
throws KrbException, IOException {
options = new KDCOptions(options.toBooleanArray());
options.set(KDCOptions.CANONICALIZE, true);
@ -362,12 +362,13 @@ public class CredentialsUtil {
PrincipalName clientAlias = asCreds.getClientAlias();
while (referrals.size() <= Config.MAX_REFERRALS) {
ReferralsCache.ReferralCacheEntry ref =
ReferralsCache.get(cname, sname, refSname.getRealmString());
ReferralsCache.get(cname, sname, user,
additionalTickets, refSname.getRealmString());
String toRealm = null;
if (ref == null) {
creds = serviceCredsSingle(options, asCreds, cname,
clientAlias, refSname, cSname, s4u2Type,
additionalTickets, extraPAs);
user, additionalTickets, extraPAs);
PrincipalName server = creds.getServer();
if (!refSname.equals(server)) {
String[] serverNameStrings = server.getNameStrings();
@ -378,15 +379,9 @@ public class CredentialsUtil {
serverNameStrings[1])) {
// Server Name (sname) has the following format:
// krbtgt/TO-REALM.COM@FROM-REALM.COM
if (s4u2Type == S4U2Type.NONE) {
// Do not store S4U2Self or S4U2Proxy referral
// TGTs in the cache. Caching such tickets is not
// 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);
}
ReferralsCache.put(cname, sname, user,
additionalTickets, server.getRealmString(),
serverNameStrings[1], creds);
toRealm = serverNameStrings[1];
isReferral = true;
}
@ -411,7 +406,7 @@ public class CredentialsUtil {
}
additionalTickets[0] = credsInOut[1].getTicket();
} else if (s4u2Type == S4U2Type.SELF) {
handleS4U2SelfReferral(extraPAs, asCreds, creds);
handleS4U2SelfReferral(extraPAs, user, creds);
}
if (referrals.contains(toRealm)) {
// Referrals loop detected
@ -440,8 +435,8 @@ public class CredentialsUtil {
KDCOptions options, Credentials asCreds,
PrincipalName cname, PrincipalName clientAlias,
PrincipalName refSname, PrincipalName sname,
S4U2Type s4u2Type, Ticket[] additionalTickets,
PAData[] extraPAs)
S4U2Type s4u2Type, PrincipalName user,
Ticket[] additionalTickets, PAData[] extraPAs)
throws KrbException, IOException {
Credentials theCreds = null;
boolean[] okAsDelegate = new boolean[]{true};
@ -469,7 +464,7 @@ public class CredentialsUtil {
Credentials.printDebug(newTgt);
}
if (s4u2Type == S4U2Type.SELF) {
handleS4U2SelfReferral(extraPAs, asCreds, newTgt);
handleS4U2SelfReferral(extraPAs, user, newTgt);
}
asCreds = newTgt;
cname = asCreds.getClient();
@ -498,7 +493,7 @@ public class CredentialsUtil {
* different realm or when using a referral TGT.
*/
private static void handleS4U2SelfReferral(PAData[] pas,
Credentials oldCeds, Credentials newCreds)
PrincipalName user, Credentials newCreds)
throws Asn1Exception, KrbException, IOException {
if (DEBUG) {
System.out.println(">>> Handling S4U2Self referral");
@ -506,11 +501,8 @@ public class CredentialsUtil {
for (int i = 0; i < pas.length; i++) {
PAData pa = pas[i];
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,
new PAForUserEnc(paForUser.getName(),
new PAForUserEnc(user,
newCreds.getSessionKey()).asn1Encode());
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.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,12 +25,14 @@
package sun.security.krb5.internal;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import sun.security.krb5.Credentials;
import sun.security.krb5.PrincipalName;
@ -51,19 +53,33 @@ final class ReferralsCache {
private static final class ReferralCacheKey {
private PrincipalName cname;
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.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) {
if (!(other instanceof ReferralCacheKey))
return false;
ReferralCacheKey that = (ReferralCacheKey)other;
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() {
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,
* 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.
*
* 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.
*/
static synchronized void put(PrincipalName cname, PrincipalName service,
String fromRealm, String toRealm, Credentials creds) {
ReferralCacheKey k = new ReferralCacheKey(cname, service);
PrincipalName user, Ticket[] userSvcTickets, String fromRealm,
String toRealm, Credentials creds) {
Ticket userSvcTicket = (userSvcTickets != null ?
userSvcTickets[0] : null);
ReferralCacheKey k = new ReferralCacheKey(cname, service,
user, userSvcTicket);
pruneExpired(k);
if (creds.getEndTime().before(new Date())) {
return;
@ -125,11 +146,16 @@ final class ReferralsCache {
/*
* 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,
PrincipalName service, String fromRealm) {
ReferralCacheKey k = new ReferralCacheKey(cname, service);
PrincipalName service, PrincipalName user,
Ticket[] userSvcTickets, String fromRealm) {
Ticket userSvcTicket = (userSvcTickets != null ?
userSvcTickets[0] : null);
ReferralCacheKey k = new ReferralCacheKey(cname, service,
user, userSvcTicket);
pruneExpired(k);
Map<String, ReferralCacheEntry> entries = referralsMap.get(k);
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.
*
* This code is free software; you can redistribute it and/or modify it
@ -287,6 +287,16 @@ public class ReferralsTest {
* on different realms.
*/
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);
s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
GSSName impName = s.impersonate(userKDC1Name).cred().getName();
@ -306,6 +316,16 @@ public class ReferralsTest {
* because the server and the backend are on different realms.
*/
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);
c.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID);
Context s = Context.fromUserPass(serviceKDC2Name, password, true);