- StreamSSLManager 管理服务端 TLS 终端和客户端 TLS 连接 - 支持证书加载、mTLS 客户端验证 - 并发安全的证书配置管理 Co-Authored-By: Claude <noreply@anthropic.com>
311 lines
7.8 KiB
Go
311 lines
7.8 KiB
Go
// Package stream 提供 TCP/UDP Stream 代理功能。
|
||
//
|
||
// 该文件实现 Stream 模块的 SSL/TLS 支持,包括:
|
||
// - 服务端 TLS 终端
|
||
// - 客户端 TLS 连接(上游 SSL)
|
||
// - 证书加载和配置
|
||
// - mTLS 客户端证书验证
|
||
//
|
||
// 作者:xfy
|
||
package stream
|
||
|
||
import (
|
||
"crypto/tls"
|
||
"crypto/x509"
|
||
"fmt"
|
||
"os"
|
||
"sync"
|
||
|
||
"rua.plus/lolly/internal/config"
|
||
)
|
||
|
||
// StreamSSLManager 管理 Stream SSL/TLS 配置。
|
||
//
|
||
// 负责加载证书、配置 TLS 连接,支持服务端和客户端两种模式。
|
||
type StreamSSLManager struct {
|
||
// config SSL 配置
|
||
config config.StreamSSLConfig
|
||
|
||
// cert 服务器证书
|
||
cert tls.Certificate
|
||
|
||
// clientCAPool 客户端 CA 证书池(mTLS)
|
||
clientCAPool *x509.CertPool
|
||
|
||
// mu 保护并发访问
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
// StreamProxySSLManager 管理上游 SSL 连接。
|
||
//
|
||
// 负责创建到上游服务器的 TLS 连接,支持证书验证和客户端证书。
|
||
type StreamProxySSLManager struct {
|
||
// config 代理 SSL 配置
|
||
config config.StreamProxySSLConfig
|
||
|
||
// cert 客户端证书
|
||
cert tls.Certificate
|
||
|
||
// rootCAPool 根 CA 证书池
|
||
rootCAPool *x509.CertPool
|
||
|
||
// mu 保护并发访问
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
// NewStreamSSLManager 创建 Stream SSL 管理器。
|
||
//
|
||
// 参数:
|
||
// - cfg: SSL 配置
|
||
//
|
||
// 返回值:
|
||
// - *StreamSSLManager: SSL 管理器实例
|
||
// - error: 证书加载失败时返回错误
|
||
func NewStreamSSLManager(cfg config.StreamSSLConfig) (*StreamSSLManager, error) {
|
||
if !cfg.Enabled {
|
||
return &StreamSSLManager{config: cfg}, nil
|
||
}
|
||
|
||
// 加载服务器证书
|
||
cert, err := tls.LoadX509KeyPair(cfg.Cert, cfg.Key)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to load server certificate: %w", err)
|
||
}
|
||
|
||
mgr := &StreamSSLManager{
|
||
config: cfg,
|
||
cert: cert,
|
||
}
|
||
|
||
// 加载客户端 CA 证书(mTLS)
|
||
if cfg.ClientCA != "" {
|
||
pool, err := loadCertPool(cfg.ClientCA)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to load client CA: %w", err)
|
||
}
|
||
mgr.clientCAPool = pool
|
||
}
|
||
|
||
return mgr, nil
|
||
}
|
||
|
||
// NewStreamProxySSLManager 创建上游 SSL 管理器。
|
||
//
|
||
// 参数:
|
||
// - cfg: 代理 SSL 配置
|
||
//
|
||
// 返回值:
|
||
// - *StreamProxySSLManager: 代理 SSL 管理器实例
|
||
// - error: 证书加载失败时返回错误
|
||
func NewStreamProxySSLManager(cfg config.StreamProxySSLConfig) (*StreamProxySSLManager, error) {
|
||
if !cfg.Enabled {
|
||
return &StreamProxySSLManager{config: cfg}, nil
|
||
}
|
||
|
||
mgr := &StreamProxySSLManager{config: cfg}
|
||
|
||
// 加载客户端证书(mTLS)
|
||
if cfg.Cert != "" && cfg.Key != "" {
|
||
cert, err := tls.LoadX509KeyPair(cfg.Cert, cfg.Key)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to load client certificate: %w", err)
|
||
}
|
||
mgr.cert = cert
|
||
}
|
||
|
||
// 加载信任的 CA 证书
|
||
if cfg.TrustedCA != "" {
|
||
pool, err := loadCertPool(cfg.TrustedCA)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to load trusted CA: %w", err)
|
||
}
|
||
mgr.rootCAPool = pool
|
||
}
|
||
|
||
return mgr, nil
|
||
}
|
||
|
||
// GetTLSConfig 获取服务端 TLS 配置。
|
||
//
|
||
// 返回值:
|
||
// - *tls.Config: TLS 配置对象
|
||
func (m *StreamSSLManager) GetTLSConfig() *tls.Config {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
|
||
if !m.config.Enabled {
|
||
return nil
|
||
}
|
||
|
||
tlsConfig := &tls.Config{
|
||
Certificates: []tls.Certificate{m.cert},
|
||
MinVersion: tls.VersionTLS12,
|
||
}
|
||
|
||
// 设置协议版本
|
||
if len(m.config.Protocols) > 0 {
|
||
tlsConfig.MinVersion = parseMinTLSVersion(m.config.Protocols)
|
||
}
|
||
|
||
// 设置加密套件
|
||
if len(m.config.Ciphers) > 0 {
|
||
tlsConfig.CipherSuites = parseCipherSuites(m.config.Ciphers)
|
||
}
|
||
|
||
// 配置客户端证书验证(mTLS)
|
||
if m.clientCAPool != nil {
|
||
tlsConfig.ClientCAs = m.clientCAPool
|
||
tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven
|
||
}
|
||
|
||
return tlsConfig
|
||
}
|
||
|
||
// GetClientTLSConfig 获取客户端 TLS 配置。
|
||
//
|
||
// 用于连接上游服务器。
|
||
//
|
||
// 参数:
|
||
// - serverName: 服务器名称(用于 SNI)
|
||
//
|
||
// 返回值:
|
||
// - *tls.Config: TLS 配置对象
|
||
func (m *StreamProxySSLManager) GetClientTLSConfig(serverName string) *tls.Config {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
|
||
if !m.config.Enabled {
|
||
return nil
|
||
}
|
||
|
||
tlsConfig := &tls.Config{
|
||
MinVersion: tls.VersionTLS12,
|
||
}
|
||
|
||
// 设置服务器名称(SNI)
|
||
if m.config.ServerName != "" {
|
||
tlsConfig.ServerName = m.config.ServerName
|
||
} else if serverName != "" {
|
||
tlsConfig.ServerName = serverName
|
||
}
|
||
|
||
// 设置客户端证书
|
||
if m.cert.Certificate != nil {
|
||
tlsConfig.Certificates = []tls.Certificate{m.cert}
|
||
}
|
||
|
||
// 设置协议版本
|
||
if len(m.config.Protocols) > 0 {
|
||
tlsConfig.MinVersion = parseMinTLSVersion(m.config.Protocols)
|
||
}
|
||
|
||
// 配置服务器证书验证
|
||
if m.config.Verify && m.rootCAPool != nil {
|
||
tlsConfig.RootCAs = m.rootCAPool
|
||
} else if !m.config.Verify {
|
||
// 跳过证书验证
|
||
tlsConfig.InsecureSkipVerify = true
|
||
}
|
||
|
||
// 会话复用
|
||
if m.config.SessionReuse {
|
||
tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(100)
|
||
}
|
||
|
||
return tlsConfig
|
||
}
|
||
|
||
// IsEnabled 检查是否启用 SSL。
|
||
func (m *StreamSSLManager) IsEnabled() bool {
|
||
return m.config.Enabled
|
||
}
|
||
|
||
// IsEnabled 检查是否启用代理 SSL。
|
||
func (m *StreamProxySSLManager) IsEnabled() bool {
|
||
return m.config.Enabled
|
||
}
|
||
|
||
// loadCertPool 从文件加载证书池。
|
||
//
|
||
// 参数:
|
||
// - certFile: 证书文件路径
|
||
//
|
||
// 返回值:
|
||
// - *x509.CertPool: 证书池
|
||
// - error: 加载失败时返回错误
|
||
func loadCertPool(certFile string) (*x509.CertPool, error) {
|
||
data, err := os.ReadFile(certFile)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
pool := x509.NewCertPool()
|
||
if !pool.AppendCertsFromPEM(data) {
|
||
return nil, fmt.Errorf("failed to parse certificates from %s", certFile)
|
||
}
|
||
|
||
return pool, nil
|
||
}
|
||
|
||
// parseMinTLSVersion 解析最小 TLS 版本。
|
||
//
|
||
// 参数:
|
||
// - protocols: 协议版本列表
|
||
//
|
||
// 返回值:
|
||
// - uint16: TLS 版本常量
|
||
func parseMinTLSVersion(protocols []string) uint16 {
|
||
for _, p := range protocols {
|
||
switch p {
|
||
case "TLSv1.3":
|
||
return tls.VersionTLS13
|
||
case "TLSv1.2":
|
||
return tls.VersionTLS12
|
||
}
|
||
}
|
||
return tls.VersionTLS12
|
||
}
|
||
|
||
// parseCipherSuites 解析加密套件列表。
|
||
//
|
||
// 参数:
|
||
// - ciphers: 加密套件名称列表
|
||
//
|
||
// 返回值:
|
||
// - []uint16: 加密套件 ID 列表
|
||
func parseCipherSuites(ciphers []string) []uint16 {
|
||
var suites []uint16
|
||
for _, c := range ciphers {
|
||
if id, ok := cipherNameToID[c]; ok {
|
||
suites = append(suites, id)
|
||
}
|
||
}
|
||
if len(suites) == 0 {
|
||
return nil // 使用默认值
|
||
}
|
||
return suites
|
||
}
|
||
|
||
// cipherNameToID 加密套件名称到 ID 的映射
|
||
var cipherNameToID = map[string]uint16{
|
||
"ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||
"ECDHE-RSA-AES256-GCM-SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||
"ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||
"ECDHE-ECDSA-AES256-GCM-SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||
"ECDHE-RSA-CHACHA20-POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||
"ECDHE-ECDSA-CHACHA20-POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||
"AES128-GCM-SHA256": tls.TLS_AES_128_GCM_SHA256,
|
||
"AES256-GCM-SHA384": tls.TLS_AES_256_GCM_SHA384,
|
||
"CHACHA20-POLY1305": tls.TLS_CHACHA20_POLY1305_SHA256,
|
||
"ECDHE-RSA-AES128-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||
"ECDHE-RSA-AES256-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||
"ECDHE-ECDSA-AES128-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||
"ECDHE-ECDSA-AES256-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||
"RSA-AES128-GCM-SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||
"RSA-AES256-GCM-SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||
"RSA-AES128-CBC-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||
"RSA-AES256-CBC-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||
"ECDHE-RSA-3DES-EDE-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||
"RSA-3DES-EDE-CBC-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||
}
|