Простое двустороннее соединение RMI SSL, сбой построения пути PKIX

1

У меня есть программа, у программы есть два запускаемых файла, один из которых называется 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, разговаривают друг с другом? Благодарю.

Теги:
ssl
rmi

1 ответ

0
Лучший ответ

Хорошо, я понял это. Проблема заключалась в том, что соответствующие клиентские/серверные 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, чтобы вы могли видеть этот текст без шифрования.

Мне потребовалось больше времени, чем я хотел признаться, чтобы понять все это, и в то же время я не мог найти никаких хороших учебников по этому вопросу. Поэтому, надеюсь, если кто-то еще застрянет в этой проблеме, это поможет.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню