Code Signing has the additional advantage of preventing classes with different Certificate's from sharing a package. It would also allow service providers to sign their jar files to prevent them being shared with other services.

Cheers,

Peter.

Peter Firmstone wrote:
Peter Firmstone wrote:
Note this code doesn't protect against DNS cache poisoning attacks. We could easily extend this to require the jar file be signed as per Sim's suggestion, by a Certificate[] known to the Service, currently set to null in the CodeSource grant, easily passed into the constructor or perhaps as static list, or perhaps even supplied by the authentication proxy.

The CodeSource (containing Certificate[]'s) PermissionGrant, would contain something like a ClassLoadingPermission.

The DownloadPermission protects against unauthorised download, while the ClassLoadingPermission would protect against unauthorised class loading.

The ClassLoadingPermission would only apply if the downloaded codebase is signed by the required certificates, provided in advance by the proxy.

Any untrusted public http codebase server will be suitable.

If we isolate unmarshalling to an Executor Service Thread, handling error conditions like StackOverflowError, were starting to look relatively secure. We can have Socket time-outs too. The Executor service thread doesn't run untrusted code, it's only really protecting against an untrusted http codebase from supplying excessively large files.

Cheers,

Peter.

The Certificate[] would guarantee that a DNS cache poisoning attack couldn't work.

/*
* 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 org.apache.river.imp.security.io;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Proxy;
import java.util.Collection;
import net.jini.io.MarshalOutputStream;

/**
* This code was inspired by Michal Kleczek's suggestions for solving the
* DOS hole during unmarshalling of untrusted code.
*
* @author Peter Firmstone
*/
public class AuthMarshalOutputStream extends MarshalOutputStream {
   private final Proxy proxy;
     public AuthMarshalOutputStream(OutputStream out,
                   Collection context,
                   Proxy authenticationProxy)
   throws IOException
   {
   super(out, context);
   if (authenticationProxy == null) {
       throw new NullPointerException("Null Authentication Proxy");
   }
   proxy = authenticationProxy;
   }
     @Override
protected void writeAnnotation(String annotation) throws IOException {
   writeObject(annotation);
   writeObject(proxy);
   }

}


/*
* 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 org.apache.river.imp.security.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectStreamClass;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.CodeSource;
import java.security.Policy;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import net.jini.io.MarshalInputStream;
import net.jini.loader.ClassLoading;
import net.jini.loader.DownloadPermission;
import net.jini.security.ProxyPreparer;
import org.apache.river.api.security.PermissionGrant;
import org.apache.river.api.security.PermissionGrantBuilder;
import org.apache.river.api.security.RevokeableDynamicPolicy;

/**
* AuthMarshalInputStream requires the AuthMarshalOutputStream to send a proxy * in order to authenticate itself, it is best if the size of this proxy is * kept to a minimum by only implementing ProxyPreparer and RemoteMethodControl.
*
* Once the remote end has authenticated, the Annotation string can be read
* and a Class file returned.
*
* The AuthMarshalInputStream will dynamically grant DownloadPermission to the
* each URL CodeSource if the Server authenticates itself.
*
* Note that the DownloadPermission is not granted to the Server Principal
* but to the CodeSource that the Server wants the client to download.
*
* Authentication is only used to determine if we trust the server to inform * us of a suitable URL for download. Once DownloadPermission has been granted
* to the CodeSource, the codebase (jar file) can be downloaded.
*
* Authentication is only performed once for each codebase String.
*
* @author Peter Firmstone
*/
public class AuthMarshalInputStream extends MarshalInputStream {

   /**
    * maps keywords for primitive types and void to corresponding
    * Class objects
    **/
   private static final Map<String,Class> specialClasses
       = new HashMap<String,Class>(9);
   static {
   specialClasses.put("boolean", boolean.class);
   specialClasses.put("byte", byte.class);
   specialClasses.put("char", char.class);
   specialClasses.put("short", short.class);
   specialClasses.put("int", int.class);
   specialClasses.put("long", long.class);
   specialClasses.put("float", float.class);
   specialClasses.put("double", double.class);
   specialClasses.put("void", void.class);
   }
private static final DownloadPermission[] perm = {new DownloadPermission()}; private static List<String> dynamicGrants = new ArrayList<String>();

   /**
    * value to pass as the "default loader" argument to loadClass and
    * loadProxyClass
    **/
   private final ClassLoader defaultLoader;

   /** true if this stream verifies codebase integrity */
   private final boolean verifyCodebaseIntegrity;

   /** loader to pass to Security.verifyCodebaseIntegrity */
   private final ClassLoader verifierLoader;

   /**
    * if false, pass null codebase values to loadClass and
    * loadProxyClass methods; if true, pass codebase values from
    * stream class annotations
    **/
   private boolean usingCodebaseAnnotations;
     private final ProxyPreparer preparer;
/**
    * Creates a new <code>AuthMarshalInputStream</code> that reads
    * marshalled data from the specified underlying
    * <code>InputStream</code>.
    *
    * <p>This constructor passes <code>in</code> to the superclass
    * constructor that has an <code>InputStream</code> parameter.
    *
    * <p><code>defaultLoader</code> will be passed as the
    * <code>defaultLoader</code> argument to {...@link
    * ClassLoading#loadClass ClassLoading.loadClass} and {...@link
    * ClassLoading#loadProxyClass ClassLoading.loadProxyClass}
    * whenever those methods are invoked by {...@link #resolveClass
    * resolveClass} and {...@link #resolveProxyClass resolveProxyClass}.
    *
    * <p>If <code>verifyCodebaseIntegrity</code> is
    * <code>true</code>, then the created stream will verify that all
    * codebase annotation URLs that are used to load classes resolved
    * by the stream provide content integrity, and whenever {...@link
    * Security#verifyCodebaseIntegrity
    * Security.verifyCodebaseIntegrity} is invoked to enforce that
    * verification, <code>verifierLoader</code> will be passed as the
    * <code>loader</code> argument.  See {...@link
    * ClassLoading#loadClass ClassLoading.loadClass} and {...@link
    * ClassLoading#loadProxyClass ClassLoading.loadProxyClass} for
    * details of how codebase integrity verification is performed.
    *
    * <p><code>context</code> will be used as the return value of the
    * created stream's {...@link #getObjectStreamContext
    * getObjectStreamContext} method.
    *
    * @param in the input stream to read marshalled data from
    *
    * @param defaultLoader the class loader value (possibly
    * <code>null</code>) to pass as the <code>defaultLoader</code>
    * argument to <code>ClassLoading</code> methods
    *
    * @param verifyCodebaseIntegrity if <code>true</code>, this
    * stream will verify that codebase annotation URLs used to load
    * classes resolved by this stream provide content integrity
    *
    * @param verifierLoader the class loader value (possibly
    * <code>null</code>) to pass to
    * <code>Security.verifyCodebaseIntegrity</code>, if
    * <code>verifyCodebaseIntegrity</code> is <code>true</code>
    *
    * @param context the collection of context information objects to
    * be returned by this stream's {...@link #getObjectStreamContext
    * getObjectStreamContext} method
    *
    * @param preparer the proxy preparer used to authenticate the server
    * prior to downloading any classes.
    *
    * @throws IOException if the superclass's constructor throws an
    * <code>IOException</code>
    *
    * @throws SecurityException if the superclass's constructor
    * throws a <code>SecurityException</code>
    *
    * @throws NullPointerException if <code>in</code> or
    * <code>context</code> is <code>null</code>
    **/

   public AuthMarshalInputStream(InputStream in,
                 ClassLoader defaultLoader,
                 boolean verifyCodebaseIntegrity,
                 ClassLoader verifierLoader,
                 Collection context,
                 ProxyPreparer preparer )
   throws IOException
   {
   super (in, defaultLoader, verifyCodebaseIntegrity, verifierLoader,
       context);
   this.defaultLoader = defaultLoader;
   this.verifyCodebaseIntegrity = verifyCodebaseIntegrity;
   this.verifierLoader = verifierLoader;
   this.preparer = preparer;
   }

   // Inherit documentation from MarshalInputStream
   @Override
   public void useCodebaseAnnotations() {
   usingCodebaseAnnotations = true;
   super.useCodebaseAnnotations();
   }

   // Inherit documentation from MarshalInputStream
   @Override
   protected Class resolveClass(ObjectStreamClass classDesc)
   throws IOException, ClassNotFoundException
   {
   if (classDesc == null) {
       throw new NullPointerException();
   }

   // must always consume annotation written by MarshalOutputStream
   String annotation = readAnnotation();
   String codebase = usingCodebaseAnnotations ? annotation : null;
   authenticate(codebase);
   String name = classDesc.getName();
   try {
       return ClassLoading.loadClass(codebase,
                     name,
                     defaultLoader,
                     verifyCodebaseIntegrity,
                     verifierLoader);
   } catch (ClassNotFoundException e) {
       Class c = specialClasses.get(name);
       if (c != null) {
       return c;
       } else {
       throw e;
       }
   }
   }

   // Inherit documentation from MarshalInputStream
   @Override
   protected Class resolveProxyClass(String[] interfaceNames)
   throws IOException, ClassNotFoundException
   {
   for (int i = 0; i < interfaceNames.length; i++) {
       if (interfaceNames[i] == null) {
       throw new NullPointerException();
       }
   }

   // must always consume annotation written by MarshalOutputStream
   String annotation = readAnnotation();
   String codebase = usingCodebaseAnnotations ? annotation : null;
   authenticate(codebase);
   return ClassLoading.loadProxyClass(codebase,
                      interfaceNames,
                      defaultLoader,
                      verifyCodebaseIntegrity,
                      verifierLoader);
   }
     private void authenticate(String codebase)
       throws IOException, ClassNotFoundException{
   // Always read the proxy from the AuthMarshalInputStream
   Object proxy = readObject();
   if (codebase == null) return;
   if (dynamicGrants.contains(codebase)) return;
   try {
       //Authenticate
preparer.prepareProxy(proxy); // Dynamically Grant DownloadPermission for each URL via a
       // CodeSource grant.
       Policy policy = Policy.getPolicy();
       if (policy instanceof RevokeableDynamicPolicy){
StringTokenizer st = new StringTokenizer(codebase); // divide by spaces
       URL[] urls = new URL[st.countTokens()];
       for (int i = 0; st.hasMoreTokens(); i++) {
           urls[i] = new URL(st.nextToken());
       }
       PermissionGrantBuilder pgb
           = ((RevokeableDynamicPolicy) policy).getGrantBuilder();
       pgb.permissions(perm);
       int l = urls.length;
       List<PermissionGrant> grants = new ArrayList<PermissionGrant>(l);
       for (int i = 0; i < l; i++){
CodeSource cs = new CodeSource(urls[i], (Certificate[]) null);
           PermissionGrant pg = pgb.codeSource(cs).build();
           grants.add(pg);
       }
       ((RevokeableDynamicPolicy) policy).grant(grants);
       }
       dynamicGrants.add(codebase);
   } catch (SecurityException e) {
       throw new IOException(e);
   } catch (MalformedURLException e){
       throw new IOException(e);
   }
   }
}





Reply via email to