Java.net - the Source for Java(tm) Technology Collaboration

## The New RMI

by Krishnan Viswanath
10/06/2005

Contents
RMI Security
Security Overview
Securing RMI
RMI as Service
Stubless RMI
Conclusion
Resources

Java Remote Method Invocation (RMI) introduced a powerful mechanism for distributing application logic across different machines. Instead of having to perform tasks in one monolithic central system, RMI made it possible to build modular and manageable applications where the computing logic could be distributed. RMI has been the primary communication mechanism for various server-side component architectures, including Enterprise JavaBeans (EJB). Introduced with Java 1.1, RMI has been steadily evolving with every major release and has seen the introduction of three new important features with the release of Java 5.0. The new features include support for dynamic stub generation, RMI over SSL, and the ability to launch a Java RMI sever as an extended Internet service `(xinetd)` daemon in Unix systems. In this article we will cover these additions.

## RMI Security

The default RMI communication mechanism—the Java Remote Method Protocol (JRMP)—is not secure. It is possible to secure the communication by writing custom socket factories using Java Secure Socket Extension (JSSE). But this approach puts the burden of writing additional code on the developers, who must take care of securing data exchange using cryptography. Java 5.0 alleviates this issue by introducing two new classes, `javax.rmi.ssl.SslRMIClientSocketFactory` and `javax.rmi.ssl.SslRMIServerSocketFactory`, that provide the ability to secure the communication channel between the client and the server using the Secure Sockets Layer (SSL)/Transport Layer Security (TLS) protocols. These socket factory classes provide a simple and elegant way to use JSSE for secure Java RMI communication, which enables enforcement of data integrity, data confidentiality (through encryption), server authentication, and (optionally) client authentication for remote method invocations. This means developers can focus on the business logic of the distributed application instead of dealing with security-related plumbing.

In this section we will go over the details of using the new SSL socket factories for RMI communication. But before we delve into details, we will provide a brief introduction on transport level security. Secure Sockets Layer (SSL) is the most widely used protocol for implementing cryptography over a distributed communication protocol such as HTTP. The primary purpose of SSL is to provide privacy, data integrity, authenticity, and non-repudiation. This is achieved by using symmetric key cryptography for data encryption between the client and server, and asymmetric key cryptography (or public/private key cryptography) to authenticate the identities of communicating parties as well as to encrypt the shared encryption key that is used during establishing SSL session.

##### Security Overview

A client and server that are about to exchange information via the SSL protocol first establish an SSL session after exchanging a series of messages, a process known as an SSL handshake. This is a multi-step process, so we will take a look at a few important messages that are sent between the client and server. The messages in the following sections are from the SSL debug output from the sample application that is provided along with this article (see Resources). The client starts the handshake process by sending a "Hello" message to the server with a list of cipher suites that it supports. This message is as follows:

    *** ClientHello, TLSv1
    RandomCookie: GMT: 1101964544 bytes = { 99,...,232 }
    Cipher Suites: [SSL_RSA_WITH_RC4_128_MD5,
    SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA,
    TLS_DHE_RSA_WITH_AES_128_CBC_SHA, ...]

The server responds with a "Hello" message along with a decision on the cipher suite and the compression method. In this particular case, the server chose `SSL_RSA_WITH_RC4_128_MD5` for cipher method and 0 for compression method.

    *** ServerHello, TLSv1
    RandomCookie:  GMT: 1101964544 bytes = { 78,...,209}
    Cipher Suite: SSL_RSA_WITH_RC4_128_MD5
    Compression Method: 0
    ***
    %% Created:  [Session-1, SSL_RSA_WITH_RC4_128_MD5]
    ** SSL_RSA_WITH_RC4_128_MD5

What this means is that the client-server communication is going to perform the following:

- Use `RSA` (Rivest Shamir Adleman) public/private key cryptography for key exchange.
- Use `RC4` (Rivest Cipher Version 4) with 128-bit encryption for data exchange.
- Use `MD5` (Message Digest Version 5) for hashing to ensure message integrity during the exchange.

