lolly/internal/ssl/client_verify.go

259 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package ssl 提供 mTLS 客户端证书验证支持。
//
// 该文件包含客户端证书验证的核心逻辑,包括:
// - CA 证书池加载和管理
// - 证书吊销列表 (CRL) 支持
// - 验证模式配置
// - 客户端证书信息提取
//
// mTLS (Mutual TLS) 提供双向认证,服务器验证客户端证书,
// 客户端验证服务器证书,适用于高安全场景。
//
// 作者xfy
package ssl
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"
"time"
"rua.plus/lolly/internal/config"
"rua.plus/lolly/internal/sslutil"
)
// ClientVerifyMode 客户端证书验证模式
type ClientVerifyMode int
const (
// VerifyOff 不验证客户端证书。
VerifyOff ClientVerifyMode = iota
// VerifyOn 强制验证客户端证书。
VerifyOn
// VerifyOptional 可选验证(客户端可选择不提供证书)。
VerifyOptional
// VerifyOptionalNoCA 可选验证但不验证 CA。
VerifyOptionalNoCA
// verifyModeOff 验证模式字符串常量
verifyModeOff = "off"
verifyModeOn = "on"
verifyModeOptional = "optional"
verifyModeOptionalNoCA = "optional_no_ca"
)
// ParseVerifyMode 解析验证模式字符串。
//
// 参数:
// - mode: 模式字符串on/off/optional/optional_no_ca
//
// 返回值:
// - ClientVerifyMode: 验证模式
// - error: 无效模式时返回错误
func ParseVerifyMode(mode string) (ClientVerifyMode, error) {
switch mode {
case verifyModeOff, "":
return VerifyOff, nil
case verifyModeOn:
return VerifyOn, nil
case verifyModeOptional:
return VerifyOptional, nil
case verifyModeOptionalNoCA:
return VerifyOptionalNoCA, nil
default:
return VerifyOff, fmt.Errorf("invalid verify mode: %s", mode)
}
}
// TLSClientAuth 返回对应的 tls.ClientAuthType。
//
// 返回值:
// - tls.ClientAuthType: TLS 客户端认证类型
func (m ClientVerifyMode) TLSClientAuth() tls.ClientAuthType {
switch m {
case VerifyOff:
return tls.NoClientCert
case VerifyOn:
return tls.RequireAndVerifyClientCert
case VerifyOptional:
return tls.VerifyClientCertIfGiven
case VerifyOptionalNoCA:
return tls.RequestClientCert
default:
return tls.NoClientCert
}
}
// ClientVerifier 客户端证书验证器。
//
// 管理客户端证书验证所需的 CA 证书池和 CRL。
type ClientVerifier struct {
caPool *x509.CertPool
crl *x509.RevocationList
caFile string
crlFile string
mode ClientVerifyMode
verifyDepth int
}
// NewClientVerifier 创建新的客户端证书验证器。
//
// 参数:
// - cfg: 客户端验证配置
//
// 返回值:
// - *ClientVerifier: 验证器实例
// - error: 配置无效时返回错误
func NewClientVerifier(cfg config.ClientVerifyConfig) (*ClientVerifier, error) {
if !cfg.Enabled {
return &ClientVerifier{
mode: VerifyOff,
}, nil
}
mode, err := ParseVerifyMode(cfg.Mode)
if err != nil {
return nil, err
}
verifier := &ClientVerifier{
mode: mode,
verifyDepth: cfg.VerifyDepth,
caFile: cfg.ClientCA,
crlFile: cfg.CRL,
}
// 加载 CA 证书池(如果需要验证)
if mode == VerifyOn || mode == VerifyOptional {
if cfg.ClientCA == "" {
return nil, errors.New("client_ca is required when verify is enabled")
}
caPool, err := sslutil.LoadCACertPool(cfg.ClientCA)
if err != nil {
return nil, fmt.Errorf("failed to load CA certificate pool: %w", err)
}
verifier.caPool = caPool
}
// 加载 CRL如果配置
if cfg.CRL != "" {
crl, err := LoadCRL(cfg.CRL)
if err != nil {
return nil, fmt.Errorf("failed to load CRL: %w", err)
}
verifier.crl = crl
}
return verifier, nil
}
// ConfigureTLS 配置 TLS 以启用客户端证书验证。
//
// 参数:
// - tlsCfg: TLS 配置对象
func (v *ClientVerifier) ConfigureTLS(tlsCfg *tls.Config) {
if tlsCfg == nil || v.mode == VerifyOff {
return
}
tlsCfg.ClientAuth = v.mode.TLSClientAuth()
tlsCfg.ClientCAs = v.caPool
// 设置验证深度(通过 VerifyConnection 回调实现)
if v.verifyDepth > 0 {
tlsCfg.VerifyConnection = v.verifyConnection
}
}
// verifyConnection 验证 TLS 连接。
//
// 实现额外的验证逻辑,如证书深度检查。
//
// 参数:
// - cs: 连接状态
//
// 返回值:
// - error: 验证失败时返回错误
func (v *ClientVerifier) verifyConnection(cs tls.ConnectionState) error {
// 检查 CRL
if v.crl != nil && len(cs.PeerCertificates) > 0 {
if err := v.checkCRL(cs.PeerCertificates[0]); err != nil {
return err
}
}
// 检查证书链深度
if v.verifyDepth > 0 && len(cs.PeerCertificates) > v.verifyDepth {
return fmt.Errorf("certificate chain too long: %d > %d", len(cs.PeerCertificates), v.verifyDepth)
}
return nil
}
// checkCRL 检查证书是否在吊销列表中。
//
// 参数:
// - cert: 要检查的证书
//
// 返回值:
// - error: 证书已吊销时返回错误
func (v *ClientVerifier) checkCRL(cert *x509.Certificate) error {
if v.crl == nil || len(v.crl.RevokedCertificateEntries) == 0 {
return nil
}
for _, revoked := range v.crl.RevokedCertificateEntries {
if cert.SerialNumber.Cmp(revoked.SerialNumber) == 0 {
return fmt.Errorf("certificate %s has been revoked", cert.SerialNumber.String())
}
}
return nil
}
// LoadCRL 从文件加载证书吊销列表。
//
// 支持 PEM 和 DER 格式的 CRL 文件。
//
// 参数:
// - crlFile: CRL 文件路径
//
// 返回值:
// - *pkix.CertificateList: CRL 对象
// - error: 加载失败时返回错误
func LoadCRL(crlFile string) (*x509.RevocationList, error) {
data, err := os.ReadFile(crlFile)
if err != nil {
return nil, fmt.Errorf("failed to read CRL file: %w", err)
}
// 尝试 PEM 解码
block, _ := pem.Decode(data)
if block != nil {
data = block.Bytes
}
crl, err := x509.ParseRevocationList(data)
if err != nil {
return nil, fmt.Errorf("failed to parse CRL: %w", err)
}
return crl, nil
}
// ClientCertInfo 客户端证书信息。
type ClientCertInfo struct {
NotBefore time.Time
NotAfter time.Time
Subject string
Issuer string
Serial string
Fingerprint string
DNSNames []string
Email []string
}