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