1 /*
   2  * Copyright 2000-2009 Sun Microsystems, Inc.  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.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any 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 
  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     public static final Oid NT_HOSTBASED_SERVICE2 =
  70                 GSSUtil.createOid("1.2.840.113554.1.2.1.4");
  71 
  72     private static final String DEFAULT_HANDLER =
  73             "auth.login.defaultCallbackHandler";
  74 
  75     static final boolean DEBUG;
  76     static {
  77         DEBUG = (AccessController.doPrivileged
  78                         (new GetBooleanAction("sun.security.jgss.debug"))).
  79                                 booleanValue();
  80     }
  81 
  82     static void debug(String message) {
  83         if (DEBUG) {
  84             assert(message != null);
  85             System.out.println(message);
  86         }
  87     }
  88 
  89     // NOTE: this method is only for creating Oid objects with
  90     // known to be valid <code>oidStr</code> given it ignores
  91     // the GSSException
  92     public static Oid createOid(String oidStr) {
  93         try {
  94             return new Oid(oidStr);
  95         } catch (GSSException e) {
  96             debug("Ignored invalid OID: " + oidStr);
  97             return null;
  98         }
  99     }
 100 
 101     public static boolean isSpNegoMech(Oid oid) {
 102         return (GSS_SPNEGO_MECH_OID.equals(oid));
 103     }
 104 
 105     public static boolean isKerberosMech(Oid oid) {
 106         return (GSS_KRB5_MECH_OID.equals(oid) ||
 107                 GSS_KRB5_MECH_OID2.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 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      */
 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 Vector searchSubject(final GSSNameSpi name,
 323                                        final Oid mech,
 324                                        final boolean initiate,
 325                                        final Class credCls) {
 326         debug("Search Subject for " + getMechStr(mech) +
 327               (initiate? " INIT" : " ACCEPT") + " cred (" +
 328               (name == null? "<<DEF>>" : name.toString()) + ", " +
 329               credCls.getName() + ")");
 330         final AccessControlContext acc = AccessController.getContext();
 331         try {
 332             Vector creds =
 333                 AccessController.doPrivileged
 334                 (new PrivilegedExceptionAction<Vector>() {
 335                     public Vector run() throws Exception {
 336                         Subject accSubj = Subject.getSubject(acc);
 337                         Vector<GSSCredentialSpi> result = null;
 338                         if (accSubj != null) {
 339                             result = new Vector<GSSCredentialSpi>();
 340                             Iterator<GSSCredentialImpl> iterator =
 341                                 accSubj.getPrivateCredentials
 342                                 (GSSCredentialImpl.class).iterator();
 343                             while (iterator.hasNext()) {
 344                                 GSSCredentialImpl cred = iterator.next();
 345                                 debug("...Found cred" + cred);
 346                                 try {
 347                                     GSSCredentialSpi ce =
 348                                         cred.getElement(mech, initiate);
 349                                     debug("......Found element: " + ce);
 350                                     if (ce.getClass().equals(credCls) &&
 351                                         (name == null ||
 352                                          name.equals((Object) ce.getName()))) {
 353                                         result.add(ce);
 354                                     } else {
 355                                         debug("......Discard element");
 356                                     }
 357                                 } catch (GSSException ge) {
 358                                     debug("...Discard cred (" + ge + ")");
 359                                 }
 360                             }
 361                         } else debug("No Subject");
 362                         return result;
 363                     }
 364                 });
 365             return creds;
 366         } catch (PrivilegedActionException pae) {
 367             debug("Unexpected exception when searching Subject:");
 368             if (DEBUG) pae.printStackTrace();
 369             return null;
 370         }
 371     }
 372 }