1 /*
2  * Copyright (c) 2003, 2019, 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 import javax.security.auth.login.LoginException;
34 import java.security.AccessControlContext;
35 import sun.security.jgss.GSSUtil;
36 import sun.security.jgss.GSSCaller;
37 
38 import sun.security.krb5.Credentials;
39 import sun.security.krb5.EncryptionKey;
40 import sun.security.krb5.KrbException;
41 import java.io.IOException;
42 import java.util.ArrayList;
43 import java.util.List;
44 import sun.security.krb5.KerberosSecrets;
45 import sun.security.krb5.PrincipalName;
46 /**
47  * Utilities for obtaining and converting Kerberos tickets.
48  *
49  */
50 public class Krb5Util {
51 
52     static final boolean DEBUG =
53         java.security.AccessController.doPrivileged(
54             new sun.security.action.GetBooleanAction
55             ("sun.security.krb5.debug")).booleanValue();
56 
57     /**
58      * Default constructor
59      */
Krb5Util()60     private Krb5Util() {  // Cannot create one of these
61     }
62 
63     /**
64      * Retrieve the service ticket for serverPrincipal from caller's Subject
65      * or from Subject obtained by logging in, or if not found, via the
66      * Ticket Granting Service using the TGT obtained from the Subject.
67      *
68      * Caller must have permission to:
69      *    - access and update Subject's private credentials
70      *    - create LoginContext
71      *    - read the auth.login.defaultCallbackHandler security property
72      *
73      * NOTE: This method is used by JSSE Kerberos Cipher Suites
74      */
getTicketFromSubjectAndTgs(GSSCaller caller, String clientPrincipal, String serverPrincipal, String tgsPrincipal, AccessControlContext acc)75     public static KerberosTicket getTicketFromSubjectAndTgs(GSSCaller caller,
76         String clientPrincipal, String serverPrincipal, String tgsPrincipal,
77         AccessControlContext acc)
78         throws LoginException, KrbException, IOException {
79 
80         // 1. Try to find service ticket in acc subject
81         Subject accSubj = Subject.getSubject(acc);
82         KerberosTicket ticket = SubjectComber.find(accSubj,
83             serverPrincipal, clientPrincipal, KerberosTicket.class);
84 
85         if (ticket != null) {
86             return ticket;  // found it
87         }
88 
89         Subject loginSubj = null;
90         if (!GSSUtil.useSubjectCredsOnly(caller)) {
91             // 2. Try to get ticket from login
92             try {
93                 loginSubj = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
94                 ticket = SubjectComber.find(loginSubj,
95                     serverPrincipal, clientPrincipal, KerberosTicket.class);
96                 if (ticket != null) {
97                     return ticket; // found it
98                 }
99             } catch (LoginException e) {
100                 // No login entry to use
101                 // ignore and continue
102             }
103         }
104 
105         // Service ticket not found in subject or login
106         // Try to get TGT to acquire service ticket
107 
108         // 3. Try to get TGT from acc subject
109         KerberosTicket tgt = SubjectComber.find(accSubj,
110             tgsPrincipal, clientPrincipal, KerberosTicket.class);
111 
112         boolean fromAcc;
113         if (tgt == null && loginSubj != null) {
114             // 4. Try to get TGT from login subject
115             tgt = SubjectComber.find(loginSubj,
116                 tgsPrincipal, clientPrincipal, KerberosTicket.class);
117             fromAcc = false;
118         } else {
119             fromAcc = true;
120         }
121 
122         // 5. Try to get service ticket using TGT
123         if (tgt != null) {
124             Credentials tgtCreds = ticketToCreds(tgt);
125             Credentials serviceCreds = Credentials.acquireServiceCreds(
126                         serverPrincipal, tgtCreds);
127             if (serviceCreds != null) {
128                 ticket = credsToTicket(serviceCreds);
129 
130                 // Store service ticket in acc's Subject
131                 if (fromAcc && accSubj != null && !accSubj.isReadOnly()) {
132                     accSubj.getPrivateCredentials().add(ticket);
133                 }
134             }
135         }
136         return ticket;
137     }
138 
139     /**
140      * Retrieves the ticket corresponding to the client/server principal
141      * pair from the Subject in the specified AccessControlContext.
142      */
getServiceTicket(GSSCaller caller, String clientPrincipal, String serverPrincipal, AccessControlContext acc)143     static KerberosTicket getServiceTicket(GSSCaller caller,
144         String clientPrincipal, String serverPrincipal,
145         AccessControlContext acc) throws LoginException {
146 
147         // Try to get ticket from acc's Subject
148         Subject accSubj = Subject.getSubject(acc);
149         KerberosTicket ticket =
150             SubjectComber.find(accSubj, serverPrincipal, clientPrincipal,
151                   KerberosTicket.class);
152 
153         return ticket;
154     }
155 
156     /**
157      * Retrieves the initial TGT corresponding to the client principal
158      * from the Subject in the specified AccessControlContext.
159      * If the ticket can not be found in the Subject, and if
160      * useSubjectCredsOnly is false, then obtain ticket from
161      * a LoginContext.
162      */
getInitialTicket(GSSCaller caller, String clientPrincipal, AccessControlContext acc)163     static KerberosTicket getInitialTicket(GSSCaller caller,
164             String clientPrincipal,
165             AccessControlContext acc) throws LoginException {
166 
167         // Try to get ticket from acc's Subject
168         Subject accSubj = Subject.getSubject(acc);
169         KerberosTicket ticket =
170                 SubjectComber.find(accSubj, null, clientPrincipal,
171                         KerberosTicket.class);
172 
173         // Try to get ticket from Subject obtained from GSSUtil
174         if (ticket == null && !GSSUtil.useSubjectCredsOnly(caller)) {
175             Subject subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
176             ticket = SubjectComber.find(subject,
177                     null, clientPrincipal, KerberosTicket.class);
178         }
179         return ticket;
180     }
181 
182     /**
183      * Retrieves the caller's Subject, or Subject obtained by logging in
184      * via the specified caller.
185      *
186      * Caller must have permission to:
187      *    - access the Subject
188      *    - create LoginContext
189      *    - read the auth.login.defaultCallbackHandler security property
190      *
191      * NOTE: This method is used by JSSE Kerberos Cipher Suites
192      */
getSubject(GSSCaller caller, AccessControlContext acc)193     public static Subject getSubject(GSSCaller caller,
194         AccessControlContext acc) throws LoginException {
195 
196         // Try to get the Subject from acc
197         Subject subject = Subject.getSubject(acc);
198 
199         // Try to get Subject obtained from GSSUtil
200         if (subject == null && !GSSUtil.useSubjectCredsOnly(caller)) {
201             subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
202         }
203         return subject;
204     }
205 
206     /**
207      * Retrieves the ServiceCreds for the specified server principal from
208      * the Subject in the specified AccessControlContext. If not found, and if
209      * useSubjectCredsOnly is false, then obtain from a LoginContext.
210      *
211      * NOTE: This method is also used by JSSE Kerberos Cipher Suites
212      */
getServiceCreds(GSSCaller caller, String serverPrincipal, AccessControlContext acc)213     public static ServiceCreds getServiceCreds(GSSCaller caller,
214         String serverPrincipal, AccessControlContext acc)
215                 throws LoginException {
216 
217         Subject accSubj = Subject.getSubject(acc);
218         ServiceCreds sc = null;
219         if (accSubj != null) {
220             sc = ServiceCreds.getInstance(accSubj, serverPrincipal);
221         }
222         if (sc == null && !GSSUtil.useSubjectCredsOnly(caller)) {
223             Subject subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
224             sc = ServiceCreds.getInstance(subject, serverPrincipal);
225         }
226         return sc;
227     }
228 
credsToTicket(Credentials serviceCreds)229     public static KerberosTicket credsToTicket(Credentials serviceCreds) {
230         EncryptionKey sessionKey =  serviceCreds.getSessionKey();
231         KerberosTicket kt = new KerberosTicket(
232             serviceCreds.getEncoded(),
233             new KerberosPrincipal(serviceCreds.getClient().getName()),
234             new KerberosPrincipal(serviceCreds.getServer().getName(),
235                                 KerberosPrincipal.KRB_NT_SRV_INST),
236             sessionKey.getBytes(),
237             sessionKey.getEType(),
238             serviceCreds.getFlags(),
239             serviceCreds.getAuthTime(),
240             serviceCreds.getStartTime(),
241             serviceCreds.getEndTime(),
242             serviceCreds.getRenewTill(),
243             serviceCreds.getClientAddresses());
244         PrincipalName clientAlias = serviceCreds.getClientAlias();
245         PrincipalName serverAlias = serviceCreds.getServerAlias();
246         if (clientAlias != null) {
247             KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
248                     .kerberosTicketSetClientAlias(kt, new KerberosPrincipal(
249                             clientAlias.getName(), clientAlias.getNameType()));
250         }
251         if (serverAlias != null) {
252             KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
253                     .kerberosTicketSetServerAlias(kt, new KerberosPrincipal(
254                             serverAlias.getName(), serverAlias.getNameType()));
255         }
256         return kt;
257     };
258 
ticketToCreds(KerberosTicket kerbTicket)259     public static Credentials ticketToCreds(KerberosTicket kerbTicket)
260             throws KrbException, IOException {
261         KerberosPrincipal clientAlias = KerberosSecrets
262                 .getJavaxSecurityAuthKerberosAccess()
263                 .kerberosTicketGetClientAlias(kerbTicket);
264         KerberosPrincipal serverAlias = KerberosSecrets
265                 .getJavaxSecurityAuthKerberosAccess()
266                 .kerberosTicketGetServerAlias(kerbTicket);
267         return new Credentials(
268             kerbTicket.getEncoded(),
269             kerbTicket.getClient().getName(),
270             (clientAlias != null ? clientAlias.getName() : null),
271             kerbTicket.getServer().getName(),
272             (serverAlias != null ? serverAlias.getName() : null),
273             kerbTicket.getSessionKey().getEncoded(),
274             kerbTicket.getSessionKeyType(),
275             kerbTicket.getFlags(),
276             kerbTicket.getAuthTime(),
277             kerbTicket.getStartTime(),
278             kerbTicket.getEndTime(),
279             kerbTicket.getRenewTill(),
280             kerbTicket.getClientAddresses());
281     }
282 
283     /**
284      * A helper method to get a sun..KeyTab from a javax..KeyTab
285      * @param ktab the javax..KeyTab object
286      * @return the sun..KeyTab object
287      */
288     public static sun.security.krb5.internal.ktab.KeyTab
snapshotFromJavaxKeyTab(KeyTab ktab)289             snapshotFromJavaxKeyTab(KeyTab ktab) {
290         return KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
291                 .keyTabTakeSnapshot(ktab);
292     }
293 
294     /**
295      * A helper method to get EncryptionKeys from a javax..KeyTab
296      * @param ktab the javax..KeyTab object
297      * @param cname the PrincipalName
298      * @return the EKeys, never null, might be empty
299      */
keysFromJavaxKeyTab( KeyTab ktab, PrincipalName cname)300     public static EncryptionKey[] keysFromJavaxKeyTab(
301             KeyTab ktab, PrincipalName cname) {
302         return snapshotFromJavaxKeyTab(ktab).readServiceKeys(cname);
303     }
304 }
305