优化
This commit is contained in:
truewhile
2026-08-25 14:12:59 +08:00
parent eb09251424
commit da1fb02c9d
18 changed files with 1011 additions and 86 deletions
+160
View File
@@ -0,0 +1,160 @@
package service
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
// ResolveSSLMaterial 解析一份 SSL 材料(证书或私钥)的 PEM 内容:
// 优先读取 path 指向的文件,其次使用内容;两者都为空时返回错误。
// what 用于错误提示("证书" / "私钥")。
func ResolveSSLMaterial(content, path, what string) (string, error) {
p := strings.TrimSpace(path)
if p != "" {
if info, err := os.Stat(p); err != nil {
return "", fmt.Errorf("SSL %s文件不可访问:%s(%v)", what, p, err)
} else if info.IsDir() {
return "", fmt.Errorf("SSL %s路径指向的是目录,请填写文件路径:%s", what, p)
}
b, err := os.ReadFile(p)
if err != nil {
return "", fmt.Errorf("读取 SSL %s文件失败:%s(%v)", what, p, err)
}
return strings.TrimSpace(string(b)), nil
}
c := strings.TrimSpace(content)
if c == "" {
return "", fmt.Errorf("SSL %s未配置:请填写内容或文件路径", what)
}
return c, nil
}
// ResolveSSLKeyPair 解析证书与私钥(各自支持 内容或路径),校验格式与匹配后
// 返回可用的 tls.Certificate。任何一步失败都会给出明确错误。
func ResolveSSLKeyPair(certContent, certPath, keyContent, keyPath string) (*tls.Certificate, error) {
certPEM, err := ResolveSSLMaterial(certContent, certPath, "证书")
if err != nil {
return nil, err
}
keyPEM, err := ResolveSSLMaterial(keyContent, keyPath, "私钥")
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM))
if err != nil {
return nil, fmt.Errorf("SSL 证书/私钥无效:%v", err)
}
if !sslKeyMatchesCert(cert) {
return nil, errors.New("SSL 证书与私钥不匹配")
}
return &cert, nil
}
// PathFingerprint 返回文件路径的内容指纹(路径 + 大小 + 修改时间),用于检测
// 文件是否被替换过;文件不存在时返回 ("", false)。
func PathFingerprint(p string) (string, bool) {
p = strings.TrimSpace(p)
if p == "" {
return "", false
}
info, err := os.Stat(p)
if err != nil {
return "", false
}
return fmt.Sprintf("path:%s|size:%d|mtime:%d", filepath.Clean(p), info.Size(), info.ModTime().UnixNano()), true
}
// ValidateSSLCert 校验 s 是一个可解析的 PEM 编码 X.509 证书。
func ValidateSSLCert(s string) error {
block, _ := pem.Decode([]byte(strings.TrimSpace(s)))
if block == nil {
return errors.New("SSL 证书格式无效:未找到 PEM 数据")
}
if block.Type != "CERTIFICATE" {
return fmt.Errorf("SSL 证书格式无效:期望 CERTIFICATE,实际为 %s", block.Type)
}
if _, err := x509.ParseCertificate(block.Bytes); err != nil {
return fmt.Errorf("SSL 证书解析失败:%v", err)
}
return nil
}
// ValidateSSLKey 校验 s 是一个可解析的 PEM 编码私钥。
func ValidateSSLKey(s string) error {
block, _ := pem.Decode([]byte(strings.TrimSpace(s)))
if block == nil {
return errors.New("SSL 私钥格式无效:未找到 PEM 数据")
}
if _, err := parsePrivateKeyBlock(block); err != nil {
return fmt.Errorf("SSL 私钥解析失败:%v", err)
}
return nil
}
// ValidateSSLKeyPair 校验证书与私钥都存在、可解析且相互匹配。
func ValidateSSLKeyPair(certPEM, keyPEM string) error {
cert, err := tls.X509KeyPair([]byte(strings.TrimSpace(certPEM)), []byte(strings.TrimSpace(keyPEM)))
if err != nil {
return fmt.Errorf("SSL 证书/私钥无效:%v", err)
}
if !sslKeyMatchesCert(cert) {
return errors.New("SSL 证书与私钥不匹配")
}
return nil
}
// sslKeyMatchesCert 通过公钥是否一致来判断私钥确实对应证书。
func sslKeyMatchesCert(cert tls.Certificate) bool {
if len(cert.Certificate) == 0 || cert.PrivateKey == nil {
return false
}
leaf, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return false
}
privPub := publicKeyOf(cert.PrivateKey)
if privPub == nil {
return false
}
eq, ok := leaf.PublicKey.(interface {
Equal(x crypto.PublicKey) bool
})
return ok && eq.Equal(privPub)
}
// publicKeyOf 从各类私钥中提取对应的公钥。
func publicKeyOf(priv crypto.PrivateKey) crypto.PublicKey {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
case ed25519.PrivateKey:
return k.Public()
}
return nil
}
// parsePrivateKeyBlock 支持 PKCS#8 / PKCS#1 RSA / EC 三种常见私钥格式。
func parsePrivateKeyBlock(block *pem.Block) (crypto.PrivateKey, error) {
if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
return key, nil
}
if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
return key, nil
}
if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil {
return key, nil
}
return nil, errors.New("无法解析私钥(支持 PKCS#8 / PKCS#1 RSA / EC)")
}