Java中的SSL套接字连接

3
在阅读了许多关于这些话题的答案后,我发现自己完全无法将拼图的碎片串在一起,希望您能原谅我。
我正在尝试将Java中简单的socket连接改为使用SSL。如果可能的话,我希望服务器和客户端都能进行身份验证,但仅对服务器进行身份验证将是一个好的开始。
目前,在服务器端,这是极其简单的代码:
    ServerSocket serverSocket = new ServerSocket(port);
    Socket socket = serverSocket.accept();

以下是客户端代码:

    Socket socket = null;
    while (true) {
        try {
            socket = new Socket(ipAddress, port);
            break;
        } catch (Exception e) {}
    }

这段代码在没有SSL的情况下可以很好地工作。

我使用OpenSSL为服务器和客户端生成了SSL证书,最终得到了:

  • 服务器证书(PEM格式)
  • 客户端证书(PEM格式)
  • 服务器私钥(PEM格式)
  • 客户端私钥(PEM格式)
  • CA文件(PEM、CER和CRT格式)

接下来,我使用OpenSSL为客户端和服务器创建了PKCS12(.p12)密钥库,步骤如下:

  • 通过执行openssl pkcs12 -export -in server-cert.pem -inkey server-private-key.pem -out server.p12创建server.p12
  • 通过执行openssl pkcs12 -export -in client-cert.pem -inkey client-private-key.pem -out client.p12创建client.p12

然后,我使用keytool将这些文件转换为JKS密钥库(例如,对于服务器,命令为keytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 -destkeystore server.jks -deststoretype JKS),结果得到两个文件: server.jksclient.jks

然后,我使用以下代码替换前面的服务器片段:

    char[] keyStorePassword =  "JKSPassword".toCharArray();
    FileInputStream keyStoreFile = new FileInputStream("somepath/server.jks");
    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(keyStoreFile, keyStorePassword);
    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(keyStore, "PKCS12Password".toCharArray());
    SSLContext sslContext = SSLContext.getDefault();
    ServerSocket serverSocket = sslContext.getServerSocketFactory().createServerSocket(port);
    Socket socket = serverSocket.accept();

以下代码可用作客户端片段的替代方案:
    Socket socket = null;
    while (true) {
        try {
            char[] keyStorePassword =  "JKSPassword".toCharArray();
            FileInputStream keyStoreFile = new FileInputStream("somepath/client.jks");
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(keyStoreFile, keyStorePassword);
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, "PKCS12Password".toCharArray());
            SSLContext sslContext = SSLContext.getDefault();
            socket = sslContext.getSocketFactory().createSocket(ipAddress, port);
            break;
        } catch (Exception e) {}
    }

我现在在服务器上仍然遇到javax.net.ssl.SSLHandshakeException: no cipher suites in common错误(在客户端上则是javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure)。

可能出了什么问题?


你在客户端和服务器上使用哪个Java版本? - Robert
Java 8u20(当然包括SDK)需要在客户端和服务器上都安装(它们是同一台机器,并且目前使用的IP地址是localhost)。 - Infima
你的服务器和客户端代码都是无意义的。你初始化了一个KeyStore但是你从未使用过它。好好看一下JSSE参考指南。 - user207421
1个回答

1
我找到了缺失的拼图部分(我相信)。
在客户端,替换以下行:SSLContext sslContext = SSLContext.getDefault();,替换为:
    BufferedInputStream serverCertificateFile = new BufferedInputStream(new FileInputStream("somepath/server-cert.der"));
    X509Certificate serverCertificate = (X509Certificate)
              CertificateFactory.getInstance("X.509").generateCertificate(serverCertificateFile);
            sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[] {
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] arg0,
                            String arg1) throws CertificateException {
                        throw new CertificateException();
                    }
                    @Override
                    public void checkServerTrusted(X509Certificate[] arg0,
                            String arg1) throws CertificateException {
                        boolean valid = false;
                        for (X509Certificate certificate : arg0) {
                            try {
                                certificate.verify(serverCertificate.getPublicKey());
                                valid = true;
                                break;
                            } catch (SignatureException e) {}
                        }
                        if (!valid) {
                            throw new CertificateException();
                        }
                    }
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
            }, new SecureRandom());

server-cert.der是一个文件,使用openssl509 -outform der -in server-cert.pem -out server-cert.der创建。

我在StackOverflow上没有找到好的教程或问题,所以我试图通过理解参考指南来创建这个。

本质上,我正在创建一个TrustManager,对于一个服务器,在提供的证书之一属于您自己的服务器时,它会信任它。

它似乎起作用了,请让我知道这种方法是否存在任何根本性的错误(不幸的是,我假设存在)。感谢您阅读这么多!


我不明白为什么你先去 StackOverflow 而不是官方文档查找答案。一旦你找对了地方,就立刻找到了答案。这里有一个教训。 - user207421
1
我确实先看了官方文档,但在事情开始运作之前,我仍然尝试了各种方法,所以现在我不确定自己是否真正理解了它。就像我之前说的那样,我无法将这些谜团的碎片连接起来...官方文档可以说是一个迷宫。我希望有一个教程。 - Infima

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接