У меня есть программа, у программы есть два запускаемых файла, один из которых называется ServerImpl, а другой - ClientImpl. Моя цель - иметь возможность подключения ClientImpl к ServerImpl с использованием RMI с SSL и вызова метода. Сервер также должен иметь возможность вызывать методы на клиенте, используя RMI и SSL через обратный вызов. Я могу заставить клиента подключиться к серверу с помощью SSL и вызывать методы на этом сервере, однако я не могу заставить сервер подключиться к клиенту с помощью RMI с SSL.
Программа также имеет две директории, которые содержат два разных набора файлов cert, keystore, truststore:
resources/client/
client_cert.cer
client_keystore.jks
client_truststore.jks
resources/server/
server_cert.cer
server_keystore.jks
server_truststore.jks
Файл ServerImpl.java:
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");
}
}
Файл ClientImpl.java:
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);
}
}
Затем я запускаю файл ServerImpl.java, и никаких проблем с ним не работает. Затем я запускаю файл ClientImpl.java и получаю сообщение об ошибке при вызове метода connectToServer:
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
Может ли кто-нибудь пролить свет на то, что проблема здесь и как я могу ее решить? В противном случае, может ли кто-нибудь указать мне на хороший учебник о том, что два объекта RMI, которые используют SSL, разговаривают друг с другом? Благодарю.
Хорошо, я понял это. Проблема заключалась в том, что соответствующие клиентские/серверные Java файлы по-прежнему используют стандартный Java TrustStore по умолчанию, а не пользовательские файлы Truststore, которые я определил в исходном коде проблемы. Ниже приведен правильный код для любого другого, кто ищет простую демонстрацию двухстороннего соединения клиент-сервер RMI с использованием SSL.
После создания пустого Java-проекта добавьте в папку "resources" в каталог верхнего уровня, в которой есть два подкаталога "клиент" и "сервер". Затем создайте два отдельных набора сертификатов, хранилищ ключей и доверительных магазинов и поместите их в соответствующие вспомогательные каталоги:
resources/client/
client_cert.cer
client_keystore.jks
client_truststore.jks
resources/server/
server_cert.cer
server_keystore.jks
server_truststore.jks
Затем создайте интерфейс для сервера под названием "ServerInt":
public interface ServerInt extends Remote {
public void connect(ClientInt ci) throws RemoteException;
}
Другой интерфейс для клиента под названием "ClientInt":
public interface ClientInt extends Remote {
public void sayHelloToClient(String helloText) throws RemoteException;
}
Теперь создайте новый класс java для сервера под названием "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);
}
}
Наконец, нам нужен класс для клиента под названием "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);
}
}
Это все, что есть. Сначала запустите файл ServerImpl и создайте сервер RMI. Затем запустите файл "ClientImpl", и это создаст собственный реестр RMI, а затем отправит себя на сервер в методе connectToServer. Сервер получит это сообщение с объектом RMI клиента, а затем будет использовать этот экземпляр объекта RMI клиента для вызова методов клиентов. Все при использовании SSL.
Чтобы убедиться, что он использует SSL, сервер генерирует очень длинную строку текста и отправляет ее обратно клиенту. Используя инструмент захвата пакетов, такой как Wireshark, вы можете легко увидеть, что это сообщение зашифровано. Я включил комментарии в код, которые упрощают отключение SSL, чтобы вы могли видеть этот текст без шифрования.
Мне потребовалось больше времени, чем я хотел признаться, чтобы понять все это, и в то же время я не мог найти никаких хороших учебников по этому вопросу. Поэтому, надеюсь, если кто-то еще застрянет в этой проблеме, это поможет.