1 /*
2  * Copyright (c) 2000, 2017, 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;
27 
28 import com.sun.security.auth.callback.TextCallbackHandler;
29 import javax.security.auth.Subject;
30 import javax.security.auth.kerberos.KerberosPrincipal;
31 import javax.security.auth.kerberos.KerberosTicket;
32 import javax.security.auth.kerberos.KerberosKey;
33 import org.ietf.jgss.*;
34 import sun.security.jgss.spi.GSSNameSpi;
35 import sun.security.jgss.spi.GSSCredentialSpi;
36 import sun.security.action.GetPropertyAction;
37 import sun.security.jgss.krb5.Krb5NameElement;
38 import sun.security.jgss.spnego.SpNegoCredElement;
39 import java.util.Set;
40 import java.util.HashSet;
41 import java.util.Vector;
42 import java.util.Iterator;
43 import java.security.AccessController;
44 import java.security.AccessControlContext;
45 import java.security.PrivilegedExceptionAction;
46 import java.security.PrivilegedActionException;
47 import javax.security.auth.callback.CallbackHandler;
48 import javax.security.auth.login.LoginContext;
49 import javax.security.auth.login.LoginException;
50 import sun.security.action.GetBooleanAction;
51 
52 /**
53  * The GSSUtilImplementation that knows how to work with the internals of
54  * the GSS-API.
55  */
56 public class GSSUtil {
57 
58     public static final Oid GSS_KRB5_MECH_OID =
59                 GSSUtil.createOid("1.2.840.113554.1.2.2");
60     public static final Oid GSS_KRB5_MECH_OID2 =
61                 GSSUtil.createOid("1.3.5.1.5.2");
62     public static final Oid GSS_KRB5_MECH_OID_MS =
63                 GSSUtil.createOid("1.2.840.48018.1.2.2");
64 
65     public static final Oid GSS_SPNEGO_MECH_OID =
66                 GSSUtil.createOid("1.3.6.1.5.5.2");
67 
68     public static final Oid NT_GSS_KRB5_PRINCIPAL =
69                 GSSUtil.createOid("1.2.840.113554.1.2.2.1");
70 
71     private static final String DEFAULT_HANDLER =
72             "auth.login.defaultCallbackHandler";
73 
74     static final boolean DEBUG;
75     static {
76         DEBUG = (AccessController.doPrivileged
77                         (new GetBooleanAction("sun.security.jgss.debug"))).
78                                 booleanValue();
79     }
80 
debug(String message)81     static void debug(String message) {
82         if (DEBUG) {
83             assert(message != null);
84             System.out.println(message);
85         }
86     }
87 
88     // NOTE: this method is only for creating Oid objects with
89     // known to be valid <code>oidStr</code> given it ignores
90     // the GSSException
createOid(String oidStr)91     public static Oid createOid(String oidStr) {
92         try {
93             return new Oid(oidStr);
94         } catch (GSSException e) {
95             debug("Ignored invalid OID: " + oidStr);
96             return null;
97         }
98     }
99 
isSpNegoMech(Oid oid)100     public static boolean isSpNegoMech(Oid oid) {
101         return (GSS_SPNEGO_MECH_OID.equals(oid));
102     }
103 
isKerberosMech(Oid oid)104     public static boolean isKerberosMech(Oid oid) {
105         return (GSS_KRB5_MECH_OID.equals(oid) ||
106                 GSS_KRB5_MECH_OID2.equals(oid) ||
107                 GSS_KRB5_MECH_OID_MS.equals(oid));
108 
109     }
110 
getMechStr(Oid oid)111     public static String getMechStr(Oid oid) {
112         if (isSpNegoMech(oid)) {
113             return "SPNEGO";
114         } else if (isKerberosMech(oid)) {
115             return "Kerberos V5";
116         } else {
117             return oid.toString();
118         }
119     }
120 
121     /**
122      * Note: The current impl only works with Sun's impl of
123      * GSSName and GSSCredential since it depends on package
124      * private APIs.
125      */
getSubject(GSSName name, GSSCredential creds)126     public static Subject getSubject(GSSName name,
127                                      GSSCredential creds) {
128 
129         HashSet<Object> privCredentials = null;
130         HashSet<Object> pubCredentials = new HashSet<Object>(); // empty Set
131 
132         Set<GSSCredentialSpi> gssCredentials = null;
133 
134         Set<KerberosPrincipal> krb5Principals =
135                                 new HashSet<KerberosPrincipal>();
136 
137         if (name instanceof GSSNameImpl) {
138             try {
139                 GSSNameSpi ne = ((GSSNameImpl) name).getElement
140                     (GSS_KRB5_MECH_OID);
141                 String krbName = ne.toString();
142                 if (ne instanceof Krb5NameElement) {
143                     krbName =
144                         ((Krb5NameElement) ne).getKrb5PrincipalName().getName();
145                 }
146                 KerberosPrincipal krbPrinc = new KerberosPrincipal(krbName);
147                 krb5Principals.add(krbPrinc);
148             } catch (GSSException ge) {
149                 debug("Skipped name " + name + " due to " + ge);
150             }
151         }
152 
153         if (creds instanceof GSSCredentialImpl) {
154             gssCredentials = ((GSSCredentialImpl) creds).getElements();
155             privCredentials = new HashSet<Object>(gssCredentials.size());
156             populateCredentials(privCredentials, gssCredentials);
157         } else {
158             privCredentials = new HashSet<Object>(); // empty Set
159         }
160         debug("Created Subject with the following");
161         debug("principals=" + krb5Principals);
162         debug("public creds=" + pubCredentials);
163         debug("private creds=" + privCredentials);
164 
165         return new Subject(false, krb5Principals, pubCredentials,
166                            privCredentials);
167 
168     }
169 
170     /**
171      * Populates the set credentials with elements from gssCredentials. At
172      * the same time, it converts any subclasses of KerberosTicket
173      * into KerberosTicket instances and any subclasses of KerberosKey into
174      * KerberosKey instances. (It is not desirable to expose the customer
175      * to sun.security.jgss.krb5.Krb5InitCredential which extends
176      * KerberosTicket and sun.security.jgss.krb5.Kbr5AcceptCredential which
177      * extends KerberosKey.)
178      */
populateCredentials(Set<Object> credentials, Set<?> gssCredentials)179     private static void populateCredentials(Set<Object> credentials,
180                                             Set<?> gssCredentials) {
181 
182         Object cred;
183 
184         Iterator<?> elements = gssCredentials.iterator();
185         while (elements.hasNext()) {
186 
187             cred = elements.next();
188 
189             // Retrieve the internal cred out of SpNegoCredElement
190             if (cred instanceof SpNegoCredElement) {
191                 cred = ((SpNegoCredElement) cred).getInternalCred();
192             }
193 
194             if (cred instanceof KerberosTicket) {
195                 if (!cred.getClass().getName().equals
196                     ("javax.security.auth.kerberos.KerberosTicket")) {
197                     KerberosTicket tempTkt = (KerberosTicket) cred;
198                     cred = new KerberosTicket(tempTkt.getEncoded(),
199                                               tempTkt.getClient(),
200                                               tempTkt.getServer(),
201                                               tempTkt.getSessionKey().getEncoded(),
202                                               tempTkt.getSessionKeyType(),
203                                               tempTkt.getFlags(),
204                                               tempTkt.getAuthTime(),
205                                               tempTkt.getStartTime(),
206                                               tempTkt.getEndTime(),
207                                               tempTkt.getRenewTill(),
208                                               tempTkt.getClientAddresses());
209                 }
210                 credentials.add(cred);
211             } else if (cred instanceof KerberosKey) {
212                 if (!cred.getClass().getName().equals
213                     ("javax.security.auth.kerberos.KerberosKey")) {
214                     KerberosKey tempKey = (KerberosKey) cred;
215                     cred = new KerberosKey(tempKey.getPrincipal(),
216                                            tempKey.getEncoded(),
217                                            tempKey.getKeyType(),
218                                            tempKey.getVersionNumber());
219                 }
220                 credentials.add(cred);
221             } else {
222                 // Ignore non-KerberosTicket and non-KerberosKey elements
223                 debug("Skipped cred element: " + cred);
224             }
225         }
226     }
227 
228     /**
229      * Authenticate using the login module from the specified
230      * configuration entry.
231      *
232      * @param caller the caller of JAAS Login
233      * @param mech the mech to be used
234      * @return the authenticated subject
235      */
login(GSSCaller caller, Oid mech)236     public static Subject login(GSSCaller caller, Oid mech) throws LoginException {
237 
238         CallbackHandler cb = null;
239         if (caller instanceof HttpCaller) {
240             cb = new sun.net.www.protocol.http.spnego.NegotiateCallbackHandler(
241                     ((HttpCaller)caller).info());
242         } else {
243             String defaultHandler =
244                     java.security.Security.getProperty(DEFAULT_HANDLER);
245             // get the default callback handler
246             if ((defaultHandler != null) && (defaultHandler.length() != 0)) {
247                 cb = null;
248             } else {
249                 cb = new TextCallbackHandler();
250             }
251         }
252 
253         // New instance of LoginConfigImpl must be created for each login,
254         // since the entry name is not passed as the first argument, but
255         // generated with caller and mech inside LoginConfigImpl
256         LoginContext lc = new LoginContext("", null, cb,
257                 new LoginConfigImpl(caller, mech));
258         lc.login();
259         return lc.getSubject();
260     }
261 
262     /**
263      * Determines if the application doesn't mind if the mechanism obtains
264      * the required credentials from outside of the current Subject. Our
265      * Kerberos v5 mechanism would do a JAAS login on behalf of the
266      * application if this were the case.
267      *
268      * The application indicates this by explicitly setting the system
269      * property javax.security.auth.useSubjectCredsOnly to false.
270      */
useSubjectCredsOnly(GSSCaller caller)271     public static boolean useSubjectCredsOnly(GSSCaller caller) {
272 
273         String propValue = GetPropertyAction.privilegedGetProperty(
274                 "javax.security.auth.useSubjectCredsOnly");
275 
276         // Invalid values should be ignored and the default assumed.
277         if (caller instanceof HttpCaller) {
278             // Default for HTTP/SPNEGO is false.
279             return "true".equalsIgnoreCase(propValue);
280         } else {
281             // Default for JGSS is true.
282             return !("false".equalsIgnoreCase(propValue));
283         }
284     }
285 
286     /**
287      * Determines the SPNEGO interoperability mode with Microsoft;
288      * by default it is set to true.
289      *
290      * To disable it, the application indicates this by explicitly setting
291      * the system property sun.security.spnego.interop to false.
292      */
useMSInterop()293     public static boolean useMSInterop() {
294         /*
295          * Don't use GetBooleanAction because the default value in the JRE
296          * (when this is unset) has to treated as true.
297          */
298         String propValue = AccessController.doPrivileged(
299                 new GetPropertyAction("sun.security.spnego.msinterop",
300                 "true"));
301         /*
302          * This property has to be explicitly set to "false". Invalid
303          * values should be ignored and the default "true" assumed.
304          */
305         return (!propValue.equalsIgnoreCase("false"));
306     }
307 
308     /**
309      * Searches the private credentials of current Subject with the
310      * specified criteria and returns the matching GSSCredentialSpi
311      * object out of Sun's impl of GSSCredential. Returns null if
312      * no Subject present or a Vector which contains 0 or more
313      * matching GSSCredentialSpi objects.
314      */
315     public static <T extends GSSCredentialSpi> Vector<T>
searchSubject(final GSSNameSpi name, final Oid mech, final boolean initiate, final Class<? extends T> credCls)316             searchSubject(final GSSNameSpi name,
317                           final Oid mech,
318                           final boolean initiate,
319                           final Class<? extends T> credCls) {
320         debug("Search Subject for " + getMechStr(mech) +
321               (initiate? " INIT" : " ACCEPT") + " cred (" +
322               (name == null? "<<DEF>>" : name.toString()) + ", " +
323               credCls.getName() + ")");
324         final AccessControlContext acc = AccessController.getContext();
325         try {
326             Vector<T> creds =
327                 AccessController.doPrivileged
328                 (new PrivilegedExceptionAction<Vector<T>>() {
329                     public Vector<T> run() throws Exception {
330                         Subject accSubj = Subject.getSubject(acc);
331                         Vector<T> result = null;
332                         if (accSubj != null) {
333                             result = new Vector<T>();
334                             Iterator<GSSCredentialImpl> iterator =
335                                 accSubj.getPrivateCredentials
336                                 (GSSCredentialImpl.class).iterator();
337                             while (iterator.hasNext()) {
338                                 GSSCredentialImpl cred = iterator.next();
339                                 debug("...Found cred" + cred);
340                                 try {
341                                     GSSCredentialSpi ce =
342                                         cred.getElement(mech, initiate);
343                                     debug("......Found element: " + ce);
344                                     if (ce.getClass().equals(credCls) &&
345                                         (name == null ||
346                                          name.equals((Object) ce.getName()))) {
347                                         result.add(credCls.cast(ce));
348                                     } else {
349                                         debug("......Discard element");
350                                     }
351                                 } catch (GSSException ge) {
352                                     debug("...Discard cred (" + ge + ")");
353                                 }
354                             }
355                         } else debug("No Subject");
356                         return result;
357                     }
358                 });
359             return creds;
360         } catch (PrivilegedActionException pae) {
361             debug("Unexpected exception when searching Subject:");
362             if (DEBUG) pae.printStackTrace();
363             return null;
364         }
365     }
366 }
367