Java HTTPS 客户机证书认证

2022-08-31 05:29:49

我是新手,我对客户端在使用证书进行身份验证时应该提供的内容感到有些困惑。HTTPS/SSL/TLS

我正在编写一个Java客户端,它需要对特定.该部分工作正常,唯一的问题是它应该完成 。该部分相当容易处理(无论是使用还是使用Java的内置支持),但我坚持使用客户端证书进行身份验证。我注意到这里已经有一个非常相似的问题,我还没有用我的代码尝试过(很快就会这样做)。我目前的问题是 - 无论我做什么 - Java客户端从不发送证书(我可以通过转储检查这一点)。POSTURLHTTPSHTTPSHTTPclientHTTPSPCAP

我想知道在使用证书进行身份验证时,客户端应该向服务器提供什么(特别是对于Java - 如果这很重要)?这是一个文件,还是?他们应该在里面;只是客户端证书,还是密钥?如果是,哪个密钥?关于所有不同类型的文件,证书类型等,存在相当多的混淆。JKSPKCS#12

正如我之前所说,我是新手,所以我也会喜欢一些背景信息(不一定是一篇文章;我会满足于好文章的链接)。HTTPS/SSL/TLS


答案 1

终于设法解决了所有问题,所以我会回答我自己的问题。这些是我用来管理解决特定问题的设置/文件;

客户机的密钥库是一个 PKCS#12 格式文件,其中包含

  1. 客户端的公共证书(在本例中为自签名 CA 签名)
  2. 客户端的私钥

例如,为了生成它,我使用了OpenSSL的命令;pkcs12

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever"

提示:请确保您获得最新的OpenSSL,而不是版本0.9.8h,因为这似乎存在一个错误,该错误不允许您正确生成PKCS#12文件。

Java 客户机将使用此 PKCS#12 文件,当服务器显式请求客户机进行身份验证时,向服务器提供客户机证书。有关客户端证书身份验证协议实际工作原理的概述,请参阅有关 TLS 的维基百科文章(此处还解释了为什么我们需要客户端的私钥)。

客户机的信任库是包含中间 CA 证书的直接 JKS 格式文件。这些 CA 证书将确定您将被允许与哪些端点进行通信,在这种情况下,它将允许您的客户机连接到提供由信任库的 CA 之一签名的证书的任何服务器。

例如,要生成它,您可以使用标准的Java keytool;

keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever
keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca

使用此信任库,您的客户机将尝试与提供由 CA 标识的 CA 签名的证书的所有服务器进行完整的 SSL 握手。myca.crt

上述文件仅供客户端使用。当您还想设置服务器时,服务器需要自己的密钥库和信任库文件。在这个网站上可以找到为Java客户端和服务器(使用Tomcat)设置一个完全工作的示例的很好的演练。

问题/备注/提示

  1. 客户端证书身份验证只能由服务器强制执行。
  2. (重要提示!当服务器请求客户端证书(作为 TLS 握手的一部分)时,它还将提供受信任的 CA 列表作为证书请求的一部分。当您希望提供的用于身份验证的客户端证书不是由这些CA之一签名时,它根本不会出现(在我看来,这是奇怪的行为,但我确信这是有原因的)。这是我问题的主要原因,因为另一方没有正确配置他们的服务器以接受我的自签名客户端证书,并且我们认为问题出在我这边,因为没有在请求中正确提供客户端证书。
  3. 获取 Wireshark。它具有出色的SSL / HTTPS数据包分析功能,对于调试和查找问题将有很大的帮助。它类似于,但更有条理,如果你对Java SSL调试输出不满意,(可以说)更容易解释。-Djavax.net.debug=ssl
  4. 完全有可能使用Apache httpclient库。如果要使用 httpclient,只需将目标 URL 替换为 HTTPS 等效项,并添加以下 JVM 参数(这些参数对于任何其他客户端都是相同的,无论您要使用哪个库通过 HTTP/HTTPS 发送/接收数据):

    -Djavax.net.debug=ssl
    -Djavax.net.ssl.keyStoreType=pkcs12
    -Djavax.net.ssl.keyStore=client.p12
    -Djavax.net.ssl.keyStorePassword=whatever
    -Djavax.net.ssl.trustStoreType=jks
    -Djavax.net.ssl.trustStore=client-truststore.jks
    -Djavax.net.ssl.trustStorePassword=whatever

答案 2

其他答案显示了如何全局配置客户端证书。但是,如果要以编程方式为一个特定连接定义客户端密钥,而不是在 JVM 上运行的每个应用程序中全局定义它,则可以像这样配置自己的 SSLContext:

String keyPassphrase = "";

KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray());

SSLContext sslContext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, null)
        .build();

HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
HttpResponse response = httpClient.execute(new HttpGet("https://example.com"));