mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 14:50:45 +00:00
被控端 - 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%) - 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败) - 每分钟回头找局域网,自动切回快 400 倍的路径 - 画面心跳帧:静止时省流量,客户端仍知道流是活的 - 锁屏时给出可操作的提示 手表端 - 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException - 连接错误翻译成人话(证书/时间/解析/超时分别提示) - 经中转连上后用服务器报的地址刷新已配对记录 - 新增局域网连接页面、服务器延迟检测 服务器端 - 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502) - 网络调优脚本 net-tune.sh(开机自动应用) - 管理页:批量删除、网络体检、下载区 - 隧道改 HTTP/2 + 双连接 文档 - 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
307 lines
12 KiB
Python
307 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""生成 TlsCompat.java —— 修手表端 "SSLHandshakeException: Chain validation failed"。
|
||
|
||
问题:
|
||
手表连中转报 Chain validation failed。
|
||
证书链是:xxmxxs.top <- GTS WE1 <- GTS Root R4 <- GlobalSign Root CA
|
||
廉价手表的根证书库常常缺 GTS Root R4(2021 年才加进 AOSP),
|
||
于是找不到信任锚 -> 握手直接失败。
|
||
|
||
修法:
|
||
把这两个根证书**内嵌进 APK**,校验时"系统库 或 内置库"任一通过即可。
|
||
这两个都是公开的合法根证书,不是"跳过校验" ——
|
||
安全性仍然完整:证书必须由可信 CA 签发、且域名匹配。
|
||
只是把可信 CA 集合从"手表自带的那一份"扩大到"手表自带的 + 这两个"。
|
||
|
||
另外把错误信息翻译成人话:
|
||
* 如果报的是时间相关(not yet valid / expired)-> 提示手表时间不对
|
||
* 如果还是校验失败 -> 提示可能被中间人劫持
|
||
|
||
顺带还把 "HTTP 错误码" 和 "网络不通" 区分开,便于用户自查。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||
CERTS = ROOT / "android" / "certs"
|
||
OUT = (ROOT / "android" / "src" / "com" / "lanremote" / "watch"
|
||
/ "TlsCompat.java")
|
||
|
||
|
||
def pem_to_java(text: str) -> str:
|
||
"""把 PEM 变成 Java 字符串拼接(每行一个常量,避免超长行)。"""
|
||
lines = [line.strip() for line in text.strip().splitlines()
|
||
if line.strip()]
|
||
parts = " +\n ".join(f'"{line}\\n"' for line in lines)
|
||
return parts
|
||
|
||
|
||
def main() -> int:
|
||
gts = (CERTS / "gts_root_r4.pem").read_text(encoding="ascii")
|
||
globalsign = (CERTS / "globalsign_root_ca.pem").read_text(encoding="ascii")
|
||
|
||
source = '''package com.lanremote.watch;
|
||
|
||
import java.io.ByteArrayInputStream;
|
||
import java.net.HttpURLConnection;
|
||
import java.net.URL;
|
||
import java.security.KeyStore;
|
||
import java.security.cert.CertificateFactory;
|
||
import java.security.cert.X509Certificate;
|
||
|
||
import javax.net.ssl.HttpsURLConnection;
|
||
import javax.net.ssl.SSLContext;
|
||
import javax.net.ssl.SSLHandshakeException;
|
||
import javax.net.ssl.SSLSocketFactory;
|
||
import javax.net.ssl.TrustManager;
|
||
import javax.net.ssl.TrustManagerFactory;
|
||
import javax.net.ssl.X509TrustManager;
|
||
|
||
/**
|
||
* TLS 兼容层:让老手表也能连上中转。
|
||
*
|
||
* <p>为什么需要:
|
||
* 手表端报 {@code SSLHandshakeException: Chain validation failed}。
|
||
* 中转的证书链是
|
||
* {@code relay.xxmxxs.top <- GTS WE1 <- GTS Root R4 <- GlobalSign Root CA},
|
||
* 而很多廉价手表的系统根证书库缺 GTS Root R4(AOSP 2021 年才加),
|
||
* 于是找不到信任锚,握手直接失败。
|
||
*
|
||
* <p>做法:
|
||
* 把这两个根证书内嵌进 APK,校验时<b>系统库或内置库任一通过即可</b>。
|
||
* 这两个都是公开的合法根证书,<b>不是跳过校验</b>:
|
||
* 证书仍必须由可信 CA 签发、且域名匹配,只是把可信 CA 的集合
|
||
* 从"手表自带的那一份"扩大到"手表自带的 + 这两个"。
|
||
*
|
||
* <p>同时把难懂的异常翻译成用户能看懂的话。
|
||
*/
|
||
public final class TlsCompat {
|
||
|
||
private TlsCompat() {
|
||
}
|
||
|
||
// ---------------- 内置的根证书 ----------------
|
||
|
||
/** Google Trust Services Root R4(自签名根,EC P-384)。 */
|
||
private static final String GTS_ROOT_R4 =
|
||
%GTS%;
|
||
|
||
/** GlobalSign Root CA(自签名根,RSA 2048,1998 年起就在各系统里)。 */
|
||
private static final String GLOBALSIGN_ROOT_CA =
|
||
%GS%;
|
||
|
||
private static SSLSocketFactory factory;
|
||
private static X509TrustManager bundled;
|
||
private static boolean ready;
|
||
private static boolean initTried;
|
||
|
||
// ---------------- 对外 ----------------
|
||
|
||
/**
|
||
* 打开一个连接,HTTPS 时挂上兼容的 SSLSocketFactory。
|
||
*
|
||
* <p>HTTP(局域网中转)不走这里,原样返回。
|
||
*/
|
||
public static HttpURLConnection open(String urlText, int timeoutMs)
|
||
throws Exception {
|
||
HttpURLConnection connection =
|
||
(HttpURLConnection) new URL(urlText).openConnection();
|
||
connection.setConnectTimeout(timeoutMs);
|
||
connection.setReadTimeout(timeoutMs);
|
||
connection.setRequestProperty("User-Agent", "LanRemote/1.4.0");
|
||
connection.setRequestProperty("Accept-Encoding", "identity");
|
||
if (connection instanceof HttpsURLConnection) {
|
||
SSLSocketFactory ssl = factory();
|
||
if (ssl != null) {
|
||
((HttpsURLConnection) connection).setSSLSocketFactory(ssl);
|
||
}
|
||
}
|
||
return connection;
|
||
}
|
||
|
||
/** 拿到兼容的 SSLSocketFactory;准备失败就返回 null(退回系统默认)。 */
|
||
public static synchronized SSLSocketFactory factory() {
|
||
if (!initTried) {
|
||
initTried = true;
|
||
try {
|
||
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
|
||
store.load(null, null);
|
||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||
int index = 0;
|
||
for (String pem : new String[]{GTS_ROOT_R4, GLOBALSIGN_ROOT_CA}) {
|
||
X509Certificate cert = (X509Certificate) cf.generateCertificate(
|
||
new ByteArrayInputStream(pem.getBytes("UTF-8")));
|
||
store.setCertificateEntry("lanremote-ca-" + (index++), cert);
|
||
}
|
||
|
||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
|
||
TrustManagerFactory.getDefaultAlgorithm());
|
||
tmf.init(store);
|
||
for (TrustManager tm : tmf.getTrustManagers()) {
|
||
if (tm instanceof X509TrustManager) {
|
||
bundled = (X509TrustManager) tm;
|
||
break;
|
||
}
|
||
}
|
||
|
||
SSLContext context = SSLContext.getInstance("TLS");
|
||
context.init(null, new TrustManager[]{new CompatTrustManager()}, null);
|
||
factory = context.getSocketFactory();
|
||
ready = true;
|
||
} catch (Throwable error) {
|
||
// 准备失败就退回系统默认,不影响正常设备
|
||
factory = null;
|
||
ready = false;
|
||
}
|
||
}
|
||
return ready ? factory : null;
|
||
}
|
||
|
||
/**
|
||
* 把 TLS / 网络异常翻译成用户能看懂的一句话。
|
||
*
|
||
* <p>手表上原本只会显示一长串英文类名,用户完全不知道该怎么办。
|
||
*/
|
||
public static String explain(Throwable error) {
|
||
if (error == null) {
|
||
return "未知错误";
|
||
}
|
||
String text = String.valueOf(error);
|
||
String lower = text.toLowerCase();
|
||
|
||
if (lower.contains("chain validation failed")
|
||
|| lower.contains("trust anchor")
|
||
|| lower.contains("certpathvalidator")) {
|
||
return "证书校验失败(中转的根证书手表不认识)。"
|
||
+ "可能是手表系统太旧,或网络被劫持。";
|
||
}
|
||
if (lower.contains("not yet valid") || lower.contains("expired")
|
||
|| lower.contains("certificate expired")) {
|
||
return "证书时间不对 —— 把手表的【日期和时间】调成自动获取,"
|
||
+ "再试一次。";
|
||
}
|
||
if (lower.contains("timeout") || lower.contains("timed out")) {
|
||
return "连接超时(网络太慢或被挡住)";
|
||
}
|
||
if (lower.contains("unable to resolve host")
|
||
|| lower.contains("unknownhost")) {
|
||
return "域名解析不了(检查手表能不能上网)";
|
||
}
|
||
if (lower.contains("connection refused")) {
|
||
return "对方拒绝了连接(电脑上 ykxmz 没开?)";
|
||
}
|
||
if (lower.contains("network is unreachable")
|
||
|| lower.contains("no route")) {
|
||
return "手表没联网(检查 WiFi)";
|
||
}
|
||
if (lower.contains("eof") || lower.contains("reset")) {
|
||
return "连接被中断(网络不稳)";
|
||
}
|
||
if (text.length() > 80) {
|
||
return text.substring(0, 80);
|
||
}
|
||
return text;
|
||
}
|
||
|
||
/**
|
||
* 组合信任:系统默认 或 内置根证书,任一通过即认可。
|
||
*
|
||
* <p>这是修这个问题的关键 ——
|
||
* 老手表缺 GTS Root R4,但内置的那份有,于是校验通过;
|
||
* 新设备走系统默认照样通过。两边都不削弱安全性。
|
||
*/
|
||
private static final class CompatTrustManager implements X509TrustManager {
|
||
|
||
private final X509TrustManager system = findSystem();
|
||
|
||
private static X509TrustManager findSystem() {
|
||
try {
|
||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
|
||
TrustManagerFactory.getDefaultAlgorithm());
|
||
tmf.init((KeyStore) null); // null = 系统默认
|
||
for (TrustManager tm : tmf.getTrustManagers()) {
|
||
if (tm instanceof X509TrustManager) {
|
||
return (X509TrustManager) tm;
|
||
}
|
||
}
|
||
} catch (Throwable ignored) {
|
||
// 拿不到就算了,下面用内置的
|
||
}
|
||
return null;
|
||
}
|
||
|
||
@Override
|
||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||
throws java.security.cert.CertificateException {
|
||
if (system != null) {
|
||
system.checkClientTrusted(chain, authType);
|
||
return;
|
||
}
|
||
if (bundled != null) {
|
||
bundled.checkClientTrusted(chain, authType);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||
throws java.security.cert.CertificateException {
|
||
java.security.cert.CertificateException first = null;
|
||
if (system != null) {
|
||
try {
|
||
system.checkServerTrusted(chain, authType);
|
||
return;
|
||
} catch (java.security.cert.CertificateException error) {
|
||
first = error;
|
||
}
|
||
}
|
||
if (bundled != null) {
|
||
try {
|
||
bundled.checkServerTrusted(chain, authType);
|
||
return;
|
||
} catch (java.security.cert.CertificateException error) {
|
||
if (first == null) {
|
||
first = error;
|
||
}
|
||
}
|
||
}
|
||
throw (first != null ? first
|
||
: new java.security.cert.CertificateException(
|
||
"证书校验失败"));
|
||
}
|
||
|
||
@Override
|
||
public X509Certificate[] getAcceptedIssuers() {
|
||
X509Certificate[] a = system == null
|
||
? new X509Certificate[0] : system.getAcceptedIssuers();
|
||
X509Certificate[] b = bundled == null
|
||
? new X509Certificate[0] : bundled.getAcceptedIssuers();
|
||
X509Certificate[] all = new X509Certificate[a.length + b.length];
|
||
System.arraycopy(a, 0, all, 0, a.length);
|
||
System.arraycopy(b, 0, all, a.length, b.length);
|
||
return all;
|
||
}
|
||
}
|
||
}
|
||
'''
|
||
|
||
source = source.replace("%GTS%", pem_to_java(gts))
|
||
source = source.replace("%GS%", pem_to_java(globalsign))
|
||
|
||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||
OUT.write_text(source, encoding="utf-8", newline="\n")
|
||
print(f" 已生成 {OUT.name}({OUT.stat().st_size} 字节)")
|
||
print(f" 内置 GTS Root R4: {len(gts)} 字节 PEM")
|
||
print(f" 内置 GlobalSign Root CA: {len(globalsign)} 字节 PEM")
|
||
|
||
# 语法自检:括号配平 + 关键结构在
|
||
for needle in ("class TlsCompat", "CompatTrustManager",
|
||
"checkServerTrusted", "setSSLSocketFactory", "explain("):
|
||
print(f" 含 {needle}: {'✓' if needle in source else '✗'}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|