記一次 gradle 編譯過程中的 SSL handshake_failure

1. 背景:Android Studio中 Gradle 同步拉取library信息失敗

2022-12-14T17:13:14.916+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
2022-12-14T17:13:14.916+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436)
2022-12-14T17:13:14.916+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384)
2022-12-14T17:13:14.916+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
2022-12-14T17:13:14.916+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:374)
2022-12-14T17:13:14.916+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393)
2022-12-14T17:13:14.917+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
2022-12-14T17:13:14.917+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
2022-12-14T17:13:14.917+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
2022-12-14T17:13:14.917+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
2022-12-14T17:13:14.917+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
2022-12-14T17:13:14.917+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
2022-12-14T17:13:14.917+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.gradle.internal.resource.transport.http.HttpClientHelper.performHttpRequest(HttpClientHelper.java:141)
2022-12-14T17:13:14.917+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.gradle.internal.resource.transport.http.HttpClientHelper.performHttpRequest(HttpClientHelper.java:121)
2022-12-14T17:13:14.918+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.gradle.internal.resource.transport.http.HttpClientHelper.executeGetOrHead(HttpClientHelper.java:106)
2022-12-14T17:13:14.918+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   at org.gradle.internal.resource.transport.http.HttpClientHelper.performRequest(HttpClientHelper.java:97)
2022-12-14T17:13:14.919+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]   ... 50 more


Daemon worker: released lock on root.1
2022-12-14T17:13:14.860+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] 
2022-12-14T17:13:14.862+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] FAILURE: Build failed with an exception.
2022-12-14T17:13:14.863+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] 
2022-12-14T17:13:14.863+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] * What went wrong:
2022-12-14T17:13:14.864+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] Could not determine the dependencies of task ':app:compileJunoUatDebugKotlin'.
2022-12-14T17:13:14.867+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] > Could not resolve all files for configuration ':app:googleplayUatDebugRuntimeClasspath'.
2022-12-14T17:13:14.867+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]    > Could not download base-1.1.2-SNAPSHOT.aar (com.your.pkg:base:1.1.2-SNAPSHOT:20210628.100523-1)
2022-12-14T17:13:14.867+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]       > Could not get resource 'https://nexus3.***.io/repository/maven-mobile-snapshots/com/your/pkg/base/1.1.2-SNAPSHOT/base-1.1.2-20210628.100523-1.aar'.
2022-12-14T17:13:14.868+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]          > Could not HEAD 'https://nexus3.***.io/repository/maven-mobile-snapshots/com/your/pkg/base/1.1.2-SNAPSHOT/base-1.1.2-20210628.100523-1.aar'.
2022-12-14T17:13:14.868+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]             > Received fatal alert: handshake_failure
2022-12-14T17:13:14.868+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] 
2022-12-14T17:13:14.868+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter] * Try:
2022-12-14T17:13:14.868+0800 [ERROR] [org.gradle.internal.buildevents.BuildExceptionReporter]  Run with --scan to get full insights.

2. 查看nexus服務(wù)器支持的tls 版本以及客戶端的設(shè)置

經(jīng)過在網(wǎng)上搜索,有一些線索。Https握手階段失敗,最直接的想法??是 客戶端與服務(wù)器 TLS 版本協(xié)商失敗。重新看了下客戶端JDK的版本已經(jīng)是JDK11,再 分析 下 nexus 服務(wù)器上支持的協(xié)議,看到并不存在它所提到的問題。

3. 工具分析

通過 openssl 命令 openssl s_client -connect nexus3.***.io:443 [-servername nexus3.***.io] (帶或不帶 參數(shù)servername)的結(jié)果來看, 服務(wù)器端是否支持 SNI,即它存在多臺虛擬主機,可以根據(jù)客戶端請求中不同的host,將請求分發(fā)給不同的域名(虛擬主機)來處理。

注意到 JDK 1.8中 參數(shù) jsse.enableSNIExtension 默認值為 true。
于是在AS 項目中附加了參數(shù) -Djsse.enableSNIExtension=trueorg.gradle.jvmargs中。操作完成后重試,發(fā)現(xiàn)本地編譯偶有成功,但問題依舊存在。

可以使用openssl s_client和不使用-servername選項:

without SNI

$ openssl s_client -connect host:port

use SNI

$ openssl s_client -connect host:port -servername host
如果你獲得兩個同名的不同證書,則表示支持并正確配置了 SNI。
但是,如果返回的證書中的輸出不同,或者沒有SNI的調(diào)用無法建立SSL連接,則表明需要SNI但沒有正確配置。解決此問題可能需要切換到專用 IP 地址。

