【问题标题】:Simple two way RMI SSL Connection, PKIX path building failed简单的两路 RMI SSL 连接,PKIX 路径构建失败
【发布时间】:2014-05-31 03:30:11
【问题描述】:

我有一个程序,该程序有两个可运行文件,一个称为 ServerImpl,另一个称为 ClientImpl。我的目标是能够让 ClientImpl 使用带有 SSL 的 RMI 连接到 ServerImpl 并调用一个方法。服务器还应该能够使用 RMI 和 SSL 通过回调调用客户端上的方法。我可以让客户端使用 SSL 连接到服务器并调用该服务器上的方法,但是我无法让服务器使用带有 SSL 的 RMI 连接回客户端。

该程序还有两个目录,其中包含两组不同的证书、密钥库、信任库文件:

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 ssl rmi


    【解决方案1】:

    好的,我想通了。问题是各个客户端/服务器 java 文件仍在使用默认的 Java TrustStore,而不是我在原始问题代码中定义的自定义 Truststore 文件。对于正在寻找使用 SSL 的双向 RMI 客户端-服务器连接的简单演示的任何其他人,这里是正确的完整代码。

    创建一个空白 Java 项目后,将“resources”文件夹添加到具有两个子目录“client”和“server”的顶级目录中。然后生成两组单独的证书、密钥库和信任库,并将它们放在各自的子目录中,如下所示:

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

    现在为服务器创建一个名为“ServerImpl”的新 java 类:

    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 这样的数据包捕获工具,您可以轻松地看到此消息已加密。我在代码中包含了 cmets,可以轻松关闭 SSL,这样您就可以在不加密的情况下查看此文本。

    我花了比我愿意承认的时间更长的时间来弄清楚这一切,同时我找不到任何关于这个主题的好教程。所以希望如果其他人遇到这个问题,这会有所帮助。

    【讨论】:

      猜你喜欢
      • 2011-01-18
      • 2019-05-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-23
      • 2012-11-15
      • 2016-09-16
      相关资源
      最近更新 更多