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" "go.uber.org/zap" "strconv" "strings" "time" "gorm.io/gorm" ) // 常量定义 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 // 证书过期时间 ) // 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 { logger *zap.Logger redisClient *redis.Client } func NewSignatureService(logger *zap.Logger, redisClient *redis.Client) *SignatureService { return &SignatureService{ 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) if err != nil { logger.Error("[ERROR] 解码私钥失败: ", zap.Error(err)) return "", fmt.Errorf("解码私钥失败: %w", err) } // 计算SHA1哈希 hashed := sha1.Sum([]byte(data)) // 使用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格式 privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) // 编码为PEM格式 pemBlock := &pem.Block{ Type: pemType, Bytes: privateKeyBytes, } 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, 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), ) // 继续执行,即使保存失败 } } // 计算expiresAt的毫秒时间戳 expiresAtMillis := keyPair.Expiration.UnixMilli() // 准备签名 publicKeySignature := "" publicKeySignatureV2 := "" // 获取服务器私钥用于签名 serverPrivateKey, err := DecodePrivateKeyFromPEM(logger, redisClient) if err != nil { // 日志修改:logger → s.logger,zap结构化字段 logger.Error("[ERROR] 获取服务器私钥失败: %v", zap.Error(err), zap.String("uuid", uuid), ) return nil, fmt.Errorf("获取服务器私钥失败: %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 // 准备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[:]) 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) // 准备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[:]) 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) } publicKeySignatureV2 = base64.StdEncoding.EncodeToString(signatureV2) // 创建玩家证书结构 certificate := &PlayerCertificate{ KeyPair: struct { PrivateKey string `json:"privateKey"` PublicKey string `json:"publicKey"` }{ PrivateKey: keyPair.PrivateKey, PublicKey: keyPair.PublicKey, }, 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 } // GeneratePlayerCertificateService 生成玩家证书(结构体方法版本,保持向后兼容) func (s *SignatureService) GeneratePlayerCertificate(uuid string) (*PlayerCertificate, error) { return GeneratePlayerCertificate(nil, s.logger, s.redisClient, uuid) // TODO: 需要传入db参数 } // 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) } // 获取DER编码的密钥 keyDER, err := x509.MarshalPKCS8PrivateKey(privateKey) if err != nil { logger.Error("[ERROR] 编码私钥为PKCS8格式失败: %v", zap.Error(err), ) return nil, fmt.Errorf("编码私钥为PKCS8格式失败: %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{ Type: "RSA PRIVATE KEY", Bytes: keyDER, }) 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, } return keyPair, nil } // WrapString 将字符串按指定宽度进行换行(函数式版本) func WrapString(str string, width int) string { if width <= 0 { return str } 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") } } return b.String() } // NewKeyPairService 生成新的密钥对(结构体方法版本,保持向后兼容) func (s *SignatureService) NewKeyPair() (*model.KeyPair, error) { return NewKeyPair(s.logger) }