licensenext

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 10 Imported by: 0

README

license-next

轻量级客户端 License 验证框架,将 issuer(服务端签发)与 checker(客户端校验)分离。

业务选择

1、纯离线签名

无法吊销license,但是不依赖服务端。

                       │  客户提供机器码
                       ▼
┌─────────────────────────────────────────────────────────┐
│           开发者(浏览器 / CLI)                          │
│  official/issuer.html → WASM encodeCData → 下载lic文件   │
└──────────────────────┬──────────────────────────────────┘
                       │ 手动复制lic给客户
                       ▼  
                       │  客户配置到客户端
                       ▼
┌─────────────────────────────────────────────────────────┐
│           客户端(Go 应用)                              │
│  licensenext.New(cfg) → SimpleCheck()                   │
│    1. 本地缓存校验(签名有效性 / 机器码 / 过期 / 产品)     │
│    2. 不校验签名新鲜度                                   │ 
└─────────────────────────────────────────────────────────┘
2、在线刷新签名

依赖服务端,提供license自动刷新签名端点,可以通过配置revoked主动吊销license

                       │  客户提供机器码
                       ▼
┌─────────────────────────────────────────────────────────┐
│           开发者(浏览器 / CLI)                          │
│  official/issuer.html → WASM encodeCData → CData 字符串  │
└──────────────────────┬──────────────────────────────────┘
                       │ 手动复制
                       ▼
┌─────────────────────────────────────────────────────────┐
│           Cloudflare Worker(开发者自行部署)             │
│                                                         │
│  KV: key=machine_id, value=CData                        │
│                                                         │
│  GET /v1/query?machine_id=xxx                           │
│    1. 从 KV 读取 CData                                   │
│    2. Web Crypto ECDSA P-256 签名                       │
│    3. 返回 LicenseSign JSON                             │
└──────────────────────┬──────────────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────────────┐
│           客户端(Go 应用)                              │
│  licensenext.New(cfg) → Check()                         │
│    1. 本地缓存校验(签名 / 机器码 / 过期 / 产品)          │
│    2. 新鲜度超窗 → GET /v1/query 联网自动刷新签名         │
│    3. 异步预刷新(3 天节流)                              │
└─────────────────────────────────────────────────────────┘

核心概念

License — 授权合同
type License struct {
    Customer         string    // 客户标识(机器码匹配依据)
    CustomerNickname string    // 客户昵称(仅前台反显,可选)
    CustomerEmail    string    // 客户邮箱(仅前台反显,可选)
    ExpireAt         time.Time // 过期时间
    Product          string    // 产品名
    Features         []string  // 功能列表(可选)
    MachineID        string    // 绑定的机器码
    Extensions       string    // 自定义扩展数据(建议 JSON 字符串,框架透明透传)
}

Extensions 说明:如果需要携带销售信息、订单号等自定义元数据,建议使用 JSON 字符串存入 Extensions 字段。框架只做透传不做解析,开发者自行 json.Unmarshal 使用。例如 {"salesperson":"张三","order_id":"ORD-001"}

CData — License 编码字符串

CData 是 License 原始合同明文经过 CKD 协议编码后的字符串,使用 MasterKey 派生,可逆、无状态、不暴露明文。 客户端配置相同的 MasterKey,通过 CKD.Parse 即可解码还原 License。

LicenseSign — 签名结构
{
  "c_data":    "CKD 编码的 License",
  "timestamp": 1750000000,
  "signature": "ECDSA P-256 ASN.1 DER base64 签名",
  "revoked":   false
}

签名覆盖 CData + "|" + Timestamp,防止 CData 单独被重放。

校验结果三态
结果 触发条件 处理方式
ResultOK 本地全部校验通过且新鲜 直接放行,按需异步预刷新
ResultNeedRemote 仅新鲜度过期,或本地无文件 联网校验,失败则拒绝启动
ResultInvalid 签名/机器码/过期/吊销明确不匹配 直接拒绝启动,保留旧文件
MachineID — 获取本地机器码

用于首次部署时获取机器码,发给开发者签发 License。

mid, err := licensenext.MachineID()
if err != nil {
    log.Fatal(err)
}
fmt.Println("请将机器码发给管理员:", mid)
SimpleCheck — 离线本地校验

SimpleCheckCheck 的区别:

Check SimpleCheck
签名有效性 / 机器码一致性 / 产品一致性 / 是否过期
本地缓存读取
本地验签
本地手动配置.lic ❌不需要,自动远端fetch ✅需要
新鲜度窗口 ✅ 超窗触发远端 ❌ 不参与
远端请求 ✅ 自动 ❌ 不触发
调用方式 Check() Verify(licenseString) + SimpleCheck()

