Replace manual PEM text scanning with pem.Decode(). Returns proper DER-encoded bytes instead of raw PEM text, fixing potential TLS handshake failures with certificate chains. Remove unused findMarker and matchMarker helpers.
366 lines
9.4 KiB
Go
366 lines
9.4 KiB
Go
// Package ssl 提供 SSL/TLS 支持。
|
||
//
|
||
// 该文件包含 TLS 配置管理的核心逻辑,包括:
|
||
// - 安全的 TLS 默认配置(仅 TLSv1.2 和 TLSv1.3)
|
||
// - 证书加载和管理
|
||
// - SNI(服务器名称指示)支持
|
||
// - OCSP Stapling 支持
|
||
//
|
||
// 主要用途:
|
||
//
|
||
// 用于管理 HTTPS 服务器的 TLS 配置,支持多证书虚拟主机。
|
||
//
|
||
// 安全默认值:
|
||
// - TLS 版本:仅启用 TLSv1.2 和 TLSv1.3
|
||
// - TLSv1.0 和 TLSv1.1 被强制禁用(不安全)
|
||
// - 安全加密套件,支持前向保密
|
||
// - 配置 TLS 时自动启用 HTTP/2
|
||
//
|
||
// 使用示例:
|
||
//
|
||
// cfg := &config.SSLConfig{
|
||
// Cert: "/path/to/cert.pem",
|
||
// Key: "/path/to/key.pem",
|
||
// Protocols: []string{"TLSv1.2", "TLSv1.3"},
|
||
// }
|
||
//
|
||
// manager, err := ssl.NewTLSManager(cfg)
|
||
// if err != nil {
|
||
// log.Fatal(err)
|
||
// }
|
||
//
|
||
// // 配合 fasthttp 使用
|
||
// server := &fasthttp.Server{
|
||
// TLSConfig: manager.GetTLSConfig(),
|
||
// }
|
||
//
|
||
// 作者:xfy
|
||
package ssl
|
||
|
||
import (
|
||
"crypto/tls"
|
||
"crypto/x509"
|
||
"encoding/pem"
|
||
"errors"
|
||
"fmt"
|
||
"os"
|
||
"sync"
|
||
|
||
"rua.plus/lolly/internal/config"
|
||
"rua.plus/lolly/internal/logging"
|
||
"rua.plus/lolly/internal/sslutil"
|
||
)
|
||
|
||
// TLSManager TLS 配置管理器。
|
||
//
|
||
// 管理单个或多个证书的 TLS 配置,支持 SNI(服务器名称指示)
|
||
// 用于多证书虚拟主机,以及 OCSP Stapling 用于证书状态验证。
|
||
type TLSManager struct {
|
||
// configs TLS 配置映射,按服务器名称索引
|
||
configs map[string]*tls.Config
|
||
|
||
// defaultCfg 默认配置,用于 fallback
|
||
defaultCfg *tls.Config
|
||
|
||
// ocspManager OCSP Stapling 管理器
|
||
ocspManager *OCSPManager
|
||
|
||
// sessionTicketMgr Session Ticket 管理器
|
||
sessionTicketMgr *SessionTicketManager
|
||
|
||
// clientVerifier 客户端证书验证器
|
||
clientVerifier *ClientVerifier
|
||
|
||
// certificates 解析后的证书映射,用于 OCSP
|
||
certificates map[string]*x509.Certificate
|
||
|
||
// issuers 颁发者证书映射,用于 OCSP
|
||
issuers map[string]*x509.Certificate
|
||
|
||
// mu 保护并发访问的读写锁
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
// NewTLSManager 创建新的 TLS 配置管理器。
|
||
//
|
||
// 对于单服务器模式,传入单个 SSLConfig。
|
||
//
|
||
// 参数:
|
||
// - cfg: SSL 配置,包含证书路径和 TLS 设置
|
||
//
|
||
// 返回值:
|
||
// - *TLSManager: 配置好的 TLS 管理器
|
||
// - error: 证书加载失败或配置无效时返回错误
|
||
func NewTLSManager(cfg *config.SSLConfig) (*TLSManager, error) {
|
||
if cfg == nil {
|
||
return nil, errors.New("ssl config is nil")
|
||
}
|
||
|
||
if cfg.Cert == "" || cfg.Key == "" {
|
||
return nil, errors.New("certificate and key paths are required")
|
||
}
|
||
|
||
// 加载证书
|
||
cert, err := loadCertificate(cfg.Cert, cfg.Key, cfg.CertChain)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to load certificate: %w", err)
|
||
}
|
||
|
||
// 创建 TLS 配置,使用安全默认值
|
||
tlsCfg := &tls.Config{
|
||
Certificates: []tls.Certificate{cert},
|
||
MinVersion: tls.VersionTLS12, // 强制 TLS 1.2 最低版本
|
||
MaxVersion: tls.VersionTLS13,
|
||
NextProtos: []string{"h2", "http/1.1"}, // 启用 HTTP/2 ALPN 支持
|
||
}
|
||
|
||
// 应用 TLS 1.2 的加密套件
|
||
if len(cfg.Ciphers) > 0 {
|
||
ciphers, err := sslutil.ParseCipherSuites(cfg.Ciphers)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("invalid cipher suites: %w", err)
|
||
}
|
||
tlsCfg.CipherSuites = ciphers
|
||
} else {
|
||
// 使用安全的默认加密套件
|
||
tlsCfg.CipherSuites = sslutil.DefaultCipherSuites()
|
||
}
|
||
|
||
// 解析 TLS 协议版本
|
||
if len(cfg.Protocols) > 0 {
|
||
minVer, maxVer, err := sslutil.ParseTLSVersions(cfg.Protocols)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("invalid TLS protocols: %w", err)
|
||
}
|
||
tlsCfg.MinVersion = minVer
|
||
tlsCfg.MaxVersion = maxVer
|
||
}
|
||
|
||
manager := &TLSManager{
|
||
configs: make(map[string]*tls.Config),
|
||
certificates: make(map[string]*x509.Certificate),
|
||
issuers: make(map[string]*x509.Certificate),
|
||
}
|
||
|
||
// 初始化 Session Tickets(如果启用)
|
||
if cfg.SessionTickets.Enabled {
|
||
sessionTicketMgr, err := NewSessionTicketManager(cfg.SessionTickets)
|
||
if err != nil {
|
||
logging.Warn().Err(err).Msg("Session Ticket 初始化失败,TLS 性能可能降级")
|
||
} else {
|
||
manager.sessionTicketMgr = sessionTicketMgr
|
||
// 应用 Session Tickets 到 TLS 配置
|
||
sessionTicketMgr.ApplyToTLSConfig(tlsCfg)
|
||
sessionTicketMgr.Start()
|
||
}
|
||
}
|
||
|
||
// 初始化 OCSP Stapling(如果启用)
|
||
if cfg.OCSPStapling {
|
||
ocspMgr := NewOCSPManager(DefaultOCSPConfig())
|
||
manager.ocspManager = ocspMgr
|
||
|
||
// 解析证书用于 OCSP
|
||
if len(cert.Certificate) > 0 {
|
||
parsedCert, err := x509.ParseCertificate(cert.Certificate[0])
|
||
if err == nil && len(parsedCert.OCSPServer) > 0 {
|
||
// 存储证书用于 OCSP 查询
|
||
serial := parsedCert.SerialNumber.String()
|
||
manager.certificates[serial] = parsedCert
|
||
|
||
// 尝试从证书链解析颁发者证书
|
||
if len(cert.Certificate) > 1 {
|
||
issuerCert, err := x509.ParseCertificate(cert.Certificate[1])
|
||
if err == nil {
|
||
manager.issuers[serial] = issuerCert
|
||
if err := ocspMgr.RegisterCertificate(parsedCert, issuerCert); err != nil {
|
||
logging.Warn().Err(err).Msg("OCSP Stapling 注册失败")
|
||
}
|
||
}
|
||
}
|
||
|
||
// 设置 GetConfigForClient 回调用于 OCSP Stapling
|
||
tlsCfg.GetConfigForClient = manager.getConfigForClientWithOCSP
|
||
}
|
||
}
|
||
|
||
ocspMgr.Start()
|
||
}
|
||
|
||
// 初始化客户端证书验证(如果启用)
|
||
if cfg.ClientVerify.Enabled {
|
||
clientVerifier, err := NewClientVerifier(cfg.ClientVerify)
|
||
if err != nil {
|
||
logging.Warn().Err(err).Msg("客户端证书验证配置失败")
|
||
} else {
|
||
manager.clientVerifier = clientVerifier
|
||
clientVerifier.ConfigureTLS(tlsCfg)
|
||
}
|
||
}
|
||
|
||
// 设置为默认配置
|
||
manager.defaultCfg = tlsCfg
|
||
|
||
return manager, nil
|
||
}
|
||
|
||
// GetTLSConfig 返回默认的 TLS 配置。
|
||
//
|
||
// 用于单服务器模式。
|
||
//
|
||
// 返回值:
|
||
// - *tls.Config: TLS 配置对象
|
||
func (m *TLSManager) GetTLSConfig() *tls.Config {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
return m.defaultCfg
|
||
}
|
||
|
||
// Close 停止 OCSP 管理器和 Session Ticket 管理器并释放资源。
|
||
func (m *TLSManager) Close() {
|
||
if m.ocspManager != nil {
|
||
m.ocspManager.Stop()
|
||
}
|
||
if m.sessionTicketMgr != nil {
|
||
m.sessionTicketMgr.Stop()
|
||
}
|
||
}
|
||
|
||
// getConfigForClientWithOCSP 返回启用 OCSP Stapling 的 TLS 配置。
|
||
//
|
||
// 该回调在每次 TLS 握手时调用,附加最新的 OCSP 响应。
|
||
//
|
||
// 参数:
|
||
// - hello: 客户端 Hello 信息
|
||
//
|
||
// 返回值:
|
||
// - *tls.Config: 带有 OCSP 响应的 TLS 配置
|
||
// - error: 配置错误
|
||
func (m *TLSManager) getConfigForClientWithOCSP(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||
m.mu.RLock()
|
||
defer m.mu.RUnlock()
|
||
|
||
// 获取基础配置
|
||
var baseCfg *tls.Config
|
||
if hello.ServerName != "" {
|
||
if cfg, ok := m.configs[hello.ServerName]; ok {
|
||
baseCfg = cfg
|
||
}
|
||
}
|
||
if baseCfg == nil {
|
||
baseCfg = m.defaultCfg
|
||
}
|
||
|
||
// 无 OCSP 管理器或无证书时,返回基础配置
|
||
if m.ocspManager == nil || len(baseCfg.Certificates) == 0 {
|
||
return baseCfg, nil
|
||
}
|
||
|
||
// 创建配置副本并附加 OCSP 响应
|
||
cfgCopy := baseCfg.Clone()
|
||
|
||
// 将 OCSP 响应附加到证书
|
||
cert := &cfgCopy.Certificates[0]
|
||
if len(cert.Certificate) > 0 {
|
||
// 解析叶子证书以获取序列号
|
||
leafCert, err := x509.ParseCertificate(cert.Certificate[0])
|
||
if err == nil {
|
||
serial := leafCert.SerialNumber.String()
|
||
ocspResp := m.ocspManager.GetOCSPResponse(serial)
|
||
if ocspResp != nil {
|
||
// 将 OCSP 响应附加到证书
|
||
cert.OCSPStaple = ocspResp
|
||
}
|
||
}
|
||
}
|
||
|
||
return cfgCopy, nil
|
||
}
|
||
|
||
// OCSPStatusInfo OCSP 响应状态信息。
|
||
type OCSPStatusInfo struct {
|
||
Serial string // 证书序列号
|
||
Subject string // 证书主题 CN
|
||
Status OCSPStatus // OCSP 响应状态
|
||
HasResponse bool // 是否有可用响应
|
||
}
|
||
|
||
// loadCertificate 从给定路径加载 TLS 证书。
|
||
//
|
||
// 如果提供了证书链路径,则合并证书链。
|
||
//
|
||
// 参数:
|
||
// - certPath: 证书文件路径
|
||
// - keyPath: 私钥文件路径
|
||
// - certChainPath: 证书链文件路径(可选)
|
||
//
|
||
// 返回值:
|
||
// - tls.Certificate: 加载的证书
|
||
// - error: 加载失败时返回错误
|
||
func loadCertificate(certPath, keyPath, certChainPath string) (tls.Certificate, error) {
|
||
// 加载主证书
|
||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||
if err != nil {
|
||
return tls.Certificate{}, err
|
||
}
|
||
|
||
// 合并证书链(如果提供)
|
||
if certChainPath != "" {
|
||
chainData, err := os.ReadFile(certChainPath)
|
||
if err != nil {
|
||
return tls.Certificate{}, fmt.Errorf("failed to read certificate chain: %w", err)
|
||
}
|
||
|
||
// 将证书链追加到证书(每个证书作为独立的 [][]byte 条目)
|
||
certs := parsePEMChain(chainData)
|
||
cert.Certificate = append(cert.Certificate, certs...)
|
||
}
|
||
|
||
return cert, nil
|
||
}
|
||
|
||
// parsePEMChain 解析 PEM 编码的证书链数据。
|
||
//
|
||
// 返回 ASN.1 DER 编码的证书切片。
|
||
//
|
||
// 参数:
|
||
// - data: PEM 编码的数据
|
||
//
|
||
// 返回值:
|
||
// - [][]byte: DER 编码的证书列表
|
||
func parsePEMChain(data []byte) [][]byte {
|
||
var certs [][]byte
|
||
var block []byte
|
||
rest := data
|
||
|
||
for {
|
||
block, rest = extractPEMBlock(rest)
|
||
if block == nil {
|
||
break
|
||
}
|
||
if len(block) > 0 {
|
||
certs = append(certs, block)
|
||
}
|
||
}
|
||
|
||
return certs
|
||
}
|
||
|
||
// extractPEMBlock 从数据中提取单个 PEM 块。
|
||
//
|
||
// 返回 DER 编码的块和剩余数据。
|
||
//
|
||
// 参数:
|
||
// - data: PEM 数据
|
||
//
|
||
// 返回值:
|
||
// - []byte: DER 编码的块
|
||
// - []byte: 剩余数据
|
||
func extractPEMBlock(data []byte) ([]byte, []byte) {
|
||
block, rest := pem.Decode(data)
|
||
if block == nil || block.Type != "CERTIFICATE" {
|
||
return nil, nil
|
||
}
|
||
return block.Bytes, rest
|
||
}
|