Files
chongming-utils-license/README.md
T
2026-09-21 11:00:37 +08:00

92 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# chongming-license
机器码授权共享包:硬件指纹采集、license 载荷格式、Ed25519 验签与插件白名单判定。
网关与厂商侧签发工具共用同一份实现 —— 尤其是**规范化 JSON 只有一处实现**,
避免"自己签的自己验不过"。
## 设计要点
- **纯离线**:设备用内置公钥验签,运行期零联网。与现有 air-gapped 部署形态
(Zenoh localhost、Caddy `tls internal`、无 phone-home)一致。
- **非对称**:Ed25519。注意根仓 `resources/*.key` 那套 `.mbank` 验签是
**HMAC 对称**且密钥与银行同目录发布,只能算防篡改/混淆,**不能**作为厂商
信任根 —— 授权必须用非对称,公钥可安全内置。
- **N-of-M 容错**:license 里存各来源的原始指纹,校验按"命中数达阈值"判定,
而非把所有来源哈希成一个值 —— 否则换一块网卡就直接失效。
展示给客户的仍是单个 `machine_code`。
- **weak 来源**:`/etc/machine-id` 在同镜像批量刷机场景下**所有机器相同**,
故标记 `weak` 且**不参与判定阈值**(只作诊断参考),避免它给攻击者白送一票。
## 采集来源
| 来源 | 位置 | 说明 |
| --- | --- | --- |
| `board_serial` | `/proc/device-tree/serial-number`、DMI `board_serial` | 逐机唯一,最稳定 |
| `emmc_cid` | `/sys/block/mmcblk*/device/cid` | 芯片出厂唯一,不随系统重装变化 |
| `cpu_serial` | `/proc/cpuinfo` 的 `Serial` | ARM 常见;部分型号全 0(会被剔除) |
| `mac` | `/sys/class/net/*/address` | 取字典序最小者:插入/移除其它网卡不改变它 |
| `product_uuid` | `/sys/class/dmi/id/product_uuid` | x86 有效,ARM 通常缺失 |
| `machine_id` | `/etc/machine-id` | ⚠ weak,见上 |
## 文件格式
```json
{
"payload": {
"v": 1,
"lic_id": "L2026-0001",
"customer": "某某科技",
"machine_code": "A3F9-1B77-9E04-2C8D-6F31-0A55",
"sources": {"board_serial": "...", "emmc_cid": "...", "mac": "..."},
"plugins": ["aibox_sophon_adapter", "ip_sound_column"],
"not_before": "2026-09-01",
"expires_at": "2027-09-01",
"issued_at": "2026-09-01T10:00:00Z",
"note": ""
},
"alg": "ed25519",
"kid": "vendor-2026",
"sig": "<base64,覆盖 payload 的规范化 JSON>"
}
```
签名覆盖 payload **原始字节的规范化形式**(`canonical_json`:键排序、无空白),
因此载荷内不允许浮点数(float 文本表示存在多种合法写法)。校验顺序是
先验签、后解析模型 —— 反过来会让默认值填充/字段顺序改变字节导致验签失败。
## 用法
网关侧:
```python
from chongming_license import LicenseManager
mgr = LicenseManager(
license_path="license.lic",
public_key=config["license"]["public_key"],
clock_store=cache, # chongming_cache.Cache,存时间高水位
enforce=is_packaged(), # 打包产物强制校验;开发/测试放行
)
decision = mgr.check_plugin("aibox_sophon_adapter")
if not decision.allowed:
... # 不加载该插件
```
厂商侧(`chongming-cli`,私钥只在这边):
```bash
uv run chongming-license keygen -o license_private.pem # 一次性生成密钥对
uv run chongming-license issue --machine-code <码> --sources-json <采集导出> \
--plugins aibox_sophon_adapter,ip_sound_column \
--customer 某某科技 --expires 2027-09-01 -k license_private.pem -o license.lic
```
## 诚实的边界
离线 + root 的天花板是**阻止随手拷贝**,不是阻止逆向:有 root 者可改二进制
跳过校验,或 `LD_PRELOAD` 伪造指纹读取。`clock` 模块的时间回拨检测同理 ——
删掉缓存文件即失效,它只是抬高随手改时间的门槛。
真正难以绕过的是**签名**:伪造 license 需要厂商私钥,而公钥可明文放置
(换公钥只会验签失败,无法据以伪造)。