Thanks Michal, that was helpful, sorry for taking so long to reply.

Having chewed the details, I think now it is safe for me to comment, I'm wondering if your open to some refactoring?

You've managed to produce a lot of code in a very short time, your productivity is quite impressive and you seem proficient using Secure Jini Services.

I've been thinking about your use of objects as annotations, which I readily took to, something that's bothering me is backward compatibility, an Object could be changed to a String, but a String is final and existing implementations are stuck with it. MarshalledInstance is part of the Discovery 2 Protocol, and Reggie, so it's serialized form matters to remote foreign parties.

The current way that MarshalledInstance Serialized Form is implemented leaves it open to expansion due to readObject's implementation. This means we can add fields without breaking backward compatibility. The earlier implementations can utilise the existing fields and drop any superfluous objects they don't know about.

I'm wondering if it's possible to implement a Module URL scheme, similar to Codebase Services?

I've also wondered if using an authenticating verifier proxy during deserialization of MarshalledInstance would be enough, saving the need to Cache using the caching proxy trust verifier. MarshalledInstance is the point of contact for Discovery and Lookup, if we require authentication and verification, then that might be sufficient for MarshalledInstance. We can utilise your Module annotated streams via Jeri, unbeknown to external implementations. I've attached a very rough example draft rudimentary untested MarshalledInstance (TODO: server min principal authentication) which uses defensive copying and message digests to confirm the deserialized state as part of the private implementation of MarshalledInstance. This makes object integrity the responsibility of the Object itself, rather than an external mechanism. So in this case privacy is the responsibility of external mechanisms, but object integrity is an internal concern.

Gregg Wonderly created a CodebaseAnnotationClassloader (I think that's the name I mentioned earlier), this might remove the need to use RMIClassLoaderSPI and provide the opportunity to add some additional functionality.

See below for some more comments:

Best Regards,

Peter.

Michal Kleczek wrote:
On Thursday 28 of October 2010 10:36:38 Peter Firmstone wrote:
Ok Interesting, anyone implementing a Service should be quite capable of
implementing equals, we should then specify that equals and hashcode
methods should be implemented for ProxyTrust.

For my benefit can you go over the process of how your new code works?
I'm interested in the choices you've made & why.


Sure.
Several choices were made because:
1. I wanted to reuse as much as possible from existing River
2. I wanted to have a working prototype fast :)

Anyway:
1. The main idea is to have annotations as objects that could be verified using standard River proxy verification facilities. To be honest the idea of having Module interface that enables plugging different classloading mechanisms is something completely optional.
2. For the client the basic scenario is:
a) The client gets a serialized object
b) When it is deserialized annotations (Modules) are read and "installed"
c) Installing a Module means:
c1) checking if it was already installed
c2) if not - prepare it using VerifyingProxyPreparer (I've choosen not to place any InvocationConstraints on Modules - I don't think it is necessary)

I'm curious why client MethodConstraints aren't needed?

d) after a Module is installed it is used to load classes
3. There are several places in River that depend on annotations being Strings provided by RMIClassLoader. The most important places are
a) Discovery
b) Reggie implementation
c) ProxyTrustVerifier
Since I did not want to modify this code I had to implement RMIClassLoaderSpi so that it would provide serialized Modules as Strings. I've choosen to simply serialize them to byte arrays and Base64 encode them.
4. There are two important Module implementations available:
a) DefaultRmiModule
b) ProxyTrustModule
DefaultRmiModule can be trusted by the client without contacting any service - it uses RequireDlPermissionClassProvider to load classes. ProxyTrustModule on the other hand uses a simple PreferredClassProvider to load classes. It is a smart proxy that implements getProxyTrustIterator() so that it can be verified by ProxyTrustVerifier.

On the server before we can annotate our objects with modules we have to register a Module that will be used as annotation. If the server does not register any Module - DefaultRmiModule is going to be used as annotation. The server can register a ProxyTrustModule as annotation - to do that it first must export an object that can be contacted to obtain a Module verifier.
In other words - a service must either:
a) export two ProxyTrusts (one for Modules and another one for the service itself) b) its ProxyTrust must provide a verifier that is able to verify both a Module and a service proxy

To make it possible to use my code with existing services without modifying them I've decided to implement a special ModuleExporter which will override the service ServerProxyTrust implementation so that getProxyVerifier will return a Verifier capable of verifying both a Module and the service proxy.
This verifier works as follows:
1. Check if an object being verified is a Module.
2. If so - delegate to a module verifier
3. If not it means we're verifying a service proxy so delegate to a default verifier. The problem is though that when this verifier is deserialized the module that is capable of loading the service proxy verifier is not yet installed (the client got this verifier to verify a Module before it can load any classes). That's why I called it LazyCompositeVerifier - the default verifier is not deserialized until it is actually needed.

The only problem left is that every time a client gets a service proxy it will issue a remote call twice to get a verifier: first to get a verifier for a Module and then to get a verifier for a service proxy. But it is going to be the same verifier!!! So I've decided to implement a CachingProxyTrust that 1) can be trusted by the client without the need to issue any remote call (so we need a CachingProxyTrustVerifier configured on the client)
2) Will cache a verifier it obtains from its delegate

The problem with CachingProxyTrust is that it has to be used by:
a) a Module
b) a service proxy
That's why we need OverrideProxy - it is returned from ModuleExporter to the service so that the service is unaware of the CachingProxyTrust but still can use it to obtain its verifier. To trust OverrideProxy the client has to have OverrideProxyVerifier configured locally.

I hope I clarified everything a little bit...

Michal




