License XML 如何工作
License XML 使用 Ed25519 数字签名保护许可证载荷。许可证是可读的 XML 文件;签名用于验证载荷是否由可信签发方签署,以及签发后是否被修改。签名本身不加密许可证内容,也不负责在线激活或用量计量。
参与方与密钥
| 参与方 | 持有内容 | 职责 |
|---|---|---|
| 签发方 | Ed25519 私钥、许可证记录 | 校验授权信息并签发许可证 |
| 应用 | 一个或多个可信公钥 | 严格解析许可证、验证签名和授权条件 |
| 用户 | 许可证 XML 文件 | 保存并向应用提供许可证 |
私钥只保存在签发系统中,绝不能嵌入客户端或分发给用户。应用使用 keyId 从可信公钥集合中选择验证密钥。轮换密钥时,可在应用中增加新公钥,并按产品版本策略决定是否继续信任旧公钥。
签发流程
签发方 / 许可证系统
│
├─ 1. 创建载荷
│ licenseId、产品、客户、有效期、
│ 功能、数量限制、机器绑定
▼
校验载荷结构与字段
│ 失败 → 拒绝签发
▼
序列化为 XML <payload>
▼
Canonical XML 1.0(不含注释)
▼
使用 Ed25519 私钥签名 UTF-8 字节
▼
Base64 编码签名,附加 algorithm 与 keyId
▼
输出许可证 XML ───────────────→ 分发给用户
(只分发许可证,不分发私钥)签名只覆盖 <payload>。许可证 ID、产品、客户、有效期、功能、限制和机器绑定等字段都在签名范围内;签名元素和根元素不在范围内。规范化后的载荷字节相同,签名才可验证。XML 属性顺序等词法差异可能不影响签名,但载荷文本中的空白属于签名内容。
离线验证流程
用户提供许可证 XML
│
▼
检查 XML 大小、语法与安全限制
(拒绝 DTD、实体声明、命名空间等)
│ 失败 → INVALID_FORMAT
▼
检查许可证结构、字段与签名编码
│ 失败 → INVALID_FORMAT
▼
读取 signature.keyId
│
├─ 未知 keyId / 不支持算法
│ → INVALID_SIGNATURE
▼
规范化原始 <payload> 并验证 Ed25519 签名
│ 失败 → INVALID_SIGNATURE
▼
检查调用方提供的产品 ID / 版本
│ 不匹配 → PRODUCT_MISMATCH
▼
检查 issuedAt 与 expiresAt
├─ 尚未生效 → NOT_YET_VALID
└─ 已过期 → EXPIRED
▼
检查机器绑定
└─ 指纹缺失或不匹配 → MACHINE_MISMATCH
▼
VALID:将已签名的功能和限制交给应用层执行验证顺序很重要:只有签名有效后,应用才能信任载荷中的授权声明。若机器类型为 none,验证器不会进行机器指纹比较;若为 fingerprint,集成应用必须提供当前指纹。指纹采集、稳定性及防伪均由应用负责。
verifyLicense 执行本地格式、签名、产品、时间和机器指纹检查。产品 ID、版本及机器指纹由调用方传入;未传产品参数时不会检查对应匹配项。返回 VALID 只代表许可证通过这些验证,不代表应用已经强制执行功能开关或用量上限。
可选的在线激活与撤销
完全离线时,客户端没有服务器状态,无法判断许可证是否已被撤销,也无法阻止同一文件被复制到多台未绑定设备。若业务要求限制重复激活、设备席位或远程撤销,需要由集成应用另外实现服务端流程。下面是一个非本协议定义的示意;本项目不提供激活/撤销 API 或服务端状态数据库。
首次启动 / 定期联网
│
▼
客户端先执行本地签名及授权检查
│ 无效 → 拒绝使用
▼
向应用服务端提交 licenseId 与设备标识
(具体认证与设备标识由应用设计)
│
▼
服务端查许可证状态与已激活设备
│
├─ 已撤销 / 已过期 → 拒绝
├─ 超出设备席位 → 拒绝或要求解绑
└─ 可激活 → 原子地登记或更新设备
│
▼
返回应用定义的结果服务端应将许可证状态与设备席位作为权威状态,并妥善处理并发激活、重复请求、换机和解绑。licenseId 是许可证标识符,不是秘密或防复制凭证。只要客户端可以被修改,在线检查也无法从根本上保证应用一定执行检查;需结合应用自身的防篡改和业务策略评估风险。
安全边界
- 有效签名证明载荷与签发时一致,并证明签名由对应私钥产生;它不证明许可证尚未撤销。
- 离线验证无法获知服务端状态,也无法保证系统时钟可信。
- 许可证明文可读;签名不提供保密性。
- 机器绑定不能代替签名验证;设备标识的采集与可靠性属于应用层。
- 功能权限和用量限制需要由应用实际执行。客户端可被 Patch 或调试,许可证格式本身无法阻止此事。