适用于无需联网刷新的离线场景:

//首次客户上传.lic文件,读取json字符串,调用验证和自动归档
license, err := checker.Verify(licenseString)
if err != nil {
    log.Fatalf("license校验失败: %v", err)
}
log.Printf("欢迎 %s", lic.CustomerNickname)

//之后每次启动自动寻址验证
lic, err := checker.SimpleCheck()
if err != nil {
    log.Fatalf("license校验失败: %v", err)
}
log.Printf("欢迎 %s", lic.CustomerNickname)

安装

go get github.com/doc-war/license-next

快速开始

1. 生成密钥对
openssl ecparam -genkey -name prime256v1 -out key-private.pem
openssl ec -in key-private.pem -pubout -out key-public.pem
2. 生成 CData(License 编码字符串)

打开 official/issuer.html(浏览器),输入 License 参数和 MasterKey,点击生成。 或者使用签发子包(其他语言课使用wasm):

package main

import "github.com/doc-war/license-next/issuer"
//初始化签发器
iss, _ := issuer.New(issuer.Config{
    PrivateKey: privPEM,
    MasterKey:  masterKey,
})
//签发带签名的LicenseSign对象
ls, _ := iss.Sign(&issuer.License{
    Customer:         "acme-001",
    CustomerNickname: "Acme Corp",
    CustomerEmail:    "admin@acme.com",
    ExpireAt:         time.Date(2030, 12, 31, 23, 59, 59, 0, time.UTC),
    Product:          "myapp",
    MachineID:        "target-machine-id",
    Features:         []string{"premium", "audit-log"},
})
// ls.CData 就是 License 编码字符串
// ls进行json序列化,再放入一个.lic文件里,传递给客户,让客户端支持客户上传这个文件,读取json字符串调用verify接口,框架会自动验证并将其规范缓存到本地目录。
3. 部署 Cloudflare Worker

worker/ 目录部署到 Cloudflare Workers:

cd worker
wrangler deploy
  • 将 CData 存入 KV,key 为对应的 machine_id
  • 私钥通过 wrangler secret put PRIVATE_KEY 设置
4. 客户端集成
import licensenext "github.com/doc-war/license-next"
//初始化检查器
checker, err := licensenext.New(licensenext.Config{
    Product:   "myapp",
    PublicKey: pubPEM,
    MasterKey: masterKey,
    RemoteURL: "https://your-worker.workers.dev/v1/query",
})
if err != nil {
    log.Fatal(err)
}
//自动检查
lic, err := checker.Check()
if err != nil {
    log.Fatalf("license校验失败: %v", err)
}

log.Printf("欢迎 %s,有效期至 %s", lic.CustomerNickname, lic.ExpireAt.Format("2006-01-02"))
log.Printf("可用功能: %v", lic.Features)

Config

字段 默认值 说明
Product string 必填 产品名,用于隔离本地存储目录
MasterKey string 必填 CKD 主密钥
PublicKey string 必填 ECC 公钥 PEM
RemoteURL string "" license 查询 API 地址
StorageDir string ~/.license-next/{product}/ 本地存储根目录
FreshWindow time.Duration 7 天 新鲜度窗口
RefreshInterval time.Duration 3 天 异步预刷新节流间隔
HTTPTimeout time.Duration 5s 远端请求超时(失败后自动重试一次)

本地存储规范

~/.license-next/{product}/
  license.lic   # LicenseSign 的 JSON 编码
  .state        # {"last_refresh_at": unix}

吊销说明

服务端在 LicenseSign 中设 revoked: true,客户端下次联网校验时读到即可拒绝。旧版本客户端忽略此字段,协议向前兼容。

子项目

目录 说明
official/ 官网在线工具,issuer.html 生成 CData,index.html 文档页
wasm/ 浏览器 WASM 入口(GOOS=js 编译),暴露 encodeCData
worker/ Cloudflare Worker 模板,Web Crypto 签名 + KV 查询
issuer/ Go 服务端签发包(ECDSA 签名 + CKD 派生)
examples/ 集成示例(checker / issuer / 全流程)

验证方式

签名流程:

CData + "|" + Timestamp
  → SHA256
  → ECDSA P-256 签名(ASN.1 DER)
  → base64 StdEncoding
  → Signature 字段

客户端用公钥验证签名,确保 CData 和 Timestamp 未被篡改。Worker 侧使用 Web Crypto API 签名,与 Go 的 crypto/ecdsa 完全兼容。