/*
 * 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.io;

import com.sun.jini.config.Config;
import com.sun.jini.proxy.BasicProxyTrustVerifier;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.Serializable;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.ExportException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Permission;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;
import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.Confidentiality;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.export.Exporter;
import net.jini.export.ProxyAccessor;
import net.jini.io.context.IntegrityEnforcement;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.Endpoint;
import net.jini.jeri.ProxyTrustILFactory;
import net.jini.jeri.ServerEndpoint;
import net.jini.jeri.ssl.ConfidentialityStrength;
import net.jini.jeri.ssl.SslServerEndpoint;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.TrustVerifier;
import net.jini.security.proxytrust.ProxyTrust;
import net.jini.security.proxytrust.ProxyTrustVerifier;
import net.jini.security.proxytrust.ServerProxyTrust;

/*
 * Implementation note: This class uses the helper class
 * MarshalledObject that is in this package. To avoid confusion
 * with java.rmi.MarshalledObject the fully qualified class names
 * are used for both classes.
 */

/**
 * A <code>MarshalledInstance</code> contains an object in serialized
 * form. The contained object can be deserialized on demand when
 * explicitly requested. This allows an object to be sent from one VM
 * to another in a way that allows the receiver to control when and if
 * the object is deserialized.
 * <p>
 * The contained object is specified at construction time and can
 * either be provided in unserialized or serialized form. If provided
 * in unserialized form it will be serialized during construction
 * with the serialization semantics defined by
 * <code>MarshalOutputStream</code>. In particular, classes are annotated
 * with a codebase URL from which the class can be loaded (if available).
 * <p>
 * If the <code>MarshalledInstance</code> needs to deserialize the
 * contained object then the contained object will be deserialized with the
 * deserialization semantics defined by <code>MarshalInputStream</code>.
 * In particular, the codebase annotations associated with the contained
 * object may be used to load classes referenced by the contained object.
 * <p>
 * <code>MarshalledInstance</code> provides functionality similar to
 * <code>java.rmi.MarshalledObject</code>, but additionally provides
 * for the verification of codebase integrity. Unlike
 * <code>java.rmi.MarshalledObject</code>, it does not perform remote
 * object-to-stub replacement.
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 */
public class MarshalledInstance implements Serializable {

    /* Change this later to be read from a configuration.
     */ 
    private static volatile boolean verifyState = true;
  
    /**
     * @serial Bytes of serialized representation.  If <code>objBytes</code> is
     * <code>null</code> then the object marshalled was a <code>null</code>
     * reference.
     */  
    private byte[] objBytes = null;
 
    /**
     * @serial Bytes of location annotations, which are ignored by
     * <code>equals</code>.  If <code>locBytes</code> is null, there were no
     * non-<code>null</code> annotations during marshalling.
     */  
    private byte[] locBytes = null;
 
    /**
     * @serial Stored hash code of contained object.
     *   
     * @see #hashCode
     */  
    private int hash;

    /**
     * @serial  MarshalledInstance deserialization authentication and
     * MessageDigest based verifier, may be null.  The serialized form
     * is a remote proxy.  The creating JVM has the original remote
     * object used for authentication and verification.
     */
    /* I think the implementation needs a static weak map referencing the
     * original object, that holds a reference to a MarshalledInstance to
     * prevent unwanted garbage collection.  When the original object goes
     * away, so does the MarshalledInstance.
     */
    private AuthCheckMDIntegrity authDeserializationMDIntegrityCheck = null;
    

    static final long serialVersionUID = -5187033771082433496L;
    
    /**
     * Creates a new <code>MarshalledInstance</code> that contains the
     * marshalled representation of the current state of the supplied
     * object. The object is serialized with the semantics defined by
     * <code>MarshalOutputStream</code>. The output stream used to marshal the
     * object implements {...@link ObjectStreamContext} and returns an empty
     * collection from its {...@link ObjectStreamContext#getObjectStreamContext
     * getObjectStreamContext} method.
     *
     * @param obj The Object to be contained in the new 
     *          <code>MarshalledInstance</code>
     * @throws IOException if the object cannot be serialized
     */
    public MarshalledInstance(Object obj) throws IOException {
	this(obj, Collections.EMPTY_SET);
    }

    /**
     * Creates a new <code>MarshalledInstance</code> that contains the
     * marshalled representation of the current state of the supplied
     * object. The object is serialized with the semantics defined by
     * <code>MarshalOutputStream</code>. The output stream used to marshal the
     * object implements {...@link ObjectStreamContext} and returns the given
     * collection from its {...@link ObjectStreamContext#getObjectStreamContext
     * getObjectStreamContext} method.
     *
     * @param obj The Object to be contained in the new 
     *          <code>MarshalledInstance</code>
     * @param context the collection of context information objects
     * @throws IOException if the object cannot be serialized
     * @throws NullPointerException if <code>context</code> is <code>null</code>
     */
    public MarshalledInstance(Object obj, Collection context)
	throws IOException
    {
	if (context == null)
	    throw new NullPointerException();

	if (obj == null) {
	    hash = 13;		// null hash for java.rmi.MarshalledObject
	    return;
	}
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	ByteArrayOutputStream lout = new ByteArrayOutputStream();
	MarshalledInstanceOutputStream out =
			new MarshalledInstanceOutputStream(bout, lout, context);
	out.writeObject(obj);
	out.flush();
	objBytes = bout.toByteArray();
	// locBytes is null if no annotations
	locBytes = (out.hadAnnotations() ? lout.toByteArray() : null);

	// Calculate hash from the marshalled representation of object
	// so the hashcode will be comparable when sent between VMs.
	//
	// Note: This calculation must match the calculation in
	//	 java.rmi.MarshalledObject since we use this hash
	//	 in the converted MarshalledObject. The reverse is
	//	 also true in that we use the MarshalledObject's
	//	 hash for our hash. (see the MarshalledInstance(
	//	 MarshalledObject) constructor)
	//
	int h = 0;
	for (int i = 0; i < objBytes.length; i++) {
	    h = 31 * h + objBytes[i];
	}
	hash = h;
	createDeserializationIntegrityCheck();
    }
    
