Return-Path: Delivered-To: apmail-incubator-river-dev-archive@minotaur.apache.org Received: (qmail 8863 invoked from network); 17 Oct 2010 05:28:26 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 17 Oct 2010 05:28:26 -0000 Received: (qmail 9020 invoked by uid 500); 17 Oct 2010 05:28:26 -0000 Delivered-To: apmail-incubator-river-dev-archive@incubator.apache.org Received: (qmail 8875 invoked by uid 500); 17 Oct 2010 05:28:23 -0000 Mailing-List: contact river-dev-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: river-dev@incubator.apache.org Delivered-To: mailing list river-dev@incubator.apache.org Received: (qmail 8867 invoked by uid 99); 17 Oct 2010 05:28:22 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 17 Oct 2010 05:28:22 +0000 X-ASF-Spam-Status: No, hits=0.7 required=10.0 tests=RCVD_IN_DNSWL_NONE,SPF_NEUTRAL X-Spam-Check-By: apache.org Received-SPF: neutral (nike.apache.org: local policy) Received: from [61.9.189.146] (HELO nschwmtas04p.mx.bigpond.com) (61.9.189.146) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 17 Oct 2010 05:28:12 +0000 Received: from nschwotgx01p.mx.bigpond.com ([61.9.223.241]) by nschwmtas04p.mx.bigpond.com with ESMTP id <20101017052746.RJPV26932.nschwmtas04p.mx.bigpond.com@nschwotgx01p.mx.bigpond.com> for ; Sun, 17 Oct 2010 05:27:46 +0000 Received: from [10.1.1.2] (really [61.9.223.241]) by nschwotgx01p.mx.bigpond.com with ESMTP id <20101017052744.PODF8424.nschwotgx01p.mx.bigpond.com@[10.1.1.2]> for ; Sun, 17 Oct 2010 05:27:44 +0000 Message-ID: <4CBA87F4.9080201@zeus.net.au> Date: Sun, 17 Oct 2010 15:21:56 +1000 From: Peter Firmstone User-Agent: Thunderbird 2.0.0.14 (X11/20080531) MIME-Version: 1.0 To: river-dev@incubator.apache.org Subject: Re: Towards Internet Jini Services (trust) References: <4C9DB5BF.8090307@zeus.net.au> <201010111617.43428.michal.kleczek@xpro.biz> <4CB38F40.8000208@zeus.net.au> <201010120810.06313.michal.kleczek@xpro.biz> <4CBA3FB9.5010201@zeus.net.au> In-Reply-To: <4CBA3FB9.5010201@zeus.net.au> Content-Type: text/plain; charset=ISO-8859-15; format=flowed Content-Transfer-Encoding: 7bit X-RPD-ScanID: Class unknown; VirusThreatLevel unknown, RefID str=0001.0A150201.4CBA8951.0129,ss=1,fgs=0 X-Virus-Checked: Checked by ClamAV on apache.org 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 specialClasses > = new HashMap(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 dynamicGrants = new ArrayList(); > > /** > * 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 AuthMarshalInputStream that reads > * marshalled data from the specified underlying > * InputStream. > * > *

This constructor passes in to the superclass > * constructor that has an InputStream parameter. > * > *

defaultLoader will be passed as the > * defaultLoader 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}. > * > *

If verifyCodebaseIntegrity is > * true, 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, verifierLoader will be passed as the > * loader argument. See {@link > * ClassLoading#loadClass ClassLoading.loadClass} and {@link > * ClassLoading#loadProxyClass ClassLoading.loadProxyClass} for > * details of how codebase integrity verification is performed. > * > *

context 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 > * null) to pass as the defaultLoader > * argument to ClassLoading methods > * > * @param verifyCodebaseIntegrity if true, 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 > * null) to pass to > * Security.verifyCodebaseIntegrity, if > * verifyCodebaseIntegrity is true > * > * @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 > * IOException > * > * @throws SecurityException if the superclass's constructor > * throws a SecurityException > * > * @throws NullPointerException if in or > * context is null > **/ > > 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 grants = new ArrayList(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); > } > } > } > >