aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/sun/security/krb5/internal/ReferralsCache.java
blob: 970d432aba1e52a39548d3fb66162b34e6810fdc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
 * 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());
                }
            }
        }
    }
}