Skip to content

TLS 安全层

简介

Xray-core 中的 TLS 层提供标准 TLS 加密,并具有广泛的自定义能力。它同时支持 Go 标准 crypto/tls 和用于 TLS 指纹模拟的 uTLS(refraction-networking/utls),支持从 CA 动态生成证书、OCSP 装订、证书固定、会话恢复、ECH(加密客户端 Hello)、自定义密码套件、曲线偏好以及密钥日志。TLS 本身不是传输协议,而是可以包装任何传输协议的安全层。

关键文件

  • transport/internet/tls/tls.go -- 核心类型:ConnUConn、指纹注册表
  • transport/internet/tls/config.go -- Config.GetTLSConfig()、证书管理、选项
  • transport/internet/tls/pin.go -- 用于证书固定的哈希函数
  • transport/internet/tls/ech.go -- ECH(加密客户端 Hello)支持
  • transport/internet/tls/grpc.go -- gRPC 特定的 TLS 辅助函数
  • transport/internet/tls/unsafe.go -- 不安全的证书池访问

连接类型

Conn(标准 TLS)

tls.Conntls/tls.go:26-28)包装 Go 的 crypto/tls.Conn

go
type Conn struct {
    *tls.Conn
}

特性:

  • 优雅关闭超时:TLS close notify 有 250ms 超时,超时后强制关闭底层连接(tls.go:30-38
  • MultiBuffer 写入:高效地压缩和写入多缓冲区(tls.go:40-45
  • NegotiatedProtocol:报告 ALPN 结果(tls.go:54-57

UConn(uTLS 指纹)

tls.UConntls/tls.go:71-73)包装 utls.UConn 用于 TLS 指纹模拟:

go
type UConn struct {
    *utls.UConn
}

WebsocketHandshakeContext 方法(tls.go:94-117)为使用 uTLS 的 WebSocket 连接强制 http/1.1 ALPN:

go
func (c *UConn) WebsocketHandshakeContext(ctx context.Context) error {
    c.BuildHandshakeState()
    // 查找 ALPNExtension 并覆盖为 ["http/1.1"]
    // 重建握手状态
    return c.HandshakeContext(ctx)
}

接口

两种类型均实现 Interfacetls.go:15-21):

go
type Interface interface {
    net.Conn
    HandshakeContext(ctx context.Context) error
    VerifyHostname(host string) error
    HandshakeContextServerName(ctx context.Context) string
    NegotiatedProtocol() string
}

TLS 指纹模拟

指纹注册表

定义了三个级别的指纹(tls/tls.go:185-257):

PresetFingerprints(推荐的 GUI 选项):

名称uTLS ID
chromeHelloChrome_Auto
firefoxHelloFirefox_Auto
safariHelloSafari_Auto
iosHelloIOS_Auto
androidHelloAndroid_11_OkHttp
edgeHelloEdge_Auto
360Hello360_Auto
qqHelloQQ_Auto
random启动时随机选择
randomized带自定义权重的 HelloRandomizedALPN
randomizednoalpn带自定义权重的 HelloRandomizedNoALPN

ModernFingerprints:特定浏览器版本(Chrome 83-131、Firefox 99-120 等)

OtherFingerprints:旧版本和特殊指纹。

随机指纹选择

启动时(tls.go:145-167),"random" 被赋值为随机选择的现代指纹:

go
func init() {
    bigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(ModernFingerprints))))
    stopAt := int(bigInt.Int64())
    // 选择 ModernFingerprints 中第 stopAt 个条目
    PresetFingerprints["random"] = v
}

"randomized" 选项使用 uTLS 内置的随机化,并自定义权重:

  • 强制 TLS 1.3(TLSVersMax_Set_VersionTLS13 = 1
  • 禁用 P256 作为首选密钥共享(FirstKeyShare_Set_CurveP256 = 0

GetFingerprint

GetFingerprinttls.go:169-183)在三个映射中查找指纹:

go
func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) {
    if name == "" { return &utls.HelloChrome_Auto }
    if fingerprint = PresetFingerprints[name]; fingerprint != nil { return }
    if fingerprint = ModernFingerprints[name]; fingerprint != nil { return }
    if fingerprint = OtherFingerprints[name]; fingerprint != nil { return }
    return  // nil = 标准 Go TLS
}

