package services import ( "crypto/ed25519" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "strings" "time" ) type offlineClaims struct { V int `json:"v"` ProductCode string `json:"pid"` DeviceID string `json:"did"` Email string `json:"email,omitempty"` PlanType string `json:"pt"` Iat int64 `json:"iat"` Exp int64 `json:"exp"` MaxDevices int `json:"md"` } func verifyOfflineSerial(serial, deviceID string) (*offlineClaims, error) { trimmed := strings.TrimSpace(serial) body, ok := strings.CutPrefix(trimmed, "LICOFF-1.") if !ok { return nil, fmt.Errorf("不是合法的离线序列号(需 LICOFF-1. 前缀)") } idx := strings.LastIndex(body, ".") if idx <= 0 { return nil, fmt.Errorf("离线序列号格式错误") } payloadB64, sigB64 := body[:idx], body[idx+1:] sigBytes, err := base64.RawURLEncoding.DecodeString(sigB64) if err != nil { return nil, fmt.Errorf("离线序列号签名无法解码") } pub, err := hex.DecodeString(offlineLicensePublicKeyHex) if err != nil || len(pub) != ed25519.PublicKeySize { return nil, fmt.Errorf("离线验签公钥配置错误") } if !ed25519.Verify(ed25519.PublicKey(pub), []byte(payloadB64), sigBytes) { return nil, fmt.Errorf("离线序列号签名校验失败") } payload, err := base64.RawURLEncoding.DecodeString(payloadB64) if err != nil { return nil, fmt.Errorf("离线序列号载荷无法解码") } var claims offlineClaims if err := json.Unmarshal(payload, &claims); err != nil { return nil, fmt.Errorf("离线序列号载荷无法解析") } if claims.V != 0 && claims.V != 1 { return nil, fmt.Errorf("不支持的离线证版本") } if claims.ProductCode != licenceProductCode { return nil, fmt.Errorf("离线证产品不匹配(需要 %s)", licenceProductCode) } if claims.DeviceID != deviceID { return nil, fmt.Errorf("离线证与当前实例不匹配") } if claims.PlanType != "trial" && claims.PlanType != "yearly" { return nil, fmt.Errorf("离线证计划无效(仅体验/包年)") } now := time.Now().Unix() if claims.Exp <= now { return nil, fmt.Errorf("离线许可证已过期") } if claims.Exp-claims.Iat > 365*86400+3600 { return nil, fmt.Errorf("离线许可证有效期非法") } return &claims, nil }