- Refactored multiple service and repository methods to accept context as a parameter, enhancing consistency and enabling better control over request lifecycles. - Updated handlers to utilize context in method calls, improving error handling and performance. - Cleaned up Dockerfile by removing unnecessary whitespace.
277 lines
8.1 KiB
Go
277 lines
8.1 KiB
Go
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, "")
|
||
}
|