After sending the "Hello" message, the server sends its certificate and the client verifies it. The certificate that is sent to the client is served up from the server's `keystore`, which is essentially a flat file that serves as a database for the contents, such as key pairs and their associated certificates or certificate chains. The client that receives the certificate from the server will pursue establishing the SSL connection only when it can authenticate the server. This trust can be established only when the client can verify the digital signature on the server's certificate. This is possible when the client trusts the server or it trusts at least one of the signers in the certificate chain provided by the server. This trust assertion is based on the certificates present in the `truststore` that the client is configured with. Essentially, `truststore` is a database of certificates or certificate chains that the client can trust. The default truststore is `cacerts`, which can be found in `$JAVA_HOME\jre\lib\security`.

Once the server certificate verification process is complete, the client uses the public key of the server to send a `ClientKeyExchange` message to the server. This message contains some random information that will be used for the generation of a symmetric key. This key will be used for encrypting the content during data exchange. The server checks to see if any other client already uses the symmetric key; if so, it will ask the client to regenerate another random key.

    *** ClientKeyExchange, RSA PreMasterSecret, TLSv1
    Random Secret:  { 3, 1, 59, 151, 102, 56, 204, 6,
    249, 100, 25, 171, 9, 221, 105, 97, 0, 106, 77,
    10, 180, 237, 222, 165, 9, 116, 216, 10, 181, 54,
    32, 244, 46, 158, 73, 18, 17, 249, 32, 254, 10,
    249, 196, 196, 185, 139, 70, 17 }

Once the client and server agree on a symmetric key, the client sends a `ChangeCipherSpec` message indicating that it is now ready to communicate; this message is followed by a `Finished` message. The server responds by sending its `ChangeCipherSpec` message and a `Finished` message:

    main, WRITE: TLSv1 Change Cipher Spec, length = 1
    [Raw write]: length = 6
    0000: 14 03 01 00 01 01
    *** Finished

The setup configuration that an application developer needs to provide for the above client-server exchange is to specify a `keystore` for the server application and a `truststore` for the client application. We will go over how to create a keystore and a truststore and once we are done with infrastructure-related aspects, we will write RMI code and integrate it in order to establish secure communication. The keystore and truststore can be created using the `keytool` utility that is bundled with the JDK distribution. This utility can be found under `$JAVA_HOME/bin`. If the JDK's bin directory is in the `PATH`, then the keytool utility can be executed from a command window or shell.

- First, we will generate a keystore that has a key pair (public and private key) along with a self-signed certificate. This is accomplished by executing the following command, which will create a file called Server_Keystore. The key algorithm is RSA, and the keysize is 1024 bits.

      $keytool -genkey -alias SecureServer
                 -keyalg RSA -keystore Server_Keystore
      Enter keystore password:  password
      What is your first and last name?
        [Unknown]:  kv
      What is the name of your organizational unit?
        [Unknown]:  IT
      What is the name of your organization?
        [Unknown]:  ABC
      What is the name of your City or Locality?
        [Unknown]:  KC
      What is the name of your State or Province?
        [Unknown]:  MO
      What is the two-letter country code for this unit?
        [Unknown]:  US
      Is CN=kv, OU=IT, O=ABC, L=KC, ST=MO, C=US correct?
        [no]:  y

      Enter key password for <SecureServer>
          (RETURN if same as keystore password):

- Next, we will examine the contents of the generated Server Keystore, which is accomplished by the following command.

      $keytool -list -v  -keystore Server_Keystore

      Enter keystore password:  password

      Keystore type: jks
      Keystore provider: SUN

      Your keystore contains 1 entry

      Alias name: secureserver
      Creation date: Aug 12, 2005
      Entry type: keyEntry
      Certificate chain length: 1
      Certificate[1]:
      Owner: CN=kv, OU=IT, O=ABC, L=KC, ST=MO, C=US
      Issuer: CN=kv, OU=IT, O=ABC, L=KC, ST=MO, C=US
      Serial number: 42fc999e
      Valid from: Fri Aug 12 07:44:14 CDT 2005
            until: Thu Nov 10 06:44:14 CST 2005
      Certificate fingerprints:
        MD5:  08:09:5D:2C:4B:28:D9:94:48:69:6D:AE:8E:
              B2:43:CB
        SHA1: 14:BE:5F:88:1F:8D:2D:04:93:F6:22:02:84:
              C0:DD:51:4F:B0:E8:97

      *******************************************
      *******************************************

  As it can be seen from the output of the above command that there is a "keyEntry" which is the private key for corresponding self signed certificate.
