相互身份验证客户端“PKIX 路径构建失败”和“无法找到请求目标的有效证书路径”

问题描述 投票:0回答:1

我正在编写一个具有相互身份验证的客户端。服务器向我提供了证书(.cer)、密钥和密码。有了这些,我就可以毫无问题地通过 Insomnia 进行连接。通过 openSSL,我生成了一个 pfx 文件,用于从 FireFox 连接(以及我在这里尝试使用什么)。但是我无法从 JAVA 连接,因为我总是收到消息“PKIX...”(getOutputStream 行)。下面是我的代码:

private static void testConnection(String urlS) throws Exception {
        KeyStore keyStore;
        keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load((Main.class.getResourceAsStream("serverCertificate.pfx")), "password".toCharArray());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keyStore, "password".toCharArray());
        KeyManager[] kms = kmf.getKeyManagers();

        KeyStore trustStore = KeyStore.getInstance("JKS");
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(keyStore);

        TrustManager[] tms = tmf.getTrustManagers();

        SSLContext sc;
        sc = SSLContext.getInstance("TLSv1.2");
        sc.init(kms, tms, new java.security.SecureRandom());

        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        URL url = new URL(urlS);
        HttpsURLConnection urlConn = (HttpsURLConnection) url.openConnection();
        urlConn.setSSLSocketFactory(sc.getSocketFactory());
        urlConn.setRequestMethod("POST");

        urlConn.setRequestProperty("KeyId", "myKeyID");
        urlConn.setRequestProperty("X-Request-ID", "XXX");
        urlConn.setRequestProperty("Content-Type", "application/json");
        urlConn.setDoOutput(true);
        String jsonString = "my json string  here";
        OutputStream os = urlConn.getOutputStream();
        os.write(jsonString.getBytes());
        os.flush();
        os.close();

        int response = urlConn.getResponseCode();

    }

有错吗?

PS:我在这个link中看到了这个问题,我认为这不是同一个问题,因为我的代码不同,而且我不打算创建一个密钥库并将其添加到jvm(如果可能),因为解决方案中建议。

tls1.2 keystore truststore mutual-authentication pkix
1个回答
0
投票

我注意到没有文档明确定义如何在不使用jvm(cacerts)的情况下在相互身份验证中设置客户端。即使在 StackOverflow 上,配置模式也是最广泛且唯一的一种。我询问过如何做到这一点,由于使用了 OpenSSL,它变得有点复杂,但结果是值得的。接下来我给您留下一个分步文档,以便您可以通过这种方式配置您的客户端。

相互认证

相互身份验证要求客户端和服务器均提供其证书并验证彼此的证书。

客户端证书可以由拥有服务器的实体或认证机构颁发。

(你可以在网上找到很多信息,从维基百科开始)

本指南的目的

用于在 JVM 内配置客户端证书(cacerts)的现有文档非常丰富。事实上它是默认的。

此配置有一些缺点,例如:

  • 安全: 所有有权访问该 JVM 的程序都将被“认证”以访问服务器。
  • 可移植性/可维护性: 证书必须安装在每个环境中(开发、每个开发人员、/测试/生产)。当您更改 JVM 时,应该重新安装它。

本文档将解释如何在项目中配置两个证书,而不对 JVM 进行任何更改。

需要软件

OpenSSL(您可以从这里下载,为了安全起见,不要从第三方下载)

先决条件

你必须有:

  • 客户端证书:.crt / .cer
  • 客户端证书密钥
  • 客户端证书的密码/密码
  • 服务器证书.pem(您可以通过多种方式获取它,如此处所述。)

程序

如果您已经满足条件,请参阅以下说明:

KeyStore(客户端证书)

KeyStore 是单个文件,在本例中扩展名为 pfx(也可以是 .jks),其中包括客户端证书 + 密钥。

它是由openssl生成的,如下所示:

