Java - 当使用客户端证书身份验证时,Ssl流会被取消

3

我无法理解这个问题。

我有一个单元测试,使用客户端证书认证连接到我的服务。

// generate a valid client cert and store it in a keystore
String keystorePassword = "xxx";
InputStream pkcs12 = UnitTests.generatePkcs12ForUser(user, keystorePassword, 3600);
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(pkcs12, keystorePassword.toCharArray());


String url = getBaseServerUrl();

// prepare a ssl context that has the keystore with client cert and key
SSLContext sslContext = SSLContexts.custom()
                                   .loadKeyMaterial(ks, keystorePassword.toCharArray())
                                   // trust all SSL certs
                                   .loadTrustMaterial((X509Certificate[] chain, String authType) -> true)
                                   .build();

// validate any hostname, and don't follow 3XX responses
HttpClient httpClient = HttpClients.custom()
                                   .setSSLContext(sslContext)
                                   .setSSLHostnameVerifier((a,b) -> true)
                                   .disableRedirectHandling()
                                   .build();

// this fails catastrophically
HttpResponse response = httpClient.execute(new HttpGet(url));

我正在使用Java 8,我的服务器在反向代理Nginx后面。

我的单元测试失败,并出现以下异常:

javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake

    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1002)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
[...]
Caused by: java.io.EOFException: SSL peer shut down incorrectly
    at sun.security.ssl.InputRecord.read(InputRecord.java:505)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:983)
    ... 43 more

我可以在Nginx的错误日志中看到以下行:

2018/05/18 15:34:12 [crit] 42#42: *327 SSL_do_handshake() failed (SSL: error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag error:0D08303A:asn1 encoding routines:asn1_template_noexp_d2i:nested asn1 error) while SSL handshaking, client: 172.18.0.1, server: 0.0.0.0:443

我已经在互联网上搜索了很久,想找出这个错误的原因,但感觉已经尝试了所有可能。

以下是我尝试过的一些方法(不全):

  • 使用-Dhttps.protocols=Tlsv1.1强制Java使用TlsV1.1 -> 仍然失败
  • 尝试为loadTrustMaterial指定“密钥策略”,始终使用自己的密钥 -> 仍然失败
  • 使用相同的SSL /密钥库参数的Jersey客户端 -> 仍然失败
  • 尝试了所有JDK 1.8的版本:OpenJDK、Oracle和带有加密扩展(JCE) 的 Oracle
  • 查看nginx错误似乎暗示传递的证书不正确,所以我...
  • ...在调试模式下将密钥和证书转储到一些PEM文件中,并用curl访问相同的地址 -> 它可以正常工作?!
  • 通过Wireshark检查了TLS握手过程,与一个工作样本进行比较(上面简单的curl -k <url> 可以顺利工作):
    • 发现了有关ALPN的内容,但在Java中启用它并不能解决此异常。
    • 发现了不同的加密算法,但没有任何明显的问题。

你们有什么想法吗? 我开始感到有些疯狂了。我有一个直觉,认为我没有正确设置连接,但我找不到问题所在。


你尝试过简单的 openssl s_client 吗?这会比仅使用 curl 提供更多的调试和参数更改可能性。你也尝试过不使用 NGINX 吗? - Patrick Mevzek
"http://unrestful.io/2015/10/09/alpn-java.html" => 只有Java 9中的ALPN,而不是8。或者在这里使用一些修改过的Java 8类:https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html 我怀疑ALPN并不是问题所在。 - Patrick Mevzek
你的密钥库条目是否有多个证书,即链式证书?如果是这样,你是如何为curl“转储到...文件”的?如果你将多个证书放在一个文件中,并将其用于curl --cert file,那么它不会产生与Java相同的行为,因此不是一个有效的测试。你能发布证书或至少它们的openssl x509 -text吗(每个)?在问题的Wireshark中,展开客户端证书消息中的每个证书是否正常工作?PS:虽然有些人很古怪,但大多数互联网用户不喜欢被鞭打,所以鞭打并不是寻求帮助的好策略 :) - dave_thompson_085
帕特里克掌握了好的线索:我确实使用subjectAltName在证书中存储客户端的UUID。似乎删除这个UUID可以使测试通过。由于某种原因,导出为PEM或curl将删除此字段,但在Java中使用证书会失败。 - Gui13
1个回答

0
那些没有读评论的人:我弄清楚了:我在使用subjectAltName作为存储非替代名称的东西。
似乎Java对此不满意并拒绝连接。

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