空名称默认为 Chrome Auto。如果未找到匹配项,返回 nil,通知调用者使用标准 Go TLS。

客户端/服务端构建

客户端

go
// tls.go:60-63 -- 标准 Go TLS
func Client(c net.Conn, config *tls.Config) net.Conn {
    tlsConn := tls.Client(c, config)
    return &Conn{Conn: tlsConn}
}

// tls.go:124-127 -- uTLS 指纹模拟
func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
    utlsConn := utls.UClient(c, copyConfig(config), *fingerprint)
    return &UConn{UConn: utlsConn}
}

服务端

go
// tls.go:66-69
func Server(c net.Conn, config *tls.Config) net.Conn {
    tlsConn := tls.Server(c, config)
    return &Conn{Conn: tlsConn}
}

uTLS 的配置复制

copyConfigtls.go:133-143)将 crypto/tls.Config 转换为 utls.Config

go
func copyConfig(c *tls.Config) *utls.Config {
    return &utls.Config{
        Rand:                           c.Rand,
        RootCAs:                        c.RootCAs,
        ServerName:                     c.ServerName,
        InsecureSkipVerify:             c.InsecureSkipVerify,
        VerifyPeerCertificate:          c.VerifyPeerCertificate,
        KeyLogWriter:                   c.KeyLogWriter,
        EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
    }
}

证书管理

GetTLSConfig

Config.GetTLSConfigtls/config.go:364-481)构建 tls.Config

go
func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
    root, _ := c.getCertPool()
    // 构建包含固定/验证信息的 RandCarrier
    config := &tls.Config{
        InsecureSkipVerify:     c.AllowInsecure,
        Rand:                   randCarrier,
        ClientSessionCache:     globalSessionCache,
        RootCAs:                root,
        NextProtos:             c.NextProtocol,
        SessionTicketsDisabled: !c.EnableSessionResumption,
        VerifyPeerCertificate:  randCarrier.verifyPeerCert,
    }
    // 应用选项(WithDestination、WithNextProto 等)
    // 配置 GetCertificate(动态或静态)
    // 设置 ServerName、CurvePreferences、MinVersion、MaxVersion
    // 配置 CipherSuites、KeyLogWriter、ECH
    return config
}

证书类型

证书有两种用途模式:

  • ENCIPHERMENTCertificate_ENCIPHERMENT):常规服务端/客户端证书
  • AUTHORITY_ISSUECertificate_AUTHORITY_ISSUE):用于动态签发的 CA 证书

动态证书生成

当存在 AUTHORITY_ISSUE 证书时,getGetCertificateFuncconfig.go:172-244)按需生成证书:

go
func getGetCertificateFunc(c *tls.Config, ca []*Certificate) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
    return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
        domain := hello.ServerName
        // 检查现有证书,移除过期的
        // 从 CA 为域名生成新证书
        newCert, _ := issueCertificate(rawCert, domain)
        c.Certificates = append(c.Certificates, *newCert)
        return issuedCertificate, nil
    }
}

这支持中间人场景,无需预先生成证书。

静态证书选择

getNewGetCertificateFuncconfig.go:246-274)按 SNI 匹配证书:

go
func getNewGetCertificateFunc(certs []*tls.Certificate, rejectUnknownSNI bool) ... {
    // 按 CommonName 或 DNSNames 匹配
    // 支持通配符匹配(*.example.com)
    // 如果 rejectUnknownSNI,对未知域名返回错误
    // 否则返回第一个证书作为回退
}

热重载与 OCSP

setupOcspTickerconfig.go:96-131)定期执行:

  1. 从磁盘重新加载证书文件(如果设置了 CertificatePath/KeyPath
  2. 获取 OCSP 装订数据

默认间隔:3600 秒(1 小时),或使用 OcspStapling 配置值。

证书固定

PinnedPeerCertSha256

受信任证书的 SHA-256 哈希。在 RandCarrier.verifyPeerCertconfig.go:283-350)中验证:

