Files
GoTunnel/pkg/crypto/tls.go
Flik 3f7b72a0aa
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 44s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 2m21s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 2m19s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 3m32s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 2m5s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 3m4s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 53s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 2m46s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 2m19s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 1m54s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m47s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 2m8s
1
2026-01-01 20:57:03 +08:00

157 lines
4.4 KiB
Go
Raw Permalink 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 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
}