Simple RMI Server with SSL
Asked Answered
A

1

7

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);

    }

}
Analysand answered 5/2, 2014 at 19:28 Comment(1)
You shouldn't have edited your original post since the accepted answer makes no sense when the writer corrects your code and an external user tries to follow both the answer and the original post. You should have either made an answer yourself or at least have commented that you edited it.Feverous
A
3

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.

Adrianaadriane answered 5/2, 2014 at 21:24 Comment(6)
Thanks EJP. I tried re-generating the keys and this time importing the exported certificates into the opposite truststore. I'm now getting a signature check failed error, have enabled more ssl logging so more detail here: pastebin.com/yNaKJN2N (error towards the bottom!). I used the following script to generate keys/export/import etc: pastebin.com/Vn2uq0Vb. Any ideas?Analysand
You must have done something wrong. Can you post the exact steps you took for the server keystore and client truststore? NB Your code suggests you are using the client keystore and the server truststore: that's not correct, it should be the client truststore, and that's the one you should have exported the server certificate into.Adrianaadriane
Here is the output of the bat script I linked to so you can see my steps, as far as I know everything is okay. pastebin.com/dnTJiGrz. Additionally, I have edited my post to reflect the current code (using the filenames generated by the bat script).Analysand
EJP, I can't believe it was that simple - you are a legend, truly. I used the return of createRegistry() as opposed to calling the getRegistry() and it works a charm. And even better, I actually understand the issue! :)Analysand
Just another quick question. Should each client have a unique key/cert rather than the same keypair for every client. How would you go about facilitating that? Perhaps generate them on the server then download?Analysand
@swiss196 Normally each identity in the system will have its own key pair and certificate.Adrianaadriane

© 2022 - 2024 — McMap. All rights reserved.