/*
* Copyright (c) 2019, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.krb5.internal;
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 sun.security.krb5.Credentials;
import sun.security.krb5.PrincipalName;
/*
* ReferralsCache class implements a cache scheme for referral TGTs as
* described in RFC 6806 - 10. Caching Information. The goal is to optimize
* resources (such as network traffic) when a client requests credentials for a
* service principal to a given KDC. If a referral TGT was previously received,
* cached information is used instead of issuing a new query. Once a referral
* TGT expires, the corresponding referral entry in the cache is removed.
*/
final class ReferralsCache {
private static Map<ReferralCacheKey, Map<String, ReferralCacheEntry>>
referralsMap = new HashMap<>();
static private final class ReferralCacheKey {
private PrincipalName cname;
private PrincipalName sname;
ReferralCacheKey (PrincipalName cname, PrincipalName sname) {
this.cname = cname;
this.sname = sname;
}
public boolean equals(Object other) {
if (!(other instanceof ReferralCacheKey))
return false;
ReferralCacheKey that = (ReferralCacheKey)other;
return cname.equals(that.cname) &&
sname.equals(that.sname);
}
public int hashCode() {
return cname.hashCode() + sname.hashCode();
}
}
static final class ReferralCacheEntry {
private final Credentials creds;
private final String toRealm;
ReferralCacheEntry(Credentials creds, String toRealm) {
this.creds = creds;
this.toRealm = toRealm;
}
Credentials getCreds() {
return creds;
}
String getToRealm() {
return toRealm;
}
}
/*
* Add a new referral entry to the cache, including: client principal,
* service principal, source KDC realm, destination KDC realm and
* referral TGT.
*
* If a loop is generated when adding the new referral, the first hop is
* automatically removed. For example, let's assume that adding a
* REALM-3.COM -> REALM-1.COM referral generates the following loop:
* REALM-1.COM -> REALM-2.COM -> REALM-3.COM -> REALM-1.COM. Then,
* 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);
pruneExpired(k);
if (creds.getEndTime().before(new Date())) {
return;
}
Map<String, ReferralCacheEntry> entries = referralsMap.get(k);
if (entries == null) {
entries = new HashMap<String, ReferralCacheEntry>();
referralsMap.put(k, entries);
}
entries.remove(fromRealm);
ReferralCacheEntry newEntry = new ReferralCacheEntry(creds, toRealm);
entries.put(fromRealm, newEntry);
// Remove loops within the cache
ReferralCacheEntry current = newEntry;
List<ReferralCacheEntry> seen = new LinkedList<>();
while (current != null) {
if (seen.contains(current)) {
// Loop found. Remove the first referral to cut the loop.
entries.remove(newEntry.getToRealm());
break;
}
seen.add(current);
current = entries.get(current.getToRealm());
}
}
/*
* Obtain a referral entry from the cache given a client principal,
* service principal and a source KDC realm.
*/
static synchronized ReferralCacheEntry get(PrincipalName cname,
PrincipalName service, String fromRealm) {
ReferralCacheKey k = new ReferralCacheKey(cname, service);
pruneExpired(k);
Map<String, ReferralCacheEntry> entries = referralsMap.get(k);
if (entries != null) {
ReferralCacheEntry toRef = entries.get(fromRealm);
if (toRef != null) {
return toRef;
}
}
return null;
}
/*
* Remove referral entries from the cache when referral TGTs expire.
*/
private static void pruneExpired(ReferralCacheKey k) {
Date now = new Date();
Map<String, ReferralCacheEntry> entries = referralsMap.get(k);
if (entries != null) {
for (Entry<String, ReferralCacheEntry> mapEntry :
entries.entrySet()) {
if (mapEntry.getValue().getCreds().getEndTime().before(now)) {
entries.remove(mapEntry.getKey());
}
}
}
}
}