Files
ykxmz/_build/gen_tls_compat.py
T
xmz7891 b62ac865ea ykxmz 1.5.0:修复手表端 TLS 证书校验 + MTU 修复网络稳定性
被控端
- 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%)
- 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败)
- 每分钟回头找局域网,自动切回快 400 倍的路径
- 画面心跳帧:静止时省流量,客户端仍知道流是活的
- 锁屏时给出可操作的提示

手表端
- 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException
- 连接错误翻译成人话(证书/时间/解析/超时分别提示)
- 经中转连上后用服务器报的地址刷新已配对记录
- 新增局域网连接页面、服务器延迟检测

服务器端
- 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502)
- 网络调优脚本 net-tune.sh(开机自动应用)
- 管理页:批量删除、网络体检、下载区
- 隧道改 HTTP/2 + 双连接

文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
2026-10-06 19:01:05 +08:00

307 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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())