- We are done creating a key entry for the server, so we will shift our focus to the client side. The next step is to create a self-signed certificate and this is accomplished by executing the following commands.

      $keytool -export -alias SecureServer -keystore
                   Server_Keystore -rfc -file Server.cer
      Enter keystore password:  password
      Certificate stored in file <Server.cer>

  Just to see what the certificate looks like, we'll print to the console with the following:

      $cat Server.cer

  This will print a byte string that starts with `-----BEGIN CERTIFICATE-----` and ends with `-----END CERTIFICATE-----`

- Now that we have created a self-signed certificate, the next logical step is to import this certificate into a truststore, which then can be used by the client. This is accomplished by the following command.

      $keytool -import -alias SecureServer -file Server.cer \
              -keystore Client_Truststore
      Enter keystore password:  passsword
      Owner: CN=kv, OU=IT, O=ABC, L=KC, ST=MO, C=US
      Issuer: CN=kv, OU=IT, O=ABC, L=KC, ST=MO, C=US
      Serial number: 42fc999e
      Valid from: Fri Aug 12 07:44:14 CDT 2005
            until: Thu Nov 10 06:44:14 CST 2005
      Certificate fingerprints:
         MD5:  08:09:5D:2C:4B:28:D9:94:48:69:6D:AE:8E:
               B2:43:CB
         SHA1: 14:BE:5F:88:1F:8D:2D:04:93:F6:22:02:84:
               C0:DD:51:4F:B0:E8:97
      Trust this certificate? [no]:  y
      Certificate was added to keystore

- To verify the contents of the truststore that we created, we issue the following command. As can be seen from the output, the contents of the truststore contain a `trustedCertEntry`, which means that a private key is not available and should not be.

      keytool -list -v  -keystore Client_Truststore
      Enter keystore password:  password

      Keystore type: jks
      Keystore provider: SUN

      Your keystore contains 1 entry

      Alias name: secureserver
      Creation date: Aug 12, 2005
      Entry type: trustedCertEntry

      Owner: CN=kv, OU=IT, O=ABC, L=Kansas City, ST=MO, C=US
      Issuer: CN=kv, OU=IT, O=ABC, L=Kansas City, ST=MO, C=US
      Serial number: 42fc999e
      Valid from: Fri Aug 12 07:44:14 CDT 2005
          until: Thu Nov 10 06:44:14 CST 2005
      Certificate fingerprints:
        MD5:  08:09:5D:2C:4B:28:D9:94:48:69:6D:AE:8E:B2:43:CB
        SHA1: 14:BE:5F:88:1F:8D:2D:04:93:F6:22:02:84:C0:DD:51:4F:B0:E8:97

      *******************************************
      *******************************************

In this particular case, we are working with a self-signed certificate instead of certificates signed by Certification Authority (CA). If there is a need to get the certificate signed by a CA then a Certificate Signing Request(CSR) needs to be generated. The generated CSR, then, should to be submitted along with other pertinent information to a Certification Authority such as VeriSign or USPS, who will then digitally sign the certificate. The aspects of creating a CSR and getting it signed by a CA is beyond the scope of this article.

##### Securing RMI

We have covered all the security-related aspects. Now we will go about writing a simple secure server and a client. The server in this case is a simplified version of an RMI service that performs credit card authorization for the client. Of course, in the real world this will involve an EDI or Web service call to a credit card authorization gateway, made available by banking providers. In our case, we will just mimic this transaction by performing a simple mod 10 check and, if the check passes, we will return a good return-code; otherwise, we will return a failure return-code. This simple exercise proves the point that we need to secure the transaction because of sensitive data exchange.

The class diagram in Figure 1 provides a high-level view of the classes involved and follows the standard RMI programming semantics.

Figure 1. Typical RMI programming arrangement