    private void createDeserializationIntegrityCheck(){
	// Should we bomb out, or just leave it without a verifier?
	try {
	    authDeserializationMDIntegrityCheck = new IntegrityCheck(objBytes, locBytes, hash ,"SHA");
	} catch (NoSuchAlgorithmException ex) {
	    Logger.getLogger(MarshalledInstance.class.getName()).log(Level.SEVERE, null, ex);
	} catch (ExportException ex) {
	    Logger.getLogger(MarshalledInstance.class.getName()).log(Level.SEVERE, null, ex);
	}
    }

    /**
     * Creates a new <code>MarshalledInstance</code> from an
     * existing <code>MarshalledObject</code>. An object equivalent
     * to the object contained in the passed <code>MarshalledObject</code>
     * will be contained in the new <code>MarshalledInstance</code>.
     * <p>
     * The object contained in the passed <code>MarshalledObject</code>
     * will not be unmarshalled as part of this call.
     *
     * @param mo The <code>MarshalledObject</code> that contains
     *        the object the new <code>MarshalledInstance</code> should
     *        contain
     * @throws NullPointerException if <code>mo</code> is <code>null</code>
     */
    public MarshalledInstance(java.rmi.MarshalledObject mo) {

	if (mo == null)
	    throw new NullPointerException();

	// To extract the java.rmi.MarshalledObject's fields we
	// convert the mo into a net.jini.io.MarshalledObject.
	// (See resolveClass() in FromMOInputStream) The private
	// version of MarshalledObject allows access to the needed
	// fields.
	//
	net.jini.io.MarshalledObject privateMO = null;
	try {
	    ByteArrayOutputStream baos = new ByteArrayOutputStream();
	    ObjectOutputStream oos = new ObjectOutputStream(baos);
	    oos.writeObject(mo);
	    oos.flush();
	    byte[] bytes = baos.toByteArray();
	    ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
	    ObjectInputStream ois = new FromMOInputStream(bais);
	    privateMO =
		(net.jini.io.MarshalledObject)ois.readObject();
	} catch (IOException ioe) {
	    throw new AssertionError(ioe);
	} catch (ClassNotFoundException cnfe) {
	    throw new AssertionError(cnfe);
	}
	objBytes = privateMO.objBytes;
	locBytes = privateMO.locBytes;
	hash = privateMO.hash;
	createDeserializationIntegrityCheck();
    }
    
    /**
     * Creates a new <code>MarshalledObject</code> that will
     * contain an object equivalent to the object contained
     * in this <code>MarshalledInstance</code> object.
     * <p>
     * The object contained in this <code>MarshalledInstance</code>
     * object will not be unmarshalled as part of this call.
     * @return A new <code>MarshalledObject</code> which
     *        contains an object equivalent to the object
     *        contained in this <code>MarshalledInstance</code>
     */
    public java.rmi.MarshalledObject convertToMarshalledObject() {

	// To create a java.rmi.MarshalledObject with previously
	// serialized data we first create a private
	// net.jini.io.MarshalledObject with the
	// data and then convert it to the final object by changing
	// the class during readObject(). (See resolveClass() in
	// ToMOInputStream)
	//
	net.jini.io.MarshalledObject privateMO =
		new net.jini.io.MarshalledObject();

	privateMO.objBytes = objBytes;
	privateMO.locBytes = locBytes;
	privateMO.hash = hash;

	java.rmi.MarshalledObject mo = null;
	try {
	    ByteArrayOutputStream baos = new ByteArrayOutputStream();
	    ObjectOutputStream oos = new ObjectOutputStream(baos);
	    oos.writeObject(privateMO);
	    oos.flush();
	    byte[] bytes = baos.toByteArray();
	    ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
	    ObjectInputStream ois = new ToMOInputStream(bais);
	    mo = (java.rmi.MarshalledObject)ois.readObject();
	} catch (IOException ioe) {
	    throw new AssertionError(ioe);
	} catch (ClassNotFoundException cnfe) {
	    throw new AssertionError(cnfe);
	}
	return mo;
    }

    /**
     * Returns a new copy of the contained object. Deserialization is
     * performed with the semantics defined by <code>MarshalInputStream</code>.
     * The input stream used to unmarshal the object implements {...@link
     * ObjectStreamContext} and returns a collection from its {...@link
     * ObjectStreamContext#getObjectStreamContext getObjectStreamContext}
     * method which contains a single element of type {...@link
     * IntegrityEnforcement}; the {...@link IntegrityEnforcement#integrityEnforced
     * integrityEnforced} method of this element returns the specified
     * <code>verifyCodebaseIntegrity</code> value.
     * <p>MarshalledInstance</code> implements this method by calling
     * <code>{...@link #get(ClassLoader, boolean, ClassLoader, Collection)
     * get}(null, verifyCodebaseIntegrity, null, null)</code>.
     *
     * @param verifyCodebaseIntegrity if <code>true</code> then
     *        codebase integrity is verified, otherwise code base
     *        integrity is not verified
     * @return a new copy of the contained object
     * @throws IOException if an 
     *         <code>IOException</code> occurs while deserializing the
     *         object from its internal representation
     * @throws ClassNotFoundException if any classes necessary
     *         for reconstructing the contained object can not
     *         be found or if <code>verifyCodebaseIntegrity</code>
     *         is <code>true</code> and the integrity of the
     *         contained object's codebase cannot be confirmed
     */
    public Object get(final boolean verifyCodebaseIntegrity) 
	throws IOException, ClassNotFoundException 
    {
	return get(null, verifyCodebaseIntegrity, null, null);
    }

