ShadowTLS 协议
ShadowTLS 是一种传输层协议,通过劫持与真实服务器的 TLS handshake,将代理流量伪装为合法的 TLS 流量。它支持三个协议版本,复杂程度逐步递增。
源码: protocol/shadowtls/inbound.go, protocol/shadowtls/outbound.go, sing-shadowtls
协议概念
与传统的基于 TLS 的代理使用自签证书(可通过证书检查检测到)不同,ShadowTLS 与合法服务器(如 www.microsoft.com)执行真实的 TLS handshake,使观察者无法将 handshake 与正常的 HTTPS 流量区分开。handshake 完成后,数据通道被劫持以承载代理流量。
协议版本
版本 1
最简单的版本。客户端通过 ShadowTLS 服务器发起 TLS handshake,服务器将其转发到真实的 TLS 服务器("handshake 服务器")。handshake 完成后,TLS 连接被重新用于传输代理数据。
限制:强制使用 TLS 1.2 以确保可预测的 handshake 行为。
if options.Version == 1 {
options.TLS.MinVersion = "1.2"
options.TLS.MaxVersion = "1.2"
}版本 2
增加了基于密码的认证。服务器可以区分合法的 ShadowTLS 客户端和探测器。支持基于 SNI 的 handshake 服务器:
if options.Version > 1 {
handshakeForServerName = make(map[string]shadowtls.HandshakeConfig)
for _, entry := range options.HandshakeForServerName.Entries() {
handshakeForServerName[entry.Key] = shadowtls.HandshakeConfig{
Server: entry.Value.ServerOptions.Build(),
Dialer: handshakeDialer,
}
}
}版本 3
最先进的版本。引入了基于 session ID 的通道绑定——客户端和服务器将认证数据嵌入 TLS session ID 中,无需额外的往返即可完成验证。
case 3:
if idConfig, loaded := tlsConfig.(tls.WithSessionIDGenerator); loaded {
// 使用 TLS 库的 session ID 钩子
tlsHandshakeFunc = func(ctx, conn, sessionIDGenerator) error {
idConfig.SetSessionIDGenerator(sessionIDGenerator)
return tls.ClientHandshake(ctx, conn, tlsConfig)
}
} else {
// 回退到标准 TLS 并手动注入 session ID
stdTLSConfig := tlsConfig.STDConfig()
tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(password, stdTLSConfig)
}Inbound 架构
type Inbound struct {
inbound.Adapter
router adapter.Router
logger logger.ContextLogger
listener *listener.Listener
service *shadowtls.Service
}服务配置
service, _ := shadowtls.NewService(shadowtls.ServiceConfig{
Version: options.Version,
Password: options.Password,
Users: common.Map(options.Users, func(it option.ShadowTLSUser) shadowtls.User {
return (shadowtls.User)(it)
}),
Handshake: shadowtls.HandshakeConfig{
Server: options.Handshake.ServerOptions.Build(),
Dialer: handshakeDialer,
},
HandshakeForServerName: handshakeForServerName, // 基于 SNI 的路由
StrictMode: options.StrictMode,
WildcardSNI: shadowtls.WildcardSNI(options.WildcardSNI),
Handler: (*inboundHandler)(inbound),
Logger: logger,
})关键字段:
- Handshake:默认的 handshake 目标服务器
- HandshakeForServerName:SNI 到 handshake 服务器的映射,用于多域名支持
- StrictMode:拒绝认证失败的连接(而非静默转发)
- WildcardSNI:接受任何 SNI 值(适用于 CDN 场景)
通配符 SNI
WildcardSNI 选项控制 SNI 的处理方式:
serverIsDomain := options.Handshake.ServerIsDomain()
if options.WildcardSNI != option.ShadowTLSWildcardSNIOff {
serverIsDomain = true // 强制域名解析以支持通配符
}连接流程(Inbound)
func (h *Inbound) NewConnectionEx(ctx, conn, metadata, onClose) {
// ShadowTLS 服务处理整个 handshake 转发和数据提取
err := h.service.NewConnection(ctx, conn, metadata.Source, metadata.Destination, onClose)
N.CloseOnHandshakeFailure(conn, onClose, err)
}ShadowTLS 服务提取真实数据流后,调用 inbound 处理器:
type inboundHandler Inbound
func (h *inboundHandler) NewConnectionEx(ctx, conn, source, destination, onClose) {
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
metadata.Source = source
metadata.Destination = destination
if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
metadata.User = userName
}
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}Outbound 架构
type Outbound struct {
outbound.Adapter
client *shadowtls.Client
}ShadowTLS outbound 仅支持 TCP,作为传输层包装器——通常与其他协议(如 Shadowsocks)链式使用:
func (h *Outbound) DialContext(ctx, network, destination) (net.Conn, error) {
switch network {
case "tcp":
return h.client.DialContext(ctx) // 返回一个"干净"的连接
default:
return nil, os.ErrInvalid // 不支持 UDP
}
}
func (h *Outbound) ListenPacket(ctx, destination) (net.PacketConn, error) {
return nil, os.ErrInvalid // 不支持 UDP
}TLS 要求
ShadowTLS outbound 要求启用 TLS:
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}客户端配置
client, _ := shadowtls.NewClient(shadowtls.ClientConfig{
Version: options.Version,
Password: options.Password,
Server: options.ServerOptions.Build(),
Dialer: outboundDialer,
TLSHandshake: tlsHandshakeFunc, // 版本特定的 handshake
Logger: logger,
})版本特定的 TLS Handshake
var tlsHandshakeFunc shadowtls.TLSHandshakeFunc
switch options.Version {
case 1, 2:
// 简单:直接执行 TLS handshake
tlsHandshakeFunc = func(ctx, conn, _ TLSSessionIDGeneratorFunc) error {
return common.Error(tls.ClientHandshake(ctx, conn, tlsConfig))
}
case 3:
// 复杂:注入 session ID 生成器用于通道绑定
tlsHandshakeFunc = func(ctx, conn, sessionIDGenerator) error {
idConfig.SetSessionIDGenerator(sessionIDGenerator)
return common.Error(tls.ClientHandshake(ctx, conn, tlsConfig))
}
}ShadowTLS 工作原理(详解)
Client ShadowTLS Server Real TLS Server
| | |
|--- TLS ClientHello ---->|--- TLS ClientHello ----->|
| | |
|<-- TLS ServerHello -----|<-- TLS ServerHello ------|
|<-- Certificate ---------|<-- Certificate ----------|
|<-- ServerHelloDone -----|<-- ServerHelloDone ------|
| | |
|--- ClientKeyExchange -->|--- ClientKeyExchange --->|
|--- ChangeCipherSpec --->|--- ChangeCipherSpec ---->|
|--- Finished ----------->|--- Finished ------------>|
| | |
|<-- ChangeCipherSpec ----|<-- ChangeCipherSpec -----|
|<-- Finished ------------|<-- Finished -------------|
| | |
| [TLS handshake 完成 - 观察者看到有效证书] |
| | |
|=== Proxy Data =========>| [数据不再发送到真实 |
|<=== Proxy Data =========| TLS 服务器] |handshake 完成后,ShadowTLS 服务器:
- 断开与真实 TLS 服务器的连接
- 从客户端提取代理数据流
- 将其转发到配置的内部处理器
典型使用模式
ShadowTLS 作为另一个协议的 detour 使用:
{
"outbounds": [
{
"type": "shadowsocks",
"tag": "ss-out",
"detour": "shadowtls-out",
"method": "2022-blake3-aes-256-gcm",
"password": "ss-password"
},
{
"type": "shadowtls",
"tag": "shadowtls-out",
"server": "my-server.com",
"server_port": 443,
"version": 3,
"password": "shadowtls-password",
"tls": {
"enabled": true,
"server_name": "www.microsoft.com"
}
}
]
}Shadowsocks 连接通过 ShadowTLS 包装器进行隧道传输,ShadowTLS 使用 www.microsoft.com 的真实证书执行 handshake。
配置示例(Inbound)
{
"type": "shadowtls",
"tag": "shadowtls-in",
"listen": "::",
"listen_port": 443,
"version": 3,
"users": [
{ "name": "user1", "password": "user-password" }
],
"handshake": {
"server": "www.microsoft.com",
"server_port": 443
},
"handshake_for_server_name": {
"www.google.com": {
"server": "www.google.com",
"server_port": 443
}
},
"strict_mode": true
}