/* * 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 java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectStreamField; import java.io.Serializable; import java.rmi.RemoteException; import java.security.Permission; import java.util.Collections; import net.jini.core.constraint.MethodConstraints; import net.jini.core.constraint.RemoteMethodControl; /** * A <code>ProxyPreparer</code> for verifying that proxies are trusted, * granting them dynamic permissions, and setting their constraints, as well as * for creating other proxy preparer subclasses that include those * operations. <p> * * Applications and configurations can use this class to create proxy preparers * for several common cases. Some examples include creating proxy preparers * that: <ul> * * <li> Verify trust, grant permissions, and set new constraints, to prepare a * proxy received from an untrusted source. * * <li> Use the proxy's existing constraints when verifying trust in the proxy, * to prepare a trusted proxy received with integrity protection from a trusted * source that supplies constraints, confirming that the proxy's implementation * is trusted locally, but allowing the constraints to be downloaded from a * third party. * * <li> Set new constraints, to prepare a trusted proxy received with integrity * protection from a trusted source that is not known to supply the appropriate * constraints. * * <li> Grant permissions, to prepare a trusted proxy received with integrity * protection from a trusted source that supplies appropriate constraints, if * the proxy needs permission grants. * * <li> Do nothing, to use as a default when retrieving an optional * configuration entry, or to prepare a non-secure proxy. </ul> * * @author Sun Microsystems, Inc. * @since 2.0 */ public class BasicProxyPreparer implements ProxyPreparer, Serializable { private static final long serialVersionUID = 4439691869768577046L; /** * @serialField verify boolean * Whether to verify if proxies are trusted. * @serialField methodConstraintsSpecified boolean * Whether to use <code>methodConstraints</code> when * verifying if proxies are trusted and for setting their * constraints. * @serialField methodConstraints MethodConstraints * Method constraints to use when verifying if proxies are * trusted and for setting their constraints, if * <code>methodConstraintsSpecified</code> is * <code>true</code>. Set to <code>null</code> if * <code>methodConstraintsSpecified</code> is * <code>false</code>. * @serialField permissions Permission[] * Permissions to grant to proxies, or an empty array if no * permissions should be granted. The value is always * non-<code>null</code>. */ private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("verify", boolean.class), new ObjectStreamField("methodConstraintsSpecified", boolean.class), new ObjectStreamField("methodConstraints", MethodConstraints.class), /* Make sure the deserialized permissions array is not shared */ new ObjectStreamField("permissions", Permission[].class, true) }; /** Whether to verify if proxies are trusted. */ protected final boolean verify; /** * Whether to use {@link #methodConstraints} when verifying if proxies are * trusted and for setting their constraints. */ protected final boolean methodConstraintsSpecified; /** * Method constraints to use when verifying if proxies are trusted and for * setting their constraints, if {@link #methodConstraintsSpecified} is * <code>true</code>. Set to <code>null</code> if * <code>methodConstraintsSpecified</code> is <code>false</code>. */ protected final MethodConstraints methodConstraints; /** * Permissions to grant to proxies, or an empty array if no permissions * should be granted. The value is always non-<code>null</code>. */ protected final Permission[] permissions; /** * Creates a proxy preparer that specifies not to verify proxies, grant * them permissions, or set their constraints. */ public BasicProxyPreparer() { verify = false; methodConstraintsSpecified = false; methodConstraints = null; permissions = new Permission[0]; } /** * Creates a proxy preparer that specifies whether proxies should be * verified, using the constraints on the proxy by default, and specifies * what permissions to grant to proxies. * * @param verify whether to verify if proxies are trusted * @param permissions permissions to grant, or <code>null</code> if no * permissions should be granted * @throws NullPointerException if <code>permissions</code> is not * <code>null</code> and any of its elements are <code>null</code> */ public BasicProxyPreparer(boolean verify, Permission[] permissions) { this.verify = verify; methodConstraintsSpecified = false; methodConstraints = null; this.permissions = checkPermissions(permissions); } /* Copies the argument, if needed, and checks for null elements. */ private static Permission[] checkPermissions(Permission[] permissions) { if (permissions == null) { return new Permission[0]; } permissions = (Permission[]) permissions.clone(); for (int i = permissions.length; --i >= 0; ) { if (permissions[i] == null) { throw new NullPointerException("Permission cannot be null"); } } return permissions; } /** * Creates a proxy preparer that specifies whether proxies should be * verified, specifies permissions to grant them, and specifies what method * constraints to use when verifying and setting constraints. * * @param verify whether to verify if proxies are trusted * @param methodConstraints method constraints to use when verifying * and setting constraints * @param permissions permissions to grant, or <code>null</code> if no * permissions should be granted * @throws NullPointerException if <code>permissions</code> is not * <code>null</code> and any of its elements are <code>null</code> */ public BasicProxyPreparer(boolean verify, MethodConstraints methodConstraints, Permission[] permissions) { this.verify = verify; this.methodConstraintsSpecified = true; this.methodConstraints = methodConstraints; this.permissions = checkPermissions(permissions); } /** * Returns the method constraints to use when verifying and setting * constraints on the specified proxy. <p> * * The default implementation returns the value of {@link * #methodConstraints} if {@link #methodConstraintsSpecified} is * <code>true</code>, else returns the constraints on the specified proxy * if it implements {@link RemoteMethodControl}, else returns * <code>null</code>. <p> * * Subclasses may wish to override this method, for example, to augment the * existing constraints on the proxy rather than replacing them. * * @param proxy the proxy being prepared * @return the method constraints to use when verifying and setting * constraints on the proxy */ protected MethodConstraints getMethodConstraints(Object proxy) { if (methodConstraintsSpecified) { return methodConstraints; } else if (proxy instanceof RemoteMethodControl) { return ((RemoteMethodControl) proxy).getConstraints(); } else { return null; } } /** * Returns the permissions to grant to proxies, or an empty array if no * permissions should be granted. The return value need not be newly * created, but cannot be <code>null</code>. <p> * * The default implementation returns the value of {@link * #permissions}. <p> * * Subclasses may wish to override this method, for example, to grant * permissions that depend on principal constraints found on the proxy. * * @param proxy the proxy being prepared * @return the permissions to grant to the proxy */ protected Permission[] getPermissions(Object proxy) { return permissions; } /** * Performs operations on a proxy to prepare it for use, returning the * prepared proxy, which may or may not be the argument itself. <p> * * The default implementation provides the following behavior. If * <code>proxy</code> is <code>null</code>, throws a {@link * NullPointerException}. Otherwise, calls {@link #verify(Object) verify} * with <code>proxy</code>. If the <code>verify</code> call succeeds, calls * {@link #grant grant} with <code>proxy</code>. If the <code>grant</code> * call succeeds, returns the result of calling {@link #setConstraints * setConstraints} with <code>proxy</code>. <p> * * Subclasses may wish to override this method, for example, to perform * additional operations, typically calling the default implementation via * <code>super</code>. * * @param proxy the proxy to prepare * @return the prepared proxy * @throws NullPointerException if <code>proxy</code> is <code>null</code> * @throws RemoteException if a communication-related exception occurs * @throws SecurityException if a security exception occurs * @see #verify(Object) verify * @see #grant grant * @see #setConstraints setConstraints */ public Object prepareProxy(Object proxy) throws RemoteException { if (proxy == null) { throw new NullPointerException("Proxy cannot be null"); } verify(proxy); grant(proxy); return setConstraints(proxy); } /** * Verifies that the proxy is trusted. Called by the default implementation * of {@link #prepareProxy prepareProxy}. <p> * * The default implementation provides the following behavior. If * <code>proxy</code> is <code>null</code>, throws a {@link * NullPointerException}. Otherwise, if {@link #verify} is * <code>true</code>, calls {@link Security#verifyObjectTrust * Security.verifyObjectTrust}, with <code>proxy</code>, <code>null</code> * for the class loader, and, for the context, a collection containing the * result of calling {@link #getMethodConstraints getMethodConstraints} * with <code>proxy</code>, or an empty collection if the constraints are * <code>null</code>. <p> * * Subclasses may wish to override this method, for example, to specify a * different class loader or context when verifying the proxy. * * @param proxy the proxy to verify * @throws NullPointerException if <code>proxy</code> is <code>null</code> * @throws RemoteException if a communication-related exception occurs * @throws SecurityException if verifying that the proxy is trusted fails * @see #prepareProxy prepareProxy * @see #getMethodConstraints getMethodConstraints * @see Security#verifyObjectTrust Security.verifyObjectTrust */ protected void verify(Object proxy) throws RemoteException { if (proxy == null) { throw new NullPointerException("Proxy cannot be null"); } else if (verify) { MethodConstraints mc = getMethodConstraints(proxy); Security.verifyObjectTrust(proxy, null, (mc == null ? Collections.EMPTY_SET : Collections.singleton(mc))); } } /** * Grants permissions to the proxy. Called by the default implementation of * {@link #prepareProxy prepareProxy} unless {@link #verify(Object) verify} * throws an exception. <p> * * The default implementation provides the following behavior. If * <code>proxy</code> is <code>null</code>, throws a {@link * NullPointerException}. Otherwise, calls {@link #getPermissions * getPermissions} with <code>proxy</code> to determine what permissions * should be granted. If the permissions are not empty, calls {@link * Security#grant(Class,Permission[]) Security.grant}, with the proxy's * class as the class argument and those permissions. If <code>grant</code> * discovers that dynamic permission grants are not supported and throws a * {@link UnsupportedOperationException}, catches that exception and throws * a {@link SecurityException}. <p> * * Subclasses may wish to override this method, for example, to alter the * principals for which permission grants are made. * * @param proxy the proxy to grant permissions * @throws SecurityException if a security exception occurs * @throws NullPointerException if proxy is <code>null</code> * @see #prepareProxy prepareProxy * @see #getPermissions getPermissions * @see Security#grant(Class,Permission[]) Security.grant */ protected void grant(Object proxy) { if (proxy == null) { throw new NullPointerException("Proxy cannot be null"); } Permission[] perms = getPermissions(proxy); if (perms.length > 0) { try { Security.grant(proxy.getClass(), perms); } catch (UnsupportedOperationException e) { SecurityException se = new SecurityException( "Dynamic permission grants are not supported"); se.initCause(e); throw se; } } } /** * Sets constraints on the proxy. Called by the default implementation of * {@link #prepareProxy prepareProxy} unless {@link #verify(Object) verify} * or {@link #grant grant} throw an exception. <p> * * The default implementation provides the following behavior. If * <code>proxy</code> is <code>null</code>, throws a {@link * NullPointerException}. Otherwise, if {@link #methodConstraintsSpecified} * is <code>false</code>, returns the proxy, else if object does not * implement {@link RemoteMethodControl}, throws a {@link * SecurityException}, else returns the result of calling {@link * RemoteMethodControl#setConstraints RemoteMethodControl.setConstraints} * on the proxy, using the value returned from calling {@link * #getMethodConstraints getMethodConstraints} with <code>proxy</code>. <p> * * Subclasses may wish to override this method, for example, to support * verifying objects that do not implement {@link RemoteMethodControl}. * * @param proxy the proxy * @return the proxy with updated constraints * @throws NullPointerException if <code>proxy</code> is <code>null</code> * @throws SecurityException if a security exception occurs * @see #prepareProxy prepareProxy * @see #getMethodConstraints getMethodConstraints * @see RemoteMethodControl#setConstraints * RemoteMethodControl.setConstraints */ protected Object setConstraints(Object proxy) { if (proxy == null) { throw new NullPointerException("Proxy cannot be null"); } else if (!methodConstraintsSpecified) { return proxy; } else if (!(proxy instanceof RemoteMethodControl)) { throw new SecurityException( "Proxy must implement RemoteMethodControl"); } else { return ((RemoteMethodControl) proxy).setConstraints( getMethodConstraints(proxy)); } } /** Returns a string representation of this object. */ public String toString() { String className = getClass().getName(); int dot = className.lastIndexOf('.'); if (dot >= 0) { className = className.substring(dot + 1); } StringBuffer sb = new StringBuffer(className).append('['); if (verify) { sb.append("verify"); } if (methodConstraintsSpecified) { if (verify) { sb.append(", "); } sb.append(methodConstraints); } if (permissions.length > 0) { if (verify || methodConstraintsSpecified) { sb.append(", "); } sb.append('{'); for (int i = 0; i < permissions.length; i++) { if (i > 0) { sb.append(", "); } sb.append(permissions[i]); } sb.append('}'); } sb.append(']'); return sb.toString(); } /** * Returns <code>true</code> if the given object is an instance of the same * class as this object, with the same value for <code>verify</code>, with * method constraints that are <code>equals</code> or similarly not * specified, and with <code>permissions</code> containing the same * elements, independent of order. */ public boolean equals(Object object) { if (this == object) { return true; } else if (object == null || object.getClass() != getClass()) { return false; } BasicProxyPreparer other = (BasicProxyPreparer) object; if (verify != other.verify) { return false; } if (methodConstraintsSpecified != other.methodConstraintsSpecified) { return false; } else if (methodConstraintsSpecified && (methodConstraints == null ? other.methodConstraints != null : !methodConstraints.equals(other.methodConstraints))) { return false; } if (permissions.length != other.permissions.length) { return false; } /* * Determine if the permissions contain the same elements, including * duplicates. */ Permission[] otherPermissions = (Permission[]) other.permissions.clone(); top: for (int i = permissions.length; --i >= 0; ) { Permission p = permissions[i]; for (int j = i; j >= 0; j--) { if (p.equals(otherPermissions[j])) { otherPermissions[j] = otherPermissions[i]; continue top; } } return false; } return true; } /** Returns a hash code value for this object. */ public int hashCode() { int hash = getClass().getName().hashCode(); if (verify) { hash += 1; } if (methodConstraintsSpecified) { hash += 1<<16; if (methodConstraints != null) { hash += methodConstraints.hashCode(); } } for (int i = permissions.length; --i >= 0; ) { hash += permissions[i].hashCode(); } return hash; } /** * Verifies that fields have legal values. * * @throws InvalidObjectException if * <code>methodConstraintsSpecified</code> is <code>false</code> * and <code>methodConstraints</code> is not <code>null</code>, if * <code>permissions</code> is <code>null</code>, or if * <code>permissions</code> is not <code>null</code> and any of its * elements are <code>null</code> * @throws IOException if an I/O error occurs * @throws ClassNotFoundException if the class of a serialized object could * not be found */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); if (!methodConstraintsSpecified && methodConstraints != null) { throw new InvalidObjectException( "Method constraints not specified but not null"); } if (permissions == null) { throw new InvalidObjectException("Permissions cannot be null"); } for (int i = permissions.length; --i >= 0; ) { if (permissions[i] == null) { throw new InvalidObjectException("Permission cannot be null"); } } } /** * Throws an exception to insure that data was supplied in order to set the * permissions field to an empty array. * * @throws InvalidObjectException whenever this method is called */ private void readObjectNoData() throws InvalidObjectException { throw new InvalidObjectException("Permissions must be specified"); } }