river-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Peter Firmstone <j...@zeus.net.au>
Subject The internet - MarshalledInstance Denial of Service Attack
Date Sat, 05 Feb 2011 10:43:27 GMT
This is a draft for MarshalledInstance, to prevent codebase download 
DOS, it includes a reflective proxy verifier with MarshalledInstance to 
verify the codebase annotation.

Please suggest improvements.


 /*
 * 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.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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
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.export.Exporter;
import net.jini.io.context.IntegrityEnforcement;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.ProxyTrustILFactory;
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.ServerProxyTrust;
import org.apache.river.impl.util.ConcurrentWeakMap;

/*
 * 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;
    /* Hold references to a constructed MarshalledInstance until the object
     * key is garbage collected.  It may need to hold duplicates if
     * more than one marshalled instance is created with different 
annotations.
     * MarshalledInstance references are typically thrown away soon after
     * serialization, so this pool is here to retain a reference to
     * the deserialization verifier, while the object it holds hasn't been
     * garbage collected.
     * DGC cannot be used since it would pose a Denial Of Service Risk.
     */
    private static final ConcurrentMap<Object, Cup> pool = new 
ConcurrentWeakMap<Object, Cup>();

    /* Add an Object key, MarshalledInstance value to the pool if it
     * doesn't already exist, if it does, return the value in the pool
     * this will be used to avoid duplicating the exported MD.
     * Duplicates may exist where the MarshalledInstance is equal but not
     * fullyEqual.  This prevents an explosion of MarshalledInstance
     * objects when they are created often.
     */
    private static MarshalledInstance addToPool(Object o, 
MarshalledInstance i) {
        Cup c = pool.get(o);
        if (c == null) {
            c = new Cup();
            Cup existed = pool.putIfAbsent(o, c);
            if (existed != null) {
                c = existed;
            }
        }
        MarshalledInstance existed = c.putIfAbsent(i);
        return existed;
    }
    /**
     * @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();
        MarshalledInstance exists = addToPool(obj, this);
        if (exists != null) {
            // we just created a duplicate marshalled instance best we share
            // the deserialization check so we don't have to retain too many
            // unnecessary strong references.
            authDeserializationMDIntegrityCheck = 
exists.authDeserializationMDIntegrityCheck;
        }
    }

    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;
    // We shouldn't create a verifier proxy for MarshalledInstance
    // created from a MarshalledObject, since the actual object may
    // not be local, how can we vouch for it if we don't have the
    // original object?
//    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");
        }
    }

    /* Stores unique marshalled Instance references, as defined by 
fullyEquals()
     * duplicates are only added if they are not fullyEqual.
     */
    private static class Cup {

        List<MarshalledInstance> items = new 
ArrayList<MarshalledInstance>(2);

        MarshalledInstance putIfAbsent(MarshalledInstance t) {
            synchronized (items) {
                Iterator<MarshalledInstance> it = items.iterator();
                while (it.hasNext()) {
                    MarshalledInstance i = it.next();
                    if (t.fullyEquals(i)) {
                        return i;
                    }
                }
                items.add(t);
                return null;
            }
        }
    }
}

Mime
View raw message