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 }