OkHttp3 - 无法找到请求目标的有效证书路径
OkHttp3 - unable to find valid certification path to requested target
在我的应用程序中,我将 retrofit2(2.9.0) 与 OkHttp3(3.14.4) 结合使用。我想将 tls 客户端证书添加到我对某些 api 的所有请求中。我在 .p12
文件中获得了证书。我读取了文件,加载到 X509Certificate class 然后我使用了 .addTrustedCertificate(certificate)
方法。证书是正确的,我使用 curl 尝试过。不幸的是,当我 运行 代码时出现异常。
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:320)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:263)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:258)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:641)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:460)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:360)
at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:177)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1152)
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1063)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:402)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:336)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:300)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:185)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:224)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:108)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:88)
at okhttp3.internal.connection.Transmitter.newExchange(Transmitter.java:169)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:41)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:213)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
at okhttp3.RealCall.execute(RealCall.java:81)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:204)
at com.jakewharton.retrofit2.adapter.reactor.ExecuteSinkConsumer.accept(ExecuteSinkConsumer.java:39)
at com.jakewharton.retrofit2.adapter.reactor.ExecuteSinkConsumer.accept(ExecuteSinkConsumer.java:24)
at reactor.core.publisher.FluxCreate.subscribe(FluxCreate.java:94)
at reactor.core.publisher.Flux.subscribe(Flux.java:8143)
at com.jakewharton.retrofit2.adapter.reactor.BodyFlux.subscribe(BodyFlux.java:36)
at reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.run(FluxSubscribeOn.java:194)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:290)
at java.base/sun.security.validator.Validator.validate(Validator.java:264)
at java.base/sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:313)
at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:222)
at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:129)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:625)
... 47 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297)
at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
... 53 more
Process finished with exit code 1
我的代码是这样的
val fileName = "certyficate.p12"
val file = File(fileName)
val inputStream = file.inputStream()
val p12 = KeyStore.getInstance("pkcs12")
val password = "password"
p12.load(inputStream, password.toCharArray())
val alias = p12.aliases().nextElement()
val certificate = p12.getCertificate(alias) as X509Certificate
val ob = ObjectMapper().registerModule(KotlinModule())
val hostname = "https://myapi.com"
val timeout = 5L
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(timeout, TimeUnit.SECONDS)
.readTimeout(timeout, TimeUnit.SECONDS)
.writeTimeout(timeout, TimeUnit.SECONDS)
.apply {
addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
}
.apply {
val clientCertificate = HandshakeCertificates
.Builder()
.addTrustedCertificate(certificate)
.build()
sslSocketFactory(clientCertificate.sslSocketFactory(), clientCertificate.trustManager())
}
.build()
val retrofit = Retrofit.Builder()
.baseUrl(hostname)
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(
io.reactivex.schedulers.Schedulers.io()
))
.addCallAdapterFactory(ReactorCallAdapterFactory.createWithScheduler(Schedulers.newElastic(
MyApi::class.java.name
)))
.addConverterFactory(JacksonConverterFactory.create(ob))
.client(okHttpClient)
.build()
val p = retrofit.create(MyApi::class.java)
val res = p.doRequest().block()
println(res)
可能的原因是什么?使用OkHttp3时如何正确使用客户端证书?
编辑后我添加 .addPlatformTrustedCertificates()
我得到
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:307)
at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:285)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:180)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1152)
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1063)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:402)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:336)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:300)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:185)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:224)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:108)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:88)
at okhttp3.internal.connection.Transmitter.newExchange(Transmitter.java:169)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:41)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:213)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
at okhttp3.RealCall.execute(RealCall.java:81)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:204)
at com.jakewharton.retrofit2.adapter.reactor.ExecuteSinkConsumer.accept(ExecuteSinkConsumer.java:39)
at com.jakewharton.retrofit2.adapter.reactor.ExecuteSinkConsumer.accept(ExecuteSinkConsumer.java:24)
at reactor.core.publisher.FluxCreate.subscribe(FluxCreate.java:94)
at reactor.core.publisher.Flux.subscribe(Flux.java:8143)
at com.jakewharton.retrofit2.adapter.reactor.BodyFlux.subscribe(BodyFlux.java:36)
at reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.run(FluxSubscribeOn.java:194)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
我 运行 和 javax.net.debug=ssl,handshake
并在我的日志中找到
javax.net.ssl|DEBUG|12|my.app.MyApi-2|2021-03-03 14:00:09.656 CET|CertificateMessage.java:290|No X.509 certificate for client authentication, use empty Certificate message instead
javax.net.ssl|DEBUG|12|my.app.MyApi-2|2021-03-03 14:00:09.656 CET|CertificateMessage.java:321|Produced client Certificate handshake message (
"Certificates": <empty list>
)
还有我的build.gradle
compile group: 'com.squareup.retrofit2', name: 'retrofit', version: '2.9.0'
compile group: 'com.squareup.retrofit2', name: 'converter-jackson', version: '2.9.0'
compile group: 'com.squareup.retrofit2', name: 'adapter-rxjava2', version: '2.9.0'
compile group: 'com.jakewharton.retrofit', name: 'retrofit2-reactor-adapter', version: '2.1.0'
compile group: 'com.squareup.okhttp3', name: 'okhttp-tls', version: '3.14.4'
val handshakeCertificates = HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.addTrustedCertificate(cert)
.build()
clientBuilder
.sslSocketFactory(handshakeCertificates.sslSocketFactory(),
handshakeCertificates.trustManager)
您应该可以在调试器中查看 SunCertPathBuilderException 中的证书链。
This is a subclass of the generic CertPathBuilderException. It contains an adjacency list with information regarding the unsuccessful paths that the SunCertPathBuilder tried.
最终您可能需要在 JSSE 中启用 SSL 调试以查看它失败的原因。
val p12 = KeyStore.getInstance("pkcs12").apply {
load(inputStream, password)
}
val clientCert = p12.getCertificateChain(alias)[0] as X509Certificate
val rootCert = p12.getCertificateChain(alias)[1] as X509Certificate
val clientPrivateKey = p12.getKey(alias, password) as PrivateKey
val clientTLSCredentials = HeldCertificate(KeyPair(clientCert.publicKey, clientPrivateKey), clientCert)
val clientTlsHandShake = HandshakeCertificates.Builder()
.heldCertificate(clientTLSCredentials, rootCert)
.addPlatformTrustedCertificates()
.build()
val client = OkHttpClient.Builder()
.sslSocketFactory(clientTlsHandShake.sslSocketFactory(), clientTlsHandShake.trustManager())
.build()
能够使用 Clyde Barrow 的解决方案来解决这个问题。这是我的纯 Java 解决方案(与 Clyde 的解决方案没有太大区别)。这是使用由 Entrust 签名的客户端证书和中间证书。
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(new File("myKeyStore.jks")), "mypassword".toCharArray());
Certificate[] certificates = keyStore.getCertificateChain("myalias");
X509Certificate clientCertificate = (X509Certificate) certificates[0];
X509Certificate intermediateCertificate = (X509Certificate) certificates[1];
PrivateKey privateKey = (PrivateKey) keyStore.getKey("myalias", "mypassword".toCharArray());
PublicKey publicKey = clientCertificate.getPublicKey();
KeyPair keyPair = new KeyPair(publicKey, privateKey);
HeldCertificate heldCertificate = new HeldCertificate(keyPair, clientCertificate);
HandshakeCertificates.Builder builder =
new HandshakeCertificates.Builder()
.heldCertificate(heldCertificate, intermediateCertificate)
.addPlatformTrustedCertificates();
HandshakeCertificates handshakeCertificates = builder.build();
OkHttpClient okClient =
new OkHttpClient().newBuilder().readTimeout(30, TimeUnit.SECONDS)
.sslSocketFactory(handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
.build();
在我的应用程序中,我将 retrofit2(2.9.0) 与 OkHttp3(3.14.4) 结合使用。我想将 tls 客户端证书添加到我对某些 api 的所有请求中。我在 .p12
文件中获得了证书。我读取了文件,加载到 X509Certificate class 然后我使用了 .addTrustedCertificate(certificate)
方法。证书是正确的,我使用 curl 尝试过。不幸的是,当我 运行 代码时出现异常。
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:320)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:263)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:258)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:641)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:460)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:360)
at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:177)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1152)
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1063)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:402)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:336)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:300)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:185)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:224)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:108)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:88)
at okhttp3.internal.connection.Transmitter.newExchange(Transmitter.java:169)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:41)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:213)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
at okhttp3.RealCall.execute(RealCall.java:81)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:204)
at com.jakewharton.retrofit2.adapter.reactor.ExecuteSinkConsumer.accept(ExecuteSinkConsumer.java:39)
at com.jakewharton.retrofit2.adapter.reactor.ExecuteSinkConsumer.accept(ExecuteSinkConsumer.java:24)
at reactor.core.publisher.FluxCreate.subscribe(FluxCreate.java:94)
at reactor.core.publisher.Flux.subscribe(Flux.java:8143)
at com.jakewharton.retrofit2.adapter.reactor.BodyFlux.subscribe(BodyFlux.java:36)
at reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.run(FluxSubscribeOn.java:194)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:290)
at java.base/sun.security.validator.Validator.validate(Validator.java:264)
at java.base/sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:313)
at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:222)
at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:129)
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:625)
... 47 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297)
at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
... 53 more
Process finished with exit code 1
我的代码是这样的
val fileName = "certyficate.p12"
val file = File(fileName)
val inputStream = file.inputStream()
val p12 = KeyStore.getInstance("pkcs12")
val password = "password"
p12.load(inputStream, password.toCharArray())
val alias = p12.aliases().nextElement()
val certificate = p12.getCertificate(alias) as X509Certificate
val ob = ObjectMapper().registerModule(KotlinModule())
val hostname = "https://myapi.com"
val timeout = 5L
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(timeout, TimeUnit.SECONDS)
.readTimeout(timeout, TimeUnit.SECONDS)
.writeTimeout(timeout, TimeUnit.SECONDS)
.apply {
addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
}
.apply {
val clientCertificate = HandshakeCertificates
.Builder()
.addTrustedCertificate(certificate)
.build()
sslSocketFactory(clientCertificate.sslSocketFactory(), clientCertificate.trustManager())
}
.build()
val retrofit = Retrofit.Builder()
.baseUrl(hostname)
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(
io.reactivex.schedulers.Schedulers.io()
))
.addCallAdapterFactory(ReactorCallAdapterFactory.createWithScheduler(Schedulers.newElastic(
MyApi::class.java.name
)))
.addConverterFactory(JacksonConverterFactory.create(ob))
.client(okHttpClient)
.build()
val p = retrofit.create(MyApi::class.java)
val res = p.doRequest().block()
println(res)
可能的原因是什么?使用OkHttp3时如何正确使用客户端证书?
编辑后我添加 .addPlatformTrustedCertificates()
我得到
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:307)
at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:285)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:180)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1152)
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1063)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:402)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:336)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:300)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:185)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:224)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:108)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:88)
at okhttp3.internal.connection.Transmitter.newExchange(Transmitter.java:169)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:41)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:213)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
at okhttp3.RealCall.execute(RealCall.java:81)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:204)
at com.jakewharton.retrofit2.adapter.reactor.ExecuteSinkConsumer.accept(ExecuteSinkConsumer.java:39)
at com.jakewharton.retrofit2.adapter.reactor.ExecuteSinkConsumer.accept(ExecuteSinkConsumer.java:24)
at reactor.core.publisher.FluxCreate.subscribe(FluxCreate.java:94)
at reactor.core.publisher.Flux.subscribe(Flux.java:8143)
at com.jakewharton.retrofit2.adapter.reactor.BodyFlux.subscribe(BodyFlux.java:36)
at reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.run(FluxSubscribeOn.java:194)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
我 运行 和 javax.net.debug=ssl,handshake
并在我的日志中找到
javax.net.ssl|DEBUG|12|my.app.MyApi-2|2021-03-03 14:00:09.656 CET|CertificateMessage.java:290|No X.509 certificate for client authentication, use empty Certificate message instead
javax.net.ssl|DEBUG|12|my.app.MyApi-2|2021-03-03 14:00:09.656 CET|CertificateMessage.java:321|Produced client Certificate handshake message (
"Certificates": <empty list>
)
还有我的build.gradle
compile group: 'com.squareup.retrofit2', name: 'retrofit', version: '2.9.0'
compile group: 'com.squareup.retrofit2', name: 'converter-jackson', version: '2.9.0'
compile group: 'com.squareup.retrofit2', name: 'adapter-rxjava2', version: '2.9.0'
compile group: 'com.jakewharton.retrofit', name: 'retrofit2-reactor-adapter', version: '2.1.0'
compile group: 'com.squareup.okhttp3', name: 'okhttp-tls', version: '3.14.4'
val handshakeCertificates = HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.addTrustedCertificate(cert)
.build()
clientBuilder
.sslSocketFactory(handshakeCertificates.sslSocketFactory(),
handshakeCertificates.trustManager)
您应该可以在调试器中查看 SunCertPathBuilderException 中的证书链。
This is a subclass of the generic CertPathBuilderException. It contains an adjacency list with information regarding the unsuccessful paths that the SunCertPathBuilder tried.
最终您可能需要在 JSSE 中启用 SSL 调试以查看它失败的原因。
val p12 = KeyStore.getInstance("pkcs12").apply {
load(inputStream, password)
}
val clientCert = p12.getCertificateChain(alias)[0] as X509Certificate
val rootCert = p12.getCertificateChain(alias)[1] as X509Certificate
val clientPrivateKey = p12.getKey(alias, password) as PrivateKey
val clientTLSCredentials = HeldCertificate(KeyPair(clientCert.publicKey, clientPrivateKey), clientCert)
val clientTlsHandShake = HandshakeCertificates.Builder()
.heldCertificate(clientTLSCredentials, rootCert)
.addPlatformTrustedCertificates()
.build()
val client = OkHttpClient.Builder()
.sslSocketFactory(clientTlsHandShake.sslSocketFactory(), clientTlsHandShake.trustManager())
.build()
能够使用 Clyde Barrow 的解决方案来解决这个问题。这是我的纯 Java 解决方案(与 Clyde 的解决方案没有太大区别)。这是使用由 Entrust 签名的客户端证书和中间证书。
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(new File("myKeyStore.jks")), "mypassword".toCharArray());
Certificate[] certificates = keyStore.getCertificateChain("myalias");
X509Certificate clientCertificate = (X509Certificate) certificates[0];
X509Certificate intermediateCertificate = (X509Certificate) certificates[1];
PrivateKey privateKey = (PrivateKey) keyStore.getKey("myalias", "mypassword".toCharArray());
PublicKey publicKey = clientCertificate.getPublicKey();
KeyPair keyPair = new KeyPair(publicKey, privateKey);
HeldCertificate heldCertificate = new HeldCertificate(keyPair, clientCertificate);
HandshakeCertificates.Builder builder =
new HandshakeCertificates.Builder()
.heldCertificate(heldCertificate, intermediateCertificate)
.addPlatformTrustedCertificates();
HandshakeCertificates handshakeCertificates = builder.build();
OkHttpClient okClient =
new OkHttpClient().newBuilder().readTimeout(30, TimeUnit.SECONDS)
.sslSocketFactory(handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
.build();