The `CreditCardAuthorizer` describes the core business operation and also extends the `java.rmi.Remote` interface. The `CreditCardAuthImpl` provides an implementation for the business method `authorizeCreditCard()`. In addition, there is a `bind()` method, which takes care of the RMI-specific plumbing. Three important things happen in `bind()`. First, the remote object is exported, so it can receive incoming calls using a transport that is specified by the socket factory parameters. Next, a `Registry` object is created on the specified port. Finally, the remote object is bound to the registry. As can be seen, a standard RMI programming paradigm is followed, and the only difference is the `exportObject()` method, where the SSL version of the client and server socket factory are used as method parameters.

    javax.rmi.ssl.SslRMIClientSocketFactory;
    javax.rmi.ssl.SslRMIServerSocketFactory;
    ....
    RMIClientSocketFactory rmiClientSocketFactory =
                        new SslRMIClientSocketFactory();
    RMIServerSocketFactory rmiServerSockeyFactory =
                        new SslRMIServerSocketFactory();

    CreditCardAuthorizer ccAuth = (CreditCardAuthorizer)
          UnicastRemoteObject.exportObject(this, 0,
          rmiClientSocketFactory, rmiServerSockeyFactory);
    Registry registry = LocateRegistry.createRegistry(2004);
    registry.rebind(name, ccAuth);

Having covered the key programming aspects, the next logical step is to test the server and the client. This is where the keystore and truststore will be used. The server needs to use the keystore, and this can be accomplished in either of two ways: It can be specified as a command-line argument to the JVM, or the System property can be explicit in the application code. The following code snippets demonstrate both approaches.

- Setting Keystore - Programmatically:
  `System.setProperty("javax.net.ssl.keyStore", "./resources/Server_Keystore"); System.setProperty("javax.net.ssl.keyStorePassword", "password");`
- Setting Keystore - Command Line:
  `java -Djavax.net.ssl.keyStore=./resources/Server_Keystore -Djavax.net.ssl.keyStorePassword=password com.article.jn.securermi.CreditCardAuthServer`
- Setting Truststore - Programmatically:
  `System.setProperty("javax.net.ssl.trustStore", "./resources/Client_Truststore");`
- Setting Truststore - Command Line:
  `java -Djavax.net.ssl.trustStore= ./resources/Client_Truststore`

The client specifies the truststore in a similar fashion, except that with the truststore there is no need to specify the truststore password.

That's it! Now we can start the server and then start the client to watch the transaction run in a secure manner. To get a complete idea of what is happening under the hood, we could turn the SSL debug on and watch the SSL messages exchange between the server and the client, including some of the messages presented earlier in this article. This can be accomplishing by passing `-Djavax.net.debug=all` as a JVM parameter.

## RMI as Service

In systems like Linux or FreeBSD, Internet services such as FTP and Telnet are set up to listen in on standard, well-known ports. For instance, FTP uses port 20 and 21, and remote access using the TELNET protocol is established over port 23. Any incoming TCP connection request targeted at one of these well-known port numbers is understood to be a request for a particular service. In order to establish a Telnet session to a remote host, the client requires a hostname lookup to find the remote IP address and then implicitly connect to that host's standard telnet port: 23.

In the operating systems that are hosting the service, a program called Internet Daemon—`inetd`—is started at boot time. This program takes a list of services it has to manage from a startup configuration file. The daemon creates sockets on behalf of the services and listens in on all of them simultaneously. When an incoming connection is requested for any of these sockets, `inetd` accepts the connection and spawns the service as a child process, passing the socket to it. `inetd`, also called the "super server," returns to listening in on the socket for new connections after performing the task. However, `inetd` has a few shortcomings. It is unable to: police access control on services, prevent denial of service attacks, perform detailed logging, and distinguish virtual hosts. This has resulted in the development of a better version of Internet Service deamon as a replacement for `inetd`: `xinetd` (eXtended inetd).

With the release of Java 5.0, it is possible to expose Java services via the `(x)inetd` deamon and start Java RMI services on demand. To make this happen, an application must use specific programming semantics to ensure that the application and its constituent services can be started from `xinetd`. In this section, we will build a simple Date server that is based on the sample echo server that ships with the JDK distribution and is documented in the API documentation. This server is diagrammed in Figure 2.

Figure 2. Class diagram for Date server

The `DateServer` follows typical RMI-related programming semantics: The `DateService` interface specifies the business methods, and the `DateServer` provides implementation details. However. in this case, since the server process will be started by the `inetd` process, setting up the registry will use the new Java 5.0 feature.

