Java HTTPS 客户端证书认证

新手上路,请多包涵

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

我正在编写一个 Java 客户端,它需要执行一个简单的 POST 数据到特定的 URL 。那部分工作正常,唯一的问题是它应该在 HTTPS 上完成。 HTTPS 部分相当容易处理(使用 HTTPclient 或使用 Java 的内置 HTTPS 支持),但我卡在了真实的客户端证书上.我注意到这里已经有一个非常相似的问题,我还没有用我的代码尝试过(很快就会这样做)。我当前的问题是 - 无论我做什么 - Java 客户端永远不会发送证书(我可以使用 PCAP 转储进行检查)。

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

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

原文由 tmbrggmn 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.1k
2 个回答

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

客户端的密钥库 是一个 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 的维基百科文章, 了解客户端证书身份验证协议实际工作原理的概述(也解释了为什么我们需要此处的客户端私钥)。

客户端的信任 库是一个直接的 JKS 格式 文件,其中包含 中间 CA 证书。这些 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

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

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

问题/备注/提示

  1. 客户端证书身份验证 只能由服务器强制执行。
  2. 重要! )当服务器请求客户端证书(作为 TLS 握手的一部分)时,它还将提供受信任的 CA 列表作为证书请求的一部分。如果您希望出示用于身份验证的客户端证书 由这些 CA 之一签名,则根本不会出示(在我看来,这是一种奇怪的行为,但我确信这是有原因的)。这是我出现问题的主要原因,因为对方没有正确配置他们的服务器来接受我的自签名客户端证书,我们认为问题出在我这边,因为我没有在请求中正确提供客户端证书。
  3. 获取 Wireshark。它具有出色的 SSL/HTTPS 数据包分析功能,将极大地帮助调试和发现问题。它与 -Djavax.net.debug=ssl 类似,但结构更清晰,如果您对 Java 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

原文由 tmbrggmn 发布,翻译遵循 CC BY-SA 2.5 许可协议

其他答案显示如何全局配置客户端证书。但是,如果您想以编程方式为一个特定连接定义客户端密钥,而不是在 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"));

原文由 Magnus 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题