In my last entry, I mentioned that I had reimplemented the RMI registry portably, before discovering that there was a much simpler solution to the security problem I was addressing. Here's the reimplementation for what it's worth. It allows you to go further than the socket factory hack. And if you ever need to understand gory details of the RMI protocol, this could come in useful. Because it does much less than the full-blown RMI implementation in the JDK, it's much easier to understand.
Here are some of things you can do with a reimplemented RMI registry that you can't do with just socket factories:
"foo", the string you give would actually be
"foosecretpassword". Anything else will fail.
By the way, most of these advantages would evaporate if the registry API were augmented to allow you to supply your own Registry object to LocateRegistry.createRegistry.
My reimplementation separates out the registry functionality and the RMI reimplementation. The registry functionality is in the class RegistryImpl, which is almost completely uninteresting, except possibly for the use of ConcurrentHashMap to avoid having to synchronize explicitly across modifications.
The class RegistryServer is where the real action happens. Its public API consists of just a constructor that takes a Registry parameter (typically an instance of RegistryImpl or a subclass) and a port number. It could reasonably have a close method as well, which is left as the proverbial exercise for the reader.
At a high level, an RMI request looks like (object-id, opnum, hash). The object-idis the ObjIdof the remote object being invoked. For the RMI registry, this must be the distinguished value ObjID(REGISTRY_ID) because that is the id that LocateRegistry.getRegistry will send. Since we are not exporting any other RMI objects on our port, we don't actually need to check the id, but we do anyway.
The RMI protocol exists in two variants, the 1.1 variant that was used in JDK 1.1, and the 1.2 variant that was added in JDK 1.2. In the 1.1 variant, the opnum is an index within the methods of the given Remote interface, and the hash is a checksum or digestto make sure that both ends have the same understanding of what these methods are and what order they appear in. In the 1.2 variant, the opnum is -1 and the hash is a digest of the method signature.
For compatibility reasons, LocateRegistry.getRegistry only uses the 1.1 variant, but I have coded the server to recognize both, even though I can't think of any reason you would need the 1.2 variant in this case.
In addition to these variants, there are three communication styles. The "single op protocol" is used by RMI-over-HTTP and sends a single operation over a connection before closing it. The "stream protocol" can send any number of operations over a connection, but only one at a time. The "multiplex protocol" can send any number of operations, and can send more than one operation in parallel.
We're only interested in the stream protocol here because that's what LocateRegistry.getRegistry uses. In fact, in the JDK no RMI client will ever use the multiplex protocol, which is a pity because it would allow a more efficient use of TCP connections. The JDK can handle the multiplex protocol if it receives a client request that uses it, but will never originate such a request. That's my understanding from reading the code, at least.
One thing that is not immediately clear from the protocol specification is exactly when RMI switches over to using object serialization. You can see this in the reimplementation by looking at when we switch over from using DataInputStream and DataOutputStream to using ObjectInputStream and ObjectOutputStream.
The RMI protocol is not self-describing, in that you have to know the signature of the method being invoked in order to read its parameters and write its return value correctly. I think this is a flaw, by the way.
Another thing that is not very obvious from reading the specification is that you must include "class annotations" in the serialized data you send back, even if they are null. This is why we subclass ObjectOutputStream and override annotateClass.
I've done some basic testing of this code, but it is obviously not production-quality. Some performance tuning could be done (though performance of the registry is not usually critical). I am not sure that protocol errors will produce the same exceptions as "real" RMI, though I am not sure I care either.