TLS 与指纹
Sail 通过 btls 绑定使用 BoringSSL,处理 TCP 和 QUIC 上的 TLS。TCP 上的出站 TLS 默认发送浏览器形态的 ClientHello,而不是 BoringSSL 原生的 ClientHello。
基础 TLS 出站
Section titled “基础 TLS 出站”{ "type": "trojan", "tag": "edge", "server": "203.0.113.10", "server_port": 443, "password": "replace-me", "tls": { "enabled": true, "server_name": "edge.example.com" }}server_name 同时决定证书校验和 SNI。省略时使用服务器地址。
选择 ClientHello 指纹
Section titled “选择 ClientHello 指纹”每个指纹对应一个实际抓取的浏览器或客户端的 ClientHello:
fingerprint |
发送的 ClientHello |
|---|---|
chrome(默认)、edge |
macOS 上的 Chrome 154;Android 上的 Chrome 和 Edge 发送相同的 ClientHello |
firefox |
macOS 上的 Firefox 156 |
safari、ios |
macOS 上的 Safari 26.3,与 macOS 及 iOS 26.4 上的 URLSession 相同 |
android |
基于系统 Conscrypt、使用 OkHttp 4.12 的 Android 应用 |
random |
与 sing-box 一样,从 chrome、firefox、edge、safari、ios 中选一个;每个进程只选一次,之后所有连接都用它 |
省略 utls,或 utls 未指定 fingerprint 时,默认使用 Chrome。因此与 sing-box 不同,浏览器指纹默认开启,除非显式关闭。其他名称是配置错误。
{ "tls": { "enabled": true, "server_name": "edge.example.com", "utls": { "enabled": true, "fingerprint": "firefox" } }}若要发送 BoringSSL 自己的 ClientHello,显式关闭指纹:
{ "tls": { "enabled": true, "utls": { "enabled": false } }}指纹只改变握手形态,不改变 TLS 内承载的应用协议。与浏览器一样,每个指纹都提供 TLS 1.2 和 1.3:开启 utls 时,min_version 与 max_version 会被忽略并给出警告。没有 utls 块时,这两个字段设置的范围会生效,并有警告说明此时的 ClientHello 已不再是浏览器的。
在 QUIC 上(Hysteria2、TUIC 与 quic 传输层),ClientHello 是 BoringSSL 自己的:在那里开启 utls 或 reality 是配置错误。ECH 在 QUIC 上与 TCP 上一样可用。
默认按系统根证书库校验服务器。顶层 certificate 块可选择其他证书库(store:system、mozilla、chrome 或 none),也可以加入自己的证书,与 sing-box 相同。
在出站的 tls 中,certificate 会替换上述根证书:服务器必须链到所给的 PEM 证书。可以内联:
{ "tls": { "enabled": true, "certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----" }}也可以从路径读取:
{ "tls": { "enabled": true, "certificate_path": "certs/private-ca.pem" }}相对证书路径以 -D 指定的数据目录为基准,默认以可执行文件所在目录为基准。
certificate_sha256 按整张证书(DER)的 SHA-256 接受服务器。每一项可以是十六进制,大小写均可,冒号可有可无,即 openssl x509 -noout -fingerprint -sha256 的输出,也是 Mihomo 的 fingerprint 的写法;也可以是标准、带填充的 base64,即 sing-box 1.15 同名字段的写法(openssl x509 -outform der | openssl dgst -sha256 -binary | openssl enc -base64)。它取代受信任的根证书与 insecure,二者都不再起作用。它不能与 certificate 或 certificate_path 同时使用。与固定公钥的 sing-box 字段 certificate_public_key_sha256 同时设置时,任一列表接受即接受服务器,与 sing-box 1.15 相同。
{ "tls": { "enabled": true, "server_name": "edge.example.com", "certificate_sha256": ["5A:1F:...:C3"] }}- 匹配服务器自身(叶子)证书:直接接受,不校验 CA、有效期和名称。固定叶子证书即信任这张证书用于任意服务器名。
- 匹配服务器链中的中间证书或根证书:以该证书为唯一 CA,叶子证书必须链到它且对
server_name有效,无论insecure如何设置。这是 Mihomo 的语义,超出 sing-box:sing-box 只比对叶子证书。 - 都不匹配:握手失败,错误信息给出服务器所发证书的哈希。
TCP 与 QUIC 均支持,DNS 服务器的 tls 也支持。REALITY 自行验证服务器,会忽略该字段并给出警告。
ALPN 与 ECH
Section titled “ALPN 与 ECH”服务器要求特定应用协议时使用 alpn。使用浏览器指纹且未设置 alpn 时,ClientHello 提供浏览器的 h2 与 http/1.1。
{ "tls": { "enabled": true, "alpn": ["h2", "http/1.1"], "ech": { "enabled": true } }}设置了 config 时,就使用它作为 ECHConfigList,不发任何 DNS 查询,与 sing-box 一致。未设置时,Sail 从服务器名的 HTTPS 记录(或 SVCB 记录)查找 ECHConfigList;查询失败或记录里没有 ECH 配置时,连接失败。设置 disable_dns_lookup 可强制要求显式的 base64 或 PEM config:缺少时配置被拒绝。
ECH 在 QUIC 上(Hysteria2、TUIC 与 quic 传输层)用法相同;配置的 config 若 BoringSSL 无法解析,读取配置时即报错。一条 QUIC 连接承载所有会话,因此 DNS 客户端自己的查询也会先查找 ECHConfigList。服务器拒绝 ECH 时连接失败,TCP 与 QUIC 相同:不会用服务器发来的配置重试,与 sing-box 一致。
ECH 只用于 TLS 1.3,因此开启 ECH 且 min_version 或 max_version 低于 1.3 属于配置错误。sing-box 会接受这样的配置,但之后每条连接都会失败(Go 的 TLS 要求开启 ECH 时最低版本为 1.3);Sail 在读取配置时就报错。
以证书认证客户端(双向 TLS)的服务器会在握手中索要证书。按 sing-box 的字段名给出证书与私钥,可内联 PEM,也可给路径:
{ "tls": { "enabled": true, "server_name": "edge.example.com", "client_certificate_path": "certs/client.crt", "client_key_path": "certs/client.key" }}client_certificate 与 client_key 为内联 PEM,可写成一个字符串或每行一项。二者须同时设置或都不设置,内联与路径每对至多设一个。证书文件可在证书后附上证书链。私钥可为 RSA、ECDSA 或 Ed25519,PKCS#8、PKCS#1 或 SEC1 格式的 PEM,且须与证书匹配。TCP 与 QUIC(Hysteria2、TUIC、quic 传输层)均支持;不可与 REALITY 同用(其服务器不索要证书),DNS 服务器暂不支持。
Surge 的 client-cert=<条目> 读取 [Keystore] 中的 p12 条目;Mihomo 的 certificate 与 private-key 对应这两个字段。
自签名入站证书
Section titled “自签名入站证书”作为 sail 扩展,入站的 tls 可以设置 "self_signed": true 代替证书与私钥:入站在启动时为其 server_name(未设置时为 sail.invalid)生成一张自签名的 ECDSA P-256 证书并提供给客户端,与 Mihomo 的 Hysteria2、TUIC 监听器未配置证书时的做法相同。没有 CA 为它担保,客户端需要 insecure 或固定证书(certificate_sha256 或 certificate_public_key_sha256);每生成一张新证书,固定值就会改变:每次启动时,以及重载对入站的改动超出用户与客户端证书校验时(例如修改 server_name)。与证书或私钥同设,或与 REALITY 同用时,self_signed 报错。
校验客户端证书
Section titled “校验客户端证书”入站在 tls 块中用 sing-box 的字段向客户端索要证书并校验:
{ "tls": { "enabled": true, "certificate_path": "certs/server.crt", "key_path": "certs/server.key", "client_authentication": "require-and-verify", "client_certificate_path": ["certs/clients-ca.crt"] }}client_authentication 取 sing-box 的五种模式:
| 模式 | 客户端未给证书 | 客户端给了证书 |
|---|---|---|
no(默认) |
接受 | 不索要 |
request |
接受 | 接受,不校验 |
require-any |
拒绝 | 接受,不校验 |
verify-if-given |
接受 | 校验 |
require-and-verify |
拒绝 | 校验 |
设了 CA 而未设模式(或设为 no)时按 require-and-verify 处理,与 sing-box 一致。
证书按 CA 校验:client_certificate 为内联 PEM,client_certificate_path 为 PEM 或 DER 文件。证书须经客户端发来的中间证书链到其中之一,在实例时间下有效,且用于客户端认证:扩展密钥用途只有 serverAuth 的证书会被拒绝。配置的 CA 本身是中间证书时按原样信任,与 Go 一致。
也可用固定值代替 CA:client_certificate_sha256(sing-box 1.15 的字段,整张叶证书的 SHA-256,标准 base64 或十六进制)与 client_certificate_public_key_sha256(叶证书公钥的 SHA-256,base64)。先比对证书固定值,两份列表任一通过即可。固定值只比对叶证书,不查 CA,也不查有效期。
sing-box 会放进不该放的客户端时,Sail 直接报配置错误:
- 设了固定值而未设
client_authentication或设为no:sing-box 不索要证书,也不校验; request或require-any搭配 CA 或固定值:这两种模式不校验证书;- 与 REALITY 同用其中任一字段(模式
no除外):REALITY 服务器不索要证书。
sing-box 因偏好另一字段而忽略的字段,Sail 同样忽略并给出警告:与 client_certificate 同设的 client_certificate_path,以及与 CA 同设的固定值。
client_certificate_path 的文件与入站证书文件一样受到跟踪,与 sing-box 一致:写入文件的 CA 无需重载即用于之后的连接,已建立的连接不受影响。
被拒绝的客户端会收到 bad_certificate 告警,入站的握手错误会写明原因。TCP 上的 TLS 入站(Trojan、VLESS、VMess、AnyTLS、HTTP)与 QUIC 入站(Hysteria2、TUIC、quic 传输层)同样校验客户端证书。QUIC 上也由 Sail 自己校验:QUIC 库本会依据的系统根证书不参与。
不发送 SNI
Section titled “不发送 SNI”disable_sni: true 使 ClientHello 不带服务器名,无论是否使用浏览器指纹。除非设置 insecure,证书仍按 server_name(未设置时为服务器地址)校验:
{ "tls": { "enabled": true, "server_name": "edge.example.com", "disable_sni": true }}不可与 ECH 或 REALITY 同用(二者的 ClientHello 须带服务器名),QUIC 上暂不支持。Surge 的 sni=off 即此选项。
REALITY
Section titled “REALITY”REALITY 在 TLS 块中配置:
{ "tls": { "enabled": true, "server_name": "www.example.com", "reality": { "enabled": true, "public_key": "server-public-key", "short_id": "0123456789abcdef" } }}使用服务端签发的公钥和 short ID。REALITY 的 ClientHello 总是浏览器的:与它同用时不能关闭 utls,且只能用于 TCP。REALITY 与普通证书 TLS 的认证前提不同;不要在未核对服务端的情况下从另一套部署复制字段。
REALITY 服务端(入站)接受 server_name 中的名字,以及 sail 扩展 reality.server_names 中的名字,与 Xray 的 serverNames、Mihomo 的 server-names 相同;只设其一即可。
排查握手失败
Section titled “排查握手失败”- 运行
sail -c config.json -T,发现字段与组合错误。 - 运行
sail -c config.json -t edge -d 10,单独测试该出站。 - 确认
server_name、系统时间和证书链。 - 只有确知服务器强制要求某种指纹时,才临时切换指纹。
- 开启调试日志,查看失败发生在 DNS、TCP/UDP 连接还是 TLS 阶段。