Files
backend/internal/service/signature_service.go

277 lines
8.1 KiB
Go
Raw Normal View History

package service
import (
"carrotskin/internal/model"
"carrotskin/internal/repository"
"carrotskin/pkg/redis"
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"encoding/pem"
"fmt"
"strconv"
"strings"
"time"
"go.uber.org/zap"
)
// 常量定义
const (
KeySize = 4096
ExpirationDays = 90
RefreshDays = 60
PublicKeyRedisKey = "yggdrasil:public_key"
PrivateKeyRedisKey = "yggdrasil:private_key"
KeyExpirationRedisKey = "yggdrasil:key_expiration"
RedisTTL = 0 // 永不过期,由应用程序管理过期时间
)
// SignatureService 签名服务(导出以便依赖注入)
type SignatureService struct {
profileRepo repository.ProfileRepository
redis *redis.Client
logger *zap.Logger
}
// NewSignatureService 创建SignatureService实例
func NewSignatureService(
profileRepo repository.ProfileRepository,
redisClient *redis.Client,
logger *zap.Logger,
) *SignatureService {
return &SignatureService{
profileRepo: profileRepo,
redis: redisClient,
logger: logger,
}
}
// NewKeyPair 生成新的RSA密钥对
func (s *SignatureService) NewKeyPair() (*model.KeyPair, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, KeySize)
if err != nil {
return nil, fmt.Errorf("生成RSA密钥对失败: %w", err)
}
// 获取公钥
publicKey := &privateKey.PublicKey
// PEM编码私钥
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyBytes,
})
// PEM编码公钥
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return nil, fmt.Errorf("编码公钥失败: %w", err)
}
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
})
// 计算时间
now := time.Now().UTC()
expiration := now.AddDate(0, 0, ExpirationDays)
refresh := now.AddDate(0, 0, RefreshDays)
// 获取Yggdrasil根密钥并签名公钥
yggPublicKey, yggPrivateKey, err := s.GetOrCreateYggdrasilKeyPair()
if err != nil {
return nil, fmt.Errorf("获取Yggdrasil根密钥失败: %w", err)
}
// 构造签名消息
expiresAtMillis := expiration.UnixMilli()
message := []byte(string(publicKeyPEM) + strconv.FormatInt(expiresAtMillis, 10))
// 使用SHA1withRSA签名
hashed := sha1.Sum(message)
signature, err := rsa.SignPKCS1v15(rand.Reader, yggPrivateKey, crypto.SHA1, hashed[:])
if err != nil {
return nil, fmt.Errorf("签名失败: %w", err)
}
publicKeySignature := base64.StdEncoding.EncodeToString(signature)
// 构造V2签名消息DER编码
publicKeyDER, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return nil, fmt.Errorf("DER编码公钥失败: %w", err)
}
// V2签名timestamp (8 bytes, big endian) + publicKey (DER)
messageV2 := make([]byte, 8+len(publicKeyDER))
binary.BigEndian.PutUint64(messageV2[0:8], uint64(expiresAtMillis))
copy(messageV2[8:], publicKeyDER)
hashedV2 := sha1.Sum(messageV2)
signatureV2, err := rsa.SignPKCS1v15(rand.Reader, yggPrivateKey, crypto.SHA1, hashedV2[:])
if err != nil {
return nil, fmt.Errorf("V2签名失败: %w", err)
}
publicKeySignatureV2 := base64.StdEncoding.EncodeToString(signatureV2)
return &model.KeyPair{
PrivateKey: string(privateKeyPEM),
PublicKey: string(publicKeyPEM),
PublicKeySignature: publicKeySignature,
PublicKeySignatureV2: publicKeySignatureV2,
YggdrasilPublicKey: yggPublicKey,
Expiration: expiration,
Refresh: refresh,
}, nil
}
// GetOrCreateYggdrasilKeyPair 获取或创建Yggdrasil根密钥对
func (s *SignatureService) GetOrCreateYggdrasilKeyPair() (string, *rsa.PrivateKey, error) {
ctx := context.Background()
// 尝试从Redis获取密钥
publicKeyPEM, err := s.redis.Get(ctx, PublicKeyRedisKey)
if err == nil && publicKeyPEM != "" {
privateKeyPEM, err := s.redis.Get(ctx, PrivateKeyRedisKey)
if err == nil && privateKeyPEM != "" {
// 检查密钥是否过期
expStr, err := s.redis.Get(ctx, KeyExpirationRedisKey)
if err == nil && expStr != "" {
expTime, err := time.Parse(time.RFC3339, expStr)
if err == nil && time.Now().Before(expTime) {
// 密钥有效,解析私钥
block, _ := pem.Decode([]byte(privateKeyPEM))
if block != nil {
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err == nil {
s.logger.Info("从Redis加载Yggdrasil根密钥")
return publicKeyPEM, privateKey, nil
}
}
}
}
}
}
// 生成新的根密钥对
s.logger.Info("生成新的Yggdrasil根密钥对")
privateKey, err := rsa.GenerateKey(rand.Reader, KeySize)
if err != nil {
return "", nil, fmt.Errorf("生成RSA密钥失败: %w", err)
}
// PEM编码私钥
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := string(pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyBytes,
}))
// PEM编码公钥
publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
return "", nil, fmt.Errorf("编码公钥失败: %w", err)
}
publicKeyPEM = string(pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
}))
// 计算过期时间90天
expiration := time.Now().AddDate(0, 0, ExpirationDays)
// 保存到Redis
if err := s.redis.Set(ctx, PublicKeyRedisKey, publicKeyPEM, RedisTTL); err != nil {
s.logger.Warn("保存公钥到Redis失败", zap.Error(err))
}
if err := s.redis.Set(ctx, PrivateKeyRedisKey, privateKeyPEM, RedisTTL); err != nil {
s.logger.Warn("保存私钥到Redis失败", zap.Error(err))
}
if err := s.redis.Set(ctx, KeyExpirationRedisKey, expiration.Format(time.RFC3339), RedisTTL); err != nil {
s.logger.Warn("保存密钥过期时间到Redis失败", zap.Error(err))
}
return publicKeyPEM, privateKey, nil
}
// GetPublicKeyFromRedis 从Redis获取公钥
func (s *SignatureService) GetPublicKeyFromRedis() (string, error) {
ctx := context.Background()
publicKey, err := s.redis.Get(ctx, PublicKeyRedisKey)
if err != nil {
return "", fmt.Errorf("从Redis获取公钥失败: %w", err)
}
if publicKey == "" {
// 如果Redis中没有创建新的密钥对
publicKey, _, err = s.GetOrCreateYggdrasilKeyPair()
if err != nil {
return "", fmt.Errorf("创建新密钥对失败: %w", err)
}
}
return publicKey, nil
}
// SignStringWithSHA1withRSA 使用SHA1withRSA签名字符串
func (s *SignatureService) SignStringWithSHA1withRSA(data string) (string, error) {
ctx := context.Background()
// 从Redis获取私钥
privateKeyPEM, err := s.redis.Get(ctx, PrivateKeyRedisKey)
if err != nil || privateKeyPEM == "" {
// 如果没有私钥,创建新的密钥对
_, privateKey, err := s.GetOrCreateYggdrasilKeyPair()
if err != nil {
return "", fmt.Errorf("获取私钥失败: %w", err)
}
// 使用新生成的私钥签名
hashed := sha1.Sum([]byte(data))
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA1, hashed[:])
if err != nil {
return "", fmt.Errorf("签名失败: %w", err)
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// 解析PEM格式的私钥
block, _ := pem.Decode([]byte(privateKeyPEM))
if block == nil {
return "", fmt.Errorf("解析PEM私钥失败")
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("解析RSA私钥失败: %w", err)
}
// 签名
hashed := sha1.Sum([]byte(data))
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA1, hashed[:])
if err != nil {
return "", fmt.Errorf("签名失败: %w", err)
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// FormatPublicKey 格式化公钥为单行格式去除PEM头尾和换行符
func FormatPublicKey(publicKeyPEM string) string {
// 移除PEM格式的头尾
lines := strings.Split(publicKeyPEM, "\n")
var keyLines []string
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed != "" &&
!strings.HasPrefix(trimmed, "-----BEGIN") &&
!strings.HasPrefix(trimmed, "-----END") {
keyLines = append(keyLines, trimmed)
}
}
return strings.Join(keyLines, "")
}