Java HTTPS客户端证书身份验证

问题描述:

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

I'm fairly new to HTTPS/SSL/TLS and I'm a bit confused over what exactly the clients are supposed to present when authenticating with certificates.

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

I'm writing a Java client that needs to do a simple POST of data to a particular URL. That part works fine, the only problem is it's supposed to be done over HTTPS. The HTTPS part is fairly easy to handle (either with HTTPclient or using Java's built-in HTTPS support), but I'm stuck on authenticating with client certificates. I've noticed there's already a very similar question on here, which I haven't tried out with my code yet (will do so soon enough). My current issue is that - whatever I do - the Java client never sends along the certificate (I can check this with PCAP dumps).

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

I would like to know what exactly the client is supposed to present to the server when authenticating with certificates (specifically for Java - if that matters at all)? Is this a JKS file, or PKCS#12? What's supposed to be in them; just the client certificate, or a key? If so, which key? There's quite a bit of confusion about all the different kinds of files, certificate types and such.

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

As I've said before I'm new to HTTPS/SSL/TLS so I would appreciate some background information as well (doesn't have to be an essay; I'll settle for links to good articles).

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

Finally managed to solve all the issues, so I'll answer my own question. These are the settings/files I've used to manage to get my particular problem(s) solved;

客户端的密钥库 PKCS#12格式文件包含


  1. 客户的公开证书(在此实例中已签名)通过自签名CA)

  2. 客户的私人密钥

  1. The client's public certificate (in this instance signed by a self-signed CA)
  2. The client's private key

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

To generate it I used OpenSSL's pkcs12 command, for example;

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

提示:确保您获得最新的OpenSSL,版本0.9.8h,因为它似乎遭受了一个不允许您正确生成的错误PKCS#12文件。

Tip: make sure you get the latest OpenSSL, not version 0.9.8h because that seems to suffer from a bug which doesn't allow you to properly generate PKCS#12 files.

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

This PKCS#12 file will be used by the Java client to present the client certificate to the server when the server has explicitly requested the client to authenticate. See the Wikipedia article on TLS for an overview of how the protocol for client certificate authentication actually works (also explains why we need the client's private key here).

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

The client's truststore is a straight forward JKS format file containing the root or intermediate CA certificates. These CA certificates will determine which endpoints you will be allowed to communicate with, in this case it will allow your client to connect to whichever server presents a certificate which was signed by one of the truststore's CA's.

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

To generate it you can use the standard Java keytool, for example;

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

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

Using this truststore, your client will try to do a complete SSL handshake with all servers who present a certificate signed by the CA identified by myca.crt.

上述文件仅供客户使用。当您还要设置服务器时,服务器需要自己的密钥和信任库文件。可以在本网站

The files above are strictly for the client only. When you want to set-up a server as well, the server needs its own key- and truststore files. A great walk-through for setting up a fully working example for both a Java client and server (using Tomcat) can be found on this website.

问题/备注/提示


  1. 客户端证书身份验证只能由服务器强制执行。

  2. 重要!)当服务器请求客户端证书(作为TLS握手的一部分)时,它还将提供可信CA的列表作为证书请求的一部分。当您希望提交用于身份验证的客户端证书由其中一个CA签名时,它将不会被呈现(在我看来,这是奇怪的行为,但我确信这是一个原因)。这是我的问题的主要原因,因为另一方没有正确配置他们的服务器以接受我的自签名客户端证书,我们认为问题是因为我没有在请求中正确提供客户端证书。

  3. 获取Wireshark。它具有出色的SSL / HTTPS数据包分析功能,可以帮助您调试和发现问题。它与 -Djavax.net.debug = ssl 类似,但如果您对Java SSL调试输出感到不舒服,则更易于理解,并且(可以说)更容易理解。

  4. 完全可以使用Apache httpclient库。如果要使用httpclient,只需使用HTTPS等效项替换目标URL,并添加以下JVM参数(对于任何其他客户端都是相同的,无论您要使用哪个库通过HTTP / HTTPS发送/接收数据) :

  1. Client certificate authentication can only be enforced by the server.
  2. (Important!) When the server requests a client certificate (as part of the TLS handshake), it will also provide a list of trusted CA's as part of the certificate request. When the client certificate you wish to present for authentication is not signed by one of these CA's, it won't be presented at all (in my opinion, this is weird behaviour, but I'm sure there's a reason for it). This was the main cause of my issues, as the other party had not configured their server properly to accept my self-signed client certificate and we assumed that the problem was at my end for not properly providing the client certificate in the request.
  3. Get Wireshark. It has great SSL/HTTPS packet analysis and will be a tremendous help debugging and finding the problem. It's similar to -Djavax.net.debug=ssl but is more structured and (arguably) easier to interpret if you're uncomfortable with the Java SSL debug output.
  4. It's perfectly possible to use the Apache httpclient library. If you want to use httpclient, just replace the destination URL with the HTTPS equivalent and add the following JVM arguments (which are the same for any other client, regardless of the library you want to use to send/receive data over 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