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