Wire LIC-/LICOFF- activate/verify/heartbeat to licence.rlwj.cn/api, keep legacy HMAC accepted with deprecation banner, add instance_id fingerprint and licensing docs (product lokistack still needs admin create).
75 lines
2.3 KiB
Go
75 lines
2.3 KiB
Go
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
|
|
}
|