Files
GoTunnel/pkg/crypto/tls.go
Flik 4d2a2a7117
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m23s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 56s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 52s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m42s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m19s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 54s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 2m3s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m1s
update
2025-12-29 23:08:15 +08:00

158 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"
"net"
"os"
"path/filepath"
"strings"
"time"
)
// GenerateTLSConfig 生成内存中的自签名证书并返回 TLS 配置
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,
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
DNSNames: []string{"localhost"},
}
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
}