服务端职责

  • ECC 私钥安全保管(Worker 环境变量)
  • 提供 GET /v1/query?machine_id=xxx 接口,返回 LicenseSign JSON
  • 建议对该 API 做 IP 级限流

许可

MIT

Documentation

Overview

Package licensenext 的错误定义。 所有 sentinel 错误均从 internal/core 透传,外部调用方可直接判断。

Package licensenext 是 license-next 的客户端入口包。 使用方只需 import "github.com/doc-war/license-next", 通过 New(cfg) 创建 Checker,然后调用 Check() 完成校验。

Index

Constants

View Source
const (
	// ResultOK 本地缓存验证通过,且签名新鲜度在 FreshWindow 内
	ResultOK = types.ResultOK
	// ResultNeedRemote 本地缓存验证通过但签名已过期,需要联网刷新
	ResultNeedRemote = types.ResultNeedRemote
	// ResultInvalid 校验失败(签名无效 / 过期 / 机器码不匹配等)
	ResultInvalid = types.ResultInvalid
)

Variables

View Source
var (
	ErrBadSignature    = core.ErrBadSignature    // 签名验证失败
	ErrMachineID       = core.ErrMachineID       // 机器码不匹配
	ErrExpired         = core.ErrExpired         // License 已过期
	ErrProductMismatch = core.ErrProductMismatch // 产品名不匹配
)

Functions

func MachineID added in v1.1.0

func MachineID() (string, error)

MachineID 获取当前机器的机器码(基于硬件信息加盐哈希)。 开发者可将此值发给签发方,用于生成绑定该机器的 License。

Types

type CheckResult

type CheckResult = types.CheckResult

CheckResult 校验结果三态(类型别名)

type Checker

type Checker struct {
	// contains filtered or unexported fields
}

Checker 客户端校验器,持有公钥、机器码等运行时上下文

func New

func New(cfg Config) (*Checker, error)

New 创建 Checker 实例,自动获取机器码、解析公钥

func (*Checker) Check

func (c *Checker) Check() (*License, error)

Check 执行 License 校验。优先本地缓存,必要时联网刷新。 返回 License 指针和可公开的错误。

func (*Checker) SimpleCheck

func (c *Checker) SimpleCheck() (*License, error)

SimpleCheck 执行完全的本地校验,不解码缓存、不联网、不检查新鲜度。 适用于不依赖远端刷新场景的离线校验。

func (*Checker) Verify

func (c *Checker) Verify(licJSON string) (*License, error)

Verify 验证并持久化 LicenseSign JSON(.lic 文件内容)。 成功后将 License 缓存到本地,后续可直接调用 Check() 或 SimpleCheck()。

type Config

type Config struct {
	Product   string // 必填,产品名
	PublicKey string // 必填,PEM 格式的 ECC 公钥
	MasterKey string // 必填,CKD MasterKey
	RemoteURL string // 选填,远端 License 查询接口

	StorageDir      string        // 选填,缓存目录(默认 ~/.license-next/<product>)
	FreshWindow     time.Duration // 选填,签名新鲜度(默认 7 天)
	RefreshInterval time.Duration // 选填,异步刷新间隔(默认 3 天)
	HTTPTimeout     time.Duration // 选填,HTTP 超时(默认 5 秒)
}

Config 客户端配置,传给 New 创建 Checker

type License

type License = types.License

License 授权合同内容(类型别名,对内暴露 types.License)

type LicenseSign

type LicenseSign = types.LicenseSign

LicenseSign 服务端返回的完整签名结构(类型别名)

Directories

Path Synopsis
examples
checker command
Package main 客户端集成示例。
Package main 客户端集成示例。
full command
Package main 端到端全流程演示: 加载密钥 → 签发 License → 验签 → 解码 → 篡改检测 → CKD 确定性测试
Package main 端到端全流程演示: 加载密钥 → 签发 License → 验签 → 解码 → 篡改检测 → CKD 确定性测试
issuer command
Package main 服务端签发示例。
Package main 服务端签发示例。
internal
core
Package core 封装了 license-next 所有客户端核心逻辑, 包括:错误定义、机器码获取、Base64 编解码。
Package core 封装了 license-next 所有客户端核心逻辑, 包括:错误定义、机器码获取、Base64 编解码。
types
Package types 定义 license-next 的核心数据结构和常量。
Package types 定义 license-next 的核心数据结构和常量。
Package issuer 是服务端签发 License 的工具包。
Package issuer 是服务端签发 License 的工具包。
Package main 浏览器 WASM 入口,编译目标 GOOS=js GOARCH=wasm。
Package main 浏览器 WASM 入口,编译目标 GOOS=js GOARCH=wasm。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL