Explicit Serialization API and Security
Peter Firmstone
peter.firmstone at zeus.net.au
Thu Jan 29 09:08:24 UTC 2015
Although not directly relevant to this discussion, here are some
functional examples of deserialization constructors, I now have a fully
functional validating ObjectInputStream.
Unfortunately in our project we have intra object dependencies as
demonstrated by this example; a static validator, without a constructor
falls short.
I also have an interface that provides direct access to the
ObjectInputStream before the deserializing object has been constructed.
The following two classes are a good example as they show how to check
invariants and provide full backward compatibility with existing Serial
Form. In this case validation is provided for unauthenticated network
connections, so the classes also implement standard serialization for
trusted connections, where we can relax validation.
Regards,
Peter.
/*
* 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 com.sun.jini.reggie;
import com.sun.jini.proxy.MarshalledWrapper;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.rmi.UnmarshalException;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.admin.Administrable;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceMatches;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.id.ReferentUuid;
import net.jini.id.ReferentUuids;
import net.jini.id.Uuid;
import net.jini.id.UuidFactory;
import org.apache.river.api.io.AtomicSerial;
import org.apache.river.api.io.AtomicSerial.GetArg;
import org.apache.river.api.io.AtomicSerial.ReadInput;
import org.apache.river.api.io.AtomicSerial.ReadObject;
/**
* A RegistrarProxy is a proxy for a registrar. Clients only see instances
* via the ServiceRegistrar, Administrable and ReferentUuid interfaces.
*
* @author Sun Microsystems, Inc.
*
*/
@AtomicSerial
class RegistrarProxy
implements ServiceRegistrar, Administrable, ReferentUuid, Serializable
{
private static final long serialVersionUID = 2L;
private static final Logger logger =
Logger.getLogger("com.sun.jini.reggie");
/**
* The registrar.
*
* @serial
*/
final Registrar server;
/**
* The registrar's service ID.
*/
transient ServiceID registrarID;
/**
* Returns RegistrarProxy or ConstrainableRegistrarProxy instance,
* depending on whether given server implements RemoteMethodControl.
*/
static RegistrarProxy getInstance(Registrar server,
ServiceID registrarID)
{
return (server instanceof RemoteMethodControl) ?
new ConstrainableRegistrarProxy(server, registrarID, null) :
new RegistrarProxy(server, registrarID);
}
@ReadInput
private static RO getRO(){
return new RO();
}
private static boolean check(GetArg arg) throws IOException{
Registrar server = (Registrar) arg.get("server", null);
if (server == null) throw new InvalidObjectException("null server");
RO r = (RO) arg.getReader();
if (r.registrarID == null) throw new InvalidObjectException("null
ServiceID");
return true;
}
RegistrarProxy(GetArg arg) throws IOException{
this(arg, check(arg));
}
RegistrarProxy(GetArg arg, boolean check) throws IOException{
server = (Registrar) arg.get("server", null);
RO r = (RO) arg.getReader();
registrarID = r.registrarID;
}
/** Constructor for use by getInstance(),
ConstrainableRegistrarProxy. */
RegistrarProxy(Registrar server, ServiceID registrarID) {
this.server = server;
this.registrarID = registrarID;
}
// Inherit javadoc
@Override
public Object getAdmin() throws RemoteException {
return server.getAdmin();
}
// Inherit javadoc
@Override
public ServiceRegistration register(ServiceItem srvItem,
long leaseDuration)
throws RemoteException
{
Item item = new Item(srvItem);
if (item.serviceID != null) {
Util.checkRegistrantServiceID(
item.serviceID, logger, Level.WARNING);
}
return server.register(item, leaseDuration);
}
// Inherit javadoc
@Override
public Object lookup(ServiceTemplate tmpl) throws RemoteException {
MarshalledWrapper wrapper = server.lookup(new Template(tmpl));
if (wrapper == null)
return null;
try {
return wrapper.get();
} catch (IOException e) {
throw new UnmarshalException("error unmarshalling return", e);
} catch (ClassNotFoundException e) {
throw new UnmarshalException("error unmarshalling return", e);
}
}
// Inherit javadoc
@Override
public ServiceMatches lookup(ServiceTemplate tmpl, int maxMatches)
throws RemoteException
{
return server.lookup(new Template(tmpl), maxMatches).get();
}
// Inherit javadoc
@Override
public EventRegistration notify(ServiceTemplate tmpl,
int transitions,
RemoteEventListener listener,
MarshalledObject handback,
long leaseDuration)
throws RemoteException
{
return server.notify(new Template(tmpl), transitions, listener,
handback, leaseDuration);
}
// Inherit javadoc
@Override
public Class[] getEntryClasses(ServiceTemplate tmpl)
throws RemoteException
{
return EntryClassBase.toClass(
server.getEntryClasses(new Template(tmpl)));
}
// Inherit javadoc
@Override
public Object[] getFieldValues(ServiceTemplate tmpl,
int setIndex, String field)
throws NoSuchFieldException, RemoteException
{
/* check that setIndex and field are valid, convert field to index */
ClassMapper.EntryField[] efields =
ClassMapper.getFields(
tmpl.attributeSetTemplates[setIndex].getClass());
int fidx;
for (fidx = efields.length; --fidx >= 0; ) {
if (field.equals(efields[fidx].field.getName()))
break;
}
if (fidx < 0)
throw new NoSuchFieldException(field);
Object[] values = server.getFieldValues(new Template(tmpl),
setIndex, fidx);
/* unmarshal each value, replacing with null on exception */
if (values != null && efields[fidx].marshal) {
for (int i = values.length; --i >= 0; ) {
try {
values[i] = ((MarshalledWrapper) values[i]).get();
continue;
} catch (Throwable e) {
handleException(e);
}
values[i] = null;
}
}
return values;
}
/**
* Rethrow the exception if it is an Error, unless it is a
LinkageError,
* OutOfMemoryError, or StackOverflowError. Otherwise print the
* exception stack trace if debugging is enabled.
*/
static void handleException(final Throwable e) {
if (e instanceof Error &&
!(e instanceof LinkageError ||
e instanceof OutOfMemoryError ||
e instanceof StackOverflowError))
{
throw (Error)e;
}
logger.log(Level.INFO, "unmarshalling failure", e);
}
// Inherit javadoc
@Override
public Class[] getServiceTypes(ServiceTemplate tmpl, String prefix)
throws RemoteException
{
return ServiceTypeBase.toClass(
server.getServiceTypes(new Template(tmpl),
prefix));
}
@Override
public ServiceID getServiceID() {
return registrarID;
}
// Inherit javadoc
@Override
public LookupLocator getLocator() throws RemoteException {
return server.getLocator();
}
// Inherit javadoc
@Override
public String[] getGroups() throws RemoteException {
return server.getMemberGroups();
}
// Inherit javadoc
@Override
public Uuid getReferentUuid() {
return UuidFactory.create(registrarID.getMostSignificantBits(),
registrarID.getLeastSignificantBits());
}
// Inherit javadoc
@Override
public int hashCode() {
return registrarID.hashCode();
}
/** Proxies for servers with the same service ID are considered
equal. */
@Override
public boolean equals(Object obj) {
return ReferentUuids.compare(this, obj);
}
/**
* Returns a string created from the proxy class name, the registrar's
* service ID, and the result of the underlying proxy's toString
method.
*
* @return String
*/
@Override
public String toString() {
return this.getClass().getName() + "[registrar=" + registrarID
+ " " + server + "]";
}
/**
* Writes the default serializable field value for this instance,
followed
* by the registrar's service ID encoded as specified by the
* ServiceID.writeBytes method.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
registrarID.writeBytes(out);
}
/**
* Reads the default serializable field value for this instance,
followed
* by the registrar's service ID encoded as specified by the
* ServiceID.writeBytes method. Verifies that the deserialized
registrar
* reference is non-null.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
registrarID = new ServiceID(in);
if (server == null) {
throw new InvalidObjectException("null server");
}
}
/**
* Throws InvalidObjectException, since data for this class is
required.
*/
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("no data");
}
private static class RO implements ReadObject{
ServiceID registrarID;
@Override
public void read(ObjectInput in) throws IOException,
ClassNotFoundException {
registrarID = new ServiceID(in);
}
}
}
/*
* 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 com.sun.jini.reggie;
import com.sun.jini.proxy.ConstrainableProxyUtil;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Method;
import java.rmi.MarshalledObject;
import net.jini.admin.Administrable;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.security.proxytrust.ProxyTrustIterator;
import net.jini.security.proxytrust.SingletonProxyTrustIterator;
import org.apache.river.api.io.AtomicSerial;
import org.apache.river.api.io.AtomicSerial.GetArg;
/**
* RegistrarProxy subclass that supports constraints.
*
* @author Sun Microsystems, Inc.
*
*/
@AtomicSerial
final class ConstrainableRegistrarProxy
extends RegistrarProxy implements RemoteMethodControl
{
private static final long serialVersionUID = 2L;
/** Mappings between ServiceRegistrar and Registrar methods */
static final Method[] methodMappings = {
Util.getMethod(ServiceRegistrar.class, "getEntryClasses",
new Class[]{ ServiceTemplate.class }),
Util.getMethod(Registrar.class, "getEntryClasses",
new Class[]{ Template.class }),
Util.getMethod(ServiceRegistrar.class, "getFieldValues",
new Class[]{ ServiceTemplate.class, int.class,
String.class }),
Util.getMethod(Registrar.class, "getFieldValues",
new Class[]{ Template.class, int.class, int.class }),
Util.getMethod(ServiceRegistrar.class, "getGroups", new Class[0]),
Util.getMethod(Registrar.class, "getMemberGroups", new Class[0]),
Util.getMethod(ServiceRegistrar.class, "getLocator", new Class[0]),
Util.getMethod(Registrar.class, "getLocator", new Class[0]),
Util.getMethod(ServiceRegistrar.class, "getServiceTypes",
new Class[]{ ServiceTemplate.class, String.class }),
Util.getMethod(Registrar.class, "getServiceTypes",
new Class[]{ Template.class, String.class }),
Util.getMethod(ServiceRegistrar.class, "lookup",
new Class[]{ ServiceTemplate.class }),
Util.getMethod(Registrar.class, "lookup",
new Class[]{ Template.class }),
Util.getMethod(ServiceRegistrar.class, "lookup",
new Class[]{ ServiceTemplate.class, int.class }),
Util.getMethod(Registrar.class, "lookup",
new Class[]{ Template.class, int.class }),
Util.getMethod(ServiceRegistrar.class, "notify",
new Class[]{ ServiceTemplate.class, int.class,
RemoteEventListener.class,
MarshalledObject.class, long.class }),
Util.getMethod(Registrar.class, "notify",
new Class[]{ Template.class, int.class,
RemoteEventListener.class,
MarshalledObject.class, long.class }),
Util.getMethod(ServiceRegistrar.class, "register",
new Class[]{ ServiceItem.class, long.class }),
Util.getMethod(Registrar.class, "register",
new Class[]{ Item.class, long.class }),
Util.getMethod(Administrable.class, "getAdmin", new Class[0]),
Util.getMethod(Administrable.class, "getAdmin", new Class[0])
};
/** Client constraints for this proxy, or null */
private final MethodConstraints constraints;
private static GetArg check(GetArg arg) throws IOException{
RegistrarProxy sup = new RegistrarProxy(arg);
MethodConstraints constraints = (MethodConstraints)
arg.get("constraints", null);
ConstrainableProxyUtil.verifyConsistentConstraints(
constraints, sup.server, methodMappings);
return arg;
}
ConstrainableRegistrarProxy(GetArg arg) throws IOException{
super(check(arg));
constraints = (MethodConstraints) arg.get("constraints", null);
}
/**
* Creates new ConstrainableRegistrarProxy with given server reference,
* service ID and client constraints.
*/
ConstrainableRegistrarProxy(Registrar server,
ServiceID registrarID,
MethodConstraints constraints)
{
super((Registrar) ((RemoteMethodControl) server).setConstraints(
ConstrainableProxyUtil.translateConstraints(
constraints, methodMappings)),
registrarID);
this.constraints = constraints;
}
// javadoc inherited from RemoteMethodControl.setConstraints
public RemoteMethodControl setConstraints(MethodConstraints
constraints) {
return new ConstrainableRegistrarProxy(
server, registrarID, constraints);
}
// javadoc inherited from RemoteMethodControl.getConstraints
public MethodConstraints getConstraints() {
return constraints;
}
/**
* Returns iterator used by ProxyTrustVerifier to retrieve a trust
verifier
* for this object.
*/
private ProxyTrustIterator getProxyTrustIterator() {
return new SingletonProxyTrustIterator(server);
}
/**
* Verifies that the client constraints for this proxy are
consistent with
* those set on the underlying server ref.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
ConstrainableProxyUtil.verifyConsistentConstraints(
constraints, server, methodMappings);
}
}
More information about the core-libs-dev
mailing list