    /**
     * Returns a new copy of the contained object. Deserialization is
     * performed with the semantics defined by <code>MarshalInputStream</code>.
     * If <code>context</code> is not <code>null</code>
     * the input stream used to unmarshal the object implements {...@link
     * ObjectStreamContext} and returns the given collection from its {...@link
     * ObjectStreamContext#getObjectStreamContext getObjectStreamContext}
     * method.
     * <p>If <code>context</code> is <code>null</code>
     * the input stream used to unmarshal the object implements {...@link
     * ObjectStreamContext} and returns a collection from its {...@link
     * ObjectStreamContext#getObjectStreamContext getObjectStreamContext}
     * method which contains a single element of type {...@link
     * IntegrityEnforcement}; the {...@link IntegrityEnforcement#integrityEnforced
     * integrityEnforced} method of this element returns the specified
     * <code>verifyCodebaseIntegrity</code> value.
     *
     * @param defaultLoader the class loader value (possibly
     *	      <code>null</code>) to pass as the <code>defaultLoader</code>
     *        argument to <code>RMIClassLoader</code> methods
     * @param verifyCodebaseIntegrity if <code>true</code> then
     *        codebase integrity is verified, otherwise code base
     *        integrity is not verified
     * @param verifierLoader the class loader value (possibly
     *        <code>null</code>) to pass to {...@link
     *        net.jini.security.Security#verifyCodebaseIntegrity
     *        Security.verifyCodebaseIntegrity}, if
     *        <code>verifyCodebaseIntegrity</code> is <code>true</code>
     * @param context the collection of context information objects or
     *        <code>null</code>
     * @return a new copy of the contained object
     * @throws IOException if an 
     *         <code>IOException</code> occurs while deserializing the
     *         object from its internal representation
     * @throws ClassNotFoundException if any classes necessary
     *         for reconstructing the contained object can not
     *         be found or if <code>verifyCodebaseIntegrity</code>
     *         is <code>true</code> and the integrity of the
     *         contained object's codebase cannot be confirmed
     */
    public Object get(ClassLoader defaultLoader,
		      final boolean verifyCodebaseIntegrity,
		      ClassLoader verifierLoader,
		      Collection context)
	throws IOException, ClassNotFoundException 
    {
	if (objBytes == null)   // must have been a null object
	    return null;
 
	if (context == null) {
	    context = Collections.singleton(
			new IntegrityEnforcement() {
			    public boolean integrityEnforced() {
				return verifyCodebaseIntegrity;
			    }
			} );
	}
	ByteArrayInputStream bin = new ByteArrayInputStream(objBytes);
	// locBytes is null if no annotations
	ByteArrayInputStream lin =
	    (locBytes == null ? null : new ByteArrayInputStream(locBytes));
	MarshalledInstanceInputStream in =
	    new MarshalledInstanceInputStream(bin, lin,
					      defaultLoader,
					      verifyCodebaseIntegrity,
					      verifierLoader,
					      context);
	in.useCodebaseAnnotations();
	Object obj = in.readObject();
	in.close();
	return obj;
    }

    /**
     * Compares this <code>MarshalledInstance</code> to another
     * object. Returns true if and only if the argument refers to an instance
     * of <code>MarshalledInstance</code> that contains exactly the same
     * serialized form for its contained object as this object does and
     * has the same class codebase annotations.
     *
     * @param obj the object to compare with this
     *            <code>MarshalledInstance</code>
     * @return <code>true</code> if the argument contains an object
     *         with an equivalent serialized form and codebase;
     *	       otherwise returns <code>false</code>
     */
    public boolean fullyEquals(Object obj) {
	if (equals(obj)) {
	    MarshalledInstance other = (MarshalledInstance)obj;
	    return Arrays.equals(locBytes, other.locBytes);
	}
	return false;
    }

    /**
     * Compares this <code>MarshalledInstance</code> to another
     * object. Returns true if and only if the argument refers to an instance
     * of <code>MarshalledInstance</code> that contains exactly the same
     * serialized form for its contained object as this object does. The
     * comparison ignores any class codebase annotations, so that
     * two objects can be equivalent if they have the same serialized
     * representation, except for the codebase of each class in the
     * serialized representation.
     * @param obj the object to compare with this
     *            <code>MarshalledInstance</code>
     * @return <code>true</code> if the argument contains an object
     *         with an equivalent serialized form; otherwise returns
     *         <code>false</code>
     */
    public boolean equals(Object obj) {
	if (obj == this)
	    return true;

	if (obj instanceof MarshalledInstance) {
	    MarshalledInstance other = (MarshalledInstance)obj;
	    if (hash != other.hash)
		return false;
	    return Arrays.equals(objBytes, other.objBytes);
	}
	return false;
    }

    /**
     * Returns the hash code for this <code>MarshalledInstance</code>.
     * The hash code is calculated only from the serialized form
     * of the contained object.
     * @return The hash code for this object
     */
    public int hashCode() {
	return hash;
    }

    /**
     * Verify the case of null contained object.
     */
    private void readObject(ObjectInputStream in)
	throws IOException, ClassNotFoundException
    {
	// This allows backward compatible evolution of serialized form.
	in.defaultReadObject();

	// If contained object is null, then hash and locBytes must be
	// proper
	//
	if ((objBytes == null) && ((hash != 13) || (locBytes != null))) {
	    throw new InvalidObjectException("Bad hash or annotation");
	}
	// Defensive copy mutable arrays.
	objBytes = objBytes.clone();
	locBytes = locBytes.clone();
	// During deserialization we verify the MarshalledInstance using
	// the proxy, for now until we get some proper tests up and
	// running, I'm not going to require authentication.
	// Later this will require authentication and a server
	// minimum principal.
	if (authDeserializationMDIntegrityCheck == null && verifyState == true ){
	    throw new IOException("MarshalledInstance deserialization aborted" +
		    "unable to verify state, uable to " +
		    "authenticate or prevent codebase download denial of " +
		    "service, missing deserialization integrity check proxy");
	}
	if (authDeserializationMDIntegrityCheck != null){
	    ProxyPreparer preparer = new BasicProxyPreparer();
	    // Don't overwrite proxy reference we might want to serialize it again.
	    AuthCheckMDIntegrity proxy = (AuthCheckMDIntegrity) preparer.prepareProxy(authDeserializationMDIntegrityCheck);
	    // Verify state has been transferred successfully.
	    String algorithm = proxy.getAlgorithm();
	    MessageDigest md = null;
	    try{
		md = MessageDigest.getInstance(algorithm);
	    } catch (NoSuchAlgorithmException ex) {
		if (verifyState == true){
		    throw new IOException("Unable to verify state", ex);
		} else {
		    Logger.getLogger(MarshalledInstance.class.getName()).log(
			    Level.SEVERE, null, ex);
		}
	    }
	    if ( md != null){
		md.update(objBytes);
		byte[] objMD = md.digest();
		md.reset();
		md.update(locBytes);
		byte[] locMD = md.digest();
		if ( Arrays.equals(proxy.getLocMD(), locMD) 
			&& Arrays.equals(proxy.getObjMD(), objMD)
			&& hash == proxy.getHashCode()
		    ) {
		    // It would now be safe, if we have authenticated the proxy
		    // with the minimum server principal and confirmed the 
		    // Codebase annotation is correct, to grant DownloadPermission.
		    // WE COULD ASSUME THAT DownloadPermission MUST BE GRANTED
		    // IF DESERIALIZATION HAS BEEN SUCCESSFUL.
		    // Granting DownloadPermission prevents a trusted service
		    // from returning another proxy that we don't trust, and
		    // having it's codebase downloaded automatically.
		    return;
		} else {
		    throw new IOException("MarshalledInstance serial form is corrupt");
		}
	    }	
	}
    }

    private void writeObject(ObjectOutputStream out) throws IOException{
	out.defaultWriteObject();
    }

    /**
     * Protect against missing superclass.
     */
    private void readObjectNoData() throws ObjectStreamException {
	throw new InvalidObjectException("Bad class hierarchy");
    }

    /**
     * This class is used to marshal objects for
     * <code>MarshalledInstance</code>.  It places the location annotations
     * to one side so that two <code>MarshalledInstance</code>s can be
     * compared for equality if they differ only in location
     * annotations.  Objects written using this stream should be read back
     * from a <code>MarshalledInstanceInputStream</code>.
     *   
     * @see MarshalledInstanceInputStream
     */  
    private static class MarshalledInstanceOutputStream
        extends MarshalOutputStream
    {
	/** The stream on which location objects are written. */
	private ObjectOutputStream locOut;
 
	/** <code>true</code> if non-<code>null</code> annotations are
	 *  written.
	 */
	private boolean hadAnnotations;

	/**
	 * Creates a new <code>MarshalledObjectOutputStream</code> whose
	 * non-location bytes will be written to <code>objOut</code> and whose
	 * location annotations (if any) will be written to
	 * <code>locOut</code>.
	 */
	public MarshalledInstanceOutputStream(OutputStream objOut,
					      OutputStream locOut,
					      Collection context)
	    throws IOException
	{
	    super(objOut, context);
	    this.locOut = new ObjectOutputStream(locOut);
	    hadAnnotations = false;
	}
 
	/**
	 * Returns <code>true</code> if any non-<code>null</code> location
	 * annotations have been written to this stream.
	 */
	public boolean hadAnnotations() {
	    return hadAnnotations;
	}
 
	/**
	 * Overrides <code>MarshalOutputStream.writeAnnotation</code>
	 * implementation to write annotations to the location stream.
	 */
	protected void writeAnnotation(String loc) throws IOException {
	    hadAnnotations |= (loc != null);
	    locOut.writeObject(loc);
	}

	public void flush() throws IOException {
	    super.flush();
	    locOut.flush();
	}
    }