go
func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
    // 检查叶证书是否匹配任何固定哈希 -> 成功
    // 检查 CA 证书是否匹配任何固定哈希 -> 用固定 CA 验证叶证书
    // 如果设置了 VerifyPeerCertByName,对多个域名进行验证
    // 否则用 ServerName 验证
}

RandCarrier 结构体(config.go:352-357)附载在 tls.Config.Rand 上传递验证状态(因为 VerifyPeerCertificate 没有传递自定义数据的机制):

go
type RandCarrier struct {
    Config               *tls.Config
    RootCAs              *x509.CertPool
    VerifyPeerCertByName []string
    PinnedPeerCertSha256 [][]byte
}

哈希生成

GenerateCertHashtls/pin.go:10-19)计算证书的 SHA-256:

go
func GenerateCertHash[T *x509.Certificate | []byte](cert T) []byte {
    var out [32]byte
    switch v := any(cert).(type) {
    case *x509.Certificate:
        out = sha256.Sum256(v.Raw)
    case []byte:
        out = sha256.Sum256(v)
    }
    return out[:]
}

选项系统

Option 函数(config.go:484)在初始构建后修改 tls.Config

go
type Option func(*tls.Config)

可用选项:

  • WithDestination:从目标地址设置 ServerName(仅当未设置时)(config.go:490-496
  • WithOverrideName:强制使用特定 ServerNameconfig.go:498-502
  • WithNextProto:设置 ALPN 协议(仅当未设置时)(config.go:505-511

SNI 解析优先级

由于 GetTLSConfig 的结构(config.go:415-417),ServerName 的解析顺序为:

  1. 配置中的 ServerName(如果非空且不为"frommitm")
  2. WithDestination 选项(目标地址)
  3. 空值(用于服务端配置)

会话管理

全局 LRU 会话缓存(config.go:24):

go
var globalSessionCache = tls.NewLRUClientSessionCache(128)

会话票据默认禁用(SessionTicketsDisabled: !c.EnableSessionResumption)。

版本与密码配置

TLS 版本

可配置的最低/最高版本(config.go:427-447):

  • "1.0""1.3"

密码套件

从冒号分隔的名称列表自定义密码套件(config.go:449-459):

go
if len(c.CipherSuites) > 0 {
    for _, n := range strings.Split(c.CipherSuites, ":") {
        config.CipherSuites = append(config.CipherSuites, id[n])
    }
}

曲线偏好

ParseCurveNameconfig.go:525-545)将字符串名称映射为 tls.CurveID

名称曲线
curvep256P-256
curvep384P-384
curvep521P-521
x25519X25519
x25519mlkem768X25519MLKEM768(后量子)
secp256r1mlkem768SecP256r1MLKEM768
secp384r1mlkem1024SecP384r1MLKEM1024

ConfigFromStreamSettings

提取函数(config.go:514-523)被每个传输协议调用以检查 TLS 配置:

go
func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {
    if settings == nil { return nil }
    config, ok := settings.SecuritySettings.(*Config)
    if !ok { return nil }
    return config
}

未配置 TLS 时返回 nil,通知传输协议跳过 TLS 包装。

实现说明

  • 默认 ALPN:如果未配置 NextProtocol,默认为 ["h2", "http/1.1"]config.go:423-425)。
  • 关闭超时:TLS 关闭有 250ms 硬超时,防止优雅关闭时挂起(tls.go:30-38)。
  • 中间人支持IsFromMitm 函数(config.go:547-549)检查特殊的 "frommitm" ServerName 标记,用于中间人代理功能。
  • VerifyPeerCertByName:设置后,InsecureSkipVerify 被强制为 trueconfig.go:393-397),验证在 verifyPeerCert 中手动对多个域名执行。
  • 密钥日志:通过 MasterKeyLog 配置启用,写入文件供 Wireshark 调试使用(config.go:461-468)。
  • ECH:配置 EchConfigListEchServerKeys 时通过 ApplyECH 应用加密客户端 Hello(config.go:469-478)。
  • 证书过期:检查时有 2 分钟的宽限期(config.go:133-142)。
  • 证书链构建:当 CA 证书的 BuildChain 为 true 时,CA 证书会附加到新签发的证书中(config.go:155)。

用于重新实现目的的技术分析。