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