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:
@@ -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.logger,zap结构化字段
|
||||
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.logger,zap结构化字段
|
||||
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.logger,zap结构化字段
|
||||
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)) // 预分配缓冲区
|
||||
|
||||
// 添加UUID(16字节)
|
||||
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, "")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user