Using the SSL/TLS-based RMI Socket Factories in J2SE 5.0

Since J2SE 5.0 client and server SSL/TLS-based RMI Socket Factories are part of the Java platform. The newly defined java package javax.rmi.ssl defines two new classes:

These two new classes allow to export SSL/TLS-protected remote objects and RMI registries in a standard and portable way. You can specify the cipher suites and protocols to be enabled and if client authentication is required by the server. You don't need anymore to implement and deploy your custom SSL/TLS-based RMI Socket Factories thus avoiding the hassle of having to add to your client classpath your custom factories.

Let's introduce the SSL/TLS-based RMI Socket Factories capabilities through an example that will be incrementally modified.

The example is comprised of the following java classes:

  • Hello: The remote interface defining a single remote method sayHello().
  • HelloImpl: The remote object implementing the Hello remote interface.
  • HelloClient: The client invoking the sayHello() remote method in the Hello remote interface.
  • RmiRegistry: This class denotes the RMI registry and allows to create it with custom factories. The RMI registry can be also created in the same JVM as HelloImpl but let's create it in a separate JVM because this will make clearer the use of SSL/TLS to export remote objects and RMI registries.

Let's have a look first at the example without any SSL/TLS protection at all.

  • Hello:
    public interface Hello extends Remote {
        public String sayHello() throws RemoteException;
    }
    
  • HelloImpl:
    public class HelloImpl extends UnicastRemoteObject implements Hello {
        public HelloImpl() throws RemoteException {
            super();
        }
        public String sayHello() {
            return "Hello World!";
        }
        public static void main(String args[]) throws Exception {
            // Get reference to the RMI registry running on port 3000 in the local host
            Registry registry = LocateRegistry.getRegistry(null, 3000);
            // Bind this object instance to the name "HelloServer"
            HelloImpl obj = new HelloImpl();
            registry.bind("HelloServer", obj);
            System.out.println("HelloServer bound in registry");
        }
    }
    
  • HelloClient:
    public class HelloClient {
        public static void main(String args[]) throws Exception {
            // Get reference to the RMI registry running on port 3000 in the local host
            Registry registry = LocateRegistry.getRegistry(null, 3000);
            // Lookup the remote reference bound to the name "HelloServer"
            Hello obj = (Hello) registry.lookup("HelloServer");
            String message = obj.sayHello();
            System.out.println(message);
        }
    }
    
  • RmiRegistry:
    public class RmiRegistry {
        public static void main(String[] args) throws Exception {
            // Start RMI registry on port 3000
            LocateRegistry.createRegistry(3000);
            System.out.println("RMI registry running on port 3000");
            // Sleep forever
            Thread.sleep(Long.MAX_VALUE);
        }
    }
    

In order to run the example open a shell window, go to the directory containing the compiled class files and call:

  • $ java RmiRegistry &
    RMI registry running on port 3000
  • $ java HelloImpl &
    HelloServer bound in registry
  • $ java HelloClient
    Hello World!

Now let's export the HelloImpl remote object with the SSL/TLS-based RMI Socket Factories using the default constructors. This means that the default protocol and cipher suites will be chosen by the default SSL socket factory implementation and only server authentication will be required. Let's assume a keystore containing a self-signed certificate has been created beforehand. Also, the server's certificate has been imported as a trusted certificate into a truststore. More detailed information about how to set up all the SSL configuration can be found in the JSSE Reference Guide. The keystore and trustore location and their related passwords are supplied in the command-line through the system properties used by the Sun's JSSE implementation:

  • javax.net.ssl.keyStore
  • javax.net.ssl.keyStorePassword
  • javax.net.ssl.trustStore
  • javax.net.ssl.trustStorePassword

When the client invokes the sayHello() method the server will send its certificate to the client. The client will then verify it against its truststore to see if it is a trusted certificate. If true, the method invocation goes on. Otherwise, the SSL handshake fails and an exception is thrown.

The following file needs to be changed as follows:

  • HelloImpl:
    public class HelloImpl extends UnicastRemoteObject implements Hello {
        public HelloImpl() throws RemoteException {
            super(0, new SslRMIClientSocketFactory(), new SslRMIServerSocketFactory());
        }
        public String sayHello() {
            return "Hello World!";
        }
        public static void main(String args[]) throws Exception {
            // Get reference to the RMI registry running on port 3000 in the local host
            Registry registry = LocateRegistry.getRegistry(null, 3000);
            // Bind this object instance to the name "HelloServer"
            HelloImpl obj = new HelloImpl();
            registry.bind("HelloServer", obj);
            System.out.println("HelloServer bound in registry");
        }
    }
    

In order to run the example open a shell window, go to the directory containing the compiled class files and call:

  • $ java -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword RmiRegistry &
    RMI registry running on port 3000
  • $ java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password HelloImpl &
    HelloServer bound in registry
  • $ java -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword HelloClient
    Hello World!