The `DateServer` delegates setting up the RMI Registry to the `initializeWithInheritedChannel()` method of the `InitializeRegistry` object. At a high level, this method obtains a channel that was inherited from the entity that spawned the Java VM, and then creates a registry where the remote proxy is bound for the clients to look up. The following code snippet demonstrates how to obtain the server socket that is inherited from the process that launched this virtual machine:

    Channel channel = System.inheritedChannel();
    ServerSocket serverSocket = null;

    if (channel instanceof ServerSocketChannel) {
        serverSocket =
          ((ServerSocketChannel) channel).socket();
    }

As the code snippet shows, the static `inheritedChannel()` method of the `System` class enables the code to obtain the `Channel` that was inherited from the process that spawned the Java service. This method returns an instance of `java.nio.channels.SocketChannel` or `java.nio.channels.ServerSocketChannel`, depending on how `xinetd` is configured. A `SocketChannel` is used to service a single incoming connection, and a `ServerSocketChannel` is used to service multiple incoming connections. We will discuss the `xinetd` configuration later.

Now that we have an instance of a server socket, the next step is to create a registry and bind the remote object to the registry. This is accomplished as follows:

    RMIServerSocketFactory ssf =
        new RegistryServerSocketFactory(serverSocket);
    Registry registry =
       LocateRegistry.createRegistry(port, null, ssf);
    try {
        registry.bind(name, proxy);
    } catch (...) {...}

The registry that is created listens for incoming requests on a given port using an instance of `ServerSocket` created from the custom `RMIServerSocketFactory`. This custom server socket factory creates and returns an instance of `DelayedAcceptServerSocket`, which is a subclass of `ServerSocket`. The core idea in doing this is to ensure that this instance blocks while accepting requests from the inherited `ServerSocketChannel` until the specified proxy is completely bound in the registry. This is accomplished by overriding the `accept()` method of the `DelayedAcceptServerSocket`. This method blocks until the thread that binds the proxy to the registry notifies the blocked thread that it is okay to accept the incoming socket request. Essentially, this operation prevents the clients from getting a `java.rmi.NotBoundException` while trying to look up a proxy that is not yet bound or may be in the process of binding to the registry.

The following code snippet demonstrates the overridden `accept()` method that blocks until the thread completes the bind, which is provided in the code snippet that follows:

    public Socket accept() throws IOException {
      synchronized (lock) {
        try {
           while (!serviceAvailable) {
            lock.wait();
           }
        }catch (InterruptedException e) {
           throw (IOException)
            (new InterruptedIOException()).initCause(e);
        }
      }
      return serverSocket.accept();
    }

Here, once the bind operation finishes successfully, all the blocked threads are notified:

    Registry registry =
     LocateRegistry.createRegistry(port, null, ssf);
    try {
       registry.bind(name, proxy);
    } catch (...){
      ...
    }

    synchronized (lock) {
     serviceAvailable = true;
      lock.notifyAll();
    }

Now that we have defined our RMI code, the next step in the process is to set it up as an RMI Service. This can be accomplished two ways: editing the appropriate `xinetd/inetd` configuration files directly, or using a GUI editor that is used for manipulating the configuration files, which is typically supplied along with the OS. The GUI tool that is bundled along is dependent on the flavor of Unix or Linux in use. In this particular case, we will be using YaST, which is bundled with Novell's SuSE Linux.

Figure 3 shows the SuSE Linux Control Center, and we are interested in Network Services.

Figure 3. YaST Control Center GUI

Opening "Network Services" will result in the configuration screen shown in Figures 4 and 5, which enables you to add, edit, or delete network services, as well as turn services on or off individually.

Figure 4. YaST list of services

Figure 5. Editing a service in YaST

We add our RMI Service by providing details for the following fields.

- `Service name` - The name of a valid service.
- `Socket type` - The choices are `stream`, `dgram`, `raw`, `rdm` (reliably delivered message), or `seqpacket` (sequenced packet socket).
- `Protocol` - A protocol listed in /etc/protocols, which is some type of network protocol such as IP, ICMP, TCP, and UDP.
- `Flags` - `wait/nowait` - Wait applies to datagram sockets only. All other socket types should have the "nowait" option in this entry. Nowait entries are used for multithreaded servers that free their sockets after each request so they can continue receiving more requests on the same socket.
- `User` - The name of the user the server will run as.
- `Group` - Specify the group so that the server can run with a different group ID than the one specified in the password file for that user.
- `Server` - The path and name of the program to be executed.
- `Server Arguments` - Command-line arguments for the server program that is being run.
- `Comment` - Any relevant comment that describes what this service does.

