/* * 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; } }