V1.4.3: 修复老系统(API<24)不认 Let's Encrypt 新根,导致无法下载曲库
根因(用远程日志的 STORAGE 行 + 本地复现 Muse 协议定位): - 曲库服务 mconn.cherryonline.cn 与点歌登录 mws.cherryonline.cn 都用 Let's Encrypt 新链 leaf ← YR2 ← Root YR ← ISRG Root X1。 - res/xml/network_security_config.xml 里虽然已把这三个根加为信任锚,但 **该配置只在 Android 7.0(API 24)+ 被系统读取**;Android 5.1.1 上被完全忽略, 系统信任库又没有 ISRG 新根 → SSLHandshakeException: Trust anchor for certification path not found → login 失败 → fetchVersion()=null → 根本不触发下载, 界面只剩兜底文案「未找到可用曲库」,而失败只写 logcat,远程日志里看不到。 - 这也解释了为什么只有这台老设备出问题(其它设备都 >= API 24)。 修复: - 新增 TlsCompat: 在 API<24 上把「系统默认信任 ∪ 内置根证书」的复合 X509TrustManager 装到全局 HttpsURLConnection.setDefaultSSLSocketFactory,覆盖 App 内所有 HttpsURLConnection (曲库同步/JS 热更新下载/更新检查/头像下载)。主机名校验保持系统默认,不放行任意证书。 - WebView 的 TLS 无法用 SSLContext 覆盖(JS 会直连 mws): 在 KtvJsBridge 的 onReceivedSslError 里不盲目 proceed,先用 TlsCompat.isHostTrustedByOurStore(host) 对同一主机独立校验,通过才放行。 - TlsCompat.install 在 KtvApplication.attachBaseContext 调用(早于任何网络请求)。 - 补齐可观测性: 曲库同步每一步失败改为 MiliLog 上报 (MUSE-DB login/sync ...、DB-SYNC download failed/空间不足/成功); fetchRemoteVersion 失败时设置明确的 bootstrapError,不再只显示兜底文案。 - AGENTS.md 增补「老系统 TLS 根证书」条目。 版本 1.4.2(22) → 1.4.3(23);签名证书不变。
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package com.local.ktv
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.net.http.SslError
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import org.json.JSONObject
|
||||
@@ -108,6 +111,28 @@ class KtvJsBridge(context: Context) {
|
||||
settings.domStorageEnabled = true
|
||||
addJavascriptInterface(NativeBridge(), "android")
|
||||
webViewClient = object : WebViewClient() {
|
||||
/**
|
||||
* 老系统(API < 24)的 WebView 不读 network_security_config,
|
||||
* 系统信任库也没有 Let's Encrypt 新根 → 访问 mws.cherryonline.cn 会直接握手失败。
|
||||
* 这里**不做盲目放行**:用 [TlsCompat] 增强后的信任库对同一主机独立校验一次,
|
||||
* 只有校验通过(证书链合法且主机名匹配)才继续。
|
||||
*/
|
||||
override fun onReceivedSslError(
|
||||
view: WebView?,
|
||||
handler: SslErrorHandler?,
|
||||
error: SslError?,
|
||||
) {
|
||||
val host = runCatching { Uri.parse(view?.url)?.host }.getOrNull()
|
||||
val onlyUntrusted = error?.primaryError == SslError.SSL_UNTRUSTED
|
||||
if (onlyUntrusted && host != null && TlsCompat.isHostTrustedByOurStore(host)) {
|
||||
Log.w(TAG, "legacy TLS: $host 已用增强信任库复核通过,放行 WebView")
|
||||
handler?.proceed()
|
||||
} else {
|
||||
Log.e(TAG, "SSL error NOT overridden: host=$host primary=${error?.primaryError}")
|
||||
handler?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
view.evaluateJavascript("typeof window.KtvApi === 'function'") { result ->
|
||||
if (result == "true") {
|
||||
|
||||
Reference in New Issue
Block a user