1/*
2 * Copyright (c) 2000, 2011, 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;
27
28import javax.security.auth.Subject;
29import javax.security.auth.kerberos.KerberosPrincipal;
30import javax.security.auth.kerberos.KerberosTicket;
31import javax.security.auth.kerberos.KerberosKey;
32import org.ietf.jgss.*;
33import sun.security.jgss.spi.GSSNameSpi;
34import sun.security.jgss.spi.GSSCredentialSpi;
35import sun.security.action.GetPropertyAction;
36import sun.security.jgss.krb5.Krb5NameElement;
37import sun.security.jgss.spnego.SpNegoCredElement;
38import java.util.Set;
39import java.util.HashSet;
40import java.util.Vector;
41import java.util.Iterator;
42import java.security.AccessController;
43import java.security.AccessControlContext;
44import java.security.PrivilegedExceptionAction;
45import java.security.PrivilegedActionException;
46import javax.security.auth.callback.CallbackHandler;
47import javax.security.auth.login.LoginContext;
48import javax.security.auth.login.LoginException;
49import sun.security.action.GetBooleanAction;
50import sun.security.util.ConsoleCallbackHandler;
51
52/**
53 * The GSSUtilImplementation that knows how to work with the internals of
54 * the GSS-API.
55 */
56public 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
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
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
100    public static boolean isSpNegoMech(Oid oid) {
101        return (GSS_SPNEGO_MECH_OID.equals(oid));
102    }
103
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
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     */
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     */
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     */
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 ConsoleCallbackHandler();
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     */
271    public static boolean useSubjectCredsOnly(GSSCaller caller) {
272
273        // HTTP/SPNEGO doesn't use the standard JAAS framework. Instead, it
274        // uses the java.net.Authenticator style, therefore always return
275        // false here.
276        if (caller instanceof HttpCaller) {
277            return false;
278        }
279        /*
280         * Don't use GetBooleanAction because the default value in the JRE
281         * (when this is unset) has to treated as true.
282         */
283        String propValue = AccessController.doPrivileged(
284                new GetPropertyAction("javax.security.auth.useSubjectCredsOnly",
285                "true"));
286        /*
287         * This property has to be explicitly set to "false". Invalid
288         * values should be ignored and the default "true" assumed.
289         */
290        return (!propValue.equalsIgnoreCase("false"));
291    }
292
293    /**
294     * Determines the SPNEGO interoperability mode with Microsoft;
295     * by default it is set to true.
296     *
297     * To disable it, the application indicates this by explicitly setting
298     * the system property sun.security.spnego.interop to false.
299     */
300    public static boolean useMSInterop() {
301        /*
302         * Don't use GetBooleanAction because the default value in the JRE
303         * (when this is unset) has to treated as true.
304         */
305        String propValue = AccessController.doPrivileged(
306                new GetPropertyAction("sun.security.spnego.msinterop",
307                "true"));
308        /*
309         * This property has to be explicitly set to "false". Invalid
310         * values should be ignored and the default "true" assumed.
311         */
312        return (!propValue.equalsIgnoreCase("false"));
313    }
314
315    /**
316     * Searches the private credentials of current Subject with the
317     * specified criteria and returns the matching GSSCredentialSpi
318     * object out of Sun's impl of GSSCredential. Returns null if
319     * no Subject present or a Vector which contains 0 or more
320     * matching GSSCredentialSpi objects.
321     */
322    public static <T extends GSSCredentialSpi> Vector<T>
323            searchSubject(final GSSNameSpi name,
324                          final Oid mech,
325                          final boolean initiate,
326                          final Class<? extends T> credCls) {
327        debug("Search Subject for " + getMechStr(mech) +
328              (initiate? " INIT" : " ACCEPT") + " cred (" +
329              (name == null? "<<DEF>>" : name.toString()) + ", " +
330              credCls.getName() + ")");
331        final AccessControlContext acc = AccessController.getContext();
332        try {
333            Vector<T> creds =
334                AccessController.doPrivileged
335                (new PrivilegedExceptionAction<Vector<T>>() {
336                    public Vector<T> run() throws Exception {
337                        Subject accSubj = Subject.getSubject(acc);
338                        Vector<T> result = null;
339                        if (accSubj != null) {
340                            result = new Vector<T>();
341                            Iterator<GSSCredentialImpl> iterator =
342                                accSubj.getPrivateCredentials
343                                (GSSCredentialImpl.class).iterator();
344                            while (iterator.hasNext()) {
345                                GSSCredentialImpl cred = iterator.next();
346                                debug("...Found cred" + cred);
347                                try {
348                                    GSSCredentialSpi ce =
349                                        cred.getElement(mech, initiate);
350                                    debug("......Found element: " + ce);
351                                    if (ce.getClass().equals(credCls) &&
352                                        (name == null ||
353                                         name.equals((Object) ce.getName()))) {
354                                        result.add(credCls.cast(ce));
355                                    } else {
356                                        debug("......Discard element");
357                                    }
358                                } catch (GSSException ge) {
359                                    debug("...Discard cred (" + ge + ")");
360                                }
361                            }
362                        } else debug("No Subject");
363                        return result;
364                    }
365                });
366            return creds;
367        } catch (PrivilegedActionException pae) {
368            debug("Unexpected exception when searching Subject:");
369            if (DEBUG) pae.printStackTrace();
370            return null;
371        }
372    }
373}
374