此時發(fā)現(xiàn) 不帶 -servername 的命令返回的結(jié)果確實是有問題的:

> openssl s_client -connect nexus3.***.io:443 -tlsextdebug

CONNECTED(00000006)
140704677139648:error:1404B410:SSL routines:ST_CONNECT:sslv3 alert handshake failure:/AppleInternal/Library/BuildRoots/810eba08-405a-11ed-86e9-6af958a02716/Library/Caches/com.apple.xbs/Sources/libressl/libressl-3.3/ssl/tls13_lib.c:129:SSL alert number 40
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 287 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : 0000
    Session-ID:
    Session-ID-ctx:
    Master-Key:
    Start Time: 1671086879
    Timeout   : 7200 (sec)
    Verify return code: 0 

4. 和SRE一起解決問題

于是找到公司的SRE同事一起來看,他建議斷開cloud flare,設(shè)置本地host為固定IP地址后再嘗試。果然Jenkins上的編譯成功了。回頭再看看服務(wù)器 不帶 -servername 的返回,證書信息也有了。此刻真相大白了:上了cloud flare后 SNI的配置出現(xiàn)了問題!

5. 番外: regression ?!

又有一天看,其它業(yè)務(wù)組的同事也報出了相同的問題導(dǎo)致Jenkins上打包?? 出錯。在升級 gradle 版本到6.8后,發(fā)現(xiàn)除了上面的錯誤外,還額外有一條錯誤信息是

[org.gradle.internal.buildevents.BuildExceptionReporter] > 
The server may not support the client's requested TLS protocol versions: (TLSv1.2, TLSv1.3). 
You may need to configure the client to allow other protocols to be used. 
See: https://docs.gradle.org/6.8/userguide/build_environment.html#gradle_system_properties

這無疑之中讓我想到除了在Jenkins 主機上抓網(wǎng)絡(luò)數(shù)據(jù)包外,是不是還有其它辦法可以看到更多HTTPS 握手階段出錯的更多細節(jié)呢?果然萬能的SO給出類似問題的了一條很贊的回復(fù)[1], 在JVM中設(shè)置參數(shù)-Djavax.net.debug=all ,就可以調(diào)試 HTTP SSL/TLS 連接了。

在單獨指定tls 版本為 TLSv1.3后,再看Jenkins上詳細的log 輸出:

The server does not support the client's requested TLS protocol versions: (TLSv1.3). You may need to configure the client to allow other protocols to be used. See: https://docs.gradle.org/6.8/userguide/build_environment.html#gradle_system_properties
Received fatal alert: protocol_version
......
 [ERROR] [system.err] javax.net.ssl|DEBUG|24|Build operations Thread 2|2023-02-09 10:41:02.495 UTC|SSLSocketInputRecord.java:213|READ: TLSv1.2 alert, length = 2
 [ERROR] [system.err] javax.net.ssl|DEBUG|24|Build operations Thread 2|2023-02-09 10:41:02.495 UTC|SSLSocketInputRecord.java:458|Raw read (
 [ERROR] [system.err]   0000: 02 46                                              .F
 [ERROR] [system.err] )
 [ERROR] [system.err] javax.net.ssl|DEBUG|24|Build operations Thread 2|2023-02-09 10:41:02.495 UTC|SSLSocketInputRecord.java:249|READ: TLSv1.2 alert, length = 2
 [ERROR] [system.err] javax.net.ssl|DEBUG|24|Build operations Thread 2|2023-02-09 10:41:02.496 UTC|Alert.java:232|Received alert message (
 [ERROR] [system.err] "Alert": {
 [ERROR] [system.err]   "level"      : "fatal",
 [ERROR] [system.err]   "description": "protocol_version"
 [ERROR] [system.err] }
 [ERROR] [system.err] )
 [ERROR] [system.err] javax.net.ssl|ERROR|24|Build operations Thread 2|2023-02-09 10:41:02.497 UTC|TransportContext.java:313|Fatal (PROTOCOL_VERSION): 
 Received fatal alert: protocol_version

?? 至此為止看判斷是與服務(wù)端協(xié)商支持的tls 版本不一致導(dǎo)致的問題,將客戶端的tls 版本設(shè)置為TLSv1.2后,再次嘗試編譯,果然就成功了??。

6. Ref


  1. handshake_failure SO 鏈接 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容