如何解决bad_certificate 使用带有改造的客户端证书okhhtp时出错,但与 curl
我在使用改造、okhttp-tls 和 Spring Boot 时遇到了 mTLS 的一些问题。
如果 server.ssl.client-auth
未设置为 need
,则通信工作正常,因此服务器的身份验证工作正常。
问题是,当 server.ssl.client-auth
设置为 need
(应该如此)时,尝试使用改造结果进行 API 调用
javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
在服务器端:
*** Certificate chain
<Empty>
***
https-jsse-nio-8443-exec-4,Fatal error: 43: null cert chain
javax.net.ssl.SSLHandshakeException: null cert chain
客户端证书没问题 - 我可以使用 curl 进行相同的调用:
cat file_with_private_key.pem file_with_certificate.pem > combined.pem
curl https:example.com/path --cert combined.pem
服务器然后显示证书链并返回响应。
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(keystoreInputStream,keystorePass);
X509Certificate clientCert = (X509Certificate) keystore.getCertificate(clientCertAlias);
PrivateKey clientKey = (PrivateKey) keystore.getKey(clientCertAlias,keystorePass);
KeyPair clientKeyPair = new KeyPair(clientCert.getPublicKey(),clientKey);
HeldCertificate heldCertificate = new HeldCertificate(clientKeyPair,clientCert);
HandshakeCertificates handshakeCerts = new HandshakeCertificates.Builder()
.addplatformTrustedCertificates()
.heldCertificate(heldCertificate)
.build();
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(handshakeCerts.sslSocketFactory(),handshakeCerts.trustManager())
.build();
OkHttpClient 客户端变量稍后传递给改造。 当服务器和客户端证书都是自签名时,完全相同的代码可以正常工作,但现在它不适用于 CA 签名证书。
如果我调用 heldCertificate.certificatePem()
,我会得到与 file_with_certificate.pem 文件中的 BEGIN CERTIFICATE 块完全相同的字符串。
如果我调用 heldCertificate.privateKeyPkcs1Pem()
,我会得到与 file_with_private_key.pem 文件中的 BEGIN RSA PRIVATE KEY 块完全相同的字符串。
我使用的是改造 2.4.0、okhttp-tls 3.14.6、java 8,我将无法升级。
编辑: 'file_with_certificate.pem' 只包含一个证书(中间 CA 签署的叶证书)。
编辑2:
我用作密钥库的 pkcs12 文件包含整个证书链(叶证书、中间证书、根证书)和叶证书私钥。尽管 keytool 列出了 3 个带有 keytool -list -rfc
的证书,但似乎只有叶证书实际上是从密钥库中加载的。
我通过从单独的文件导入每个证书并给每个证书一个单独的别名来解决这个问题,然后迭代别名以加载每个证书,如下所示:
HandhsakeCertificates.Builder handshakeCertBuilder = new HandhsakeCertificates.Builder()
.addplatformTrustedCertificates()
.heldCertificate(heldCertificate);
Collections.list(keyStore.aliases())
.foreach(alias -> {
X509Certificate cert = (X509Certificate) keystore.getCertificate(alias);
handshakeCertBuilder.addTrustedCertificate(cert);
})
编辑3: 需要将中间证书添加到服务器信任库,以便服务器信任客户端叶证书。 感谢@SteffenUlrich 提出的问题为我指明了正确的方向。 我不确定为什么 curl 使用叶证书工作 - 当时服务器的“可接受的 CA 名称”部分中唯一可用的 CA 是根证书,所以叶证书不应该被接受(因为它由中间 CA 签名,而不是根 CA)。
解决方法
查看您的设置,我可以得出结论,您的客户端密钥对和您的服务器可信证书存在于同一个密钥库文件中。我建议将它们分成包含客户端身份又名密钥对的 identity.jks
。以及包含所有受信任证书的 truststore.jks
。这将使它更易于维护。
我将根据您当前的设置提供此示例,因此请记住,所有内容都存储在单个密钥库文件中。而且我还注意到您使用了 HandshakeCertificates
和 HeldCertificate
,我认为它们不需要,因为默认的 jdk 库已经提供了使用 Retrofit 配置 OkHttp 所需的对象
你能试试下面的代码片段并分享你的结果吗:
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(keystoreInputStream,keystorePass);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore,"your-key-password".toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers,trustManagers,null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory,trustManagers[0])
.build();
,
文档示例不是很好,它没有显示您在不使用自签名证书时可能需要发送的中间证书。
https://github.com/square/okhttp/blob/ef5d0c83f7bbd3a0c0534e7ca23cbc4ee7550f3b/okhttp-tls/README.md
一个更好的例子来自单元测试
builder.heldCertificate(heldCertificate,intermediates);
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。