- Updated main.go to initialize email service and include it in the dependency injection container. - Refactored handlers to utilize context in service method calls, improving consistency and error handling. - Introduced new service options for upload, security, and captcha services, enhancing modularity and testability. - Removed unused repository implementations to streamline the codebase. This commit continues the effort to improve the architecture by ensuring all services are properly injected and utilized across the application.
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, "")
|
||
}
|