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     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 }