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é)果來(lái)看, 服務(wù)器端是否支持 SNI,即它存在多臺(tái)虛擬主機(jī)烘豹,可以根據(jù)客戶端請(qǐng)求中不同的host瓜贾,將請(qǐng)求分發(fā)給不同的域名(虛擬主機(jī))來(lái)處理诺祸。
注意到 JDK 1.8中 參數(shù) jsse.enableSNIExtension
默認(rèn)值為 true
携悯。
于是在AS 項(xiàng)目中附加了參數(shù) -Djsse.enableSNIExtension=true
到 org.gradle.jvmargs
中。操作完成后重試筷笨,發(fā)現(xiàn)本地編譯偶有成功憔鬼,但問題依舊存在。
可以使用openssl s_client和不使用-servername選項(xiàng):
without SNI
$ openssl s_client -connect host:port
use SNI
$ openssl s_client -connect host:port -servername host
如果你獲得兩個(gè)同名的不同證書胃夏,則表示支持并正確配置了 SNI轴或。
但是,如果返回的證書中的輸出不同仰禀,或者沒有SNI的調(diào)用無(wú)法建立SSL連接照雁,則表明需要SNI但沒有正確配置。解決此問題可能需要切換到專用 IP 地址答恶。
此時(shí)發(fā)現(xiàn) 不帶 -servername 的命令返回的結(jié)果確實(shí)是有問題的:
> 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同事一起來(lái)看饺蚊,他建議斷開cloud flare萍诱,設(shè)置本地host為固定IP地址后再嘗試。果然Jenkins上的編譯成功了污呼≡7唬回頭再看看服務(wù)器 不帶 -servername 的返回,證書信息也有了燕酷。此刻真相大白了:上了cloud flare后 SNI的配置出現(xiàn)了問題籍凝!
5. 番外: regression ?!
又有一天看,其它業(yè)務(wù)組的同事也報(bào)出了相同的問題導(dǎo)致Jenkins上打包?? 出錯(cuò)苗缩。在升級(jí) gradle 版本到6.8后饵蒂,發(fā)現(xiàn)除了上面的錯(cuò)誤外,還額外有一條錯(cuò)誤信息是
[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
這無(wú)疑之中讓我想到除了在Jenkins 主機(jī)上抓網(wǎng)絡(luò)數(shù)據(jù)包外挤渐,是不是還有其它辦法可以看到更多HTTPS
握手階段出錯(cuò)的更多細(xì)節(jié)呢苹享?果然萬(wàn)能的SO給出類似問題的了一條很贊的回復(fù)[1], 在JVM中設(shè)置參數(shù)-Djavax.net.debug=all ,就可以調(diào)試 HTTP SSL/TLS
連接了浴麻。
在單獨(dú)指定tls 版本為 TLSv1.3
后得问,再看Jenkins上詳細(xì)的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
后软免,再次嘗試編譯宫纬,果然就成功了??。