1/*
2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.security.jgss.krb5;
27
28import javax.security.auth.kerberos.KerberosTicket;
29import javax.security.auth.kerberos.KerberosKey;
30import javax.security.auth.kerberos.KerberosPrincipal;
31import javax.security.auth.kerberos.KeyTab;
32import javax.security.auth.Subject;
33
34import sun.security.krb5.Credentials;
35import sun.security.krb5.EncryptionKey;
36import sun.security.krb5.KrbException;
37import java.io.IOException;
38import java.util.ArrayList;
39import java.util.List;
40import java.util.Set;
41import sun.security.krb5.*;
42import sun.security.krb5.internal.Krb5;
43
44/**
45 * Credentials of a kerberos acceptor. A KerberosPrincipal object (kp) is
46 * the principal. It can be specified as the serverPrincipal argument
47 * in the getInstance() method, or uses only KerberosPrincipal in the subject.
48 * Otherwise, the creds object is unbound and kp is null.
49 *
50 * The class also encapsulates various secrets, which can be:
51 *
52 *   1. Some KerberosKeys (generated from password)
53 *   2. Some KeyTabs (for a typical service based on keytabs)
54 *   3. A TGT (for S4U2proxy extension or user2user)
55 *
56 * Note that some secrets can coexist. For example, a user2user service
57 * can use its keytab (or keys) if the client can successfully obtain a
58 * normal service ticket, or it can use the TGT (actually, the session key
59 * of the TGT) if the client can only acquire a service ticket
60 * of ENC-TKT-IN-SKEY style.
61 *
62 * @since 1.8
63 */
64public final class ServiceCreds {
65    // The principal, or null if unbound
66    private KerberosPrincipal kp;
67
68    // All principals in the subject's princ set
69    private Set<KerberosPrincipal> allPrincs;
70
71    // All private credentials that can be used
72    private List<KeyTab> ktabs;
73    private List<KerberosKey> kk;
74    private KerberosTicket tgt;
75
76    private boolean destroyed;
77
78    private ServiceCreds() {
79        // Make sure this class cannot be instantiated externally.
80    }
81
82    /**
83     * Creates a ServiceCreds object based on info in a Subject for
84     * a given principal name (if specified).
85     * @return the object, or null if there is no private creds for it
86     */
87    public static ServiceCreds getInstance(
88            Subject subj, String serverPrincipal) {
89
90        ServiceCreds sc = new ServiceCreds();
91
92        sc.allPrincs =
93                subj.getPrincipals(KerberosPrincipal.class);
94
95        // Compatibility. A key implies its own principal
96        for (KerberosKey key: SubjectComber.findMany(
97                subj, serverPrincipal, null, KerberosKey.class)) {
98            sc.allPrincs.add(key.getPrincipal());
99        }
100
101        if (serverPrincipal != null) {      // A named principal
102            sc.kp = new KerberosPrincipal(serverPrincipal);
103        } else {
104            // For compatibility reason, we set the name of default principal
105            // to the "only possible" name it can take, which means there is
106            // only one KerberosPrincipal and there is no unbound keytabs
107            if (sc.allPrincs.size() == 1) {
108                boolean hasUnbound = false;
109                for (KeyTab ktab: SubjectComber.findMany(
110                        subj, null, null, KeyTab.class)) {
111                    if (!ktab.isBound()) {
112                        hasUnbound = true;
113                        break;
114                    }
115                }
116                if (!hasUnbound) {
117                    sc.kp = sc.allPrincs.iterator().next();
118                    serverPrincipal = sc.kp.getName();
119                }
120            }
121        }
122
123        sc.ktabs = SubjectComber.findMany(
124                    subj, serverPrincipal, null, KeyTab.class);
125        sc.kk = SubjectComber.findMany(
126                    subj, serverPrincipal, null, KerberosKey.class);
127        sc.tgt = SubjectComber.find(
128                subj, null, serverPrincipal, KerberosTicket.class);
129        if (sc.ktabs.isEmpty() && sc.kk.isEmpty() && sc.tgt == null) {
130            return null;
131        }
132
133        sc.destroyed = false;
134
135        return sc;
136    }
137
138    // can be null
139    public String getName() {
140        if (destroyed) {
141            throw new IllegalStateException("This object is destroyed");
142        }
143        return kp == null ? null : kp.getName();
144    }
145
146    /**
147     * Gets keys for "someone". Used in 2 cases:
148     * 1. By TLS because it needs to get keys before client comes in.
149     * 2. As a fallback in getEKeys() below.
150     * This method can still return an empty array.
151     */
152    public KerberosKey[] getKKeys() {
153        if (destroyed) {
154            throw new IllegalStateException("This object is destroyed");
155        }
156        KerberosPrincipal one = kp;                 // named principal
157        if (one == null && !allPrincs.isEmpty()) {  // or, a known principal
158            one = allPrincs.iterator().next();
159        }
160        if (one == null) {                          // Or, some random one
161            for (KeyTab ktab: ktabs) {
162                // Must be unbound keytab, otherwise, allPrincs is not empty
163                PrincipalName pn =
164                        Krb5Util.snapshotFromJavaxKeyTab(ktab).getOneName();
165                if (pn != null) {
166                    one = new KerberosPrincipal(pn.getName());
167                    break;
168                }
169            }
170        }
171        if (one != null) {
172            return getKKeys(one);
173        } else {
174            return new KerberosKey[0];
175        }
176    }
177
178    /**
179     * Get kkeys for a principal,
180     * @param princ the target name initiator requests. Not null.
181     * @return keys for the princ, never null, might be empty
182     */
183    public KerberosKey[] getKKeys(KerberosPrincipal princ) {
184        if (destroyed) {
185            throw new IllegalStateException("This object is destroyed");
186        }
187        ArrayList<KerberosKey> keys = new ArrayList<>();
188        if (kp != null && !princ.equals(kp)) {      // named principal
189            return new KerberosKey[0];
190        }
191        for (KerberosKey k: kk) {
192            if (k.getPrincipal().equals(princ)) {
193                keys.add(k);
194            }
195        }
196        for (KeyTab ktab: ktabs) {
197            if (ktab.getPrincipal() == null && ktab.isBound()) {
198                // legacy bound keytab. although we don't know who
199                // the bound principal is, it must be in allPrincs
200                if (!allPrincs.contains(princ)) {
201                    continue;   // skip this legacy bound keytab
202                }
203            }
204            for (KerberosKey k: ktab.getKeys(princ)) {
205                keys.add(k);
206            }
207        }
208        return keys.toArray(new KerberosKey[keys.size()]);
209    }
210
211    /**
212     * Gets EKeys for a principal.
213     * @param princ the target name initiator requests. Not null.
214     * @return keys for the princ, never null, might be empty
215     */
216    public EncryptionKey[] getEKeys(PrincipalName princ) {
217        if (destroyed) {
218            throw new IllegalStateException("This object is destroyed");
219        }
220        KerberosKey[] kkeys = getKKeys(new KerberosPrincipal(princ.getName()));
221        if (kkeys.length == 0) {
222            // Fallback: old JDK does not perform real name checking. If the
223            // acceptor has host.sun.com but initiator requests for host,
224            // as long as their keys match (i.e. keys for one can decrypt
225            // the other's service ticket), the authentication is OK.
226            // There are real customers depending on this to use different
227            // names for a single service.
228            kkeys = getKKeys();
229        }
230        EncryptionKey[] ekeys = new EncryptionKey[kkeys.length];
231        for (int i=0; i<ekeys.length; i++) {
232            ekeys[i] =  new EncryptionKey(
233                        kkeys[i].getEncoded(), kkeys[i].getKeyType(),
234                        kkeys[i].getVersionNumber());
235        }
236        return ekeys;
237    }
238
239    public Credentials getInitCred() {
240        if (destroyed) {
241            throw new IllegalStateException("This object is destroyed");
242        }
243        if (tgt == null) {
244            return null;
245        }
246        try {
247            return Krb5Util.ticketToCreds(tgt);
248        } catch (KrbException | IOException e) {
249            return null;
250        }
251    }
252
253    public void destroy() {
254        // Do not wipe out real keys because they are references to the
255        // priv creds in subject. Just make it useless.
256        destroyed = true;
257        kp = null;
258        ktabs.clear();
259        kk.clear();
260        tgt = null;
261    }
262}
263