licensx

package
v0.49.0 Latest Latest
Warning

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

Go to latest
Published: Nov 14, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package licensx 提供 Ed25519 license 签发/验签机制内核:JSON 载荷 + Ed25519 签名(覆盖除 signature 外全部字段)+ 到期/吊销判定 + 类型化哨兵 + 公钥注入。 纯 stdlib(crypto/ed25519 + encoding/json)。载荷结构是宿主产品决策—— customer/features/实例数等业务字段不入本包,内核收任意 JSON 载荷(固定序 序列化钩子:宿主用 json.Marshal 宿主 struct 产出,禁 map)。

包名循 pathx/stringx/secretx 的 x 后缀惯例(#486);另一层原因:子包目录 名 license 会与 macOS/Windows 大小写不敏感文件系统上的仓根 LICENSE 文件 冲突,无法共存检出。

密钥保管边界

签发私钥只在运营方(签发侧)手里,永不入仓、不进配置中心、不下发到验签侧。 本包不持有任何密钥常量:验签公钥由宿主注入(编译进二进制是防篡改惯例—— 改环境变量、换文件、改配置都拿不到授权能力;「公钥怎么交付」是宿主策略, 包只提供注入点)。轮换:Keygen 生成新密钥对,私钥落仓外路径,公钥随宿主 重新编译/分发——旧 license 将全部失效(签名对不上),轮换窗口要与客户约定。

fail-loud

验签失败一律返回类型化哨兵错误(ErrSignatureInvalid / ErrExpired / ErrRevoked),消费方一律 errors.Is 判定,不要匹配错误文本。本包不做任何 静默降级:「降级不静默」的机制语义由 Load 兑现——非有效形态必带 Reason 可观测;「无效 license 拿不到授权能力」由宿主把 Status.Valid 映射到自己的 档位来兑现(community/enterprise 分支与能力词表是产品决策,留在宿主)。

典型用法

// 签发侧(运营方工具,私钥不出仓):
pub, priv, err := licensx.Keygen()
payload, err := json.Marshal(hostClaims{Customer: "acme", Features: []string{"sso"}})
lic := &licensx.License{
    Serial:   "2026-001",
    IssuedAt: time.Now().UTC(),
    Payload:  payload, // 固定序序列化:json.Marshal 宿主 struct
}
err = licensx.Sign(lic, priv)

// 验签侧(产品二进制,公钥编译进宿主):
st := licensx.Load("/etc/app/license.json", compiledPub, time.Now())
if !st.Valid {
    // community 档位运行,st.Reason 透出原因(不静默)
}
var claims hostClaims
err = json.Unmarshal(st.Payload, &claims) // 载荷按宿主结构解码

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrSignatureInvalid 签名校验失败(文件被篡改 / 公钥不匹配 / 签名或公钥
	// 格式非法 / 载荷不是合法 JSON)。fail-loud:不提供任何「验签失败当有效」
	// 的降级路径。
	ErrSignatureInvalid = errors.New("license 签名校验失败")
	// ErrExpired license 已过期(Validate 判定;ExpiresAt nil/零值 = 永久授权)。
	ErrExpired = errors.New("license 已过期")
	// ErrRevoked license 序列号在吊销清单内(清单由宿主从签发侧分发/拉取)。
	ErrRevoked = errors.New("license 已吊销")
)

哨兵错误(errors.Is 判定,消费方不要匹配错误文本)。

Functions

func Keygen

func Keygen() (pub ed25519.PublicKey, priv ed25519.PrivateKey, err error)

Keygen 生成新的签名密钥对(供首次签发与轮换)。**私钥必须写到仓外路径** ——只存运营方签发侧,永不入仓、不进验签侧;公钥怎么交付(编译期常量 vs 配置注入)是宿主策略。

func Sign

func Sign(l *License, priv ed25519.PrivateKey) error

Sign 对给定 license 计算签名并写回 Signature 字段。**私钥在运营方手里**, 本函数只应出现在签发侧工具里,不在产品运行期路径上。

Types

type License

type License struct {
	// Serial 序列号(签发侧编号;吊销清单的键。可选,但要做吊销就必须有)。
	Serial string `json:"serial,omitempty"`
	// IssuedAt 签发时间。
	IssuedAt time.Time `json:"issued_at,omitempty"`
	// ExpiresAt 到期时间;nil/零值 = 永久授权。
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
	// Payload 宿主载荷(任意 JSON)。必须固定序序列化:用 json.Marshal 宿主
	// struct 产出(字段序 = 声明序),禁 map——键序随机则同一 license 两次
	// 序列化字节不同,签名随之不确定。签名覆盖本字段的字节(经 encoding/json
	// 紧凑化,键序保持)。
	Payload json.RawMessage `json:"payload"`
	// Signature base64 的 Ed25519 签名(覆盖除本字段外的全部字段)。
	Signature string `json:"signature,omitempty"`
}

License license 文件内容(JSON 信封):载荷是宿主定义的任意 JSON,签名覆盖 除 Signature 外的全部字段——改任何一项都会验签失败。

func (*License) Decode

func (l *License) Decode(v any) error

Decode 把载荷解码到宿主 claims 结构(Validate 通过后调用——载荷结构是 宿主产品决策,本包不解码也不解释业务字段)。

func (*License) Validate

func (l *License) Validate(pub ed25519.PublicKey, now time.Time, opts ...ValidateOption) error

Validate 全量校验:验签 + 过期 + 吊销(授权生命周期面)。now 由调用方注入 (测试确定性;生产传 time.Now())。失败返回类型化哨兵(ErrSignatureInvalid / ErrExpired / ErrRevoked)包装的错误——签名不过先报签名,过期/吊销只在 签名可信后判定。

func (*License) Verify

func (l *License) Verify(pub ed25519.PublicKey) error

Verify 用给定公钥验签(纯签名面:不含过期/吊销——那是授权生命周期, 见 Validate)。失败返回 ErrSignatureInvalid 包装的错误(fail-loud)。

type Status

type Status struct {
	// Loaded 文件存在且 JSON 解析成功(license 骨架可读)。
	Loaded bool `json:"loaded"`
	// Valid 签名 + 未过期 + 未吊销 全过。
	Valid bool `json:"valid"`
	// Serial 序列号(解析出来即透出,无论有效与否——吊销对账用)。
	Serial string `json:"serial,omitempty"`
	// ExpiresAt 到期时间(nil = 永久)。
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
	// Payload 载荷原始字节(Valid 后宿主自行解码到自己的 claims 结构)。
	Payload json.RawMessage `json:"payload,omitempty"`
	// Reason 未装/无效/已过期/已吊销的原因(完全有效时为空)——不静默。
	Reason string `json:"reason,omitempty"`
}

Status license 加载结果(机制结论,不含档位决策——Valid → 宿主档位的 映射是产品侧的事)。自解释:非有效形态必带 Reason(降级不静默)。

func Load

func Load(path string, pub ed25519.PublicKey, now time.Time, opts ...ValidateOption) Status

Load 加载并校验 license 文件(机制内核;公钥由宿主注入——编译期常量或 配置都是宿主策略)。语义:

  • path 为空或文件不存在 → 非 Valid + 明确 Reason(无授权运行,不阻塞 启动——要让运维**看见**授权状态,而不是让人只知道「起不来」);
  • 文件存在但解析失败/验签失败/已过期/已吊销 → Loaded 可读 + 非 Valid + 明确 Reason(防篡改靠「无效 license 拿不到授权能力」,由宿主把 Valid 映射到档位兑现)。

type ValidateOption

type ValidateOption func(*validateConfig)

ValidateOption Validate 的选项(命名循仓规:XxxOption,禁裸 Option)。

func WithRevokedSerials

func WithRevokedSerials(serials []string) ValidateOption

WithRevokedSerials 注入吊销清单(序列号集合;由宿主从签发侧分发/拉取并 传入)。nil/空 = 显式无吊销(回退默认,不算非法)。

Jump to

Keyboard shortcuts

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