    /**
     * The counterpart to <code>MarshalledInstanceOutputStream</code>.
     *   
     * @see MarshalledInstanceOutputStream
     */  
    private static class MarshalledInstanceInputStream
        extends MarshalInputStream
    {
	/**
	 * The stream from which annotations will be read.  If this is
	 * <code>null</code>, then all annotations were <code>null</code>.
	 */
	private ObjectInputStream locIn;
 
	/**
	 * Creates a new <code>MarshalledObjectInputStream</code> that
	 * reads its objects from <code>objIn</code> and annotations
	 * from <code>locIn</code>.  If <code>locIn</code> is
	 * <code>null</code>, then all annotations will be
	 * <code>null</code>.
	 */
	MarshalledInstanceInputStream(InputStream objIn,
				      InputStream locIn,
				      ClassLoader defaultLoader,
				      boolean verifyCodebaseIntegrity,
				      ClassLoader verifierLoader,
				      Collection context)
	    throws IOException
	{
	    super(objIn,
		  defaultLoader,
		  verifyCodebaseIntegrity,
		  verifierLoader,
		  context);
	    this.locIn = (locIn == null ? null : new ObjectInputStream(locIn));
	}
 
	/**
	 * Overrides <code>MarshalInputStream.readAnnotation</code> to
	 * return locations from the stream we were given, or <code>null</code>
	 * if we were given a <code>null</code> location stream.
	 */
	protected String readAnnotation()
	    throws IOException, ClassNotFoundException
	{
	    return (locIn == null ? null : (String)locIn.readObject());
	}
    }    

    /**
     * Input stream to convert <code>java.rmi.MarshalledObject</code>
     * into <code>net.jini.io.MarshalledObject</code>.
     */
    private static class FromMOInputStream extends ObjectInputStream {

	public FromMOInputStream(InputStream in) throws IOException {
	    super(in);
	}
 
	/**
	 * Overrides <code>ObjectInputStream.resolveClass</code> to change
	 * an occurence of class <code>java.rmi.MarshalledObject</code> to
	 * class <code>net.jini.io.MarshalledObject</code>.
	 */
	protected Class resolveClass(ObjectStreamClass desc)
	    throws IOException, ClassNotFoundException
	{
	    if (desc.getName().equals("java.rmi.MarshalledObject")) {
		return net.jini.io.MarshalledObject.class;
	    }
	    return super.resolveClass(desc);
	}
    }

    /**
     * Input stream to convert
     * <code>net.jini.io.MarshalledObject</code> into
     * <code>java.rmi.MarshalledObject</code>.
     */
    private static class ToMOInputStream extends ObjectInputStream {

	public ToMOInputStream(InputStream in) throws IOException {
	    super(in);
	}
 
	/**
	 * Overrides <code>ObjectInputStream.resolveClass</code>
	 * to change an occurence of class
	 * <code>net.jini.io.MarshalledObject</code>
	 * to class <code>java.rmi.MarshalledObject</code>.
	 */
	protected Class resolveClass(ObjectStreamClass desc)
	    throws IOException, ClassNotFoundException
	{
	    if (desc.getName().equals("net.jini.io.MarshalledObject")) {
		return java.rmi.MarshalledObject.class;
	    }
	    return super.resolveClass(desc);
	}
    }
    
    interface AuthCheckMDIntegrity {
	byte [] getLocMD() throws IOException;
	byte [] getObjMD() throws IOException;
	String getAlgorithm() throws IOException;
	int getHashCode() throws IOException;
}
    
    private static class IntegrityCheck implements AuthCheckMDIntegrity,
	    ServerProxyTrust, Remote, Serializable {
	private static final long serialVersionUID = 1L;
	private final byte[] objMD;
	private final byte[] locMD;
	private final int hash;
	private final String algorithm;
	private final Object proxy;
	IntegrityCheck(byte[] object, byte[] annotation, int hashCode,
		String algorithm)  
		throws NoSuchAlgorithmException, ExportException{
	    hash = hashCode;
	    MessageDigest md = MessageDigest.getInstance(algorithm);
	    md.update(object);
	    objMD = md.digest();
	    md.reset();
	    md.update(annotation);
	    locMD = md.digest();
	    this.algorithm = algorithm;
	    /* We could get the exporter from a configuration, but that would
	     * risk inadequate Confidentiality, worse, it may keep the vm
	     * alive while the exporter remains exported, or worse still the
	     * server may be subject to DOS attacks if DGC is enabled, 
	     * which is what we're trying to avoid in the first place, 
	     * since the proxy is used to allow the client to Authenticate 
	     * the server to prevent DOS attacks in the client, the fix would be
	     * worse than the original bug.
	     * 
	     * I'll add a configuration item later to allow for different 
	     * secure ServerEndpoint's
	     * 
	     * This exported object only remains exported while a strong
	     * reference is kept to the enclosing MarshalledInstance.  Since
	     * it is the registrar proxy (reggie) that creates a 
	     * MarshalledInstance, a reference would need to be maintained in the
	     * client.
	     */
	    Exporter exporter = 
		new BasicJeriExporter(
		    SslServerEndpoint.getInstance(0),
		    new ProxyTrustILFactory(
			new BasicMethodConstraints(
			    new InvocationConstraints( 
				new InvocationConstraint[] { 
					Confidentiality.YES,
					ConfidentialityStrength.STRONG 
				    }, null
				) //End InvocationConstraints constructor
			    )//End BasicMethodConstraints constructor
			    , null
			)//End ProxyTrustILFactory constructor
			,false, false
		    );//End BasiceJeriExporter constructor
	    proxy = exporter.export(this);    
	}

	public TrustVerifier getProxyVerifier() throws RemoteException {
	    return new BasicProxyTrustVerifier(proxy);
	    //return new ProxyTrustVerifier();
	}

	public byte[] getLocMD() throws RemoteException {
	    return locMD;
	}

	public byte[] getObjMD() throws RemoteException {
	    return objMD;
	}

	public String getAlgorithm() throws RemoteException {
	    return algorithm;
	}

	public int getHashCode() throws IOException {
	    return hash;
	}
	
	private Object writeReplace(){
	    return proxy;
	}
	
	// Prevent an attacker fabricating an instance.
	private void readObject(ObjectInputStream in) 
		throws InvalidObjectException{
	    throw new InvalidObjectException("Proxy Required");
	}
	
    }
}
# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: /opt/src/apache_river/pepe/src/net/jini/io
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and \n newlines.
# Above lines and this line are ignored by the patching process.
Index: MarshalledInstance.java
--- MarshalledInstance.java Base (BASE)
+++ MarshalledInstance.java Locally Modified (Based On LOCAL)
@@ -17,6 +17,8 @@
  */
 package net.jini.io;
 
