first commit

This commit is contained in:
2026-09-21 11:00:37 +08:00
commit f4a319337a
10 changed files with 2415 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
# 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 需要厂商私钥,而公钥可明文放置
(换公钥只会验签失败,无法据以伪造)。