Metadata-Version: 2.4
Name: lsyods_license_sdk
Version: 0.3.0
Summary: Shared machine identity and offline license SDK for LSY ODS products
Author: 成都领数云科技有限公司
License-Expression: MIT
Project-URL: Homepage, https://github.com/9kl/lsyods_license_sdk
Project-URL: Repository, https://github.com/9kl/lsyods_license_sdk.git
Project-URL: Issues, https://github.com/9kl/lsyods_license_sdk/issues
Keywords: license,offline-license,ed25519,machine-fingerprint,lsyods
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security :: Cryptography
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography<49.0.0,>=41.0.0
Dynamic: license-file

# LSY ODS 授权 SDK

[![Python](https://img.shields.io/pypi/pyversions/lsyods-license-sdk.svg)](https://pypi.org/project/lsyods-license-sdk/)
[![PyPI](https://img.shields.io/pypi/v/lsyods-license-sdk.svg)](https://pypi.org/project/lsyods-license-sdk/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/9kl/lsyods_license_sdk/blob/main/LICENSE)

`lsyods-license-sdk` 是 LSY ODS 提供的 Python 公用 SDK，用于机器身份识别、离线软件授权以及部署制品签名。SDK 提供稳定的机器指纹、安装实例 ID、Ed25519 许可证签发与校验、部署 ZIP 签名与验签，以及许可证状态判断等能力。

- 作者：成都领数云科技有限公司
- 项目地址：<https://github.com/9kl/lsyods_license_sdk>
- 开源许可：MIT

## 功能特性

- 在 Windows 和 Linux 上计算带版本标识、不可逆的机器指纹。
- 为不同产品生成独立的安装实例 ID。
- 签发采用 Ed25519 数字签名的离线许可证。
- 使用显式配置的可信公钥集合校验许可证和部署制品签名。
- 将许可证与产品、软件版本、产品版本、安装实例 ID 和机器指纹绑定。
- 判断订阅授权、永久授权、授权已过期和授权尚未生效等状态。
- 通过 `supersedes_license_id` 保留许可证续期关系。
- 将服务端确认的部署方式与 ZIP 摘要一起签名，供客户端独立验签。

产品版本表示 `basic`、`advanced` 等商业版本。具体功能或模块是否可用，由被授权产品根据商业版本自行判断。永久授权没有到期时间，但仍然只适用于签名声明中指定的软件版本。

## 安装

从 PyPI 安装：

```bash
pip install lsyods-license-sdk
```

使用 uv 安装：

```bash
uv add lsyods-license-sdk
```

SDK 要求 Python 3.10 或更高版本。

## 生成许可证签名密钥

SDK 提供 `lsyods-license-keygen` 命令，用于生成软件许可证专用的 Ed25519 密钥对。默认情况下，命令会要求输入密码，并使用该密码加密生成的私钥。

PowerShell 示例：

```powershell
$env:LSYODS_LICENSE_KEY_PASSWORD = Read-Host "请输入密钥密码" -MaskInput

lsyods-license-keygen `
  --private-key D:\secure\license-private.pem `
  --public-key D:\secure\license-public.pem
```

命令执行成功后会输出公开的 `key_id` 和 `public_key_base64`，供被授权产品配置可信公钥。私钥只能由受控的许可证签发服务访问，禁止将私钥或私钥密码分发到客户端或被授权产品中。

在具备可靠系统访问控制的自动化环境中，可以使用 `--no-password` 生成不加密的私钥。此时必须通过操作系统文件权限严格保护私钥文件。

## 机器指纹

```python
from lsyods_license_sdk import calculate_machine_fingerprint

machine_fingerprint = calculate_machine_fingerprint()
print(machine_fingerprint)  # sha256:<64位小写十六进制字符>
```

SDK 使用带版本标识的域分隔符，对操作系统中相对稳定的机器标识进行哈希计算。返回结果不会包含原始的 Windows `MachineGuid` 或 Linux `machine-id`。

## 部署制品签名

许可证签名和部署制品签名必须使用不同的密钥对。请使用 `lsyods-artifact-keygen` 为部署服务端生成专用的制品签名密钥，不要复用许可证签名密钥。

```bash
lsyods-artifact-keygen --private-key artifact-private.pem --public-key artifact-public.pem
```

该命令默认从 `LSYODS_ARTIFACT_KEY_PASSWORD` 环境变量读取私钥密码，并输出客户端配置所需的 `key_id` 和 `public_key_base64`。

服务端只会对已经通过安全审计和超级管理员审批的 ZIP 文件进行签名。部署方式由服务端审批记录提供，ZIP 中不需要额外的部署清单文件。

部署服务端和客户端通过 SDK 使用同一套制品签名协议：

```python
from pathlib import Path

from lsyods_license_sdk import (
    create_artifact_signature,
    load_artifact_private_key,
    verify_artifact_signature,
)

package = Path("deployment.zip")
private_key = load_artifact_private_key(Path("artifact-private.pem"))
signature = create_artifact_signature(package, private_key, "powershell_deploy")

verified = verify_artifact_signature(
    package,
    signature,
    "powershell_deploy",
    {signature["key_id"]: "<public_key_base64>"},
    signature["sha256"],
)
```

许可证签名协议和部署制品签名协议使用不同的域分隔符。生产环境还必须为两类签名分别使用独立私钥，以便单独控制权限、轮换和吊销密钥。

## 签发许可证

许可证只能在受控的服务端环境中签发：

```python
from datetime import datetime, timedelta, timezone
from pathlib import Path

from lsyods_license_sdk import (
    generate_installation_id,
    issue_license,
    load_private_key,
)

now = datetime.now(timezone.utc).replace(microsecond=0)
private_key = load_private_key(
    Path("D:/secure/license-private.pem"),
    password="private-key-password",
)

claims = {
    "license_id": "L260819000001",
    "issuer": "LSY ODS",
    "customer": "示例客户",
    "product": {
        "code": "example-product",
        "version": "V1.0",
        "edition": "advanced",
    },
    "installation": {
        "installation_id": generate_installation_id(),
        "machine_fingerprint": "sha256:" + "a" * 64,
    },
    "license_type": "subscription",
    "issued_at": now.isoformat(),
    "not_before": now.isoformat(),
    "expires_at": (now + timedelta(days=90)).isoformat(),
}

license_document = issue_license(claims, private_key)
```

签发永久许可证时，将 `license_type` 设置为 `"perpetual"`，并将 `expires_at` 设置为 `None`。

## 校验许可证

被授权产品只保存可信公钥，并根据当前产品、安装实例和运行机器校验本地许可证：

```python
from lsyods_license_sdk import calculate_machine_fingerprint, verify_license

trusted_keys = {
    "<key_id>": "<public_key_base64>",
}

result = verify_license(
    license_document,
    trusted_keys,
    expected_product="example-product",
    expected_installation_id=installation_id,
    expected_machine_fingerprint=calculate_machine_fingerprint(),
)

if not result["valid"]:
    raise RuntimeError(f"许可证状态异常：{result['status']}")

licensed_version = result["claims"]["product"]["version"]
licensed_edition = result["claims"]["product"]["edition"]
```

被授权产品负责根据自身的版本兼容规则和功能开放规则，对 `licensed_version` 和 `licensed_edition` 进行判断。

## 许可证续期

续期会生成一份新的签名许可证，同时沿用原有的安装实例 ID 和机器指纹。原许可证保持不变，仍然可以独立验签。新许可证声明中可以包含：

```json
{
  "supersedes_license_id": "L260819000001"
}
```

该字段表示新许可证替代的历史许可证，便于保留完整的授权续期关系。

## 安全模型

- 系统安全依赖于妥善保护 Ed25519 私钥，而不是隐藏 SDK 的实现代码。
- 被授权产品和部署客户端只能信任明确配置的公钥。
- 许可证和制品签名文档在完成签名后必须视为不可变数据。
- 机器指纹是设备标识，不是身份认证密码或安全凭据。
- 离线授权无法阻止客户修改其完全控制的应用程序，被授权产品应在合适的信任边界执行授权检查。
- 密钥轮换时，应先将新公钥加入可信公钥集合，再使用新私钥签发许可证或签名制品。
- 许可证私钥与部署制品私钥必须分别存储、分别授权和分别轮换。

## 开发与构建

项目使用 uv 管理依赖，并通过 `pyproject.toml` 配置阿里云 PyPI 镜像：

```bash
uv sync --locked
uv run pytest
uv run pyflakes src tests
uv build --no-sources
```

## 开源许可

Copyright (c) 2026 成都领数云科技有限公司。

本项目基于 [MIT 许可证](https://github.com/9kl/lsyods_license_sdk/blob/main/LICENSE) 开源。