+import com.sun.jini.config.Config;
+import com.sun.jini.proxy.BasicProxyTrustVerifier;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -28,10 +30,43 @@
 import java.io.ObjectStreamException;
 import java.io.OutputStream;
 import java.io.Serializable;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.server.ExportException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Permission;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.jini.config.Configuration;
+import net.jini.config.ConfigurationException;
+import net.jini.config.ConfigurationProvider;
+import net.jini.constraint.BasicMethodConstraints;
+import net.jini.core.constraint.Confidentiality;
+import net.jini.core.constraint.InvocationConstraint;
+import net.jini.core.constraint.InvocationConstraints;
+import net.jini.core.constraint.MethodConstraints;
+import net.jini.core.constraint.RemoteMethodControl;
+import net.jini.export.Exporter;
+import net.jini.export.ProxyAccessor;
 import net.jini.io.context.IntegrityEnforcement;
+import net.jini.jeri.BasicILFactory;
+import net.jini.jeri.BasicJeriExporter;
+import net.jini.jeri.Endpoint;
+import net.jini.jeri.ProxyTrustILFactory;
+import net.jini.jeri.ServerEndpoint;
+import net.jini.jeri.ssl.ConfidentialityStrength;
+import net.jini.jeri.ssl.SslServerEndpoint;
+import net.jini.security.BasicProxyPreparer;
+import net.jini.security.ProxyPreparer;
+import net.jini.security.ProxyPreparer;
+import net.jini.security.TrustVerifier;
+import net.jini.security.proxytrust.ProxyTrust;
+import net.jini.security.proxytrust.ProxyTrustVerifier;
+import net.jini.security.proxytrust.ServerProxyTrust;
 
 /*
  * Implementation note: This class uses the helper class
@@ -71,6 +106,10 @@
  */
 public class MarshalledInstance implements Serializable {
 
+    /* Change this later to be read from a configuration.
+     */ 
+    private static volatile boolean verifyState = true;
+  
     /**
      * @serial Bytes of serialized representation.  If <code>objBytes</code> is
      * <code>null</code> then the object marshalled was a <code>null</code>
@@ -92,6 +131,20 @@
      */  
     private int hash;
 
+    /**
+     * @serial  MarshalledInstance deserialization authentication and
+     * MessageDigest based verifier, may be null.  The serialized form
+     * is a remote proxy.  The creating JVM has the original remote
+     * object used for authentication and verification.
+     */
+    /* I think the implementation needs a static weak map referencing the
+     * original object, that holds a reference to a MarshalledInstance to
+     * prevent unwanted garbage collection.  When the original object goes
+     * away, so does the MarshalledInstance.
+     */
+    private AuthCheckMDIntegrity authDeserializationMDIntegrityCheck = null;
+    
+
     static final long serialVersionUID = -5187033771082433496L;
     
     /**
@@ -161,8 +214,20 @@
 	    h = 31 * h + objBytes[i];
 	}
 	hash = h;
+	createDeserializationIntegrityCheck();
     }
 
+    private void createDeserializationIntegrityCheck(){
+	// Should we bomb out, or just leave it without a verifier?
+	try {
+	    authDeserializationMDIntegrityCheck = new IntegrityCheck(objBytes, locBytes, hash ,"SHA");
+	} catch (NoSuchAlgorithmException ex) {
+	    Logger.getLogger(MarshalledInstance.class.getName()).log(Level.SEVERE, null, ex);
+	} catch (ExportException ex) {
+	    Logger.getLogger(MarshalledInstance.class.getName()).log(Level.SEVERE, null, ex);
+	}
+    }
+
     /**
      * Creates a new <code>MarshalledInstance</code> from an
      * existing <code>MarshalledObject</code>. An object equivalent
@@ -207,6 +272,7 @@
 	objBytes = privateMO.objBytes;
 	locBytes = privateMO.locBytes;
 	hash = privateMO.hash;
+	createDeserializationIntegrityCheck();
     }
     
     /**
@@ -425,15 +491,76 @@
     private void readObject(ObjectInputStream in)
 	throws IOException, ClassNotFoundException
     {
+	// This allows backward compatible evolution of serialized form.
 	in.defaultReadObject();
 
 	// If contained object is null, then hash and locBytes must be
 	// proper
 	//
-	if ((objBytes == null) && ((hash != 13) || (locBytes != null)))
+	if ((objBytes == null) && ((hash != 13) || (locBytes != null))) {
 	    throw new InvalidObjectException("Bad hash or annotation");
     }
+	// Defensive copy mutable arrays.
+	objBytes = objBytes.clone();
+	locBytes = locBytes.clone();
+	// During deserialization we verify the MarshalledInstance using
+	// the proxy, for now until we get some proper tests up and
+	// running, I'm not going to require authentication.
+	// Later this will require authentication and a server
+	// minimum principal.
+	if (authDeserializationMDIntegrityCheck == null && verifyState == true ){
+	    throw new IOException("MarshalledInstance deserialization aborted" +
+		    "unable to verify state, uable to " +
+		    "authenticate or prevent codebase download denial of " +
+		    "service, missing deserialization integrity check proxy");
+	}
+	if (authDeserializationMDIntegrityCheck != null){
+	    ProxyPreparer preparer = new BasicProxyPreparer();
+	    // Don't overwrite proxy reference we might want to serialize it again.
+	    AuthCheckMDIntegrity proxy = (AuthCheckMDIntegrity) preparer.prepareProxy(authDeserializationMDIntegrityCheck);
+	    // Verify state has been transferred successfully.
+	    String algorithm = proxy.getAlgorithm();
+	    MessageDigest md = null;
+	    try{
+		md = MessageDigest.getInstance(algorithm);
+	    } catch (NoSuchAlgorithmException ex) {
+		if (verifyState == true){
+		    throw new IOException("Unable to verify state", ex);
+		} else {
+		    Logger.getLogger(MarshalledInstance.class.getName()).log(
+			    Level.SEVERE, null, ex);
+		}
+	    }
+	    if ( md != null){
+		md.update(objBytes);
+		byte[] objMD = md.digest();
+		md.reset();
+		md.update(locBytes);
+		byte[] locMD = md.digest();
+		if ( Arrays.equals(proxy.getLocMD(), locMD) 
+			&& Arrays.equals(proxy.getObjMD(), objMD)
+			&& hash == proxy.getHashCode()
+		    ) {
+		    // It would now be safe, if we have authenticated the proxy
+		    // with the minimum server principal and confirmed the 
+		    // Codebase annotation is correct, to grant DownloadPermission.
+		    // WE COULD ASSUME THAT DownloadPermission MUST BE GRANTED
+		    // IF DESERIALIZATION HAS BEEN SUCCESSFUL.
+		    // Granting DownloadPermission prevents a trusted service
+		    // from returning another proxy that we don't trust, and
+		    // having it's codebase downloaded automatically.
+		    return;
+		} else {
+		    throw new IOException("MarshalledInstance serial form is corrupt");
+		}
+	    }	
+	}
+    }
 
+    private void writeObject(ObjectOutputStream out) throws IOException{
+	out.defaultWriteObject();
+    }
+
     /**
      * Protect against missing superclass.
      */
@@ -601,4 +728,100 @@
 	    return super.resolveClass(desc);
 	}
     }
