feat: Enhance dependency injection and service integration

- 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.
This commit is contained in:
lan
2025-12-02 22:52:33 +08:00
parent 792e96b238
commit 034e02e93a
54 changed files with 2305 additions and 2708 deletions

View File

@@ -14,592 +14,263 @@ import (
"encoding/binary"
"encoding/pem"
"fmt"
"go.uber.org/zap"
"strconv"
"strings"
"time"
"gorm.io/gorm"
"go.uber.org/zap"
)
// 常量定义
const (
// RSA密钥长度
RSAKeySize = 4096
// Redis密钥名称
PrivateKeyRedisKey = "private_key"
PublicKeyRedisKey = "public_key"
// 密钥过期时间
KeyExpirationTime = time.Hour * 24 * 7
// 证书相关
CertificateRefreshInterval = time.Hour * 24 // 证书刷新时间间隔
CertificateExpirationPeriod = time.Hour * 24 * 7 // 证书过期时间
KeySize = 4096
ExpirationDays = 90
RefreshDays = 60
PublicKeyRedisKey = "yggdrasil:public_key"
PrivateKeyRedisKey = "yggdrasil:private_key"
KeyExpirationRedisKey = "yggdrasil:key_expiration"
RedisTTL = 0 // 永不过期,由应用程序管理过期时间
)
// PlayerCertificate 表示玩家证书信息
type PlayerCertificate struct {
ExpiresAt string `json:"expiresAt"`
RefreshedAfter string `json:"refreshedAfter"`
PublicKeySignature string `json:"publicKeySignature,omitempty"`
PublicKeySignatureV2 string `json:"publicKeySignatureV2,omitempty"`
KeyPair struct {
PrivateKey string `json:"privateKey"`
PublicKey string `json:"publicKey"`
} `json:"keyPair"`
}
// SignatureService 保留结构体以保持向后兼容,但推荐使用函数式版本
type SignatureService struct {
// signatureService 签名服务实现
type signatureService struct {
profileRepo repository.ProfileRepository
redis *redis.Client
logger *zap.Logger
redisClient *redis.Client
}
func NewSignatureService(logger *zap.Logger, redisClient *redis.Client) *SignatureService {
return &SignatureService{
// NewSignatureService 创建SignatureService实例
func NewSignatureService(
profileRepo repository.ProfileRepository,
redisClient *redis.Client,
logger *zap.Logger,
) *signatureService {
return &signatureService{
profileRepo: profileRepo,
redis: redisClient,
logger: logger,
redisClient: redisClient,
}
}
// SignStringWithSHA1withRSA 使用SHA1withRSA签名字符串并返回Base64编码的签名函数式版本
func SignStringWithSHA1withRSA(logger *zap.Logger, redisClient *redis.Client, data string) (string, error) {
if data == "" {
return "", fmt.Errorf("签名数据不能为空")
}
// 获取私钥
privateKey, err := DecodePrivateKeyFromPEM(logger, redisClient)
// NewKeyPair 生成新的RSA密钥对
func (s *signatureService) NewKeyPair() (*model.KeyPair, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, KeySize)
if err != nil {
logger.Error("[ERROR] 解码私钥失败: ", zap.Error(err))
return "", fmt.Errorf("解码私钥失败: %w", err)
return nil, fmt.Errorf("生成RSA密钥对失败: %w", err)
}
// 计算SHA1哈希
hashed := sha1.Sum([]byte(data))
// 获取公钥
publicKey := &privateKey.PublicKey
// 使用RSA-PKCS1v15算法签名
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA1, hashed[:])
if err != nil {
logger.Error("[ERROR] RSA签名失败: ", zap.Error(err))
return "", fmt.Errorf("RSA签名失败: %w", err)
}
// Base64编码签名
encodedSignature := base64.StdEncoding.EncodeToString(signature)
logger.Info("[INFO] 成功使用SHA1withRSA生成签名,", zap.Any("数据长度:", len(data)))
return encodedSignature, nil
}
// SignStringWithSHA1withRSAService 使用SHA1withRSA签名字符串并返回Base64编码的签名结构体方法版本保持向后兼容
func (s *SignatureService) SignStringWithSHA1withRSA(data string) (string, error) {
return SignStringWithSHA1withRSA(s.logger, s.redisClient, data)
}
// DecodePrivateKeyFromPEM 从Redis获取并解码PEM格式的私钥函数式版本
func DecodePrivateKeyFromPEM(logger *zap.Logger, redisClient *redis.Client) (*rsa.PrivateKey, error) {
// 从Redis获取私钥
privateKeyString, err := GetPrivateKeyFromRedis(logger, redisClient)
if err != nil {
return nil, fmt.Errorf("从Redis获取私钥失败: %w", err)
}
// 解码PEM格式
privateKeyBlock, rest := pem.Decode([]byte(privateKeyString))
if privateKeyBlock == nil || len(rest) > 0 {
logger.Error("[ERROR] 无效的PEM格式私钥")
return nil, fmt.Errorf("无效的PEM格式私钥")
}
// 解析PKCS1格式的私钥
privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
if err != nil {
logger.Error("[ERROR] 解析私钥失败: ", zap.Error(err))
return nil, fmt.Errorf("解析私钥失败: %w", err)
}
return privateKey, nil
}
// GetPrivateKeyFromRedis 从Redis获取私钥PEM格式函数式版本
func GetPrivateKeyFromRedis(logger *zap.Logger, redisClient *redis.Client) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
pemBytes, err := redisClient.GetBytes(ctx, PrivateKeyRedisKey)
if err != nil {
logger.Info("[INFO] 从Redis获取私钥失败尝试生成新的密钥对: ", zap.Error(err))
// 生成新的密钥对
err = GenerateRSAKeyPair(logger, redisClient)
if err != nil {
logger.Error("[ERROR] 生成RSA密钥对失败: ", zap.Error(err))
return "", fmt.Errorf("生成RSA密钥对失败: %w", err)
}
// 递归获取生成的密钥
return GetPrivateKeyFromRedis(logger, redisClient)
}
return string(pemBytes), nil
}
// DecodePrivateKeyFromPEMService 从Redis获取并解码PEM格式的私钥结构体方法版本保持向后兼容
func (s *SignatureService) DecodePrivateKeyFromPEM() (*rsa.PrivateKey, error) {
return DecodePrivateKeyFromPEM(s.logger, s.redisClient)
}
// GetPrivateKeyFromRedisService 从Redis获取私钥PEM格式结构体方法版本保持向后兼容
func (s *SignatureService) GetPrivateKeyFromRedis() (string, error) {
return GetPrivateKeyFromRedis(s.logger, s.redisClient)
}
// GenerateRSAKeyPair 生成新的RSA密钥对函数式版本
func GenerateRSAKeyPair(logger *zap.Logger, redisClient *redis.Client) error {
logger.Info("[INFO] 开始生成RSA密钥对", zap.Int("keySize", RSAKeySize))
// 生成私钥
privateKey, err := rsa.GenerateKey(rand.Reader, RSAKeySize)
if err != nil {
logger.Error("[ERROR] 生成RSA私钥失败: ", zap.Error(err))
return fmt.Errorf("生成RSA私钥失败: %w", err)
}
// 编码私钥为PEM格式
pemPrivateKey, err := EncodePrivateKeyToPEM(privateKey)
if err != nil {
logger.Error("[ERROR] 编码RSA私钥失败: ", zap.Error(err))
return fmt.Errorf("编码RSA私钥失败: %w", err)
}
// 获取公钥并编码为PEM格式
pubKey := privateKey.PublicKey
pemPublicKey, err := EncodePublicKeyToPEM(logger, &pubKey)
if err != nil {
logger.Error("[ERROR] 编码RSA公钥失败: ", zap.Error(err))
return fmt.Errorf("编码RSA公钥失败: %w", err)
}
// 保存密钥对到Redis
return SaveKeyPairToRedis(logger, redisClient, string(pemPrivateKey), string(pemPublicKey))
}
// GenerateRSAKeyPairService 生成新的RSA密钥对结构体方法版本保持向后兼容
func (s *SignatureService) GenerateRSAKeyPair() error {
return GenerateRSAKeyPair(s.logger, s.redisClient)
}
// EncodePrivateKeyToPEM 将私钥编码为PEM格式函数式版本
func EncodePrivateKeyToPEM(privateKey *rsa.PrivateKey, keyType ...string) ([]byte, error) {
if privateKey == nil {
return nil, fmt.Errorf("私钥不能为空")
}
// 默认使用 "PRIVATE KEY" 类型
pemType := "PRIVATE KEY"
// 如果指定了类型参数且为 "RSA",则使用 "RSA PRIVATE KEY"
if len(keyType) > 0 && keyType[0] == "RSA" {
pemType = "RSA PRIVATE KEY"
}
// 将私钥转换为PKCS1格式
// PEM编码私钥
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
// 编码为PEM格式
pemBlock := &pem.Block{
Type: pemType,
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)
}
return pem.EncodeToMemory(pemBlock), nil
}
// EncodePublicKeyToPEM 将公钥编码为PEM格式函数式版本
func EncodePublicKeyToPEM(logger *zap.Logger, publicKey *rsa.PublicKey, keyType ...string) ([]byte, error) {
if publicKey == nil {
return nil, fmt.Errorf("公钥不能为空")
}
// 默认使用 "PUBLIC KEY" 类型
pemType := "PUBLIC KEY"
var publicKeyBytes []byte
var err error
// 如果指定了类型参数且为 "RSA",则使用 "RSA PUBLIC KEY"
if len(keyType) > 0 && keyType[0] == "RSA" {
pemType = "RSA PUBLIC KEY"
publicKeyBytes = x509.MarshalPKCS1PublicKey(publicKey)
} else {
// 默认将公钥转换为PKIX格式
publicKeyBytes, err = x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
logger.Error("[ERROR] 序列化公钥失败: ", zap.Error(err))
return nil, fmt.Errorf("序列化公钥失败: %w", err)
}
}
// 编码为PEM格式
pemBlock := &pem.Block{
Type: pemType,
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
}
})
return pem.EncodeToMemory(pemBlock), nil
}
// SaveKeyPairToRedis 将RSA密钥对保存到Redis函数式版本
func SaveKeyPairToRedis(logger *zap.Logger, redisClient *redis.Client, privateKey, publicKey string) error {
// 创建上下文并设置超时
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
// 使用事务确保两个操作的原子性
tx := redisClient.TxPipeline()
tx.Set(ctx, PrivateKeyRedisKey, privateKey, KeyExpirationTime)
tx.Set(ctx, PublicKeyRedisKey, publicKey, KeyExpirationTime)
// 执行事务
_, err := tx.Exec(ctx)
if err != nil {
logger.Error("[ERROR] 保存RSA密钥对到Redis失败: ", zap.Error(err))
return fmt.Errorf("保存RSA密钥对到Redis失败: %w", err)
}
logger.Info("[INFO] 成功保存RSA密钥对到Redis")
return nil
}
// EncodePrivateKeyToPEMService 将私钥编码为PEM格式结构体方法版本保持向后兼容
func (s *SignatureService) EncodePrivateKeyToPEM(privateKey *rsa.PrivateKey, keyType ...string) ([]byte, error) {
return EncodePrivateKeyToPEM(privateKey, keyType...)
}
// EncodePublicKeyToPEMService 将公钥编码为PEM格式结构体方法版本保持向后兼容
func (s *SignatureService) EncodePublicKeyToPEM(publicKey *rsa.PublicKey, keyType ...string) ([]byte, error) {
return EncodePublicKeyToPEM(s.logger, publicKey, keyType...)
}
// SaveKeyPairToRedisService 将RSA密钥对保存到Redis结构体方法版本保持向后兼容
func (s *SignatureService) SaveKeyPairToRedis(privateKey, publicKey string) error {
return SaveKeyPairToRedis(s.logger, s.redisClient, privateKey, publicKey)
}
// GetPublicKeyFromRedisFunc 从Redis获取公钥PEM格式函数式版本
func GetPublicKeyFromRedisFunc(logger *zap.Logger, redisClient *redis.Client) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
pemBytes, err := redisClient.GetBytes(ctx, PublicKeyRedisKey)
if err != nil {
logger.Info("[INFO] 从Redis获取公钥失败尝试生成新的密钥对: ", zap.Error(err))
// 生成新的密钥对
err = GenerateRSAKeyPair(logger, redisClient)
if err != nil {
logger.Error("[ERROR] 生成RSA密钥对失败: ", zap.Error(err))
return "", fmt.Errorf("生成RSA密钥对失败: %w", err)
}
// 递归获取生成的密钥
return GetPublicKeyFromRedisFunc(logger, redisClient)
}
// 检查获取到的公钥是否为空key不存在时GetBytes返回nil, nil
if len(pemBytes) == 0 {
logger.Info("[INFO] Redis中公钥为空尝试生成新的密钥对")
// 生成新的密钥对
err = GenerateRSAKeyPair(logger, redisClient)
if err != nil {
logger.Error("[ERROR] 生成RSA密钥对失败: ", zap.Error(err))
return "", fmt.Errorf("生成RSA密钥对失败: %w", err)
}
// 递归获取生成的密钥
return GetPublicKeyFromRedisFunc(logger, redisClient)
}
return string(pemBytes), nil
}
// GetPublicKeyFromRedis 从Redis获取公钥PEM格式结构体方法版本
func (s *SignatureService) GetPublicKeyFromRedis() (string, error) {
return GetPublicKeyFromRedisFunc(s.logger, s.redisClient)
}
// GeneratePlayerCertificate 生成玩家证书(函数式版本)
func GeneratePlayerCertificate(db *gorm.DB, logger *zap.Logger, redisClient *redis.Client, uuid string) (*PlayerCertificate, error) {
if uuid == "" {
return nil, fmt.Errorf("UUID不能为空")
}
logger.Info("[INFO] 开始生成玩家证书用户UUID: %s",
zap.String("uuid", uuid),
)
keyPair, err := repository.GetProfileKeyPair(uuid)
if err != nil {
logger.Info("[INFO] 获取用户密钥对失败,将创建新密钥对: %v",
zap.Error(err),
zap.String("uuid", uuid),
)
keyPair = nil
}
// 如果没有找到密钥对或密钥对已过期,创建一个新的
// 计算时间
now := time.Now().UTC()
if keyPair == nil || keyPair.Refresh.Before(now) || keyPair.PrivateKey == "" || keyPair.PublicKey == "" {
logger.Info("[INFO] 为用户创建新的密钥对: %s",
zap.String("uuid", uuid),
)
keyPair, err = NewKeyPair(logger)
if err != nil {
logger.Error("[ERROR] 生成玩家证书密钥对失败: %v",
zap.Error(err),
zap.String("uuid", uuid),
)
return nil, fmt.Errorf("生成玩家证书密钥对失败: %w", err)
}
// 保存密钥对到数据库
err = repository.UpdateProfileKeyPair(uuid, keyPair)
if err != nil {
// 日志修改logger → s.loggerzap结构化字段
logger.Warn("[WARN] 更新用户密钥对失败: %v",
zap.Error(err),
zap.String("uuid", uuid),
)
// 继续执行,即使保存失败
}
}
expiration := now.AddDate(0, 0, ExpirationDays)
refresh := now.AddDate(0, 0, RefreshDays)
// 计算expiresAt的毫秒时间戳
expiresAtMillis := keyPair.Expiration.UnixMilli()
// 准备签名
publicKeySignature := ""
publicKeySignatureV2 := ""
// 获取服务器私钥用于签名
serverPrivateKey, err := DecodePrivateKeyFromPEM(logger, redisClient)
// 获取Yggdrasil根密钥并签名公钥
yggPublicKey, yggPrivateKey, err := s.GetOrCreateYggdrasilKeyPair()
if err != nil {
// 日志修改logger → s.loggerzap结构化字段
logger.Error("[ERROR] 获取服务器私钥失败: %v",
zap.Error(err),
zap.String("uuid", uuid),
)
return nil, fmt.Errorf("获取服务器私钥失败: %w", err)
return nil, fmt.Errorf("获取Yggdrasil根密钥失败: %w", err)
}
// 提取公钥DER编码
pubPEMBlock, _ := pem.Decode([]byte(keyPair.PublicKey))
if pubPEMBlock == nil {
// 日志修改logger → s.loggerzap结构化字段
logger.Error("[ERROR] 解码公钥PEM失败",
zap.String("uuid", uuid),
zap.String("publicKey", keyPair.PublicKey),
)
return nil, fmt.Errorf("解码公钥PEM失败")
}
pubDER := pubPEMBlock.Bytes
// 构造签名消息
expiresAtMillis := expiration.UnixMilli()
message := []byte(string(publicKeyPEM) + strconv.FormatInt(expiresAtMillis, 10))
// 准备publicKeySignature用于MC 1.19
// Base64编码公钥不包含换行
pubBase64 := strings.ReplaceAll(base64.StdEncoding.EncodeToString(pubDER), "\n", "")
// 按76字符一行进行包装
pubBase64Wrapped := WrapString(pubBase64, 76)
// 放入PEM格式
pubMojangPEM := "-----BEGIN RSA PUBLIC KEY-----\n" +
pubBase64Wrapped +
"\n-----END RSA PUBLIC KEY-----\n"
// 签名数据: expiresAt毫秒时间戳 + 公钥PEM格式
signedData := []byte(fmt.Sprintf("%d%s", expiresAtMillis, pubMojangPEM))
// 计算SHA1哈希并签名
hash1 := sha1.Sum(signedData)
signature, err := rsa.SignPKCS1v15(rand.Reader, serverPrivateKey, crypto.SHA1, hash1[:])
// 使用SHA1withRSA签名
hashed := sha1.Sum(message)
signature, err := rsa.SignPKCS1v15(rand.Reader, yggPrivateKey, crypto.SHA1, hashed[:])
if err != nil {
logger.Error("[ERROR] 签名失败: %v",
zap.Error(err),
zap.String("uuid", uuid),
zap.Int64("expiresAtMillis", expiresAtMillis),
)
return nil, fmt.Errorf("签名失败: %w", err)
}
publicKeySignature = base64.StdEncoding.EncodeToString(signature)
publicKeySignature := base64.StdEncoding.EncodeToString(signature)
// 准备publicKeySignatureV2用于MC 1.19.1+
var uuidBytes []byte
// 如果提供了UUID则使用它
// 移除UUID中的连字符
uuidStr := strings.ReplaceAll(uuid, "-", "")
// 将UUID转换为字节数组16字节
if len(uuidStr) < 32 {
logger.Warn("[WARN] UUID长度不足32字符使用空UUID: %s",
zap.String("uuid", uuid),
zap.String("processedUuidStr", uuidStr),
)
uuidBytes = make([]byte, 16)
} else {
// 解析UUID字符串为字节
uuidBytes = make([]byte, 16)
parseErr := error(nil)
for i := 0; i < 16; i++ {
// 每两个字符转换为一个字节
byteStr := uuidStr[i*2 : i*2+2]
byteVal, err := strconv.ParseUint(byteStr, 16, 8)
if err != nil {
parseErr = err
logger.Error("[ERROR] 解析UUID字节失败: %v, byteStr: %s",
zap.Error(err),
zap.String("uuid", uuid),
zap.String("byteStr", byteStr),
zap.Int("index", i),
)
uuidBytes = make([]byte, 16) // 出错时使用空UUID
break
}
uuidBytes[i] = byte(byteVal)
}
if parseErr != nil {
return nil, fmt.Errorf("解析UUID字节失败: %w", parseErr)
}
}
// 准备签名数据UUID + expiresAt时间戳 + DER编码的公钥
signedDataV2 := make([]byte, 0, 24+len(pubDER)) // 预分配缓冲区
// 添加UUID16字节
signedDataV2 = append(signedDataV2, uuidBytes...)
// 添加expiresAt毫秒时间戳8字节大端序
expiresAtBytes := make([]byte, 8)
binary.BigEndian.PutUint64(expiresAtBytes, uint64(expiresAtMillis))
signedDataV2 = append(signedDataV2, expiresAtBytes...)
// 添加DER编码的公钥
signedDataV2 = append(signedDataV2, pubDER...)
// 计算SHA1哈希并签名
hash2 := sha1.Sum(signedDataV2)
signatureV2, err := rsa.SignPKCS1v15(rand.Reader, serverPrivateKey, crypto.SHA1, hash2[:])
// 构造V2签名消息DER编码
publicKeyDER, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
logger.Error("[ERROR] 签名V2失败: %v",
zap.Error(err),
zap.String("uuid", uuid),
zap.Int64("expiresAtMillis", expiresAtMillis),
)
return nil, fmt.Errorf("签名V2失败: %w", err)
return nil, fmt.Errorf("DER编码公钥失败: %w", err)
}
publicKeySignatureV2 = base64.StdEncoding.EncodeToString(signatureV2)
// 创建玩家证书结构
certificate := &PlayerCertificate{
KeyPair: struct {
PrivateKey string `json:"privateKey"`
PublicKey string `json:"publicKey"`
}{
PrivateKey: keyPair.PrivateKey,
PublicKey: keyPair.PublicKey,
},
// 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,
ExpiresAt: keyPair.Expiration.Format(time.RFC3339Nano),
RefreshedAfter: keyPair.Refresh.Format(time.RFC3339Nano),
}
logger.Info("[INFO] 成功生成玩家证书,过期时间: %s",
zap.String("uuid", uuid),
zap.String("expiresAt", certificate.ExpiresAt),
zap.String("refreshedAfter", certificate.RefreshedAfter),
)
return certificate, nil
YggdrasilPublicKey: yggPublicKey,
Expiration: expiration,
Refresh: refresh,
}, nil
}
// GeneratePlayerCertificateService 生成玩家证书(结构体方法版本,保持向后兼容)
func (s *SignatureService) GeneratePlayerCertificate(uuid string) (*PlayerCertificate, error) {
return GeneratePlayerCertificate(nil, s.logger, s.redisClient, uuid) // TODO: 需要传入db参数
}
// GetOrCreateYggdrasilKeyPair 获取或创建Yggdrasil根密钥对
func (s *signatureService) GetOrCreateYggdrasilKeyPair() (string, *rsa.PrivateKey, error) {
ctx := context.Background()
// NewKeyPair 生成新的密钥对(函数式版本)
func NewKeyPair(logger *zap.Logger) (*model.KeyPair, error) {
// 生成新的RSA密钥对用于玩家证书
privateKey, err := rsa.GenerateKey(rand.Reader, 2048) // 对玩家证书使用更小的密钥以提高性能
if err != nil {
logger.Error("[ERROR] 生成玩家证书私钥失败: %v",
zap.Error(err),
)
return nil, fmt.Errorf("生成玩家证书私钥失败: %w", err)
// 尝试从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
}
}
}
}
}
}
// 获取DER编码的密钥
keyDER, err := x509.MarshalPKCS8PrivateKey(privateKey)
// 生成新的根密钥
s.logger.Info("生成新的Yggdrasil根密钥对")
privateKey, err := rsa.GenerateKey(rand.Reader, KeySize)
if err != nil {
logger.Error("[ERROR] 编码私钥为PKCS8格式失败: %v",
zap.Error(err),
)
return nil, fmt.Errorf("编码私钥为PKCS8格式失败: %w", err)
return "", nil, fmt.Errorf("生成RSA密钥失败: %w", err)
}
pubDER, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
logger.Error("[ERROR] 编码公钥为PKIX格式失败: %v",
zap.Error(err),
)
return nil, fmt.Errorf("编码公钥为PKIX格式失败: %w", err)
}
// 将密钥编码为PEM格式
keyPEM := pem.EncodeToMemory(&pem.Block{
// PEM编码私钥
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := string(pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: keyDER,
})
Bytes: privateKeyBytes,
}))
pubPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubDER,
})
// 创建证书过期和刷新时间
now := time.Now().UTC()
expiresAtTime := now.Add(CertificateExpirationPeriod)
refreshedAfter := now.Add(CertificateRefreshInterval)
keyPair := &model.KeyPair{
Expiration: expiresAtTime,
PrivateKey: string(keyPEM),
PublicKey: string(pubPEM),
Refresh: refreshedAfter,
// PEM编码公钥
publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
return "", nil, fmt.Errorf("编码公钥失败: %w", err)
}
return keyPair, nil
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
}
// WrapString 将字符串按指定宽度进行换行(函数式版本)
func WrapString(str string, width int) string {
if width <= 0 {
return str
// 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)
}
var b strings.Builder
for i := 0; i < len(str); i += width {
end := i + width
if end > len(str) {
end = len(str)
}
b.WriteString(str[i:end])
if end < len(str) {
b.WriteString("\n")
if publicKey == "" {
// 如果Redis中没有创建新的密钥对
publicKey, _, err = s.GetOrCreateYggdrasilKeyPair()
if err != nil {
return "", fmt.Errorf("创建新密钥对失败: %w", err)
}
}
return b.String()
return publicKey, nil
}
// NewKeyPairService 生成新的密钥对(结构体方法版本,保持向后兼容)
func (s *SignatureService) NewKeyPair() (*model.KeyPair, error) {
return NewKeyPair(s.logger)
// 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, "")
}