Now let's export the HelloImpl remote object with the SSL/TLS-based RMI Socket Factories which require client authentication too. Now when the client invokes the sayHello() method the server will send its certificate to the client. The client will then verify it against its truststore to see if it is a trusted certificate. What's new here is that the client has to send also a certificate to the server. The server will verify the client's certificate against its truststore in order to see if it's trusted. If both server and client authentication succeeds, the method invocation goes on. Otherwise, the SSL handshake fails and an exception is thrown.

The following file needs to be changed as follows:

  • HelloImpl:
    public class HelloImpl extends UnicastRemoteObject implements Hello {
        public HelloImpl() throws RemoteException {
            super(0, new SslRMIClientSocketFactory(),
                     new SslRMIServerSocketFactory(null, null, true));
        }
        public String sayHello() {
            return "Hello World!";
        }
        public static void main(String args[]) throws Exception {
            // Get reference to the RMI registry running on port 3000 in the local host
            Registry registry = LocateRegistry.getRegistry(null, 3000);
            // Bind this object instance to the name "HelloServer"
            HelloImpl obj = new HelloImpl();
            registry.bind("HelloServer", obj);
            System.out.println("HelloServer bound in registry");
        }
    }
    

In order to run the example open a shell window, go to the directory containing the compiled class files and call:

  • $ java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword RmiRegistry &
    RMI registry running on port 3000
  • $ java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword HelloImpl &
    HelloServer bound in registry
  • $ java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword HelloClient
    Hello World!

Now let's export the HelloImpl remote object with the SSL/TLS-based RMI Socket Factories which require client authentication and the use of the TLSv1 protocol and the SSL_RSA_WITH_RC4_128_MD5 cipher suite. Now when the client invokes the sayHello() method besides verifying the client and server certificates the SSL handshake will fail if any of the server or client JSSE implementations does not support the supplied protocol and/or cipher suite. The enabled protocols and cipher suites are specified through the SslRMIServerSocketFactory constructor in the server side and through the system properties defined by SslRMIClientSocketFactory in the client side:

  • javax.rmi.ssl.client.enabledCipherSuites
  • javax.rmi.ssl.client.enabledProtocols

The following file needs to be changed as follows:

  • HelloImpl:
    public class HelloImpl extends UnicastRemoteObject implements Hello {
        public HelloImpl() throws RemoteException {
            super(0, new SslRMIClientSocketFactory(),
                     new SslRMIServerSocketFactory(new String[] {"SSL_RSA_WITH_RC4_128_MD5"},
                                                   new String[] {"TLSv1"},
                                                   true));
        }
        public String sayHello() {
            return "Hello World!";
        }
        public static void main(String args[]) throws Exception {
            // Get reference to the RMI registry running on port 3000 in the local host
            Registry registry = LocateRegistry.getRegistry(null, 3000);
            // Bind this object instance to the name "HelloServer"
            HelloImpl obj = new HelloImpl();
            registry.bind("HelloServer", obj);
            System.out.println("HelloServer bound in registry");
        }
    }
    

In order to run the example open a shell window, go to the directory containing the compiled class files and call:

  • $ java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword -Djavax.rmi.ssl.client.enabledCipherSuites=SSL_RSA_WITH_RC4_128_MD5 -Djavax.rmi.ssl.client.enabledProtocols=TLSv1 RmiRegistry &
    RMI registry running on port 3000
  • $ java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword HelloImpl &
    HelloServer bound in registry
  • $ java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword -Djavax.rmi.ssl.client.enabledCipherSuites=SSL_RSA_WITH_RC4_128_MD5 -Djavax.rmi.ssl.client.enabledProtocols=TLSv1 HelloClient
    Hello World!

Let's finally protect the access to the RMI registry with SSL/TLS. In order to do that the methods taking as input parameters RMI socket factories in the LocateRegistry class, i.e. createRegistry and getRegistry will be used. The SSL/TLS-based RMI Socket Factories used to create the RMI registry must require client authentication as this is the only way the RMI registry can refuse requests from clients sending untrusted certificates.

