1 /* 2 * Copyright (c) 1999, 2016, 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 javax.security.auth; 27 28 import java.security.AccessController; 29 import java.security.Permission; 30 import java.security.Permissions; 31 import java.security.PermissionCollection; 32 import java.security.Policy; 33 import java.security.Principal; 34 import java.security.PrivilegedAction; 35 import java.security.ProtectionDomain; 36 import java.security.Security; 37 import java.util.Set; 38 import java.util.WeakHashMap; 39 import java.lang.ref.WeakReference; 40 41 /** 42 * A {@code SubjectDomainCombiner} updates ProtectionDomains 43 * with Principals from the {@code Subject} associated with this 44 * {@code SubjectDomainCombiner}. 45 * 46 */ 47 public class SubjectDomainCombiner implements java.security.DomainCombiner { 48 49 private Subject subject; 50 private WeakKeyValueMap<ProtectionDomain, ProtectionDomain> cachedPDs = 51 new WeakKeyValueMap<>(); 52 private Set<Principal> principalSet; 53 private Principal[] principals; 54 55 private static final sun.security.util.Debug debug = 56 sun.security.util.Debug.getInstance("combiner", 57 "\t[SubjectDomainCombiner]"); 58 59 @SuppressWarnings("deprecation") 60 // Note: check only at classloading time, not dynamically during combine() 61 private static final boolean useJavaxPolicy = 62 javax.security.auth.Policy.isCustomPolicySet(debug); 63 64 // Relevant only when useJavaxPolicy is true 65 private static final boolean allowCaching = 66 (useJavaxPolicy && cachePolicy()); 67 68 /** 69 * Associate the provided {@code Subject} with this 70 * {@code SubjectDomainCombiner}. 71 * 72 * @param subject the {@code Subject} to be associated with 73 * with this {@code SubjectDomainCombiner}. 74 */ 75 public SubjectDomainCombiner(Subject subject) { 76 this.subject = subject; 77 78 if (subject.isReadOnly()) { 79 principalSet = subject.getPrincipals(); 80 principals = principalSet.toArray 81 (new Principal[principalSet.size()]); 82 } 83 } 84 85 /** 86 * Get the {@code Subject} associated with this 87 * {@code SubjectDomainCombiner}. 88 * 89 * @return the {@code Subject} associated with this 90 * {@code SubjectDomainCombiner}, or {@code null} 91 * if no {@code Subject} is associated with this 92 * {@code SubjectDomainCombiner}. 93 * 94 * @exception SecurityException if the caller does not have permission 95 * to get the {@code Subject} associated with this 96 * {@code SubjectDomainCombiner}. 97 */ 98 public Subject getSubject() { 99 java.lang.SecurityManager sm = System.getSecurityManager(); 100 if (sm != null) { 101 sm.checkPermission(new AuthPermission 102 ("getSubjectFromDomainCombiner")); 103 } 104 return subject; 105 } 106 107 /** 108 * Update the relevant ProtectionDomains with the Principals 109 * from the {@code Subject} associated with this 110 * {@code SubjectDomainCombiner}. 111 * 112 * <p> A new {@code ProtectionDomain} instance is created 113 * for each non-static {@code ProtectionDomain} ( 114 * (staticPermissionsOnly() == false) 115 * in the {@code currentDomains} array. Each new {@code ProtectionDomain} 116 * instance is created using the {@code CodeSource}, 117 * {@code Permission}s and {@code ClassLoader} 118 * from the corresponding {@code ProtectionDomain} in 119 * {@code currentDomains}, as well as with the Principals from 120 * the {@code Subject} associated with this 121 * {@code SubjectDomainCombiner}. Static ProtectionDomains are 122 * combined as-is and no new instance is created. 123 * 124 * <p> All of the ProtectionDomains (static and newly instantiated) are 125 * combined into a new array. The ProtectionDomains from the 126 * {@code assignedDomains} array are appended to this new array, 127 * and the result is returned. 128 * 129 * <p> Note that optimizations such as the removal of duplicate 130 * ProtectionDomains may have occurred. 131 * In addition, caching of ProtectionDomains may be permitted. 132 * 133 * @param currentDomains the ProtectionDomains associated with the 134 * current execution Thread, up to the most recent 135 * privileged {@code ProtectionDomain}. 136 * The ProtectionDomains are listed in order of execution, 137 * with the most recently executing {@code ProtectionDomain} 138 * residing at the beginning of the array. This parameter may 139 * be {@code null} if the current execution Thread 140 * has no associated ProtectionDomains. 141 * 142 * @param assignedDomains the ProtectionDomains inherited from the 143 * parent Thread, or the ProtectionDomains from the 144 * privileged {@code context}, if a call to 145 * {@code AccessController.doPrivileged(..., context)} 146 * had occurred This parameter may be {@code null} 147 * if there were no ProtectionDomains inherited from the 148 * parent Thread, or from the privileged {@code context}. 149 * 150 * @return a new array consisting of the updated ProtectionDomains, 151 * or {@code null}. 152 */ 153 public ProtectionDomain[] combine(ProtectionDomain[] currentDomains, 154 ProtectionDomain[] assignedDomains) { 155 if (debug != null) { 156 if (subject == null) { 157 debug.println("null subject"); 158 } else { 159 final Subject s = subject; 160 AccessController.doPrivileged 161 (new java.security.PrivilegedAction<Void>() { 162 public Void run() { 163 debug.println(s.toString()); 164 return null; 165 } 166 }); 167 } 168 printInputDomains(currentDomains, assignedDomains); 169 } 170 171 if (currentDomains == null || currentDomains.length == 0) { 172 // No need to optimize assignedDomains because it should 173 // have been previously optimized (when it was set). 174 175 // Note that we are returning a direct reference 176 // to the input array - since ACC does not clone 177 // the arrays when it calls combiner.combine, 178 // multiple ACC instances may share the same 179 // array instance in this case 180 181 return assignedDomains; 182 } 183 184 // optimize currentDomains 185 // 186 // No need to optimize assignedDomains because it should 187 // have been previously optimized (when it was set). 188 189 currentDomains = optimize(currentDomains); 190 if (debug != null) { 191 debug.println("after optimize"); 192 printInputDomains(currentDomains, assignedDomains); 193 } 194 195 if (currentDomains == null && assignedDomains == null) { 196 return null; 197 } 198 199 // maintain backwards compatibility for developers who provide 200 // their own custom javax.security.auth.Policy implementations 201 if (useJavaxPolicy) { 202 return combineJavaxPolicy(currentDomains, assignedDomains); 203 } 204 205 int cLen = (currentDomains == null ? 0 : currentDomains.length); 206 int aLen = (assignedDomains == null ? 0 : assignedDomains.length); 207 208 // the ProtectionDomains for the new AccessControlContext 209 // that we will return 210 ProtectionDomain[] newDomains = new ProtectionDomain[cLen + aLen]; 211 212 boolean allNew = true; 213 synchronized(cachedPDs) { 214 if (!subject.isReadOnly() && 215 !subject.getPrincipals().equals(principalSet)) { 216 217 // if the Subject was mutated, clear the PD cache 218 Set<Principal> newSet = subject.getPrincipals(); 219 synchronized(newSet) { 220 principalSet = new java.util.HashSet<Principal>(newSet); 221 } 222 principals = principalSet.toArray 223 (new Principal[principalSet.size()]); 224 cachedPDs.clear(); 225 226 if (debug != null) { 227 debug.println("Subject mutated - clearing cache"); 228 } 229 } 230 231 ProtectionDomain subjectPd; 232 for (int i = 0; i < cLen; i++) { 233 ProtectionDomain pd = currentDomains[i]; 234 235 subjectPd = cachedPDs.getValue(pd); 236 237 if (subjectPd == null) { 238 if (pd.staticPermissionsOnly()) { 239 // keep static ProtectionDomain objects static 240 subjectPd = pd; 241 } else { 242 subjectPd = new ProtectionDomain(pd.getCodeSource(), 243 pd.getPermissions(), 244 pd.getClassLoader(), 245 principals); 246 } 247 cachedPDs.putValue(pd, subjectPd); 248 } else { 249 allNew = false; 250 } 251 newDomains[i] = subjectPd; 252 } 253 } 254 255 if (debug != null) { 256 debug.println("updated current: "); 257 for (int i = 0; i < cLen; i++) { 258 debug.println("\tupdated[" + i + "] = " + 259 printDomain(newDomains[i])); 260 } 261 } 262 263 // now add on the assigned domains 264 if (aLen > 0) { 265 System.arraycopy(assignedDomains, 0, newDomains, cLen, aLen); 266 267 // optimize the result (cached PDs might exist in assignedDomains) 268 if (!allNew) { 269 newDomains = optimize(newDomains); 270 } 271 } 272 273 // if aLen == 0 || allNew, no need to further optimize newDomains 274 275 if (debug != null) { 276 if (newDomains == null || newDomains.length == 0) { 277 debug.println("returning null"); 278 } else { 279 debug.println("combinedDomains: "); 280 for (int i = 0; i < newDomains.length; i++) { 281 debug.println("newDomain " + i + ": " + 282 printDomain(newDomains[i])); 283 } 284 } 285 } 286 287 // return the new ProtectionDomains 288 if (newDomains == null || newDomains.length == 0) { 289 return null; 290 } else { 291 return newDomains; 292 } 293 } 294 295 /** 296 * Use the javax.security.auth.Policy implementation 297 */ 298 private ProtectionDomain[] combineJavaxPolicy( 299 ProtectionDomain[] currentDomains, 300 ProtectionDomain[] assignedDomains) { 301 302 if (!allowCaching) { 303 java.security.AccessController.doPrivileged 304 (new PrivilegedAction<Void>() { 305 @SuppressWarnings("deprecation") 306 public Void run() { 307 // Call refresh only caching is disallowed 308 javax.security.auth.Policy.getPolicy().refresh(); 309 return null; 310 } 311 }); 312 } 313 314 315 int cLen = (currentDomains == null ? 0 : currentDomains.length); 316 int aLen = (assignedDomains == null ? 0 : assignedDomains.length); 317 318 // the ProtectionDomains for the new AccessControlContext 319 // that we will return 320 ProtectionDomain[] newDomains = new ProtectionDomain[cLen + aLen]; 321 322 synchronized(cachedPDs) { 323 if (!subject.isReadOnly() && 324 !subject.getPrincipals().equals(principalSet)) { 325 326 // if the Subject was mutated, clear the PD cache 327 Set<Principal> newSet = subject.getPrincipals(); 328 synchronized(newSet) { 329 principalSet = new java.util.HashSet<Principal>(newSet); 330 } 331 principals = principalSet.toArray 332 (new Principal[principalSet.size()]); 333 cachedPDs.clear(); 334 335 if (debug != null) { 336 debug.println("Subject mutated - clearing cache"); 337 } 338 } 339 340 for (int i = 0; i < cLen; i++) { 341 ProtectionDomain pd = currentDomains[i]; 342 ProtectionDomain subjectPd = cachedPDs.getValue(pd); 343 344 if (subjectPd == null) { 345 if (pd.staticPermissionsOnly()) { 346 // keep static ProtectionDomain objects static 347 subjectPd = pd; 348 } else { 349 // XXX 350 // we must first add the original permissions. 351 // that way when we later add the new JAAS permissions, 352 // any unresolved JAAS-related permissions will 353 // automatically get resolved. 354 355 // get the original perms 356 Permissions perms = new Permissions(); 357 PermissionCollection coll = pd.getPermissions(); 358 java.util.Enumeration<Permission> e; 359 if (coll != null) { 360 synchronized (coll) { 361 e = coll.elements(); 362 while (e.hasMoreElements()) { 363 Permission newPerm = 364 e.nextElement(); 365 perms.add(newPerm); 366 } 367 } 368 } 369 370 // get perms from the policy 371 final java.security.CodeSource finalCs = pd.getCodeSource(); 372 final Subject finalS = subject; 373 PermissionCollection newPerms = 374 java.security.AccessController.doPrivileged 375 (new PrivilegedAction<PermissionCollection>() { 376 @SuppressWarnings("deprecation") 377 public PermissionCollection run() { 378 return 379 javax.security.auth.Policy.getPolicy().getPermissions 380 (finalS, finalCs); 381 } 382 }); 383 384 // add the newly granted perms, 385 // avoiding duplicates 386 synchronized (newPerms) { 387 e = newPerms.elements(); 388 while (e.hasMoreElements()) { 389 Permission newPerm = e.nextElement(); 390 if (!perms.implies(newPerm)) { 391 perms.add(newPerm); 392 if (debug != null) 393 debug.println ( 394 "Adding perm " + newPerm + "\n"); 395 } 396 } 397 } 398 subjectPd = new ProtectionDomain 399 (finalCs, perms, pd.getClassLoader(), principals); 400 } 401 if (allowCaching) 402 cachedPDs.putValue(pd, subjectPd); 403 } 404 newDomains[i] = subjectPd; 405 } 406 } 407 408 if (debug != null) { 409 debug.println("updated current: "); 410 for (int i = 0; i < cLen; i++) { 411 debug.println("\tupdated[" + i + "] = " + newDomains[i]); 412 } 413 } 414 415 // now add on the assigned domains 416 if (aLen > 0) { 417 System.arraycopy(assignedDomains, 0, newDomains, cLen, aLen); 418 } 419 420 if (debug != null) { 421 if (newDomains == null || newDomains.length == 0) { 422 debug.println("returning null"); 423 } else { 424 debug.println("combinedDomains: "); 425 for (int i = 0; i < newDomains.length; i++) { 426 debug.println("newDomain " + i + ": " + 427 newDomains[i].toString()); 428 } 429 } 430 } 431 432 // return the new ProtectionDomains 433 if (newDomains == null || newDomains.length == 0) { 434 return null; 435 } else { 436 return newDomains; 437 } 438 } 439 440 private static ProtectionDomain[] optimize(ProtectionDomain[] domains) { 441 if (domains == null || domains.length == 0) 442 return null; 443 444 ProtectionDomain[] optimized = new ProtectionDomain[domains.length]; 445 ProtectionDomain pd; 446 int num = 0; 447 for (int i = 0; i < domains.length; i++) { 448 449 // skip domains with AllPermission 450 // XXX 451 // 452 // if (domains[i].implies(ALL_PERMISSION)) 453 // continue; 454 455 // skip System Domains 456 if ((pd = domains[i]) != null) { 457 458 // remove duplicates 459 boolean found = false; 460 for (int j = 0; j < num && !found; j++) { 461 found = (optimized[j] == pd); 462 } 463 if (!found) { 464 optimized[num++] = pd; 465 } 466 } 467 } 468 469 // resize the array if necessary 470 if (num > 0 && num < domains.length) { 471 ProtectionDomain[] downSize = new ProtectionDomain[num]; 472 System.arraycopy(optimized, 0, downSize, 0, downSize.length); 473 optimized = downSize; 474 } 475 476 return ((num == 0 || optimized.length == 0) ? null : optimized); 477 } 478 479 private static boolean cachePolicy() { 480 String s = AccessController.doPrivileged 481 (new PrivilegedAction<String>() { 482 public String run() { 483 return Security.getProperty("cache.auth.policy"); 484 } 485 }); 486 if (s != null) { 487 return Boolean.parseBoolean(s); 488 } 489 490 // cache by default 491 return true; 492 } 493 494 private static void printInputDomains(ProtectionDomain[] currentDomains, 495 ProtectionDomain[] assignedDomains) { 496 if (currentDomains == null || currentDomains.length == 0) { 497 debug.println("currentDomains null or 0 length"); 498 } else { 499 for (int i = 0; currentDomains != null && 500 i < currentDomains.length; i++) { 501 if (currentDomains[i] == null) { 502 debug.println("currentDomain " + i + ": SystemDomain"); 503 } else { 504 debug.println("currentDomain " + i + ": " + 505 printDomain(currentDomains[i])); 506 } 507 } 508 } 509 510 if (assignedDomains == null || assignedDomains.length == 0) { 511 debug.println("assignedDomains null or 0 length"); 512 } else { 513 debug.println("assignedDomains = "); 514 for (int i = 0; assignedDomains != null && 515 i < assignedDomains.length; i++) { 516 if (assignedDomains[i] == null) { 517 debug.println("assignedDomain " + i + ": SystemDomain"); 518 } else { 519 debug.println("assignedDomain " + i + ": " + 520 printDomain(assignedDomains[i])); 521 } 522 } 523 } 524 } 525 526 private static String printDomain(final ProtectionDomain pd) { 527 if (pd == null) { 528 return "null"; 529 } 530 return AccessController.doPrivileged(new PrivilegedAction<String>() { 531 public String run() { 532 return pd.toString(); 533 } 534 }); 535 } 536 537 /** 538 * A HashMap that has weak keys and values. 539 * 540 * Key objects in this map are the "current" ProtectionDomain instances 541 * received via the combine method. Each "current" PD is mapped to a 542 * new PD instance that holds both the contents of the "current" PD, 543 * as well as the principals from the Subject associated with this combiner. 544 * 545 * The newly created "principal-based" PD values must be stored as 546 * WeakReferences since they contain strong references to the 547 * corresponding key object (the "current" non-principal-based PD), 548 * which will prevent the key from being GC'd. Specifically, 549 * a "principal-based" PD contains strong references to the CodeSource, 550 * signer certs, PermissionCollection and ClassLoader objects 551 * in the "current PD". 552 */ 553 private static class WeakKeyValueMap<K,V> extends 554 WeakHashMap<K,WeakReference<V>> { 555 556 public V getValue(K key) { 557 WeakReference<V> wr = super.get(key); 558 if (wr != null) { 559 return wr.get(); 560 } 561 return null; 562 } 563 564 public V putValue(K key, V value) { 565 WeakReference<V> wr = super.put(key, new WeakReference<V>(value)); 566 if (wr != null) { 567 return wr.get(); 568 } 569 return null; 570 } 571 } 572 }