/*
* 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.proxytrust;
import com.sun.jini.logging.Levels;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
import java.rmi.UnmarshalException;
import java.rmi.server.RMIClassLoader;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.io.MarshalInputStream;
import net.jini.io.ObjectStreamContext;
import net.jini.security.SecurityContext;
import net.jini.security.TrustVerifier;
/**
* Trust verifier for service proxies that use dynamically downloaded code.
* This verifier uses a recursive algorithm to obtain one or more bootstrap
* proxies, which must be objects that are instances of both
* {@link ProxyTrust} and {@link RemoteMethodControl}. If a bootstrap proxy
* (or a derivative of it) is known to be trusted, a remote call is made
* through it to obtain a trust verifier for the original service proxy.
* This class is intended to be specified in a resource to configure the
* operation of {@link net.jini.security.Security#verifyObjectTrust
* Security.verifyObjectTrust}.
*
* @com.sun.jini.impl
* 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 ProxyTrustVerifier 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>no verifier is obtained from a {@link ProxyTrustIterator}</td>
* </tr>
* <tr>
* <td>{@link Levels#HANDLED HANDLED}</td>
* <td><code>RemoteException</code> being passed to
* {@link ProxyTrustIterator#setException ProxyTrustIterator.setException}</td>
* </tr>
* <tr>
* <td>{@link Level#FINE FINE}</td>
* <td>{@link ProxyTrust#getProxyVerifier ProxyTrust.getProxyVerifier} remote
* call returns a trust verifier</td>
* </tr>
* <tr>
* <td>{@link Level#FINER FINER}</td>
* <td>an object with a <code>getProxyTrustIterator</code> method is
* encountered</td>
* </tr>
* <tr>
* <td>{@link Level#FINER FINER}</td>
* <td>each object produced by a {@link ProxyTrustIterator} and each
* derivative bootstrap proxy</td>
* </tr>
* </table>
*
* @author Sun Microsystems, Inc.
* @since 2.0
*/
public class ProxyTrustVerifier implements TrustVerifier {
private static final Logger logger =
Logger.getLogger("net.jini.security.trust");
/** Thread-local state containing object to skip, if any */
private static final ThreadLocal state = new ThreadLocal();
/** ProxyTrust.getProxyVerifier */
private static Method gpvMethod;
static {
try {
gpvMethod =
ProxyTrust.class.getMethod("getProxyVerifier", new Class[0]);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* Creates an instance.
*/
public ProxyTrustVerifier() {
}
/**
* Returns <code>true</code> if the specified object is known to be
* trusted to correctly implement its contract; returns <code>false</code>
* otherwise.
* <p>
* This method returns <code>false</code> if the caller context collection
* of the specified trust verifier context does not contain a
* {@link MethodConstraints} instance with non-empty constraints for the
* {@link ProxyTrust#getProxyVerifier ProxyTrust.getProxyVerifier}
* method, or if a <code>TrustVerifier</code> cannot be obtained from the
* specified object using the steps described below. Otherwise a
* <code>TrustVerifier</code> is obtained, its
* {@link TrustVerifier#isTrustedObject isTrustedObject} method is called
* with the same arguments passed to this method, and the result of that
* call is returned by this method; any exception thrown by that call
* is thrown by this method. If a verifier cannot be obtained but one or
* more of the intermediate operations involved in attempting to obtain one
* throws a <code>RemoteException</code>, the last such
* <code>RemoteException</code> is thrown by this method (rather than this
* method returning <code>false</code>). If any intermediate operation
* throws a <code>SecurityException</code> exception, that exception is
* immediately thrown by this method.
* <p>
* A verifier is obtained from a candidate object as follows.
* <ul>
* <li>
* If either the candidate object's class has a non-<code>static</code>
* member method with signature:
* <pre>ProxyTrustIterator getProxyTrustIterator();</pre>
* or the candidate object is an instance of a dynamically generated
* {@link Proxy} class and the contained invocation handler's class has
* such a member method, then the <code>getProxyTrustIterator</code>
* method is called (on the candidate object or its invocation handler).
* For each object produced by the {@link ProxyTrustIterator#next next}
* method of the returned iterator, the following substeps are used, until
* either a verifier is obtained or the iteration terminates. If no
* verifier can be obtained from any object produced by the iterator,
* then there is no verifier for the candidate object. For any given
* object produced by the iterator, if a verifier cannot be obtained from
* the object but an intermediate operation involved in attempting to
* obtain a verifier throws a <code>RemoteException</code>, that
* exception is passed to the {@link ProxyTrustIterator#setException
* setException} method of the iterator, and the iteration continues.
* <p>
* The <code>getProxyTrustIterator</code> method and the
* <code>ProxyTrustIterator</code> methods are all invoked in a
* restricted security context. If the specified trust verifier
* context contains an {@link UntrustedObjectSecurityContext} instance,
* then the security context returned by its
* {@link UntrustedObjectSecurityContext#getContext getContext} method
* is used. Otherwise, the security context used is equivalent to
* the current security context (as returned by
* {@link net.jini.security.Security#getContext Security.getContext}) with
* an additional protection domain combined into the access control
* context that contains an empty {@link java.security.CodeSource}
* (<code>null</code> location and certificates),
* <code>null</code> permissions, <code>null</code> class loader, and
* <code>null</code> principals.
* <ul>
* <li>If the object is an instance of both {@link ProxyTrust} and
* {@link RemoteMethodControl} (that is, if the object is a bootstrap
* proxy), it is verified for trust by calling the specified context's
* {@link net.jini.security.TrustVerifier.Context#isTrustedObject
* isTrustedObject} method with the object. If
* <code>isTrustedObject</code> returns <code>true</code>, then the
* object's {@link ProxyTrust#getProxyVerifier getProxyVerifier} method is
* called, using as the client constraints for the remote call the first
* <code>MethodConstraints</code> instance obtained from the caller
* context collection (of the specified trust verifier context) that has
* non-empty constraints for that <code>getProxyVerifier</code> method.
* The verifier returned by that remote call is the verifier for the
* original top-level object, and the entire search stops. If
* <code>isTrustedObject</code> returns <code>false</code>, but a
* verifier can be obtained from a trusted derivative bootstrap proxy as
* described below, then that verifier is the verifier for the original
* top-level object, and the entire search stops. Otherwise, no verifier
* can be obtained from the object, and the iteration continues.
* <li>If the object is not a <code>ProxyTrust</code> instance, it is
* in turn treated as a new candidate object, and the complete set of
* steps for a candidate object are used recursively to obtain a verifier
* from it. If a verifier can be obtained from it, that verifier is the
* verifier for the original top-level object, and the entire search stops.
* If a verifier cannot be obtained from it, the iteration continues.
* </ul>
* <li>If the candidate object is the original top-level object and it is
* an instance of both <code>ProxyTrust</code> and
* <code>RemoteMethodControl</code> (that is, if the original top-level
* object is itself a bootstrap proxy), and a verifier can be obtained
* from a trusted derivative bootstrap proxy as described below, that
* verifier is the verifier for the original top-level object, and the
* entire search stops.
* </ul>
* Given a bootstrap proxy, a verifier can be obtained from a trusted
* derivative bootstrap proxy as follows. A derivative can be produced
* from the bootstrap proxy if all of the following conditions are
* satisfied: the bootstrap proxy was not itself produced (either from an
* iteration or as a derivative) by the latest active invocation of
* <code>ProxyTrustVerifier</code> (not including the current one) in this
* thread; the bootstrap proxy is an instance of a dynamically generated
* <code>Proxy</code> class; neither the proxy's class nor the invocation
* handler's class has an appropriate <code>getProxyTrustIterator</code>
* method; the class loader of the proxy's class is
* the proper Java(TM) RMI class
* loader (as defined below) for its parent class loader and the class's
* codebase (as produced by {@link RMIClassLoader#getClassAnnotation
* RMIClassLoader.getClassAnnotation}); and both <code>ProxyTrust</code>
* and <code>RemoteMethodControl</code> are loadable by the parent class
* loader. The derivative that is produced is an instance of a dynamically
* generated <code>Proxy</code> class defined by the parent class loader
* that implements both <code>ProxyTrust</code> and
* <code>RemoteMethodControl</code> and contains the same invocation
* handler as the bootstrap proxy. The derivative is a trusted derivative
* bootstrap proxy if calling the specified context's
* <code>isTrustedObject</code> method with the derivative returns
* <code>true</code>. If a trusted derivative bootstrap proxy can be
* produced, its {@link ProxyTrust#getProxyVerifier getProxyVerifier}
* method is called, using as the client constraints for the remote call
* the first <code>MethodConstraints</code> instance obtained from the
* caller context collection (of the specified trust verifier context)
* that has non-empty constraints for that <code>getProxyVerifier</code>
* method. The returned verifier is used as is, if the class loader of the
* returned verifier's class is equal to the class loader of the original
* bootstrap proxy's class, or if, in generating a serialization of the
* verifier, no class passed to {@link ObjectOutputStream#annotateClass
* ObjectOutputStream.annotateClass} or
* {@link ObjectOutputStream#annotateProxyClass
* ObjectOutputStream.annotateProxyClass} has a class loader not equal
* to the class loader of the original bootstrap proxy's class but has
* a codebase that is equal to the codebase of the original bootstrap
* proxy's class. Otherwise, the verifier is remarshalled in a manner
* equivalent to creating a {@link net.jini.io.MarshalledInstance} with
* the verifier and then calling the
* {@link net.jini.io.MarshalledInstance#get(ClassLoader,boolean,ClassLoader,Collection) get}
* method of that object with the class loader of the original bootstrap
* proxy's class as the default loader, with no codebase integrity
* verification and with an empty context collection, and the remarshalled
* verifier is used instead. If an {@link IOException} or
* {@link ClassNotFoundException} is thrown by this remarshalling, the
* exception is wrapped in an {@link UnmarshalException} and the resulting
* exception is treated as if it had been thrown by the remote call that
* returned the verifier.
* <p>
* A class loader of a class is the proper Java RMI class loader for its
* parent class loader and the class's codebase if the class loader is
* not <code>null</code>, the codebase for the class is a non-empty
* string, and calling
* {@link RMIClassLoader#getClassLoader RMIClassLoader.getClassLoader}
* with that codebase, with the thread's context class loader set to the
* parent class loader, returns the class loader of the class.
*
* @throws NullPointerException {@inheritDoc}
* @throws SecurityException {@inheritDoc}
*/
public boolean isTrustedObject(Object obj, TrustVerifier.Context ctx)
throws RemoteException
{
if (obj == null || ctx == null) {
throw new NullPointerException();
}
MethodConstraints mc = null;
UntrustedObjectSecurityContext uosc = null;
for (Iterator iter = ctx.getCallerContext().iterator();
(mc == null || uosc == null) && iter.hasNext(); )
{
Object elt = iter.next();
if (mc == null && elt instanceof MethodConstraints) {
MethodConstraints emc = (MethodConstraints) elt;
if (!emc.getConstraints(gpvMethod).isEmpty()) {
mc = emc;
}
} else if (uosc == null &&
elt instanceof UntrustedObjectSecurityContext)
{
uosc = (UntrustedObjectSecurityContext) elt;
}
}
if (mc == null) {
return false;
} else if (uosc == null) {
uosc = new BasicUntrustedObjectSecurityContext(null);
}
TrustVerifier verifier = getVerifier(obj, ctx, mc, uosc);
return (verifier != null &&
verifier.isTrustedObject(obj, ctx));
}
/**
* Recursively tries to obtain a verifier from the remote server.
*/
private static TrustVerifier getVerifier(
Object obj,
TrustVerifier.Context ctx,
MethodConstraints mc,
UntrustedObjectSecurityContext uosc)
throws RemoteException
{
Method m = getMethod(obj);
if (m == null) {
if (!Proxy.isProxyClass(obj.getClass())) {
return null;
}
Object ih = Proxy.getInvocationHandler(obj);
m = getMethod(ih);
if (m != null) {
obj = ih;
} else if (obj instanceof ProxyTrust &&
obj instanceof RemoteMethodControl)
{
return getAltVerifier(obj, ctx, mc);
} else {
return null;
}
}
logger.log(Level.FINER, "{0} has ProxyTrustIterator", obj);
SecurityContext rsc = uosc.getContext();
ProxyTrustIterator iter;
try {
iter = (ProxyTrustIterator) restrictedInvoke(m, obj, rsc);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
throw (Error) t;
}
RemoteException lastEx = null;
while (restrictedHasNext(iter, rsc)) {
obj = null;
try {
obj = restrictedNext(iter, rsc);
logger.log(Level.FINER, "ProxyTrustIterator produces {0}",
obj);
if (!(obj instanceof ProxyTrust)) {
TrustVerifier verifier = getVerifier(obj, ctx, mc, uosc);
if (verifier != null) {
return verifier;
}
} else if (obj instanceof RemoteMethodControl) {
if (isTrusted(obj, ctx)) {
obj = ((RemoteMethodControl) obj).setConstraints(mc);
TrustVerifier verifier =
((ProxyTrust) obj).getProxyVerifier();
logger.log(Level.FINE, "verifier is {0}", verifier);
return verifier;
} else if (Proxy.isProxyClass(obj.getClass()) &&
getMethod(obj) == null &&
getMethod(Proxy.getInvocationHandler(obj)) ==
null)
{
TrustVerifier verifier = getAltVerifier(obj, ctx, mc);
if (verifier != null) {
return verifier;
}
}
}
} catch (RemoteException e) {
lastEx = e;
if (obj instanceof ProxyTrust) {
logger.log(Levels.HANDLED,
"setting ProxyTrustIterator exception", e);
restrictedSetException(iter, e, rsc);
}
}
}
if (lastEx != null) {
throw lastEx;
}
logger.log(Levels.FAILED,
"no verifier obtained from ProxyTrustIterator");
return null;
}
/**
* Calls m.invoke(obj, null) in context of rsc.
*/
private static Object restrictedInvoke(final Method m,
final Object obj,
SecurityContext rsc)
throws IllegalAccessException, InvocationTargetException
{
try {
return AccessController.doPrivileged(rsc.wrap(
new PrivilegedExceptionAction() {
public Object run()
throws IllegalAccessException,
InvocationTargetException
{
return m.invoke(obj, null);
}
}), rsc.getAccessControlContext());
} catch (PrivilegedActionException pae) {
Exception e = pae.getException();
if (e instanceof InvocationTargetException) {
throw (InvocationTargetException) e;
} else {
throw (IllegalAccessException) e;
}
}
}
/**
* Calls iter.hasNext() in context of acc.
*/
private static boolean restrictedHasNext(final ProxyTrustIterator iter,
SecurityContext rsc)
{
return ((Boolean)
AccessController.doPrivileged(rsc.wrap(
new PrivilegedAction() {
public Object run() {
return Boolean.valueOf(iter.hasNext());
}
}), rsc.getAccessControlContext())).booleanValue();
}
/**
* Calls iter.next() in context of rsc.
*/
private static Object restrictedNext(final ProxyTrustIterator iter,
SecurityContext rsc)
throws RemoteException
{
try {
return AccessController.doPrivileged(rsc.wrap(
new PrivilegedExceptionAction() {
public Object run() throws RemoteException {
return iter.next();
}
}), rsc.getAccessControlContext());
} catch (PrivilegedActionException e) {
throw (RemoteException) e.getException();
}
}
/**
* Calls iter.setException(e) in context of rsc.
*/
private static void restrictedSetException(final ProxyTrustIterator iter,
final RemoteException e,
SecurityContext rsc)
{
AccessController.doPrivileged(rsc.wrap(
new PrivilegedAction() {
public Object run() {
iter.setException(e);
return null;
}
}), rsc.getAccessControlContext());
}
/**
* Returns result of calling ctx.isTrustedObject(obj) with
* thread-local state set to obj.
*/
private static boolean isTrusted(Object obj, TrustVerifier.Context ctx)
throws RemoteException
{
Object saved = state.get();
try {
state.set(obj);
return ctx.isTrustedObject(obj);
} finally {
state.set(saved);
}
}
/**
* Takes a bootstrap proxy that doesn't have an iterator method
* and whose invocation handler doesn't have an iterator method.
* If its class loader is a proper RMI child of its parent,
* creates a new bootstrap proxy in the parent, and if it's trusted,
* makes remote call to get verifier, and then conditionally remarshals
* the verifier. Remarshals if the verifier's class loader is not
* the original bootstrap proxy's class loader, but the reserialized
* form of the verifier contains a descriptor for a class with the
* same codebase as but different class loader than the original
* bootstrap proxy.
*/
private static TrustVerifier getAltVerifier(Object obj,
TrustVerifier.Context ctx,
MethodConstraints mc)
throws RemoteException
{
if (obj == state.get()) {
return null;
}
final Class base = obj.getClass();
final String bcb = RMIClassLoader.getClassAnnotation(base);
if (bcb == null || bcb.length() == 0) {
return null;
}
final InvocationHandler ih = Proxy.getInvocationHandler(obj);
obj = AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
ClassLoader bcl = base.getClassLoader();
if (bcl == null) {
return null;
}
ClassLoader pcl = bcl.getParent();
Thread t = Thread.currentThread();
ClassLoader ccl = t.getContextClassLoader();
boolean proper = false;
try {
t.setContextClassLoader(pcl);
proper = (RMIClassLoader.getClassLoader(bcb) == bcl);
} catch (MalformedURLException e) {
} finally {
t.setContextClassLoader(ccl);
}
if (proper) {
try {
return Proxy.newProxyInstance(
pcl,
new Class[]{ProxyTrust.class,
RemoteMethodControl.class},
ih);
} catch (IllegalArgumentException e) {
}
}
return null;
}
});
if (obj == null) {
return null;
}
logger.log(Level.FINER, "trying derivative bootstrap proxy {0}", obj);
if (!isTrusted(obj, ctx)) {
return null;
}
obj = ((RemoteMethodControl) obj).setConstraints(mc);
TrustVerifier verifier = ((ProxyTrust) obj).getProxyVerifier();
final Class vc = verifier.getClass();
ClassLoader bcl = (ClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
ClassLoader bcl = base.getClassLoader();
if (bcl == vc.getClassLoader()) {
return null;
}
return bcl;
}
});
if (bcl != null) {
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
MOStream out = new MOStream(bout, bcb, bcl);
out.writeObject(verifier);
out.close();
if (out.replace) {
logger.log(Level.FINER, "remarshalling verifier");
MarshalInputStream in =
new MarshalInputStream(
new ByteArrayInputStream(bout.toByteArray()),
bcl, false, null, Collections.EMPTY_SET);
in.useCodebaseAnnotations();
verifier = (TrustVerifier) in.readObject();
in.close();
}
} catch (IOException e) {
throw new UnmarshalException("remarshalling verifier failed",
e);
} catch (ClassNotFoundException e) {
throw new UnmarshalException("remarshalling verifier failed",
e);
}
}
logger.log(Level.FINE, "verifier is {0}", verifier);
return verifier;
}
/**
* Marshal output stream that looks for a class with a given codebase
* but in a different class loader than the one we have.
*/
private static class MOStream
extends ObjectOutputStream implements ObjectStreamContext
{
/** bootstrap proxy codebase */
private final String bcb;
/** bootstrap proxy class loader */
private final ClassLoader bcl;
/** true if we see a class with same codebase in different loader */
boolean replace = false;
MOStream(ByteArrayOutputStream out, String bcb, ClassLoader bcl)
throws IOException
{
super(out);
this.bcb = bcb;
this.bcl = bcl;
}
public Collection getObjectStreamContext() {
return Collections.EMPTY_SET;
}
protected void annotateClass(Class c) throws IOException {
writeAnnotation(c);
}
protected void annotateProxyClass(Class c) throws IOException {
writeAnnotation(c);
}
private void writeAnnotation(final Class c) throws IOException {
String cb = RMIClassLoader.getClassAnnotation(c);
writeObject(cb);
if (bcb.equals(cb)) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
if (c.getClassLoader() != bcl) {
replace = true;
}
return null;
}
});
}
}
}
/**
* Returns getProxyTrustIterator method of object if it has a proper one.
*/
private static Method getMethod(Object obj) {
final Class base = obj.getClass();
return (Method) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
for (Class c = base; c != null; c = c.getSuperclass()) {
try {
Method m =
c.getDeclaredMethod("getProxyTrustIterator",
new Class[0]);
if (usable(m, c, base)) {
m.setAccessible(true);
return m;
}
break;
} catch (NoSuchMethodException e) {
}
}
return null;
}
});
}
/**
* Returns true if the method returns ProxyTrustIterator, has no
* declared exceptions, and is a non-static member of the base class.
*/
private static boolean usable(Method m, Class c, Class base) {
int mods = m.getModifiers();
return (m.getReturnType() == ProxyTrustIterator.class &&
m.getExceptionTypes().length == 0 &&
(mods & Modifier.STATIC) == 0 &&
((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0 ||
((mods & Modifier.PRIVATE) != 0 ?
c == base : samePackage(c, base))));
}
/**
* Returns true if the classes are in the same package, false otherwise.
*/
private static boolean samePackage(Class c1, Class c2) {
if (c1.getClassLoader() == c2.getClassLoader()) {
String n1 = c1.getName();
int i1 = n1.lastIndexOf('.');
String n2 = c2.getName();
int i2 = n2.lastIndexOf('.');
return i1 == i2 && (i1 < 0 || n1.regionMatches(0, n2, 0, i1));
}
return false;
}
}