I have a program, the program has two runnable files one called ServerImpl and the other called ClientImpl. My aim is to be able to have the ClientImpl connect to the ServerImpl using RMI with SSL and invoke a method. The server should also be able to call methods on the client using RMI and SSL via a callback. I can get the client to connect to the server using SSL and invoke methods on that server, however I can't get the Server to connect back to the client using RMI with SSL.
The program also has two directories that contain two different sets of cert, keystore, truststore files:
resources/client/
client_cert.cer
client_keystore.jks
client_truststore.jks
resources/server/
server_cert.cer
server_keystore.jks
server_truststore.jks
The ServerImpl.java file:
public class ServerImpl implements ServerInt {
private ClientInt client;
public static void main(String[] args) {
ServerImpl server = new ServerImpl();
server.bind();
}
public void bind() {
System.setProperty("java.rmi.server.hostname", "192.168.0.32");
System.setProperty("javax.net.ssl.keyStore", "./resources/server/server_keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
RMIClientSocketFactory rmiClientSocketFactory = new SslRMIClientSocketFactory();
RMIServerSocketFactory rmiServerSocketFactory = new SslRMIServerSocketFactory();
try {
ServerInt si = (ServerInt) UnicastRemoteObject.exportObject(this, 0, rmiClientSocketFactory, rmiServerSocketFactory);
Registry reg = LocateRegistry.createRegistry(1099);
reg.rebind("server", si);
System.out.println("RMIServer is bound in registry");
} catch (RemoteException ex) {
Logger.getLogger(ServerImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void connect(ClientInt ci) throws RemoteException {
System.out.println("client connected");
}
}
The ClientImpl.java file:
public class ClientImpl implements ClientInt {
private ServerInt server;
public static void main(String[] args) {
ClientImpl client = new ClientImpl();
client.bind();
client.initConnection();
client.connectToServer();
}
public void initConnection() {
System.setProperty("javax.net.ssl.trustStore", "./resources/client/client_truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
try {
Registry reg = LocateRegistry.getRegistry("192.168.0.32", 1099);
server = (ServerInt) reg.lookup("server");
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
} catch (NotBoundException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void bind() {
System.setProperty("java.rmi.server.hostname", "192.168.0.32");
System.setProperty("javax.net.ssl.keyStore", "./resources/client/client_keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
RMIClientSocketFactory rmiClientSocketFactory = new SslRMIClientSocketFactory();
RMIServerSocketFactory rmiServerSockeyFactory = new SslRMIServerSocketFactory();
try {
ClientInt ci = (ClientInt) UnicastRemoteObject.exportObject(this, 0, rmiClientSocketFactory, rmiServerSockeyFactory);
Registry reg = LocateRegistry.createRegistry(5001);
reg.rebind("client", ci);
System.out.println("RMIClient is bound in registry");
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void connectToServer() {
try {
server.connect(this);
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void sayHelloToClient(String helloText) throws RemoteException {
System.out.println(helloText);
}
}
I then run the ServerImpl.java file, and no problems it runs fine. Then I run the ClientImpl.java file and I get an error when I call the connectToServer method:
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
Can anyone shed some light on what the problem is here and how I might be able to solve it? Failing that, can anyone point me towards a good tutorial on having two RMI entities that use SSL to talk to each other? Thanks.
Ok I've figured it out. The problem was that the respective client/server java files were still using the default Java TrustStore and not the custom Truststore files that I had defined in the original problem code. Here is the correct code in full for any one else who is looking for a simple demonstration of a Two-Way RMI Client-Server connection using SSL.
After creating a blank Java Project, Add in a 'resources' folder into the top level directory that has two sub directories 'client' and 'server'. Then generate two separate sets of certificates, keystores, and truststores and put them in the respective sub directories like so:
resources/client/
client_cert.cer
client_keystore.jks
client_truststore.jks
resources/server/
server_cert.cer
server_keystore.jks
server_truststore.jks
Then create an interface for the server called 'ServerInt':
public interface ServerInt extends Remote {
public void connect(ClientInt ci) throws RemoteException;
}
Another interface for the client called 'ClientInt':
public interface ClientInt extends Remote {
public void sayHelloToClient(String helloText) throws RemoteException;
}
Now create a new java class for the server called 'ServerImpl':
public class ServerImpl implements ServerInt {
public static void main(String[] args) {
ServerImpl server = new ServerImpl();
server.bind();
}
public void bind() {
// System.setProperty("javax.net.debug", "all");
System.setProperty("javax.net.ssl.trustStore", "./resources/server/server_truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("java.rmi.server.hostname", "192.168.0.32");
System.setProperty("javax.net.ssl.keyStore", "./resources/server/server_keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
RMIClientSocketFactory rmiClientSocketFactory = new SslRMIClientSocketFactory();
RMIServerSocketFactory rmiServerSocketFactory = new SslRMIServerSocketFactory();
try {
// Uncomment this line...
//ServerInt si = (ServerInt) UnicastRemoteObject.exportObject(this, 0);
// and then comment out this line to turn off SSL (do the same in the ClientImpl.java file)
ServerInt si = (ServerInt) UnicastRemoteObject.exportObject(this, 0, rmiClientSocketFactory, rmiServerSocketFactory);
Registry reg = LocateRegistry.createRegistry(1099);
reg.rebind("server", si);
System.out.println("Server is bound in registry");
} catch (RemoteException ex) {
Logger.getLogger(ServerImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void connect(ClientInt ci) throws RemoteException {
System.out.println("Client is connected");
// Generate a really big block of text to send to the client, that way it will be easy to see in a packet
// capture tool like wireshark and verify that it is in fact encrypted.
String helloText = "";
for (int i = 0; i < 10000; i++) {
helloText += "A";
}
ci.sayHelloToClient(helloText);
}
}
Finally we need a class for the client called 'ClientImpl':
public class ClientImpl implements ClientInt {
private ServerInt server;
public static void main(String[] args) {
ClientImpl client = new ClientImpl();
client.bind();
client.initConnection();
client.connectToServer();
}
public void initConnection() {
try {
Registry reg = LocateRegistry.getRegistry("192.168.0.32", 1099);
server = (ServerInt) reg.lookup("server");
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
} catch (NotBoundException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void bind() {
// System.setProperty("javax.net.debug", "all");
System.setProperty("javax.net.ssl.trustStore", "./resources/client/client_truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("java.rmi.server.hostname", "192.168.0.32");
System.setProperty("javax.net.ssl.keyStore", "./resources/client/client_keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
RMIClientSocketFactory rmiClientSocketFactory = new SslRMIClientSocketFactory();
RMIServerSocketFactory rmiServerSockeyFactory = new SslRMIServerSocketFactory();
try {
// Uncomment this line...
// ClientInt ci = (ClientInt) UnicastRemoteObject.exportObject(this, 0);
// and comment out this line to turn off SSL (do the same in the ServerImpl.java file)
ClientInt ci = (ClientInt) UnicastRemoteObject.exportObject(this, 0, rmiClientSocketFactory, rmiServerSockeyFactory);
Registry reg = LocateRegistry.createRegistry(5001);
reg.rebind("client", ci);
System.out.println("Client is bound in registry");
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void connectToServer() {
try {
server.connect(this);
} catch (RemoteException ex) {
Logger.getLogger(ClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void sayHelloToClient(String helloText) throws RemoteException {
System.out.println(helloText);
}
}
That's all there is to it. First run the 'ServerImpl' file and that will create the RMI server. Then run the 'ClientImpl' file and this will create it's own RMI Registry, then send itself to the server in the connectToServer method. The server will receive this message with the client RMI object and then use that instance of the client RMI object to call the clients methods. All while using SSL.
To verify that it's using SSL, the server generates a really long string of text and send this back to the client. By using a packet capture tool like Wireshark you can easily see this message is encrypted. I've included comments in the code that make it easy to turn off SSL so that you can see this text without encryption.
It took me longer than I care to admit to figure all this out, and at the same time I couldn't find any good tutorials on the subject. So hopefully if anyone else is stuck on this problem this will be of some help.