带SSL的简单RMI服务器

Dav*_*rke 7 java ssl rmi keystore public-key-encryption

尝试使用SSL加密设置简单的RMI服务器.这是一个简单的聊天应用程序,它有一个java服务器应用程序和一个java客户端应用程序,但是,我现在甚至无法使用简单的RMI示例!

我能让它工作的唯一方法是客户端和服务器都具有相同的信任库和密钥库.但对我来说,这听起来不正确,因为它意味着每个客户端都有服务器的私钥.

我按照本指南创建了信任/密钥库.我首先尝试生成密钥库和信任库,然后使用密钥库和带有信任库的客户端运行服务器.这不起作用,所以我然后为每个生成一对并加载,如下面的代码所示.

它认为我可能会遗漏一些显而易见的东西,因为我的生活中我不知道我做错了什么.我目前有以下内容,但在运行服务器时,我收到以下错误:

错误:

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)
Run Code Online (Sandbox Code Playgroud)

Hello.java

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {

    String sayHello() throws RemoteException;

}
Run Code Online (Sandbox Code Playgroud)

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




    }

}
Run Code Online (Sandbox Code Playgroud)

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

    }

}
Run Code Online (Sandbox Code Playgroud)

use*_*421 3

PKIX 错误意味着客户端不信任服务器证书,在本例中服务器是注册表。

为了澄清这一点,您需要两个私钥和两个密钥库来保存它们,各一个。然后,您需要在每个密钥库中创建证书,导出它们,并将它们导入对等方的信任库中。服务器的信任库必须信任客户端的密钥库,反之亦然。

你的代码看起来基本没问题。createRegistry() 的结果应存储在静态变量中,以防止被 GC 回收。无论您的 IDE 告诉您什么,您都不需要在服务器类中使用serialVersionUID。它不会被序列化,至少不会被 RMI 序列化。

编辑问题在这里:

System.setProperty("javax.net.ssl.keyStore", "C:\\ssl\\keystore-server.jks");
System.setProperty("javax.net.ssl.trustStore", "C:\\ssl\\truststore-client.jks");
Run Code Online (Sandbox Code Playgroud)

这应该是:

System.setProperty("javax.net.ssl.keyStore", "C:\\ssl\\keystore-server.jks");
System.setProperty("javax.net.ssl.trustStore", "C:\\ssl\\truststore-server.jks");
Run Code Online (Sandbox Code Playgroud)

和这里:

System.setProperty("javax.net.ssl.keyStore", "C:\\ssl\\keystore-client.jks");
System.setProperty("javax.net.ssl.trustStore", "C:\\ssl\\truststore-server.jks"
Run Code Online (Sandbox Code Playgroud)

这应该是:

System.setProperty("javax.net.ssl.keyStore", "C:\\ssl\\keystore-client.jks");
System.setProperty("javax.net.ssl.trustStore", "C:\\ssl\\truststore-client.jks"
Run Code Online (Sandbox Code Playgroud)

编辑2根本问题是绑定到注册表时所需的信任存储是客户端信任存储,但运行服务器时所需的信任存储是服务器信任存储。

至少有三种可能的解决方案,按优劣顺序递增:

  1. 使用从客户端信任库加载的SslRMIClientSocketFactory自己的子类来SSLContext设置的子类,并覆盖。哎哟。TrustManagercreateSocket()

  2. 还将服务器的证书导入到服务器的信任库中。

  3. 使用 的返回值createRegistry()来执行此操作,bind()而不是getRegistry()在服务器中调用,从而避免整个问题。