The following files need to be changed as follows:

  • HelloImpl:
    public class HelloImpl extends UnicastRemoteObject implements Hello {
        public HelloImpl() throws RemoteException {
            super(0, new SslRMIClientSocketFactory(),
                     new SslRMIServerSocketFactory(null, null, true));
        }
        public String sayHello() {
            return "Hello World!";
        }
        public static void main(String args[]) throws Exception {
            // Get reference to the RMI registry running on port 3000 in the local host
            Registry registry = LocateRegistry.getRegistry(null, 3000, new SslRMIClientSocketFactory());
            // Bind this object instance to the name "HelloServer"
            HelloImpl obj = new HelloImpl();
            registry.bind("HelloServer", obj);
            System.out.println("HelloServer bound in registry");
        }
    }
    
  • HelloClient:
    public class HelloClient {
        public static void main(String args[]) throws Exception {
            // Get reference to the RMI registry running on port 3000 in the local host
            Registry registry = LocateRegistry.getRegistry(null, 3000, new SslRMIClientSocketFactory());
            // Lookup the remote reference bound to the name "HelloServer"
            Hello obj = (Hello) registry.lookup("HelloServer");
            String message = obj.sayHello();
            System.out.println(message);
        }
    }
    
  • RmiRegistry:
    public class RmiRegistry {
        public static void main(String[] args) throws Exception {
            // Start RMI registry on port 3000
            LocateRegistry.createRegistry(3000,
                                          new SslRMIClientSocketFactory(),
                                          new SslRMIServerSocketFactory(null, null, true));
            System.out.println("RMI registry running on port 3000");
            // Sleep forever
            Thread.sleep(Long.MAX_VALUE);
        }
    }
    

In order to run the example open a shell window, go to the directory containing the compiled class files and call:

  • $ java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword RmiRegistry &
    RMI registry running on port 3000
  • $ java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword HelloImpl &
    HelloServer bound in registry
  • $ java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword HelloClient
    Hello World!

Feel free to download the resource zip file in attachment and play with it or tailor it to your specific application needs.


Comments:

Hi, this piece of code is great and what i need, but i do get the probs trying to invoke the HelloClient (working with your .zip). I´ll always get: Exception in thread "main" java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is: javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate My OS: WinXP SP2, jdk 1.5.0.9. I tried creating keystore and truststore like described in the jsse ref-guide, but it´s the same. you´ve got any idea? prob in front of the computer?

Posted by jan on November 16, 2006 at 11:14 AM CET #

This looks like a configuration problem.

Did you uncomment the correct lines in the source files that match the security level you want your application to work with?

Did you invoke the RmiRegistry, HelloImpl and HelloClient with the correct security system properties?

Anyway, try to run the app with the -Djavax.net.debug=ssl option in order to get more detailed info.

Posted by Luis-Miguel Alventosa on November 17, 2006 at 03:24 AM CET #

great tutorial, helped me a lot! i've just one question: do you know a way to monitor such a ssl-handshake?

Posted by martin on April 05, 2007 at 12:07 PM CEST #

great example, If you want to start RMI registry by code how you can do?

Posted by Antonio on April 24, 2007 at 05:36 AM CEST #

If you want to use this examples in java web start how you can use certificate with hava web start

Posted by Antonio on June 08, 2007 at 02:11 AM CEST #

I wanted to have my SSL RMI connection working without using global java properties. So I wrote my custom RMIServerSocketFactory and RMIClientSocketFactory, which takes keystore location, keystore password, truststore location, and truststore password as parameters in their constructors.

I am stumped about how to write the equals() method for the client factory. It seems that on a different system the keystore or truststore could have a different location, and still make a successful connection. Should the paths and passwords be declared transient to start with...

Posted by Thomas Wang on September 11, 2007 at 02:06 AM CEST #

The main problem when writing custom RMI Client Socket Factories is that they get initialized in the server side. This means the keystore and trustore locations for the client are specified by the server unless you make this initialization happen in the client side either through system properties as is the case for the standard javax.rmi.ssl.SslRMIClientSocketFactory or by writing your custom KeyManager and TrustManager. I would say defining the client's factory fields transient is not a good idea because you lose the information store in these fields when the remote stub gets deserialized in the client side VM.

Posted by Luis-Miguel Alventosa on September 13, 2007 at 08:29 AM CEST #

Hi. I need custom SSL socket factories as well. I'm a bit confused about sockets use in this scenario. I mean, which side are ssl sockets created and what purpose are they used for?
If on the client side we call:
// Make reference to SSL-based registry
Registry registry = LocateRegistry.getRegistry(
HOST,PORT,
new RMISSLClientSocketFactory());

(being the RMISSLClientSocketFactory a custom one, taking up specific keystore and trustore) Doesn't it guarantee they're going to be used throughout the ssl session? Not even if the registry is created inside the server itself?

What if the RMISSLClientSocketFactory on the server side is a different one than the client's?

Sorry if silly questions... :)
Thanks

Posted by cesare pietra on October 04, 2007 at 04:29 AM CEST #

Hi Cesare,

You need to make the difference here between the RMI registry and your own RMI remote objects being exported with RMI SSL socket factories.

If you specify your own RMISSLClientSocketFactory in the client side to interact with the RMI registry then even if the server specified an RMISSLClientSocketFactory when creating the RMI registry it'll be ignored and the one you supplied in the client side used with its associated keystore/truststore configuration settings. But this concerns just the exchanges between the client and the RMI registry to lookup your RMI stubs.

In order to make the remote method invocations to your own RMI remote objects use SSL then you need to export them with RMI SSL socket factories in the server side and then make your RMISSLClientSocketFactory class available in your client's CLASSPATH (unless you want to enable dynamic code downloading by using a security manager). However, you won't be able to replace the RMISSLClientSocketFactory annotated by the server in your remote stub.

Regards,
Luis

Posted by Luis-Miguel Alventosa on October 04, 2007 at 09:18 AM CEST #

Thanks Luis.
It clarifies a bit! I still don't get you when you say "you won't be able to replace the RMISSLClientSocketFactory annotated by the server". The socket factory provided by the server will create SSL sockets based upon an SSL context making sense only on the server side, whereas I'd need to supply client's credentials. How can I find a workaround for that? I'm reading the JSSE ref. guide, but I still don't understand where do custom Keystore and Trustore come into the picture in this scenario?

Many thanks in advance for your help.

\\c

Posted by cesare pietra on October 05, 2007 at 02:48 AM CEST #

Hi Cesare,

Once you have retrieved the RMI stub from the RMI registry you can invoke your remote methods on the stub.

On every remote method invocation the stub will use the SSL RMIClientSocketFactory that you specified when you exported your RMI remote object on the server side.

The default javax.rmi.ssl.SslRMIClientSocketFactory relies on the default SSL socket factory settings that create SSL sockets using the keystore and truststore configuration supplied through the javax.net.ssl.keyStore\* and javax.net.ssl.trustStore\* system properties.

If you cannot implement your solution based on system properties then you will have to write your own SSL RMIClientSocketFactory that uses its own KeyManager and TrustManager.

Have a look at the JSSE Reference Guide for more detailed info on this.

Regards,
Luis

Posted by Luis-Miguel Alventosa on October 05, 2007 at 03:03 AM CEST #

Hi!
This really is a useful tutorial. But I do have a question... With my implementation I merged the part where the registry is created with the server implementation and I noticed that the same socket factories were created twice... So my question is, could the socket factories used to create the registry also be the same socket factories used to export the UnicastRemoteObject? Or does it has advantages to use separated socket factories for that?

-Glenn

Posted by Glenn Mosdall on January 30, 2008 at 08:19 AM CET #

Using the same SSL RMI socket factories is possible but in order to achieve that you will have to export your RMI remote objects on the same port, i.e. 3000 in the examples.

Try to replace 0 with 3000 in the HelloImpl constructor:

public HelloImpl() throws RemoteException {
super(3000, new SslRMIClientSocketFactory(), new SslRMIServerSocketFactory(null, null, true));
}

Posted by Luis-Miguel Alventosa on January 30, 2008 at 08:32 AM CET #

Thank you, that does work! :) But it doesn't has any disadvantages to use the socket factories like that... ?

-Glenn

Posted by Glenn Mosdall on January 30, 2008 at 08:41 AM CET #

As far as the keystore/truststore materials are the same for the RMIRegistry and the RMI remote objects you want to expose this is the best way to go. Moreover, you will only have to open a new single port in your firewall in order to access your RMI remote objects from outside.

Posted by Luis-Miguel Alventosa on January 30, 2008 at 09:49 AM CET #

Great introduction - many thanks.

Posted by Nicolas Baumgardt on March 10, 2008 at 01:37 PM CET #

Great Tutorial, thanks!
I'm now looking at how to get a hand on SSL information on the Server, for example in the HelloImpl.sayHello() to do some additional authorization. I'd need to get the client certificate there or at least its DN or something like that....? Is that possible?
Matt

Posted by Matt on April 10, 2008 at 06:45 AM CEST #

This is an excellent tutorial, you couldn't make it any clearer. Thanks a lot, this helps a lot trying to understand how this relatively new classes work.

Posted by Andrés Sánchez on July 01, 2009 at 08:27 PM CEST #

Hi Luis ,

Can you suggest a way that so that the constructor for the HelloImpl() does make a call for the creation of the SSLSocketFactories?

Basically i need to specify the SSLSocketFactories for RMI in some other place

Thanks
Anurag

Posted by Anurag on July 21, 2010 at 02:25 PM CEST #

Hi Luis,

This was really awsome !!! I was having a very bad time trying to get rid of "bad_certificate". There is a post in Oracle Forums on RMI and no one couldn't find any mistake, maybe you can take a look :D I'll try to take your code as reference to make better mine.

Easy and really useful that's how a tutorial is made :D
Congrats. !

Posted by Federico Martinez Lopez on November 28, 2010 at 03:10 AM CET #

Post a Comment:
  • HTML Syntax: NOT allowed
About

lmalvent

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today