/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.jini.security;
import com.sun.jini.collection.WeakIdentityMap;
import com.sun.jini.logging.Levels;
import com.sun.jini.resource.Service;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.DomainCombiner;
import java.security.Permission;
import java.security.Policy;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.SubjectDomainCombiner;
import net.jini.security.policy.DynamicPolicy;
import net.jini.security.policy.SecurityContextSource;
/**
* Provides methods for executing actions with privileges enabled, for
* snapshotting security contexts, for verifying trust in proxies, for
* verifying codebase integrity, and for dynamically granting permissions.
* This class cannot be instantiated.
*
* @com.sun.jini.impl
* This implementation uses the {@link Logger} named
* <code>net.jini.security.integrity</code> to log information at
* the following levels:
* <table summary="Describes what is logged by Security to
* the integrity logger at various logging levels" border=1 cellpadding=5>
* <tr>
* <th>Level</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>{@link Levels#FAILED FAILED}</td>
* <td><code>verifyCodebaseIntegrity</code> throws a
* <code>SecurityException</code> because no integrity verifier verifies
* a URL</td>
* </tr>
* <tr>
* <td>{@link Level#FINE FINE}</td>
* <td>integrity verifier returns <code>true</code></td>
* </tr>
* <tr>
* <td>{@link Level#FINE FINE}</td>
* <td>creation of cached integrity verifiers</td>
* </tr>
* </table>
* <p>
* This implementation uses the {@link Logger} named
* <code>net.jini.security.policy</code> to log information at
* the following level:
* <table summary="Describes what is logged by Security to
* the policy logger at various logging levels" border=1 cellpadding=5>
* <tr>
* <th>Level</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>{@link Level#FINER FINER}</td>
* <td>dynamic permission grants</td>
* </tr>
* </table>
* <p>
* This implementation uses the {@link Logger} named
* <code>net.jini.security.trust</code> to log information at
* the following levels:
* <table summary="Describes what is logged by Security to
* the trust logger at various logging levels" border=1 cellpadding=5>
* <tr>
* <th>Level</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>{@link Levels#FAILED FAILED}</td>
* <td><code>verifyObjectTrust</code> throws a <code>SecurityException</code>
* because no trust verifier trusts the specified object</td>
* </tr>
* <tr>
* <td>{@link Levels#FAILED FAILED}</td>
* <td><code>TrustVerifier.Context.isTrustedObject</code> throws an
* exception</td>
* </tr>
* <tr>
* <td>{@link Levels#HANDLED HANDLED}</td>
* <td>trust verifier throws a <code>RemoteException</code> or a
* <code>SecurityException</code></td>
* </tr>
* <tr>
* <td>{@link Level#FINE FINE}</td>
* <td>trust verifier returns <code>true</code></td>
* </tr>
* <tr>
* <td>{@link Level#FINE FINE}</td>
* <td>creation of cached trust verifiers</td>
* </tr>
* <tr>
* <td>{@link Level#FINE FINE}</td>
* <td><code>TrustVerifier.Context.isTrustedObject</code> returns
* <code>false</code> because no trust verifier trusts the specified
* object</td>
* </tr>
* </table>
*
* @author Sun Microsystems, Inc.
* @since 2.0
*/
public final class Security {
private static final Logger trustLogger =
Logger.getLogger("net.jini.security.trust");
private static final Logger integrityLogger =
Logger.getLogger("net.jini.security.integrity");
private static final Logger policyLogger =
Logger.getLogger("net.jini.security.policy");
/**
* Weak map from String to [URL[], SoftReference(key)]
*/
private static Map pathToURLsCache = new WeakHashMap(5);
/**
* Weak map from ClassLoader to SoftReference(IntegrityVerifier[]).
*/
private static final WeakIdentityMap integrityMap = new WeakIdentityMap();
/**
* SecurityManager instance used to obtain caller's Class.
*/
private static final ClassContextAccess ctxAccess = (ClassContextAccess)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() { return new ClassContextAccess(); }
});
/**
* Non-instantiable.
*/
private Security() {}
/**
* Verifies that the specified object can be trusted to correctly implement
* its contract, using verifiers from the specified class loader and
* using the specified collection of context objects as necessary. If a
* <code>null</code> class loader is specified, the context class loader
* of the current thread is used instead. Code that is itself downloaded
* and that carries its own trust verifiers (to trust other downloaded
* code) should specify an explicit class loader unless the calling code
* is known to be reachable from the context class loader.
* <p>
* A {@link TrustVerifier.Context} is created, containing an ordered list
* of trust verifiers (obtained as specified below) and the specified class
* loader and collection of context objects. The
* {@link TrustVerifier.Context#isTrustedObject isTrustedObject} method
* of that context is then called with the specified object. If that call
* returns <code>true</code>, then this method returns normally. If that
* call throws a <code>RemoteException</code> or
* <code>SecurityException</code> exception, that exception is thrown by
* this method. If that call returns <code>false</code>, a
* <code>SecurityException</code> is thrown.
* <p>
* The collection of context objects is provided as a means for the
* caller to communicate additional information to the trust verifiers.
* The meaning of an element in this collection is determined by its
* type. As a specific example, if any trust verifiers might communicate
* with a remote server (in particular, when verifying a proxy for a
* remote server), the caller might be responsible for specifying any
* necessary client constraints as a context object of type
* {@link net.jini.core.constraint.MethodConstraints}.
* <p>
* When security is a concern, this method should be called with a
* downloaded proxy before making any other use of the proxy, in order to
* verify basic trust in the proxy to correctly implement its contract.
* This method can also be used to verify trust in other types of objects,
* depending on what verifiers have been configured. In general,
* verification of an object involves verification of all of its
* constituent objects. However, for objects that are instances of
* {@link net.jini.core.constraint.RemoteMethodControl},
* the client constraints (that would be returned by
* {@link net.jini.core.constraint.RemoteMethodControl#getConstraints
* RemoteMethodControl.getConstraints}) are not verified; it is assumed
* that the caller will either replace them or independently decide that
* it trusts them. Verification of other types of objects may similarly
* exempt certain application-controlled state.
* <p>
* The list of trust verifiers is obtained as follows. For each resource
* named
* <code>META-INF/services/net.jini.security.TrustVerifier</code>
* that is visible to the specified class loader, the contents of the
* resource are parsed as UTF-8 text to produce a list of class names.
* The resource must contain a list of fully qualified class names, one per
* line. Space and tab characters surrounding each name, as well as blank
* lines, are ignored. The comment character is <tt>'#'</tt>; all
* characters on each line starting with the first comment character are
* ignored. Each class name (that is not a duplicate of any previous class
* name) is loaded through the specified class loader, and the resulting
* class must be assignable to {@link TrustVerifier} and have a public
* no-argument constructor. The constructor is invoked to create a trust
* verifier instance. An implementation of this method is permitted to
* cache the verifier instances associated with a class loader, rather than
* recreating them on every call.
*
* @param obj the object in which to verify trust
* @param loader the class loader for finding trust verifiers, or
* <code>null</code> to use the context class loader
* @param context a collection of context objects for use by trust
* verifiers
* @throws SecurityException if the object is not trusted, or if a
* <code>SecurityException</code> is thrown by the trust verifier context
* @throws RemoteException if a communication-related exception occurs
* @throws NullPointerException if the collection is <code>null</code>
*/
public static void verifyObjectTrust(Object obj,
ClassLoader loader,
Collection context)
throws RemoteException
{
if (context == null) {
throw new NullPointerException("collection cannot be null");
}
if (new Context(loader, context).isTrustedObject(obj)) {
return;
}
SecurityException e = new SecurityException(
"object is not trusted: " + obj);
if (trustLogger.isLoggable(Levels.FAILED)) {
logThrow(trustLogger, Levels.FAILED,
Security.class.getName(), "verifyObjectTrust",
"no verifier trusts {0}",
new Object[]{obj}, e);
}
throw e;
}
/**
* Verifies that the URLs in the specified codebase all provide content
* integrity, using verifiers from the specified class loader. If a
* <code>null</code> class loader is specified, the context class loader of
* the current thread is used instead. An ordered list of integrity
* verifiers is obtained as specified below. For each URL (if any) in the
* specified codebase, the {@link IntegrityVerifier#providesIntegrity
* providesIntegrity} method of each verifier is called (in order) with
* the URL. If any verifier call returns <code>true</code>, the URL is
* verified (and no further verifiers are called with that URL). If all of
* the verifier calls return <code>false</code> for a URL, this method
* throws a <code>SecurityException</code>. If all of the URLs are
* verified, this method returns normally.
* <p>
* The list of integrity verifiers is obtained as follows. For each
* resource named
* <code>META-INF/services/net.jini.security.IntegrityVerifier</code>
* that is visible to the specified class loader, the contents of the
* resource are parsed as UTF-8 text to produce a list of class names.
* The resource must contain a list of fully qualified class names, one per
* line. Space and tab characters surrounding each name, as well as blank
* lines, are ignored. The comment character is <tt>'#'</tt>; all
* characters on each line starting with the first comment character are
* ignored. Each class name (that is not a duplicate of any previous class
* name) is loaded through the specified class loader, and the resulting
* class must be assignable to {@link IntegrityVerifier} and have a public
* no-argument constructor. The constructor is invoked to create an
* integrity verifier instance. An implementation of this method is
* permitted to cache the verifier instances associated with a
* class loader, rather than recreating them on every call.
*
* @param codebase space-separated list of URLs, or <code>null</code>
* @param loader the class loader for finding integrity verifiers, or
* <code>null</code> to use the context class loader
* @throws MalformedURLException if the specified codebase contains
* an invalid URL
* @throws SecurityException if any URL in the specified codebase
* does not provide content integrity
*/
public static void verifyCodebaseIntegrity(String codebase,
ClassLoader loader)
throws MalformedURLException
{
if (codebase == null) {
return;
}
if (loader == null) {
loader = getContextClassLoader();
}
URL[] urls = pathToURLs(codebase);
IntegrityVerifier[] verifiers = getIntegrityVerifiers(loader);
outer:
for (int i = urls.length; --i >= 0; ) {
for (int j = 0; j < verifiers.length; j++) {
if (verifiers[j].providesIntegrity(urls[i])) {
if (integrityLogger.isLoggable(Level.FINE)) {
integrityLogger.log(Level.FINE,
"{0} verifies {1}",
new Object[]{verifiers[j],
urls[i]});
}
continue outer;
}
}
SecurityException e =
new SecurityException("URL does not provide integrity: " +
urls[i]);
if (integrityLogger.isLoggable(Levels.FAILED)) {
logThrow(integrityLogger, Levels.FAILED,
Security.class.getName(), "verifyCodebaseIntegrity",
"no verifier verifies {0}", new Object[]{urls[i]}, e);
}
throw e;
}
}
/**
* Log a throw.
*/
private static void logThrow(Logger logger,
Level level,
String clazz,
String method,
String msg,
Object[] args,
Throwable t)
{
LogRecord lr = new LogRecord(level, msg);
lr.setLoggerName(logger.getName());
lr.setSourceClassName(clazz);
lr.setSourceMethodName(method);
lr.setParameters(args);
lr.setThrown(t);
logger.log(lr);
}
/**
* Convert a string containing a space-separated list of URLs into a
* corresponding array of URL objects, throwing a MalformedURLException
* if any of the URLs are invalid.
*/
private static URL[] pathToURLs(String path) throws MalformedURLException {
synchronized (pathToURLsCache) {
Object[] v = (Object[]) pathToURLsCache.get(path);
if (v != null) {
return (URL[]) v[0];
}
}
StringTokenizer st = new StringTokenizer(path); // divide by spaces
URL[] urls = new URL[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++) {
urls[i] = new URL(st.nextToken());
}
synchronized (pathToURLsCache) {
pathToURLsCache.put(path,
new Object[]{urls, new SoftReference(path)});
}
return urls;
}
/**
* Return the integrity verifiers for the specified class loader.
*/
private static IntegrityVerifier[] getIntegrityVerifiers(
final ClassLoader cl)
{
SoftReference ref;
synchronized (integrityMap) {
ref = (SoftReference) integrityMap.get(cl);
}
IntegrityVerifier[] verifiers = null;
if (ref != null) {
verifiers = (IntegrityVerifier[]) ref.get();
}
if (verifiers == null) {
final ArrayList list = new ArrayList(1);
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
for (Iterator iter =
Service.providers(IntegrityVerifier.class, cl);
iter.hasNext(); )
{
list.add(iter.next());
}
return null;
}
});
if (integrityLogger.isLoggable(Level.FINE)) {
integrityLogger.logp(Level.FINE, Security.class.getName(),
"verifyCodebaseIntegrity",
"integrity verifiers {0}",
new Object[]{list});
}
verifiers = (IntegrityVerifier[]) list.toArray(
new IntegrityVerifier[list.size()]);
synchronized (integrityMap) {
integrityMap.put(cl, new SoftReference(verifiers));
}
}
return verifiers;
}
/**
* Returns a snapshot of the current security context, which can be used to
* restore the context at a later time. If either the installed security
* manager or policy provider implements the {@link SecurityContextSource}
* interface, then this method delegates to the {@link
* SecurityContextSource#getContext getContext} method of the
* implementing object, with precedence given to the security manager. If
* neither the security manager nor the policy provider implement
* <code>SecurityContextSource</code>, then a new default
* {@link SecurityContext} instance is
* returned whose methods have the following semantics:
* <ul>
* <li>The <code>wrap</code> methods each return their respective
* <code>PrivilegedAction</code> and <code>PrivilegedExceptionAction</code>
* arguments, unmodified
* <li>The <code>getAccessControlContext</code> method returns the
* <code>AccessControlContext</code> in effect when the security context
* was created
* </ul>
*
* @return snapshot of the current security context
*/
public static SecurityContext getContext() {
SecurityManager sm = System.getSecurityManager();
if (sm instanceof SecurityContextSource) {
return ((SecurityContextSource) sm).getContext();
}
Policy policy = getPolicy();
if (policy instanceof SecurityContextSource) {
return ((SecurityContextSource) policy).getContext();
}
final AccessControlContext acc = AccessController.getContext();
return new SecurityContext() {
public PrivilegedAction wrap(PrivilegedAction a) {
if (a == null) {
throw new NullPointerException();
}
return a;
}
public PrivilegedExceptionAction wrap(PrivilegedExceptionAction a)
{
if (a == null) {
throw new NullPointerException();
}
return a;
}
public AccessControlContext getAccessControlContext() {
return acc;
}
};
}
/**
* Executes the specified action's <code>run</code> method with privileges
* enabled, preserving the domain combiner (if any) of the calling context.
* If the action's <code>run</code> method throws an unchecked exception,
* that exception is thrown by this method. This method is equivalent to
* the {@link AccessController#doPrivileged(PrivilegedAction)
* AccessController.doPrivileged} method of the same signature, except that
* it maintains, instead of clears, the domain combiner (if any) in place
* at the time of the call. This typically results in preservation of the
* current {@link Subject} (if the combiner is a {@link
* SubjectDomainCombiner}), thus retaining permissions granted to
* principals of the <code>Subject</code>, as well as the ability to use
* credentials of the <code>Subject</code> for authentication.
*
* @param action the action to be executed
* @return the object returned by the action's <code>run</code> method
* @throws NullPointerException if the action is <code>null</code>
*/
public static Object doPrivileged(final PrivilegedAction action) {
final Class caller = ctxAccess.getCaller();
final AccessControlContext acc = AccessController.getContext();
return AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return AccessController.doPrivileged(
action, createPrivilegedContext(caller, acc));
}
});
}
/**
* Executes the specified action's <code>run</code> method with privileges
* enabled, preserving the domain combiner (if any) of the calling context.
* If the action's <code>run</code> method throws an unchecked exception,
* that exception is thrown by this method. This method is equivalent to
* the {@link AccessController#doPrivileged(PrivilegedExceptionAction)
* AccessController.doPrivileged} method of the same signature, except that
* it maintains, instead of clears, the domain combiner (if any) in place
* at the time of the call. This typically results in preservation of the
* current <code>Subject</code> (if the combiner is a
* <code>SubjectDomainCombiner</code>), thus retaining permissions granted
* to principals of the <code>Subject</code>, as well as the ability to use
* credentials of the <code>Subject</code> for authentication.
*
* @param action the action to be executed
* @return the object returned by the action's <code>run</code> method
* @throws PrivilegedActionException if the action's <code>run</code>
* method throws a checked exception
* @throws NullPointerException if the action is <code>null</code>
*/
public static Object doPrivileged(final PrivilegedExceptionAction action)
throws PrivilegedActionException
{
final Class caller = ctxAccess.getCaller();
final AccessControlContext acc = AccessController.getContext();
return AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
try {
return AccessController.doPrivileged(
action, createPrivilegedContext(caller, acc));
} catch (PrivilegedActionException e) {
throw e.getException();
}
}
});
}
/**
* Creates privileged context that contains the protection domain of the
* given caller class (if non-null) and uses the domain combiner of the
* specified context. This method assumes it is called from within a
* privileged block.
*/
private static AccessControlContext createPrivilegedContext(
Class caller,
AccessControlContext acc)
{
DomainCombiner comb = acc.getDomainCombiner();
ProtectionDomain pd = caller.getProtectionDomain();
ProtectionDomain[] pds = (pd != null) ?
new ProtectionDomain[]{pd} : null;
if (comb != null) {
pds = comb.combine(pds, null);
}
if (pds == null) {
pds = new ProtectionDomain[0];
}
return new AccessControlContext(new AccessControlContext(pds), comb);
}
/**
* Returns <code>true</code> if the installed security policy provider
* supports dynamic permission grants--i.e., if it implements the {@link
* DynamicPolicy} interface and calling its {@link
* DynamicPolicy#grantSupported grantSupported} method returns
* <code>true</code>. Returns <code>false</code> otherwise.
*
* @return <code>true</code> if the installed security policy provider
* supports dynamic permission grants
* @see #grant(Class,Permission[])
* @see #grant(Class,Principal[],Permission[])
* @see #grant(Class,Class)
*/
public static boolean grantSupported() {
Policy policy = getPolicy();
return (policy instanceof DynamicPolicy &&
((DynamicPolicy) policy).grantSupported());
}
/**
* If the installed security policy provider implements the
* {@link DynamicPolicy} interface, delegates to the security policy
* provider to grant the specified permissions to all protection domains
* (including ones not yet created) that are associated with the class
* loader of the given class and possess at least the principals of the
* current subject (if any). If the given class is <code>null</code>, then
* the grant applies across all protection domains that possess at least
* the current subject's principals. The current subject is determined by
* calling {@link Subject#getSubject Subject.getSubject} on the context
* returned by {@link AccessController#getContext
* AccessController.getContext}. If the current subject is
* <code>null</code> or has no principals, then principals are effectively
* ignored in determining the protection domains to which the grant
* applies.
* <p>
* The given class, if non-<code>null</code>, must belong to either the
* system domain or a protection domain whose associated class loader is
* non-<code>null</code>. If the class does not belong to such a
* protection domain, then no permissions are granted and an
* <code>UnsupportedOperationException</code> is thrown.
* <p>
* If a security manager is installed, its <code>checkPermission</code>
* method is called with a {@link GrantPermission} containing the
* permissions to grant; if the permission check fails, then no permissions
* are granted and the resulting <code>SecurityException</code> is thrown.
* The permissions array passed in is neither modified nor retained;
* subsequent changes to the array have no effect on the grant operation.
*
* @param cl class to grant permissions to the class loader of, or
* <code>null</code> if granting across all class loaders
* @param permissions if non-<code>null</code>, permissions to grant
* @throws UnsupportedOperationException if the installed security policy
* provider does not support dynamic permission grants, or if
* <code>cl</code> is non-<code>null</code> and belongs to a protection
* domain other than the system domain with an associated class loader of
* <code>null</code>
* @throws SecurityException if a security manager is installed and the
* calling context does not have <code>GrantPermission</code> for the given
* permissions
* @throws NullPointerException if any element of the permissions array is
* <code>null</code>
* @see #grantSupported()
* @see DynamicPolicy#grant(Class,Principal[],Permission[])
*/
public static void grant(Class cl, Permission[] permissions) {
grant(cl, getCurrentPrincipals(), permissions);
}
/**
* If the installed security policy provider implements the
* {@link DynamicPolicy} interface, delegates to the security policy
* provider to grant the specified permissions to all protection domains
* (including ones not yet created) that are associated with the class
* loader of the given class and possess at least the given set of
* principals. If the given class is <code>null</code>, then the grant
* applies across all protection domains that possess at least the
* specified principals. If the list of principals is <code>null</code> or
* empty, then principals are effectively ignored in determining the
* protection domains to which the grant applies.
* <p>
* The given class, if non-<code>null</code>, must belong to either the
* system domain or a protection domain whose associated class loader is
* non-<code>null</code>. If the class does not belong to such a
* protection domain, then no permissions are granted and an
* <code>UnsupportedOperationException</code> is thrown.
* <p>
* If a security manager is installed, its <code>checkPermission</code>
* method is called with a <code>GrantPermission</code> containing the
* permissions to grant; if the permission check fails, then no permissions
* are granted and the resulting <code>SecurityException</code> is thrown.
* The principals and permissions arrays passed in are neither modified nor
* retained; subsequent changes to the arrays have no effect on the grant
* operation.
*
* @param cl class to grant permissions to the class loader of, or
* <code>null</code> if granting across all class loaders
* @param principals if non-<code>null</code>, minimum set of principals to
* which grants apply
* @param permissions if non-<code>null</code>, permissions to grant
* @throws UnsupportedOperationException if the installed security policy
* provider does not support dynamic permission grants, or if
* <code>cl</code> is non-<code>null</code> and belongs to a protection
* domain other than the system domain with an associated class loader of
* <code>null</code>
* @throws SecurityException if a security manager is installed and the
* calling context does not have <code>GrantPermission</code> for the given
* permissions
* @throws NullPointerException if any element of the principals or
* permissions arrays is <code>null</code>
* @see #grantSupported()
* @see DynamicPolicy#grant(Class,Principal[],Permission[])
*/
public static void grant(Class cl,
Principal[] principals,
Permission[] permissions)
{
Policy policy = getPolicy();
if (!(policy instanceof DynamicPolicy)) {
throw new UnsupportedOperationException("grants not supported");
}
((DynamicPolicy) policy).grant(cl, principals, permissions);
if (policyLogger.isLoggable(Level.FINER)) {
policyLogger.log(Level.FINER, "granted {0} to {1}, {2}",
new Object[]{
(permissions != null) ? Arrays.asList(permissions) : null,
(cl != null) ? cl.getName() : null,
(principals != null) ? Arrays.asList(principals) : null});
}
}
/**
* If the installed security policy provider implements the {@link
* DynamicPolicy} interface, takes the set of permissions dynamically
* granted to the class loader of <code>fromClass</code> with the current
* subject's principals, determines which of those permissions the calling
* context is authorized to grant, and dynamically grants that subset of
* the permissions to the class loader of <code>toClass</code>, qualified
* with the current subject's principals. The current subject is
* determined by calling {@link Subject#getSubject Subject.getSubject} on
* the context returned by {@link AccessController#getContext
* AccessController.getContext}; the permissions dynamically granted to
* <code>fromClass</code> are determined by calling the {@link
* DynamicPolicy#getGrants getGrants} method of the currently installed
* policy, and the permission grant to <code>toClass</code> is performed by
* invoking the {@link DynamicPolicy#grant grant} method of the current
* policy.
* <p>
* Both of the given classes must be non-<code>null</code>, and must belong
* to either the system domain or a protection domain whose associated
* class loader is non-<code>null</code>. If either class does not belong
* to such a protection domain, then no permissions are granted and an
* <code>UnsupportedOperationException</code> is thrown.
*
* @param fromClass class indicating the source class loader of the dynamic
* grants to propagate
* @param toClass class indicating the target class loader of the dynamic
* grants to propagate
* @throws NullPointerException if <code>fromClass</code> or
* <code>toClass</code> is <code>null</code>
* @throws UnsupportedOperationException if currently installed policy does
* not support dynamic permission grants, or if either specified class
* belongs to a protection domain with a <code>null</code> class loader,
* other than the system domain
*/
public static void grant(Class fromClass, Class toClass) {
if (fromClass == null || toClass == null) {
throw new NullPointerException();
}
Policy policy = getPolicy();
if (!(policy instanceof DynamicPolicy)) {
throw new UnsupportedOperationException("grants not supported");
}
DynamicPolicy dpolicy = (DynamicPolicy) policy;
Principal[] principals = getCurrentPrincipals();
Permission[] permissions =
grantablePermissions(dpolicy.getGrants(fromClass, principals));
dpolicy.grant(toClass, principals, permissions);
if (policyLogger.isLoggable(Level.FINER)) {
policyLogger.log(Level.FINER, "granted {0} from {1} to {2}, {3}",
new Object[]{
(permissions != null) ? Arrays.asList(permissions) : null,
fromClass.getName(),
toClass.getName(),
(principals != null) ? Arrays.asList(principals) : null});
}
}
/**
* Returns current thread's context class loader.
*/
private static ClassLoader getContextClassLoader() {
return (ClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return Thread.currentThread().getContextClassLoader();
}
});
}
/**
* Returns currently installed security policy, if any.
*/
private static Policy getPolicy() {
return (Policy) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() { return Policy.getPolicy(); }
});
}
/**
* Returns subset of given permissions that is grantable given the current
* calling context.
*/
private static Permission[] grantablePermissions(Permission[] permissions)
{
SecurityManager sm = System.getSecurityManager();
if (sm == null || permissions.length == 0) {
return permissions;
}
try {
sm.checkPermission(new GrantPermission(permissions));
return permissions;
} catch (SecurityException e) {
}
ArrayList list = new ArrayList(permissions.length);
for (int i = 0; i < permissions.length; i++) {
try {
Permission p = permissions[i];
sm.checkPermission(new GrantPermission(p));
list.add(p);
} catch (SecurityException e) {
}
}
return (Permission[]) list.toArray(new Permission[list.size()]);
}
/**
* Returns principals of current subject, or null if no current subject.
*/
private static Principal[] getCurrentPrincipals() {
final AccessControlContext acc = AccessController.getContext();
Subject s = (Subject) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() { return Subject.getSubject(acc); }
});
if (s != null) {
Set ps = s.getPrincipals();
return (Principal[]) ps.toArray(new Principal[ps.size()]);
} else {
return null;
}
}
/**
* TrustVerifier.Context implementation.
*/
private static class Context implements TrustVerifier.Context {
/**
* Trust verifiers.
*/
private final TrustVerifier[] verifiers;
/**
* The class loader.
*/
private final ClassLoader cl;
/**
* Caller context.
*/
private final Collection context;
/**
* Weak map from ClassLoader to SoftReference(TrustVerifier[]).
*/
private static final WeakIdentityMap map = new WeakIdentityMap();
/**
* Creates an instance containing the trust verifiers found from
* the specified class loader, using the verifier cache and
* updating the cache as necessary.
*/
Context(ClassLoader cl, Collection context) {
this.cl = cl;
if (cl == null) {
cl = getContextClassLoader();
}
SoftReference ref;
synchronized (map) {
ref = (SoftReference) map.get(cl);
}
TrustVerifier[] verifiers = null;
if (ref != null) {
verifiers = (TrustVerifier[]) ref.get();
}
if (verifiers == null) {
final ArrayList list = new ArrayList(1);
final ClassLoader scl = cl;
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
for (Iterator iter =
Service.providers(TrustVerifier.class, scl);
iter.hasNext(); )
{
list.add(iter.next());
}
return null;
}
});
if (trustLogger.isLoggable(Level.FINE)) {
trustLogger.logp(Level.FINE, Security.class.getName(),
"verifyObjectTrust",
"trust verifiers {0}", list);
}
verifiers = (TrustVerifier[]) list.toArray(
new TrustVerifier[list.size()]);
synchronized (map) {
map.put(cl, new SoftReference(verifiers));
}
}
this.verifiers = verifiers;
this.context = context;
}
public boolean isTrustedObject(Object obj)
throws RemoteException
{
if (obj == null) {
return true;
}
Exception ex = null;
for (int i = 0; i < verifiers.length; i++) {
try {
if (verifiers[i].isTrustedObject(obj, this)) {
if (trustLogger.isLoggable(Level.FINE)) {
trustLogger.log(Level.FINE,
"{0} trusts {1}",
new Object[]{verifiers[i], obj});
}
return true;
}
} catch (Exception e) {
boolean rethrow = (e instanceof RuntimeException &&
!(e instanceof SecurityException));
Level level = rethrow ? Levels.FAILED : Levels.HANDLED;
if (trustLogger.isLoggable(level)) {
logThrow(trustLogger, level,
this.getClass().getName(),
"isTrustedObject",
"{0} checking {1} throws",
new Object[]{verifiers[i], obj},
e);
}
if (rethrow) {
throw (RuntimeException) e;
}
ex = e;
}
}
if (ex != null) {
if (trustLogger.isLoggable(Levels.FAILED)) {
logThrow(trustLogger, Levels.FAILED,
this.getClass().getName(), "isTrustedObject",
"checking {0} throws",
new Object[]{obj}, ex);
}
if (ex instanceof RemoteException) {
throw (RemoteException) ex;
}
throw (SecurityException) ex;
}
if (trustLogger.isLoggable(Level.FINE)) {
trustLogger.log(Level.FINE, "no verifier trusts {0}", obj);
}
return false;
}
public ClassLoader getClassLoader() {
return cl;
}
public Collection getCallerContext() {
return context;
}
}
/**
* Dummy security manager providing access to getClassContext method.
*/
private static class ClassContextAccess extends SecurityManager {
/**
* Returns caller's caller class.
*/
Class getCaller() {
return getClassContext()[2];
}
}
}