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);
}
}
}