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 
26 package sun.security.jgss.krb5;
27 
28 import javax.security.auth.kerberos.KerberosTicket;
29 import javax.security.auth.kerberos.KerberosKey;
30 import javax.security.auth.kerberos.KerberosPrincipal;
31 import javax.security.auth.kerberos.KeyTab;
32 import javax.security.auth.Subject;
33 
34 import sun.security.krb5.Credentials;
35 import sun.security.krb5.EncryptionKey;
36 import sun.security.krb5.KrbException;
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Set;
41 import sun.security.krb5.*;
42 import 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  */
64 public 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 
ServiceCreds()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      */
getInstance( Subject subj, String serverPrincipal)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
getName()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      */
getKKeys()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      */
getKKeys(KerberosPrincipal princ)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      */
getEKeys(PrincipalName princ)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 
getInitCred()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 
destroy()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