The New RMI Blog

Version 2



    RMI Security
       Security Overview
       Securing RMI
    RMI as Service
    Stubless RMI
    Conclusi on

    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 andjavax.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: [

    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 choseSSL_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_

    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'skeystore, 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 thetruststore 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 aClientKeyExchange 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 aFinished message. The server responds by sending itsChangeCipherSpec message and a Finishedmessage:

    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 akeystore for the server application and atruststore 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 thekeytool utility that is bundled with the JDK distribution. This utility can be found under$JAVA_HOME/bin. If the JDK's bindirectory 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.

    Typical RMI programming arrangement
    Figure 1. Typical RMI programming arrangement

    The CreditCardAuthorizer describes the core business operation and also extends thejava.rmi.Remote interface. TheCreditCardAuthImpl 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 inbind(). 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 theexportObject() 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("", "./resources/Server_Keystore"); System.setProperty("", "password");
    • Setting Keystore - Command Line:
      java com.article.jn.securermi.CreditCardAuthServer
    • Setting Truststore - Programmatically:
      System.setProperty("", "./resources/Client_Truststore");
    • Setting Truststore - Command Line:
      java ./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 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.

    Class diagram for Date server
    Figure 2. Class diagram for Date server

    The DateServer follows typical RMI-related programming semantics: The DateService interface specifies the business methods, and the DateServerprovides 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 staticinheritedChannel() method of the Systemclass enables the code to obtain the Channel that was inherited from the process that spawned the Java service. This method returns an instance ofjava.nio.channels.SocketChannel orjava.nio.channels.ServerSocketChannel, depending on how xinetd is configured. A SocketChannelis used to service a single incoming connection, and aServerSocketChannel is used to service multiple incoming connections. We will discuss the xinetdconfiguration 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 ofDelayedAcceptServerSocket, which is a subclass ofServerSocket. 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 theDelayedAcceptServerSocket. 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 overriddenaccept() 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/inetdconfiguration 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.

    YaST Control Center GUI
    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.

    YaST list of services
    Figure 4. YaST list of services

    Editing a service in YaST
    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= -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 byxinetd, which listens in on a specified port (9900 in this case) for client connections.

    Active listening ports
    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 notrequired 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 orjava.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.Proxyinstance (whose class is dynamically generated) with ajava.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 propertyjava.rmi.server.ignoreStubClasses to"true". If this property is set to "true," then pre-generated stub classes are never used.


    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.