package crypto import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/hex" "fmt" "math/big" "os" "path/filepath" "strings" "time" ) // GenerateTLSConfig 生成内存中的自签名证书并返回 TLS 配置 // 证书不限定具体 IP 地址,客户端使用 InsecureSkipVerify 跳过主机名验证(类似 frp) func GenerateTLSConfig() (*tls.Config, error) { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, err } serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return nil, err } template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{"GoTunnel"}, CommonName: "GoTunnel Server", }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, // 不限定 IP 地址和域名,客户端通过 InsecureSkipVerify + TOFU 验证 } certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { return nil, err } cert := tls.Certificate{ Certificate: [][]byte{certDER}, PrivateKey: priv, } return &tls.Config{ Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12, }, nil } // ClientTLSConfig 创建客户端 TLS 配置 func ClientTLSConfig() *tls.Config { return &tls.Config{ InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, } } // ClientTLSConfigWithTOFU 创建带 TOFU 验证的客户端 TLS 配置 // serverAddr: 服务器地址,用于存储指纹 // dataDir: 数据目录,用于存储指纹文件 // skipVerify: 是否跳过验证(测试环境使用) func ClientTLSConfigWithTOFU(serverAddr, dataDir string, skipVerify bool) *tls.Config { if skipVerify { return &tls.Config{ InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, } } return &tls.Config{ InsecureSkipVerify: true, // 必须为 true,因为是自签名证书 MinVersion: tls.VersionTLS12, VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error { return VerifyCertFingerprint(rawCerts, serverAddr, dataDir) }, } } // CertFingerprint 计算证书指纹 (SHA256) func CertFingerprint(certDER []byte) string { hash := sha256.Sum256(certDER) return hex.EncodeToString(hash[:]) } // GetFingerprintPath 获取指纹文件路径 func GetFingerprintPath(serverAddr, dataDir string) string { // 将服务器地址转换为安全的文件名 safeName := strings.ReplaceAll(serverAddr, ":", "_") safeName = strings.ReplaceAll(safeName, "/", "_") return filepath.Join(dataDir, ".fingerprint_"+safeName) } // LoadFingerprint 加载已保存的证书指纹 func LoadFingerprint(serverAddr, dataDir string) (string, error) { path := GetFingerprintPath(serverAddr, dataDir) data, err := os.ReadFile(path) if err != nil { return "", err } return strings.TrimSpace(string(data)), nil } // SaveFingerprint 保存证书指纹 func SaveFingerprint(serverAddr, dataDir, fingerprint string) error { if err := os.MkdirAll(dataDir, 0700); err != nil { return err } path := GetFingerprintPath(serverAddr, dataDir) return os.WriteFile(path, []byte(fingerprint), 0600) } // VerifyCertFingerprint 验证证书指纹 (TOFU 模式) func VerifyCertFingerprint(rawCerts [][]byte, serverAddr, dataDir string) error { if len(rawCerts) == 0 { return fmt.Errorf("no certificate provided") } // 计算当前证书指纹 currentFP := CertFingerprint(rawCerts[0]) // 尝试加载已保存的指纹 savedFP, err := LoadFingerprint(serverAddr, dataDir) if err != nil { // 首次连接,保存指纹 if os.IsNotExist(err) { if saveErr := SaveFingerprint(serverAddr, dataDir, currentFP); saveErr != nil { return fmt.Errorf("failed to save fingerprint: %w", saveErr) } return nil // 首次连接,信任此证书 } return fmt.Errorf("failed to load fingerprint: %w", err) } // 验证指纹是否匹配 if savedFP != currentFP { return fmt.Errorf("certificate fingerprint mismatch: possible MITM attack\n"+ " Expected: %s\n Got: %s\n"+ " If the server certificate was legitimately changed, delete: %s", savedFP, currentFP, GetFingerprintPath(serverAddr, dataDir)) } return nil }