openssl pkcs12 -export -out keystore.pfx -inkey clientCertificateKey.key -in clientCertificate.crt

openssl 会要求输入客户端证书的密码

如果您有根 CA 和中间证书,请使用各种 -in 参数包含这些证书,例如:

openssl pkcs12 -export -out keystore.pfx -inkey clientCertificateKey.key -in clientCertificate.crt -in intermediate.crt -in rootca.crt

TrustStore(服务器证书)

获得服务器证书后,必须首先将其转换为 cer 文件,如下所述:

.pem 到 .cer

openssl x509 -outform der -in serverCertificate.pem -out serverCertificate.crt

.cer 到 .jks

keytool -import -alias $CERT_ALIAS -file serverCertificate.crt -keystore truststore.jks -deststorepass $CERT_PASSWORD

您必须定义 $CERT_ALIAS 和 $CERT_PASSWORD

此时您应该拥有 KeyStore (keystore.pfx) 和 TrustStore (truststore.jks)。那么让我们看看它们在代码中是如何使用的。

编码

稍后我们将部分然后完整地了解如何在方法中使用这些文件。

在项目内的主类路径中添加 keystore.pfx 和 truststore.jks 文件。

如何获取KeyManager

KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load((Main.class.getResourceAsStream("keystore.pfx")), "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "password".toCharArray());
KeyManager[] kms = kmf.getKeyManagers();

如何获取TrustManager(IDEM KeyManager)

KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load((Main.class.getResourceAsStream("truststore.jks")), "password".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
TrustManager[] tms = tmf.getTrustManagers();

带连接的完整方法

在示例情况下,服务器请求:

  • TLSv1.2协议
  • 来自 POST 的请求
  • 标题
  • KeyId(由服务器提供,所有通信固定)
  • X-请求-ID (UUID)

以下方法旨在说明必须定义的代码部分,以便根据上面建立的前提通过相互身份验证与服务器进行通信。错误处理超出了范围 - 我将把这部分留给您,就像定义其他库的使用等一样;-)

private static String postJson (String urlS, String json) {
        String response = "";

        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load((Main.class.getResourceAsStream("keystore.pfx")), "password".toCharArray());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keyStore, "password".toCharArray());
        KeyManager[] kms = kmf.getKeyManagers();
 
        KeyStore trustStore = KeyStore.getInstance("JKS");
        trustStore.load((Main.class.getResourceAsStream("truststore.jks")), "password".toCharArray());
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustStore);
        TrustManager[] tms = tmf.getTrustManagers();
 
        SSLContext sc;
        sc = SSLContext.getInstance("TLSv1.2");
        sc.init(kms, tms, new java.security.SecureRandom());
 
        URL url = new URL(urlS);
        HttpsURLConnection urlConn = (HttpsURLConnection) url.openConnection();
        urlConn.setSSLSocketFactory(sc.getSocketFactory());
         
        urlConn.setRequestMethod("POST");
        urlConn.setRequestProperty("KeyId", "myKeyID");
        urlConn.setRequestProperty("X-Request-ID", UUID.randomUUID().toString());
        urlConn.setRequestProperty("Content-Type", "application/json");
        urlConn.setDoOutput(true);
 
        OutputStream os = urlConn.getOutputStream();
        os.write(json.getBytes());
        os.flush();
        os.close();
 
        if (urlConn.getResponseCode() != -1) {
            boolean connection_ok = (urlConn.getResponseCode() == HttpURLConnection.HTTP_OK);
 
            if (connection_ok) {
                StringBuilder responseBuilder = new StringBuilder();
                String responseLine = null;
                while ((responseLine = br.readLine()) != null) {
                    responseBuilder.append(responseLine.trim());
                }
                response = responseBuilder.toString();
            }
        }

        return response;
    }

如果觉得有用,别忘了点个“赞”哦!

如有任何疑问,请留下您的评论。 谢谢。

© www.soinside.com 2019 - 2024. All rights reserved.