feat(license): dual-path opsx-licence client alongside HMAC
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).
This commit is contained in:
1 parent
5f42d9f696
commit
af0298e994
13 files changed
+888
-37
No files matched your search
@@ -0,0 +1,74 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user