“javax.net.ssl.SSLHandshakeException:握手失败”即使在添加自定义 TrustManager 和证书固定之后
" javax.net.ssl.SSLHandshakeException: Handshake failed" Even after adding custom TrustManager and Certificate Pinning
我打算使用 Jamendo API 下载音乐,但在连接到 API 时出现以下错误
javax.net.ssl.SSLHandshakeException: Handshake failed
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:286)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt:351)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.kt:310)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:178)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:236)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:109)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:77)
at okhttp3.internal.connection.Transmitter.newExchange$okhttp(Transmitter.kt:162)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:35)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:82)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:84)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:71)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.kt:184)
at okhttp3.RealCall.execute(RealCall.kt:66)
at com.example.musicplayer.utils.CertificatePinningKt.certificatePinning(CertificatePinning.kt:26)
at com.example.musicplayer.fragments.HomeFragment$onActivityCreated.invokeSuspend(HomeFragment.kt:42)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xedb6ee48: Failure in SSL library, usually a protocol error
error:100000f0:SSL routines:OPENSSL_internal:UNSUPPORTED_PROTOCOL (external/boringssl/src/ssl/handshake_client.cc:576 0xe5faba43:0x00000000)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:375)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:224)
... 27 more
然后我了解到 Android 不知道某些 CA 证书,解决办法是启用 TrustManager 不验证 https 请求喜欢 or to add a custom TrustManager to accept the CA certificate of the server to which I need to communicate according to this google doc 的接受答案,我采用了后一种方法。
!) 首先, 我使用以下命令
检查了 Jamendo 的 服务器信息
$ openssl s_client -connect jamendo.com:443 | openssl x509 -noout -subject -issuer
结果是
depth=3 C = US, O = "The Go Daddy Group, Inc.", OU = Go Daddy Class 2 Certification Authority
verify return:1
depth=2 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", CN = Go Daddy Root Certificate Authority - G2
verify return:1
depth=1 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", OU = http://certs.godaddy.com/repository/, CN = Go Daddy Secure Certificate Authority - G2
verify return:1
depth=0 C = LU, L = Luxembourg, O = Jamendo SA, CN = .jamendo.com
verify return:1
subject= /C=LU/L=Luxembourg/O=Jamendo SA/CN=.jamendo.com
issuer= /C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./OU=http://certs.godaddy.com/repository//CN=Go Daddy Secure Certificate Authority - G2
看起来 Jamendo 正在使用 GoDaddy 的 托管服务,所以我去了他们的 certification page 并且如上输出,使用的证书来自 G2 组所以我下载了 root 和 intermediate 证书(前两个),因为它们在输出中提到。
2) And 然后我尝试创建自定义 SSLSocketFactory 和 TrustManager 结果同样的错误。 方法如下代码所示
fun getCACertificateAndroid(resources: Resources): Pair<SSLSocketFactory, X509TrustManager> {
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
// certificate 1
var caInput: InputStream = resources.openRawResource(R.raw.gdig2)
val ca: X509Certificate = caInput.use {
cf.generateCertificate(it) as X509Certificate
}
Log.d("GetCACertificateAndroid", "getCACertificateAndroid: ca= + ${ca.subjectDN}")
// certificate 2
caInput = resources.openRawResource(R.raw.gdroot_g2)
val ca1: X509Certificate = caInput.use {
cf.generateCertificate(it) as X509Certificate
}
Log.d("GetCACertificateAndroid", "getCACertificateAndroid: ca1= + ${ca1.subjectDN}")
// Create a KeyStore containing our trusted CAs
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType).apply {
load(null, null)
setCertificateEntry("ca", ca)
setCertificateEntry("ca1", ca1)
}
// Create a TrustManager that trusts the CAs inputStream our KeyStore
val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
init(keyStore)
}
// Create an SSLContext that uses our TrustManager
val context: SSLContext = SSLContext.getInstance("TLS").apply {
init(null, tmf.trustManagers, null)
}
return Pair(context.socketFactory, tmf.trustManagers[0] as X509TrustManager)
}
一些其他文件
...
val (_sslSocketFactory, x509tTrustManager) = getCACertificateAndroid(resources)
val client = OkHttpClient.Builder()
.sslSocketFactory(_sslSocketFactory, x509tTrustManager)
.build()
val gson = GsonBuilder()
.setLenient()
.create()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.jamendo.com/v3.0/playlists/?client_id=c7668145&format=jsonpretty&namesearch=cool") // "https://storage.googleapis.com/"
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build()
...
3) 然后 我开始了解 证书固定 in OKHttp 并想通过获取base64 编码 通过 运行 宁以下命令
下载的证书
openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
根据 接受的答案,然后尝试 运行 以下代码,但错误仍然存在。
fun certificatePinning() {
val hostname = "api.jamendo.com/v3.0/playlists/?client_id=c7668145&format=jsonpretty&namesearch=cool"
val sha256base64_hash1 = "sha256/Ko8tivDrEjiY90yGasP6ZpBU4jwXvHqVvQI0GS3GNdA="
val sha256base64_hash2 = "sha256/8Rw90Ej3Ttt8RRkrg+WYDS9n7IS03bk5bjP/UXPtaY8="
val certificatePinner = CertificatePinner.Builder()
.add(hostname, sha256base64_hash1)
.add(hostname, sha256base64_hash2)
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
val request = Request.Builder()
.url("https://$hostname")
.build()
client.newCall(request).execute()
}
如果有人能为我指出正确的方向,那将是一个很大的帮助。
握手问题是由于 Jamendo API 使用旧的已弃用的 TLS 协议版本 (1.0) 并且不支持较新的协议版本:
* https://github.com/square/okhttp/issues/4670
* https://medium.com/square-corner-blog/okhttp-3-13-requires-android-5-818bb78d07ce
旁注:我肯定会选择反对自定义 TrustManager 实现,这只会有意义,例如如果您的端点使用的是自签名证书。作为一项基本检查,我会通过尝试直接在 phone/emulator 浏览器上打开 Jamendo URL 来验证您的 Android System TrustStore 是否正常工作,看看您是否遇到任何问题?固定提供了额外的保护,但不能解决您看到的基本握手问题。
检查您的 'TLS' 支持。我遇到过这个问题。在我的应用程序中,我使用改造库。因此,请尝试将 'COMPATIBLE_TLS' 配置添加到您的 OkHttpClient,例如:
OkHttpClient client = new OkHttpClient();
List<ConnectionSpec> connectionSpecs = new ArrayList<>();
connectionSpecs.add(ConnectionSpec.COMPATIBLE_TLS);
client.setConnectionSpecs(connectionSpecs);
...
并至少将 gradle 中的库更新到 v2.7.5,例如:
implementation 'com.squareup.okhttp:okhttp:2.7.5'
我打算使用 Jamendo API 下载音乐,但在连接到 API 时出现以下错误
javax.net.ssl.SSLHandshakeException: Handshake failed
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:286)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt:351)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.kt:310)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:178)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:236)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:109)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:77)
at okhttp3.internal.connection.Transmitter.newExchange$okhttp(Transmitter.kt:162)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:35)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:82)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:84)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:71)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.kt:184)
at okhttp3.RealCall.execute(RealCall.kt:66)
at com.example.musicplayer.utils.CertificatePinningKt.certificatePinning(CertificatePinning.kt:26)
at com.example.musicplayer.fragments.HomeFragment$onActivityCreated.invokeSuspend(HomeFragment.kt:42)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xedb6ee48: Failure in SSL library, usually a protocol error
error:100000f0:SSL routines:OPENSSL_internal:UNSUPPORTED_PROTOCOL (external/boringssl/src/ssl/handshake_client.cc:576 0xe5faba43:0x00000000)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:375)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:224)
... 27 more
然后我了解到 Android 不知道某些 CA 证书,解决办法是启用 TrustManager 不验证 https 请求喜欢
!) 首先, 我使用以下命令
检查了 Jamendo 的 服务器信息$ openssl s_client -connect jamendo.com:443 | openssl x509 -noout -subject -issuer
结果是
depth=3 C = US, O = "The Go Daddy Group, Inc.", OU = Go Daddy Class 2 Certification Authority verify return:1
depth=2 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", CN = Go Daddy Root Certificate Authority - G2 verify return:1
depth=1 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", OU = http://certs.godaddy.com/repository/, CN = Go Daddy Secure Certificate Authority - G2 verify return:1
depth=0 C = LU, L = Luxembourg, O = Jamendo SA, CN = .jamendo.com verify return:1
subject= /C=LU/L=Luxembourg/O=Jamendo SA/CN=.jamendo.com issuer= /C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./OU=http://certs.godaddy.com/repository//CN=Go Daddy Secure Certificate Authority - G2
看起来 Jamendo 正在使用 GoDaddy 的 托管服务,所以我去了他们的 certification page 并且如上输出,使用的证书来自 G2 组所以我下载了 root 和 intermediate 证书(前两个),因为它们在输出中提到。
2) And 然后我尝试创建自定义 SSLSocketFactory 和 TrustManager 结果同样的错误。 方法如下代码所示
fun getCACertificateAndroid(resources: Resources): Pair<SSLSocketFactory, X509TrustManager> {
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
// certificate 1
var caInput: InputStream = resources.openRawResource(R.raw.gdig2)
val ca: X509Certificate = caInput.use {
cf.generateCertificate(it) as X509Certificate
}
Log.d("GetCACertificateAndroid", "getCACertificateAndroid: ca= + ${ca.subjectDN}")
// certificate 2
caInput = resources.openRawResource(R.raw.gdroot_g2)
val ca1: X509Certificate = caInput.use {
cf.generateCertificate(it) as X509Certificate
}
Log.d("GetCACertificateAndroid", "getCACertificateAndroid: ca1= + ${ca1.subjectDN}")
// Create a KeyStore containing our trusted CAs
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType).apply {
load(null, null)
setCertificateEntry("ca", ca)
setCertificateEntry("ca1", ca1)
}
// Create a TrustManager that trusts the CAs inputStream our KeyStore
val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
init(keyStore)
}
// Create an SSLContext that uses our TrustManager
val context: SSLContext = SSLContext.getInstance("TLS").apply {
init(null, tmf.trustManagers, null)
}
return Pair(context.socketFactory, tmf.trustManagers[0] as X509TrustManager)
}
一些其他文件
...
val (_sslSocketFactory, x509tTrustManager) = getCACertificateAndroid(resources)
val client = OkHttpClient.Builder()
.sslSocketFactory(_sslSocketFactory, x509tTrustManager)
.build()
val gson = GsonBuilder()
.setLenient()
.create()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.jamendo.com/v3.0/playlists/?client_id=c7668145&format=jsonpretty&namesearch=cool") // "https://storage.googleapis.com/"
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build()
...
3) 然后 我开始了解 证书固定 in OKHttp 并想通过获取base64 编码 通过 运行 宁以下命令
下载的证书openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
根据
fun certificatePinning() {
val hostname = "api.jamendo.com/v3.0/playlists/?client_id=c7668145&format=jsonpretty&namesearch=cool"
val sha256base64_hash1 = "sha256/Ko8tivDrEjiY90yGasP6ZpBU4jwXvHqVvQI0GS3GNdA="
val sha256base64_hash2 = "sha256/8Rw90Ej3Ttt8RRkrg+WYDS9n7IS03bk5bjP/UXPtaY8="
val certificatePinner = CertificatePinner.Builder()
.add(hostname, sha256base64_hash1)
.add(hostname, sha256base64_hash2)
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
val request = Request.Builder()
.url("https://$hostname")
.build()
client.newCall(request).execute()
}
如果有人能为我指出正确的方向,那将是一个很大的帮助。
握手问题是由于 Jamendo API 使用旧的已弃用的 TLS 协议版本 (1.0) 并且不支持较新的协议版本:
* https://github.com/square/okhttp/issues/4670
* https://medium.com/square-corner-blog/okhttp-3-13-requires-android-5-818bb78d07ce
旁注:我肯定会选择反对自定义 TrustManager 实现,这只会有意义,例如如果您的端点使用的是自签名证书。作为一项基本检查,我会通过尝试直接在 phone/emulator 浏览器上打开 Jamendo URL 来验证您的 Android System TrustStore 是否正常工作,看看您是否遇到任何问题?固定提供了额外的保护,但不能解决您看到的基本握手问题。
检查您的 'TLS' 支持。我遇到过这个问题。在我的应用程序中,我使用改造库。因此,请尝试将 'COMPATIBLE_TLS' 配置添加到您的 OkHttpClient,例如:
OkHttpClient client = new OkHttpClient();
List<ConnectionSpec> connectionSpecs = new ArrayList<>();
connectionSpecs.add(ConnectionSpec.COMPATIBLE_TLS);
client.setConnectionSpecs(connectionSpecs);
...
并至少将 gradle 中的库更新到 v2.7.5,例如:
implementation 'com.squareup.okhttp:okhttp:2.7.5'