After entering appropriate data and clicking the Accept button, a file called rmi-date-server will be created in the /etc/xinetd.d directory. The content of this file looks like:

    #RMI Date Server service rmi-date-server
    {
       socket_type  = stream
       protocol     = tcp
       wait         = no
       user         = root
       group        = users
       server       = /opt/jdk1.5.0_01/bin/java
       server_args  = -Djava.rmi.server.hostname=192.168.0.2
        -classpath /home/Article/RMI/bin
                            com.article.jn.service.DateServer
    }

Next, the `rmi-date-server` needs to be listed as a service in the /etc/services configuration file. This is accomplished by editing (using any text editor) the file to add the following entry. Note that editing this file will require root access. The format is:

    rmi-date-server <port>/<protocol>,

In this entry, `port` is the port number for the service's local registry.

Now that the configuration of `xinetd` has been modified, the service needs to be restarted to read the new configuration changes. To do this, send the `HUP` (hangup) signal to the `xinetd/inetd` process. This is accomplished by determining the process ID with following command:

    $ ps -ef | grep xinetd
    root 2332 1 0 Jun 30 ? 0:02 /usr/sbin/xinetd -s

In this case, the process ID for `xinetd` is 2332. Now, to send the hangup signal for the `xinetd` process, issue the following command:

    $su root
    $ kill -HUP 2332

Now `xinetd` is all set to launch the Java 5 RMI service when a client attempts to connect to the port configured in the `/etc/services` file. To test this out, we can run a simple client that does the following:

    Registry registry =
                    LocateRegistry.getRegistry(host, port);
    DateService proxy =  (DateService)
                    registry.lookup("ServiceInterface");
    System.out.println("received message from proxy: "
                                         + proxy.getDate());

This will result in an output to the client's standard out. As Figure 6 confirms, a java process is spawned by `xinetd`, which listens in on a specified port (9900 in this case) for client connections.

Figure 6. Active listening ports

## Stubless RMI

One of the best features of the new Java 5.0 RMI is that static generation of stubs using `rmic` is not required anymore. This feat is accomplished by the dynamic generation of stub classes at runtime. However, if the client is not running Java 5.0, then `rmic` must still be used to pre-generate stub classes for remote objects. Dynamic stub generation is made possible by two different changes that were part of previous JDK releases. First, the revision of the JRMP protocol in Java 2 enabled RMI to work without skeletons. Second, the introduction of Dynamic Proxies in Java 1.3 obviated the need for client-side stubs.

The JDK release notes provide the following explanation for stubless RMI: When an application exports a remote object (using the constructors or the static `exportObject` methods of the classes `java.rmi.server.UnicastRemoteObject` or `java.rmi.activation.Activatable`) and a pre-generated stub class for the remote object's class cannot be loaded, the remote object's stub will be a `java.lang.reflect.Proxy` instance (whose class is dynamically generated) with a `java.rmi.server.RemoteObjectInvocationHandler` as its invocation handler. An existing application can be deployed to use dynamically generated stub classes whether or not pre-generated stub classes exist by setting the system property `java.rmi.server.ignoreStubClasses` to `"true"`. If this property is set to "true," then pre-generated stub classes are never used.

## Conclusion

We zipped through the three most important and powerful features of RMI in Java 5.0. SSL security makes RMI client-server communication truly secure; the stubless RMI makes deployment a breeze; and the ability to expose RMI applications as a Unix service allows RMI services to be launched on demand.

Over a period of ten years, RMI has evolved incrementally to become a robust distributed computing technology. These additions will push forth RMI as the de facto solution when there is a need for developing distributed, loosely-coupled, object-oriented applications.

## Resources

- Sample code for this article
- Using Java's Keytool article
- Sun's Keytool reference information
- Sun's JSSE Documentation
- Sun's RMI Documentation
- RMI, Dynamic Proxies, and the Evolution of Deployment by William Grosso

Krishnan Viswanath is currently working for JP Morgan Chase & Co. in Kansas City, MO.

java.net RSS Feeds