+    
+    interface AuthCheckMDIntegrity {
+	byte [] getLocMD() throws IOException;
+	byte [] getObjMD() throws IOException;
+	String getAlgorithm() throws IOException;
+	int getHashCode() throws IOException;
 }
+    
+    private static class IntegrityCheck implements AuthCheckMDIntegrity,
+	    ServerProxyTrust, Remote, Serializable {
+	private static final long serialVersionUID = 1L;
+	private final byte[] objMD;
+	private final byte[] locMD;
+	private final int hash;
+	private final String algorithm;
+	private final Object proxy;
+	IntegrityCheck(byte[] object, byte[] annotation, int hashCode,
+		String algorithm)  
+		throws NoSuchAlgorithmException, ExportException{
+	    hash = hashCode;
+	    MessageDigest md = MessageDigest.getInstance(algorithm);
+	    md.update(object);
+	    objMD = md.digest();
+	    md.reset();
+	    md.update(annotation);
+	    locMD = md.digest();
+	    this.algorithm = algorithm;
+	    /* We could get the exporter from a configuration, but that would
+	     * risk inadequate Confidentiality, worse, it may keep the vm
+	     * alive while the exporter remains exported, or worse still the
+	     * server may be subject to DOS attacks if DGC is enabled, 
+	     * which is what we're trying to avoid in the first place, 
+	     * since the proxy is used to allow the client to Authenticate 
+	     * the server to prevent DOS attacks in the client, the fix would be
+	     * worse than the original bug.
+	     * 
+	     * I'll add a configuration item later to allow for different 
+	     * secure ServerEndpoint's
+	     * 
+	     * This exported object only remains exported while a strong
+	     * reference is kept to the enclosing MarshalledInstance.  Since
+	     * it is the registrar proxy (reggie) that creates a 
+	     * MarshalledInstance, a reference would need to be maintained in the
+	     * client.
+	     */
+	    Exporter exporter = 
+		new BasicJeriExporter(
+		    SslServerEndpoint.getInstance(0),
+		    new ProxyTrustILFactory(
+			new BasicMethodConstraints(
+			    new InvocationConstraints( 
+				new InvocationConstraint[] { 
+					Confidentiality.YES,
+					ConfidentialityStrength.STRONG 
+				    }, null
+				) //End InvocationConstraints constructor
+			    )//End BasicMethodConstraints constructor
+			    , null
+			)//End ProxyTrustILFactory constructor
+			,false, false
+		    );//End BasiceJeriExporter constructor
+	    proxy = exporter.export(this);    
+	}
+
+	public TrustVerifier getProxyVerifier() throws RemoteException {
+	    return new BasicProxyTrustVerifier(proxy);
+	    //return new ProxyTrustVerifier();
+	}
+
+	public byte[] getLocMD() throws RemoteException {
+	    return locMD;
+	}
+
+	public byte[] getObjMD() throws RemoteException {
+	    return objMD;
+	}
+
+	public String getAlgorithm() throws RemoteException {
+	    return algorithm;
+	}
+
+	public int getHashCode() throws IOException {
+	    return hash;
+	}
+	
+	private Object writeReplace(){
+	    return proxy;
+	}
+	
+	// Prevent an attacker fabricating an instance.
+	private void readObject(ObjectInputStream in) 
+		throws InvalidObjectException{
+	    throw new InvalidObjectException("Proxy Required");
+	}
+	
+    }
+}

Reply via email to