Question

Trying to setup a simple RMI server with SSL encryption. It's for a simple chat application that has a java server app and a java client app, however, I can't even get it working with a simple RMI example at the moment!

The only way I can get it to work is if both the client & server have both the same truststore & keystore. To me though, this sounds incorrect as it means each client has the server's private key too..

I followed this guide to create the trust/keystores. I first tried generating a keystore & truststore and just running the server with the keystore & the client with the truststore. That didn't work so I then generated a pair for each and loaded as shown in the code below.

It think I might be missing something obvious somewhere just can't for the life of my figure out what I'm doing wrong. I currently have the following, but when running the server I get the errors below:

Error:

Server exception: java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is: 
    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is: 
    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.rmi.transport.tcp.TCPChannel.createConnection(Unknown Source)
    at sun.rmi.transport.tcp.TCPChannel.newConnection(Unknown Source)
    at sun.rmi.server.UnicastRef.newCall(Unknown Source)
    at sun.rmi.registry.RegistryImpl_Stub.bind(Unknown Source)
    at Server.main(Server.java:38)

Hello.java

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {

    String sayHello() throws RemoteException;

}

Server.java

import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;


public class Server extends UnicastRemoteObject implements Hello {

    private static final long serialVersionUID = 5186776461749320975L;

    protected Server(int port) throws IOException {

        super(port, new SslRMIClientSocketFactory(), new SslRMIServerSocketFactory(null, null, true));      
    }

    @Override
    public String sayHello() {
        return "Hello, world!";
    }

    public static void main(String[] args) throws RemoteException, IllegalArgumentException {

        try {           

            setSettings();

            Server server = new Server(2020);

            LocateRegistry.createRegistry(2020, new SslRMIClientSocketFactory(), new SslRMIServerSocketFactory(null, null, true));
            System.out.println("RMI registry running on port " + 2020);             

            Registry registry = LocateRegistry.getRegistry("DAVE-PC", 2020, new SslRMIClientSocketFactory());

            registry.bind("Hello",  server);

        } catch (Exception e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }

    }

    private static void setSettings() {

        String pass = "password";

        System.setProperty("javax.net.ssl.debug", "all");

    System.setProperty("javax.net.ssl.keyStore", "C:\\ssl\\serverkeystore.jks");
    System.setProperty("javax.net.ssl.keyStorePassword", pass);
    System.setProperty("javax.net.ssl.trustStore", "C:\\ssl\\servertruststore.jks");
    System.setProperty("javax.net.ssl.trustStorePassword", pass);




    }

}

Client.java

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.rmi.ssl.SslRMIClientSocketFactory;

public class Client {

    private Client() {}

    public static void main(String[] args) {        

        try {

            setSettings();  

            Registry registry = LocateRegistry.getRegistry("DAVE-PC", 2020, new SslRMIClientSocketFactory());

            Hello hello = (Hello) registry.lookup("Hello");

            String message = hello.sayHello();

            System.out.println(message);            

        } catch (Exception e) {
            System.err.println("Client exception: " + e.toString());
            e.printStackTrace();
        }
    }

private static void setSettings() {

        String pass = "password";
        System.setProperty("javax.net.ssl.debug", "all");
    System.setProperty("javax.net.ssl.keyStore", "C:\\ssl\\clientkeystore.jks");
    System.setProperty("javax.net.ssl.keyStorePassword", pass);
    System.setProperty("javax.net.ssl.trustStore", "C:\\ssl\\clienttruststore.jks");
    System.setProperty("javax.net.ssl.trustStorePassword", pass);

    }

}
Was it helpful?

Solution

The PKIX error means that the client didn't trust the server certificate, where the server in this case was the Registry.

To clarify, you need two private keys and two keystores to hold them in, one each. You then need to create certificates in each keystore, export them, and import them into the peer's truststore. The server's truststore must trust the client's keystore, and vice versa.

Your code looks mostly OK. The result of createRegistry() should be stored in a static variable, to prevent it being GC'd. You don't need a serialVersionUID in the server class, whatever your IDE may tell you. It doesn't get serialized, at least not by RMI.

EDIT The problem is here:

System.setProperty("javax.net.ssl.keyStore", "C:\\ssl\\keystore-server.jks");
System.setProperty("javax.net.ssl.trustStore", "C:\\ssl\\truststore-client.jks");

which should be:

System.setProperty("javax.net.ssl.keyStore", "C:\\ssl\\keystore-server.jks");
System.setProperty("javax.net.ssl.trustStore", "C:\\ssl\\truststore-server.jks");

and here:

System.setProperty("javax.net.ssl.keyStore", "C:\\ssl\\keystore-client.jks");
System.setProperty("javax.net.ssl.trustStore", "C:\\ssl\\truststore-server.jks"

which should be:

System.setProperty("javax.net.ssl.keyStore", "C:\\ssl\\keystore-client.jks");
System.setProperty("javax.net.ssl.trustStore", "C:\\ssl\\truststore-client.jks"

EDIT 2 The underlying problem is that the trust store you need when binding to the Registry is the client truststore, but the truststore you need when running the server is the server truststore.

There are at least three possible solutions, in increasing order of merit:

  1. Set up a subclass of SslRMIClientSocketFactory with its own SSLContext with its own TrustManager loaded from the client truststore, and override createSocket(). Ouch.

  2. Import the server's certificate into the server's truststore as well.

  3. Use the return value of createRegistry() to do the bind() instead of calling getRegistry() in the server at all, and avoid the whole problem.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top