From 758e8130f501adae56a76a7f4894f9babcb234d6 Mon Sep 17 00:00:00 2001 From: guantw Date: Fri, 28 Aug 2026 15:02:09 +0800 Subject: [PATCH 1/2] feat: add portable signing runtime and desktop workflow --- .gitignore | 9 + CHANGELOG.md | 57 +- CONTRIBUTING.md | 9 + PORTABLE.md | 93 ++ PRIVACY.md | 48 + README.md | 183 +- SECURITY.md | 28 +- THIRD_PARTY_NOTICES.md | 78 + bundle/hapsign.manifest | 23 + bundle/hapsign.spec | 71 + bundle/hooks/hook-playwright.async_api.py | 9 + bundle/hooks/hook-playwright.sync_api.py | 9 + docs/OPEN_SOURCE_RELEASE.md | 57 + docs/PACKAGING.md | 282 ++++ hapsign/api/cert_api.py | 16 +- hapsign/api/client.py | 101 +- hapsign/api/device_api.py | 44 +- hapsign/cancellation.py | 15 + hapsign/cli.py | 6 +- hapsign/config.py | 24 +- hapsign/diagnostics.py | 125 ++ hapsign/gui.py | 1474 +++++++++++++++++ hapsign/login/browser_login.py | 453 ++++- hapsign/pipeline.py | 417 ++++- hapsign/runtime.py | 184 ++ hapsign/settings.py | 115 ++ hapsign/signing/__init__.py | 3 +- hapsign/signing/hap_inspect.py | 188 +++ hapsign/signing/hap_signer.py | 15 +- hapsign/signing/installer.py | 305 +++- hapsign/signing/keytool_util.py | 26 +- hapsign/subprocess_utils.py | 259 +++ hapsign/token/secure_token_cache.py | 157 ++ hapsign/token/token_exchange.py | 61 +- pyproject.toml | 18 +- scripts/build_portable.py | 631 +++++++ scripts/prepare_toolchain.py | 517 ++++++ tests/test_api.py | 66 +- tests/test_browser_login.py | 343 ++++ tests/test_build_portable.py | 367 ++++ tests/test_client.py | 59 +- tests/test_diagnostics.py | 66 + tests/test_gui_style.py | 50 + tests/test_hap_inspect.py | 227 +++ tests/test_pipeline.py | 621 +++++++ tests/test_prepare_toolchain.py | 90 + tests/test_runtime.py | 86 + tests/test_settings.py | 96 ++ tests/test_signing.py | 275 ++- tests/test_subprocess_utils.py | 182 ++ tests/test_token_exchange.py | 18 +- third_party/libusb/README.md | 15 + ...nharmony-third_party_libusb-b982552.tar.gz | Bin 0 -> 475852 bytes toolchain.lock.json | 73 + 54 files changed, 8518 insertions(+), 226 deletions(-) create mode 100644 PORTABLE.md create mode 100644 PRIVACY.md create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 bundle/hapsign.manifest create mode 100644 bundle/hapsign.spec create mode 100644 bundle/hooks/hook-playwright.async_api.py create mode 100644 bundle/hooks/hook-playwright.sync_api.py create mode 100644 docs/OPEN_SOURCE_RELEASE.md create mode 100644 docs/PACKAGING.md create mode 100644 hapsign/cancellation.py create mode 100644 hapsign/diagnostics.py create mode 100644 hapsign/gui.py create mode 100644 hapsign/runtime.py create mode 100644 hapsign/settings.py create mode 100644 hapsign/signing/hap_inspect.py create mode 100644 hapsign/subprocess_utils.py create mode 100644 hapsign/token/secure_token_cache.py create mode 100644 scripts/build_portable.py create mode 100644 scripts/prepare_toolchain.py create mode 100644 tests/test_build_portable.py create mode 100644 tests/test_diagnostics.py create mode 100644 tests/test_gui_style.py create mode 100644 tests/test_hap_inspect.py create mode 100644 tests/test_prepare_toolchain.py create mode 100644 tests/test_runtime.py create mode 100644 tests/test_settings.py create mode 100644 tests/test_subprocess_utils.py create mode 100644 third_party/libusb/README.md create mode 100644 third_party/libusb/openharmony-third_party_libusb-b982552.tar.gz create mode 100644 toolchain.lock.json diff --git a/.gitignore b/.gitignore index baa7ae1..a7a729c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,9 @@ dist/ build/ *.egg .pytest_cache/ +.test-tmp*/ .ruff_cache/ +.venv-codex/ .mypy_cache/ .coverage coverage.xml @@ -14,7 +16,11 @@ htmlcov/ # 签名产物(证书、密钥、Profile、签名后的 hap) signing_files/ +signed_haps/ +logs/ +hapsign-config.json output/ +/*.hap # 反编译参考源码(不参与运行,体积过大) reverse/ @@ -25,6 +31,9 @@ reverse/ *.swp *.swo +# 本地运行脚本(含个人环境变量,不提交) +run_sign_install.bat + # OS Thumbs.db Desktop.ini diff --git a/CHANGELOG.md b/CHANGELOG.md index a9b5a04..2a251ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,64 @@ - Ruff、pytest、覆盖率、pre-commit 和 Windows CI 配置。 - 贡献指南、安全策略、行为准则和统一编辑器配置。 - CLI、缓存、HTTP 响应和权限提取的单元测试。 +- 自动检测已签名 HAP:存在 Hap Signing Block 时跳过登录/申请/签名,直接安装。 +- 签名块检测对齐 `developtools_hapsigner`(两阶段 EOCD、ZIP64 Locator、blockCount/尺寸边界)。 +- PySide6 桌面界面,支持文件选择、HAP 拖放、后台执行和运行记录。 +- 桌面版增加主动设备检测,并在签名安装前强制确认 HDC 设备可用。 +- 运行记录使用统一的轻量滚动条样式,文件卡片支持移除误选 HAP。 +- Windows 便携版声明 Per-Monitor V2 DPI 感知,并使用系统字体和小数缩放。 +- 桌面版签名材料改为保存在程序目录的 `signing_files/`,便于随目录迁移。 +- Windows 下 Java、keytool 和 HDC 使用无控制台窗口方式启动,消除闪窗。 +- HDC server 采用“本次启动、本次关闭”策略,避免任务结束后遗留后台进程, + 同时不终止 DevEco 等工具已有的 HDC 服务。 +- PyInstaller 便携版构建配置,以及跨平台用户数据和工具链发现。 +- 恢复 Playwright 受控浏览器作为默认登录环境,系统默认浏览器保留为备用模式。 +- 新增 Playwright 受控系统 Edge/Chrome 模式并作为精简包默认值;兼容包仍可 + 携带内置 Chromium,普通系统默认浏览器只作为非受控备用。 +- 桌面任务使用按实际阶段推进的百分比进度条,并支持主动取消。 +- 执行中的登录、网络请求、Java/keytool、签名工具和 HDC 均响应取消信号; + 关闭窗口时可确认中断,清理完成后自动退出。 +- 新增程序目录 JSON 配置、滚动文件日志、日志级别和敏感诊断开关。 +- 桌面设置可在程序目录、用户 AppData Local 和自定义签名目录之间切换,并可 + 一键打开签名目录或日志目录。 +- 可选择是否保留签名后的 HAP;默认在程序目录 `signed_haps/` 中仅保留最新 + 一个,新文件发布成功后才删除旧文件,关闭后使用并清理任务临时文件。 +- 便携构建采用带回退开关的保守精简:只排除已知无关的 JCEF/录像组件,并在 + 裁剪后强制执行 Chromium、Java、keytool 与 hap-sign-tool 自检。 +- 设置页改为分区卡片布局,统一重绘下拉框、弹出菜单、复选框及操作按钮。 +- Windows 字体改用整数逻辑像素、Microsoft YaHei UI 和完整 hinting,降低 + 150%/200% 高 DPI 下由分数物理像素造成的笔画发虚。 +- 补充隐私说明、第三方组件声明和开源发布门禁;便携包构建会复制项目许可、 + 冻结依赖随附许可,并为本机复制的工具链生成 SHA-256 来源清单。 +- 明确区分 MIT 源码发布与第三方二进制再分发:未确认具体 DevEco/SDK 版本许可前, + 完整便携 ZIP 只用于本地构建验证。 +- 正式 Windows 便携构建改用 `toolchain.lock.json` 锁定并校验的 OpenHarmony + 6.1 公共 SDK 与 Eclipse Temurin 21;构建时仅提取 HDC、hap-sign-tool、 + libusb 和 NOTICE,并以 `jlink` 生成精简 Java 运行时,同时随包保留来源、 + 哈希、许可材料及 libusb 对应源码。DevEco 工具链只保留为显式排障回退。 +- 便携构建在 ZIP 旁自动生成标准 `.sha256` 校验文件,降低发布时手工抄录哈希出错的风险。 + +### Fixed + +- HTTP 客户端正确发送 `User-Agent` / `Accept-Language` 请求头。 +- Token 缓存缺少 `jwt_token` 时不再复用,避免后续刷新失败。 +- 设备注册将业务层重复错误码视为成功,并保留 HTTP 错误信息中的兼容判定。 +- 设备注册兼容服务端新增的 `205389858 (UDID is repeat)`,复用已注册设备, + 不再把“设备已存在”当作安装失败。 +- 签名工具默认密码统一引用 `HAPSIGN_KEYSTORE_PASSWORD` / 配置默认值。 +- 登录回调兼容根路径、`/callback` 及其他本地路径上的 GET/POST 回调, + 并支持普通表单、multipart 表单、JSON、查询参数和 CORS 预检,修复授权后 + 一直等待的问题;用户拒绝授权时也会立即结束等待。 +- 登录回调支持 Chromium Private Network Access 预检、缺失 multipart 类型推断 + 及嵌套 JSON;运行记录会显示不含 token 的回调方法、类型和字段诊断信息。 +- 已选 HAP 卡片固定显示在拖放区右侧,避免窗口布局变化时覆盖拖放区。 +- 主动取消任务后将进度条重置为 0%。 +- 登录成功响应恢复为已验证实现使用的 DevEco 成功页跳转,并为受控 Chromium + 预授予本地网络访问权限,避免授权完成后回调请求被浏览器策略拦截。 ### Security - 登录回调服务仅监听 loopback 地址。 -- 登录日志不再包含 token、请求体、CSRF code、用户 ID、设备 UDID 或完整登录 URL。 +- 日志默认不包含 token、完整请求体、CSRF code 或完整登录 URL;只有用户主动开启 + “敏感诊断”且使用 DEBUG 级别时才记录完整网络载荷,密钥库密码始终排除。 - 限制登录回调请求体大小,并尽力收紧 token 缓存文件权限。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 828c89e..902f098 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,12 @@ python -m pytest --cov 也可以安装 `pre-commit` 后运行 `pre-commit install`。仓库中的本地 hooks 使用当前 Python 环境已安装的 Ruff,不会在执行时下载额外工具。 +## 桌面版与打包 + +修改 GUI、运行时发现或打包配置后,除单元测试外还应构建一次当前平台的 +PyInstaller 便携版,并运行打包后的 `--smoke-test`。完整步骤、工具链要求和 +发布检查清单见 [docs/PACKAGING.md](docs/PACKAGING.md)。 + ## Pull request - 每个 PR 聚焦一个问题,并说明行为变化和验证方式。 @@ -35,3 +41,6 @@ Python 环境已安装的 Ruff,不会在执行时下载额外工具。 - 不在测试中调用真实华为服务;使用 pytest 的 monkeypatch 或 mock。 - 若改动用户可见行为,在 `CHANGELOG.md` 的 Unreleased 小节记录。 - API 来自非公开实现时,应在说明中标注兼容性风险,不提交第三方反编译产物。 +- 新增运行时依赖或便携包二进制时,同步更新 `THIRD_PARTY_NOTICES.md`,并提供 + 可审计的来源、版本、哈希和再分发许可。 +- 完整便携包的公开发布还必须通过 `docs/OPEN_SOURCE_RELEASE.md` 的许可与隐私门禁。 diff --git a/PORTABLE.md b/PORTABLE.md new file mode 100644 index 0000000..da58fa9 --- /dev/null +++ b/PORTABLE.md @@ -0,0 +1,93 @@ +# HapSign 便携版 + +解压 ZIP 后双击 `HapSign.exe`(macOS/Linux 使用对应平台可执行文件)。 + +## 使用方法 + +1. 用 USB 连接 HarmonyOS 设备,并确认设备已允许调试。 +2. 可点击“检测设备”确认设备已连接并授权;也可以直接开始,程序会自动检测。 +3. 将 `.hap` 文件拖入窗口,或点击选择文件;误选时点击文件右侧的“×”移除。 +4. 点击“开始签名并安装”。 +5. 如果 HAP 尚未签名,程序会控制系统 Edge(其次 Chrome)打开华为登录页; + 完成登录后程序会继续。 + +进度条按当前实际流程阶段推进。任务执行期间可以点击“取消”;如果直接关闭窗口, +程序会询问是否中断当前任务。确认后会先结束登录等待或外部工具、清理本次启动的 +HDC 服务,再退出。取消不会复用未完成任务的运行状态,可直接重新开始。 + +签名后的 HAP、证书、Profile、密钥库和登录令牌默认保存在程序目录下: + +```text +HapSign/ +├── HapSign.exe +├── signing_files/ + ├── .token_cache.json + └── / +└── signed_haps/ # 最新一个签名 HAP +``` + +请把便携版解压到当前用户可写的目录,不要放进 `Program Files` 等受保护位置。 +移动整个 `HapSign` 目录时,签名材料和缓存会一起移动。 + +标题栏的“设置”可把签名目录改为当前用户的 `AppData Local` 或自定义目录,也可 +直接打开签名目录和日志目录。配置文件是程序目录下的 `hapsign-config.json`; +日志默认写入 `logs/hapsign.log`,无法写入程序目录时会回退到用户本地数据目录。 +敏感日志默认关闭;只有主动开启且日志级别为 DEBUG 时才记录 token、用户标识及 +完整 API 请求/响应。签名库密码始终不会写入日志。 + +“保留最新一个签名后的 HAP”默认开启。程序会在新 HAP 完整签名成功后写入 +`signed_haps/`,只删除 HapSign 清单记录的旧产物,因此不会误删目录中的用户 HAP, +当前输入文件也不会被清理,签名失败也不会破坏上一份。关闭该开关后,签名 HAP 只 +作为安装临时文件,任务结束后自动清理。 + +输入已签名 HAP 时会直接安装,不产生新的签名材料。 +Java、keytool 和 HDC 等外部命令会在后台执行,不会弹出命令行窗口。 +任务结束时只关闭由本次任务启动的 HDC server;原本由 DevEco 等工具启动的 +既有 HDC server 不会被终止。 + +## 构建 + +构建机需要 Python 3.11+。目标电脑不需要安装 Python 或 DevEco Studio;默认 +精简包要求目标 Windows 已安装 Edge 或 Chrome。正式 Windows 构建先准备锁定的 +OpenHarmony/Temurin 工具链: + +```bash +python -m pip install -e ".[gui,bundle]" +python scripts/prepare_toolchain.py +python scripts/build_portable.py +``` + +要生成不依赖系统浏览器二进制的兼容包: + +```powershell +$env:PLAYWRIGHT_BROWSERS_PATH = "0" +python -m playwright install --no-shell chromium +python scripts/build_portable.py --keep-bundled-browser +``` + +构建结果位于 `dist/HapSign-portable-.zip`,同目录会生成可用于发布校验的 +`.zip.sha256` 文件。 +兼容包位于 `dist/HapSign-portable--compat.zip`。 +准备脚本使用 `jlink` 生成精简 Temurin 运行时,构建脚本会自动执行 Java、 +keytool、hap-sign-tool、HDC 和冻结程序自检。 + +仅调试 GUI、不复制外部工具链时可以运行: + +```bash +python scripts/build_portable.py --skip-toolchain +``` + +该 GUI-only 包不能在没有外部工具链的电脑上完成签名和安装。完整的构建环境、 +资源发现顺序、目录结构、验证方法和发布清单见 `docs/PACKAGING.md`;生成的便携 +目录中也会包含一份 `BUILDING.md`。 + +PyInstaller 产物与当前操作系统绑定,因此 Windows、macOS、Linux 需要分别构建。 +锁文件会记录公共 SDK、Temurin 和核心文件哈希;发布包也包含生成时的 +`PROVENANCE.txt`、完整 OpenHarmony NOTICE、Temurin legal 目录,以及 +`libusb_shared.dll` 对应的 OpenHarmony 源码快照。若使用 +`--allow-deveco-toolchain` 回退,本次产物只用于本机排障,不得公开发布。 + +发布包根目录会包含 HapSign 的 `LICENSE`、`PRIVACY.md`、 +`THIRD_PARTY_NOTICES.md` 和 `BUILDING.md`,冻结依赖随附的许可文件位于 +`licenses/python/`。Temurin legal、OpenHarmony NOTICE 和 libusb 对应源码也必须 +保留。 diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..f11aa97 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,48 @@ +# Privacy and local data + +HapSign 是本地运行的开源工具,项目维护者没有自建后端、遥测、广告或崩溃上报。 +但签名流程需要用户主动登录华为开发者服务,并直接与华为域名通信。使用这些服务时, +数据处理还受华为账号、开发者服务及所在地区适用条款约束。 + +## 网络通信 + +程序只在用户启动签名流程后访问: + +- `https://devecostudio.huawei.com`:登录、临时令牌和访问令牌交换; +- `https://connect-api.cloud.huawei.com`:团队、证书、设备、应用和 Provision + Profile 管理; +- 上述服务返回的短期证书/Profile 下载地址; +- `127.0.0.1` 的临时回调端口:浏览器把登录结果交还给本机 HapSign,不对局域网 + 或公网监听。 + +发送给华为服务的数据可能包括账号令牌、用户和团队标识、CSR 公钥请求、证书名称、 +HAP 包名、HAP 声明的待预授权权限、设备 UDID/类型/自动生成的设备名、证书 ID、 +设备 ID、应用 ID 以及 Profile 名称。HAP 文件正文、私钥和密钥库密码不会上传; +HAP 签名在本机完成。 + +程序会查询、创建和在必要时删除当前账号下的调试证书、设备记录及 Provision +Profile。重复 UDID 会复用已有设备记录。用户应确认自己有权对相应账号、应用和设备 +执行这些操作。 + +## 本地保存 + +根据设置,以下文件保存在程序目录、用户 Local AppData 或用户选择的目录: + +- `signing_files/.token_cache.json`:访问令牌、刷新令牌、JWT 和账号基本字段; +- `signing_files//`:私钥密钥库、CSR、证书、Profile 和缓存元数据; +- `signed_haps/`:可选保留的最后一个已签名 HAP; +- `logs/hapsign.log*`:诊断日志; +- `hapsign-config.json`:日志级别、保存位置和功能开关。 + +令牌和签名材料按日期复用,但不会由项目维护者远程删除。用户可关闭应用后删除上述 +目录;分享、卸载或移动便携目录前也应主动检查。签名 HAP 可能包含用户自有代码,不应 +随公开问题报告或发布包上传。 + +## 日志 + +默认日志不会记录 token、完整登录 URL、完整回调正文或完整 API 请求/响应。只有用户 +同时选择 DEBUG 级别并开启“记录敏感诊断信息”后,日志才可能包含这些内容;密钥库密码 +始终不记录。敏感排障完成后应关闭开关并删除已有日志与令牌缓存。 + +更多安全建议见 [SECURITY.md](SECURITY.md)。源代码可以审计全部网络端点;如果不接受 +上述数据流,请不要启动登录和签名流程。 diff --git a/README.md b/README.md index 0cd8808..ec6ac1c 100644 --- a/README.md +++ b/README.md @@ -4,28 +4,56 @@ > [!IMPORTANT] > 本项目是非官方工具,与华为无隶属或背书关系。它依赖可能变化的在线接口,仅用于合法的 -> 本机开发和调试。使用者应自行确认账号权限、数据安全以及相关服务条款。 +> 本机开发和调试。使用者应自行确认账号权限、数据安全以及相关服务条款。程序的数据流 +> 和本地保存行为见 [PRIVACY.md](PRIVACY.md),第三方许可边界见 +> [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。 ## 工作原理 ``` -Playwright 打开华为登录页(用户手动登录) - → 拿 tempToken → 换 accessToken - → 调华为云签名 API 生成 .cer / .p7b - → hap-sign-tool 签名 hap - → hdc install 安装到设备 +检测 HAP 是否已签名 + ├─ 已签名 → hdc install 直接安装 + └─ 未签名 → + Playwright 控制系统 Edge/Chrome 打开登录页(用户手动登录) + → 拿 tempToken → 换 accessToken + → 调华为云签名 API 生成 .cer / .p7b + → hap-sign-tool 签名 hap + → hdc install 安装到设备 ``` ## 前置条件 -1. **DevEco Studio** 已安装(需要其中的 JBR Java、hap-sign-tool.jar、hdc) +1. **签名/设备工具链**:便携版已内置;源码运行可使用已准备的公开工具链或 + DevEco Studio 2. **Python 3.11+**(推荐使用 conda 或 venv 隔离环境) 3. **HarmonyOS 设备**已通过 USB 连接并开启 USB 调试模式 4. **华为开发者账号**(需要已完成实名认证) -支持 **Windows** 与 **macOS**(命令行)。 +命令行支持 **Windows** 与 **macOS**;当前正式便携版为 Windows。 -## 安装 +## 直接使用 Windows 便携版(推荐) + +普通使用者不需要安装 Python、DevEco Studio、Java 或 HDC。到 GitHub Releases +下载 `HapSign-portable-windows.zip` 及旁边的 `.zip.sha256` 校验文件,先在 +PowerShell 中核对下载完整性: + +```powershell +Get-FileHash .\HapSign-portable-windows.zip -Algorithm SHA256 +Get-Content .\HapSign-portable-windows.zip.sha256 +``` + +确认哈希一致后,将 ZIP 解压到当前用户可写的目录(不要放入 `Program Files`), +双击 `HapSign.exe` 即可。精简包会复用系统 Edge/Chrome;如果发布页同时提供 +`HapSign-portable-windows-compat.zip`,它包含内置 Chromium,适合没有可用系统 +浏览器的电脑。 + +首次使用时连接已开启 USB 调试的 HarmonyOS 设备,并在设备上确认调试授权;在窗口中 +点击“检测设备”,然后拖入或选择 `.hap`,点击“开始签名并安装”。未签名 HAP 会打开 +受控浏览器完成华为账号登录和验证码/二次验证,之后自动申请材料、签名并安装;已签名 +HAP 会跳过登录和签名直接安装。签名材料、日志和可选的签名后 HAP 的位置见 +[便携版说明](PORTABLE.md)。 + +## 从源码安装 ```bash # 克隆仓库 @@ -35,13 +63,28 @@ cd HapSign # 安装项目(提供 hapsign 命令) python -m pip install . -# 安装 Playwright 浏览器(首次必须) -playwright install chromium ``` -### 配置 DevEco Studio 路径 +源码桌面版默认由 Playwright 控制本机 Edge(其次 Chrome),仍会预授予登录页访问 +本地回调服务的权限,因此不同于直接调用系统默认浏览器的旧方案。Windows 10/11 +通常已经包含 Edge,不需要额外下载 Chromium。 + +如需使用内置 Chromium 兼容模式: + +```powershell +$env:PLAYWRIGHT_BROWSERS_PATH = "0" +python -m playwright install --no-shell chromium +``` + +桌面设置提供“受控系统浏览器”“内置 Chromium”和“非受控系统默认浏览器”三种 +模式;环境变量 `HAPSIGN_BROWSER` 可使用 `system_controlled`、`playwright` +或 `system` 覆盖代码默认值。 + +### 可选:配置 DevEco Studio 回退路径 -默认查找路径: +正式便携版不需要 DevEco Studio。源码运行时程序会优先使用已准备的公开工具链,只有 +在排查特定 DevEco 版本兼容性时才需要本机 DevEco 回退。程序会查找系统常见安装目录 +和 `D:\Program Files\Huawei\DevEco Studio`;如果安装在其他位置,设置环境变量: - Windows: `D:\Program Files\Huawei\DevEco Studio` - macOS: `/Applications/DevEco-Studio.app/Contents` @@ -79,18 +122,87 @@ $env:HAPSIGN_PYTHON = "C:\path\to\your\python.exe" ## 使用 -### 方式一:命令行(Windows / macOS) +### 方式一:桌面应用(推荐) + +安装 GUI 依赖后运行: + +```bash +python -m pip install -e ".[gui]" +hapsign-app +``` + +桌面版支持点击选择或拖入 `.hap` 文件,也可以点击右侧文件卡片的“×”移除误选 +文件。可随时点击“检测设备”确认 HDC 连接状态,并在后台完成登录、签名和安装。 +进度条会按设备检测、账号授权、证书申请、签名和安装等实际阶段推进。执行期间 +可以点击“取消”;关闭窗口时会询问是否中断,完成子进程和 HDC 清理后再退出, +取消后的同一窗口可以直接重新开始完整流程。 +开始流程时还会自动执行一次设备可用性检查,未连接、未授权或同时连接多台设备 +时不会继续。运行记录和错误会直接显示在窗口中。 + +标题栏的“设置”可选择登录浏览器、签名文件保存位置和日志级别,并能直接打开 +签名目录或日志目录。诊断日志默认写到程序目录的 `logs/hapsign.log`,单个文件 +最多 4 MiB,保留 3 份轮转备份。敏感诊断默认关闭;主动开启并选择 DEBUG 后, +日志可能包含 token、用户标识和完整 API 请求/响应,但始终不会记录密钥库密码。 +“保留最新一个签名后的 HAP”默认开启:最终 HAP 固定写到程序目录的 +`signed_haps/`,新文件成功生成后只清理 HapSign 清单记录的旧产物,不会删除 +目录中未记录的用户 HAP;当前输入文件也会受到保护。关闭后程序使用临时文件安装, +任务结束即删除。 + +如果 HDC server 原本未运行,程序会在本次任务结束时关闭自己拉起的后台服务; +如果 DevEco Studio 或其他工具已经启动 HDC server,则会保留该既有服务。 + +### 方式二:bat 拖拽 + +将 `.hap` 文件直接拖到 `sign_install.bat` 上,自动完成签名+安装。 + +### 方式三:命令行(Windows / macOS) ```bash hapsign --hap path/to/app-unsigned.hap +hapsign --hap path/to/app-signed.hap # 已签名则跳过签名,直接安装 ``` 包名会自动从 hap 内的 `module.json` 提取,无需手动指定。 +若 HAP 已包含签名块(Hap Signing Block),会跳过登录与签名,直接安装原文件。 源码目录中仍可使用 `python main.py --hap ...`。 -### 方式二:拖拽(仅 Windows) +### 构建便携版 -将 `.hap` 文件直接拖到 `sign_install.bat` 上,自动完成签名+安装。 +便携版是一个可直接解压运行的目录,不要求目标电脑安装 Python 或 DevEco Studio。 +Windows 正式包使用锁定并校验的 OpenHarmony 6.1 公共工具链和 Eclipse Temurin +21。首次构建先准备工具链,再打包: + +```bash +python -m pip install -e ".[gui,bundle]" +python scripts/prepare_toolchain.py +python scripts/build_portable.py +``` + +`prepare_toolchain.py` 会校验 `toolchain.lock.json` 中的大小和 SHA-256,只从 +OpenHarmony 公共 SDK 提取 HDC、libusb、hap-sign-tool 和 NOTICE,再用 Temurin +JDK 的 `jlink` 生成精简 Java 运行时。公共 SDK 下载约 2.5 GB,但只在构建缓存中 +保留;最终工具链约 66 MiB。已有下载可用 `--sdk-archive` 和 `--jdk-archive` +传入,仍会执行相同校验。 + +默认产物复用系统 Edge/Chrome,不包含 Chromium。要生成包含内置 Chromium 的兼容 +包,PowerShell 中执行: + +```powershell +$env:PLAYWRIGHT_BROWSERS_PATH = "0" +python -m playwright install --no-shell chromium +python scripts/build_portable.py --keep-bundled-browser +``` + +输出文件为 `dist/HapSign-portable-.zip`。Windows、macOS 和 Linux +产物需要在各自平台分别构建。便携版使用说明见 [PORTABLE.md](PORTABLE.md), +完整构建步骤见 [docs/PACKAGING.md](docs/PACKAGING.md)。 + +> [!NOTE] +> 当前锁定的 OpenHarmony 公共工具链是 `6.1.0.31 / API 23`(HDC 3.2.0c), +> 并非 DevEco 6.1.1.125 中较新的 API 24 版本。现有签名产物验证、实际重签名和 +> 设备识别已经通过;正式发布仍应按 +> [开源发布门禁](docs/OPEN_SOURCE_RELEASE.md) 完成真实设备安装回归。 +> `--allow-deveco-toolchain` 只用于排障回退,其产物不得冒充锁定的公开构建。 ### 完整参数 @@ -98,7 +210,7 @@ hapsign --hap path/to/app-unsigned.hap hapsign --hap [选项] 选项: - --hap 未签名的 hap 文件路径(必填) + --hap hap 文件路径(必填;已签名则直接安装) --bundle-name 应用包名(不传则从 hap 内自动提取) --country 国家码,默认 CN --device-type 设备类型码,默认 4 @@ -133,12 +245,28 @@ hapsign --hap app.hap --enable-capability 此模式通过 `add.real.provision` API 创建 Real Profile(provisionType=1),对应 DevEco Studio 6.1+ 的 `enableCapability` 路径。需要应用已在 AGC(AppGallery Connect)注册且当前账号有访问权限,否则自动回退到 Test Profile。 -### 签名后的文件 +### 签名文件和缓存位置 -签名后的 hap 和签名材料保存在 `signing_files/{bundle_name}/` 下: +桌面版和便携版默认把签名材料保存在程序目录旁,解压目录可以整体移动: +```text +HapSign/ +├── HapSign.exe +├── signing_files/ + ├── .token_cache.json + └── / +└── signed_haps/ # 最新一个签名 HAP(可在设置中关闭) ``` -signing_files/com.example.myapp/ + +源码 CLI 默认保存在启动命令时所在目录的 +`signing_files//`;传入 `--work-dir` 可以指定其他目录。 +程序目录必须可写,不建议把便携版解压到 `Program Files` 等受保护目录。 +桌面版“设置”中还可以改为用户 `AppData Local` 或任意自定义目录。 + +成功完成一次未签名 HAP 的签名后,目录内容如下: + +``` +<签名目录>/com.example.myapp/ ├── auto_debug_com.example.myapp.p12 # 密钥库 ├── auto_debug_com.example.myapp.csr # CSR ├── auto_debug_com.example.myapp.cer # 调试证书 @@ -147,6 +275,8 @@ signing_files/com.example.myapp/ └── entry-default-unsigned_signed.hap # 签名后的 hap ``` +如果输入 HAP 本身已经签名,程序会直接安装原文件,不会生成上述签名材料。 + ## 缓存策略 同一天内不会重复登录或重复申请签名文件: @@ -163,6 +293,8 @@ signing_files/com.example.myapp/ - 登录验证码 / 二次验证需要用户在浏览器中手动处理 - 拖拽安装脚本仅支持 Windows(`sign_install.bat`);macOS 请使用 `hapsign` 命令行 +- 当前提供并完整验证的是 Windows 便携版;macOS/Linux 已预留运行时路径, + 仍需分别增加锁定的公开工具包并在对应系统构建、实机验证 - 签名流程依赖华为云 API,需要有网络连接和华为开发者账号 ## 开发与贡献 @@ -183,10 +315,12 @@ python -m pytest --cov hapsign/ ├── cli.py # 命令行参数和入口 ├── config.py # 配置常量(域名、SDK 路径、API 端点、密钥参数) +├── gui.py # PySide6 桌面界面 ├── models.py # 数据模型 ├── pipeline.py # 全流程编排(缓存、登录、签名、安装) +├── runtime.py # 用户数据目录与跨平台工具链发现 ├── login/ -│ └── browser_login.py # Playwright 浏览器登录 +│ └── browser_login.py # 受控系统浏览器、内置 Chromium 与普通浏览器备用后端 ├── token/ │ └── token_exchange.py # tempToken → JWT → accessToken ├── api/ @@ -197,12 +331,17 @@ hapsign/ │ └── capability_api.py # 应用信息 API (app brief info) └── signing/ ├── keytool_util.py # keytool 生成 EC 密钥对 + CSR + ├── hap_inspect.py # 检测 HAP 是否已签名 ├── hap_signer.py # hap-sign-tool 签名 hap └── installer.py # hdc install / 获取 UDID ``` ## License -本项目使用 [MIT License](LICENSE)。参与项目需遵守 [Code of Conduct](CODE_OF_CONDUCT.md)。 +HapSign 自身源代码使用 [MIT License](LICENSE)。第三方依赖和便携包工具链保持各自 +许可,不因本项目采用 MIT 而改变,详见 +[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。隐私与本地数据说明见 +[PRIVACY.md](PRIVACY.md),参与项目需遵守 +[Code of Conduct](CODE_OF_CONDUCT.md)。 Powered by [BitFun](https://github.com/GCWing/BitFun) diff --git a/SECURITY.md b/SECURITY.md index c3ddb86..9bbf664 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,14 +3,22 @@ ## 报告安全问题 请不要在公开 Issue 中提交 token、证书、私钥、设备 UDID、登录回调内容或可复现的 -账号信息。请通过 GitHub 仓库维护者提供的私密联系方式报告漏洞;若暂无私密渠道, -先提交一个不包含敏感细节的 Issue,请求维护者建立私密沟通。 +账号信息。维护者应在仓库 Settings → Code security 中启用 GitHub Private +vulnerability reporting;启用后请使用仓库 Security 页的“Report a vulnerability” +私密提交。若该入口尚不可用,只提交一个不包含敏感细节的 Issue,请求维护者建立 +私密沟通,未经确认不要公开漏洞细节。 ## 本地敏感数据 -hapsign 会在 `signing_files/` 中保存当日 token 缓存、调试证书、Profile 和 `.p12` -密钥库,以避免重复登录和申请。这些文件已被 `.gitignore` 排除,但仍是本机明文敏感 -数据。请勿上传、分享或放入云同步目录;在共享电脑上使用后应删除该目录。 +hapsign 默认会在程序目录的 `signing_files/` 中保存当日 token 缓存、调试证书、 +Profile 和 `.p12` 密钥库,以避免重复登录和申请。这些文件已被 `.gitignore` +排除,但仍是本机敏感数据。Windows 上 token 缓存通过当前用户作用域的 DPAPI +(CryptProtectData)静态加密后落盘,其他平台退化为受限权限(仅当前用户可读) +的明文存储,并在首次保存时打印告警;请勿把缓存目录放入云同步目录,在共享电脑 +上使用后应删除该目录。移动或分享便携目录前应先移除 `signing_files/`;桌面设置 +可改为用户 AppData Local 或自定义目录;同样应按敏感数据目录保护。 +程序目录的 `signed_haps/` 可能包含用户应用代码,移动或分享便携目录前也应检查; +可在设置中关闭保留签名 HAP。 默认密钥库密码只用于本机调试材料,不应被视为安全密码。可以在运行前设置 `HAPSIGN_KEYSTORE_PASSWORD` 环境变量覆盖它: @@ -19,11 +27,15 @@ hapsign 会在 `signing_files/` 中保存当日 token 缓存、调试证书、Pr $env:HAPSIGN_KEYSTORE_PASSWORD = "使用你自己的强密码" ``` -开启详细日志前请确认使用的是最新版本。程序不会记录 token、回调请求体、CSRF code、 -用户 ID、设备 UDID 或完整登录 URL。若发现日志中出现凭据,请立即停止分享日志、清除 -token 缓存并重新登录。 +敏感诊断开关默认关闭,此时程序不会记录 token、完整回调请求、CSRF code 或完整 +登录 URL。只有用户主动开启该开关并选择 DEBUG 级别后,才会记录 token、用户标识及 +完整 API 请求/响应;密钥库密码无论如何都不会记录。分享 `logs/hapsign.log*` 前请 +确认开关状态并检查内容。若日志已经包含凭据,请停止分享、删除日志、清除 token +缓存并重新登录。 ## 支持范围 安全修复只保证在最新发布版本和当前主分支提供。项目依赖第三方在线接口,接口变化、 账号策略和服务条款不在项目的安全支持范围内。 + +程序访问的域名、发送的数据和本地保留策略见 [PRIVACY.md](PRIVACY.md)。 diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..83a58d1 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,78 @@ +# Third-party notices + +HapSign 自身的源代码使用 [MIT License](LICENSE)。本文件说明源码依赖及便携版 +可能包含的第三方组件;第三方组件不因被 HapSign 使用或打包而改用 MIT License。 + +## Python 与桌面运行时 + +| 组件 | 用途 | 上游许可 | +| --- | --- | --- | +| Python | 应用运行时 | Python Software Foundation License | +| PySide6 Essentials / Shiboken6 / Qt | 桌面界面 | LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only,或 Qt 商业许可 | +| Playwright for Python / Playwright driver | 控制登录浏览器 | Apache-2.0 | +| greenlet | Playwright 运行时传递依赖 | MIT | +| pyee | Playwright 运行时传递依赖 | MIT | +| Requests | HTTPS 客户端 | Apache-2.0 | +| urllib3 | HTTPS 连接 | MIT | +| certifi | CA 证书集合 | MPL-2.0 | +| charset-normalizer | 文本编码检测 | MIT | +| idna | 国际化域名处理 | BSD-3-Clause | +| PyInstaller bootloader | 生成可执行文件 | GPL-2.0-or-later,带允许分发生成程序的特殊例外 | + +Windows 便携版以独立 DLL 的方式携带未修改的 Qt/PySide6 运行库,选择 LGPL-3.0 +路径进行分发。接收者可以用 ABI 兼容的修改版动态库替换 +`_internal/PySide6/` 中相应文件;HapSign 不对调试这种修改施加额外限制。Qt 的 +版权、商标和许可仍归各权利人所有。 + +构建脚本会把当前构建环境中上述 Python distribution 随附的 LICENSE、COPYING +和 NOTICE 文件复制到便携包的 `licenses/python/`。Playwright 自带的 driver +许可及第三方声明也保留在 `_internal/playwright/driver/`。兼容包如果包含 +Chromium,还必须保留该目录内 Playwright/Chromium 的第三方声明。 + +## 便携工具链 + +Windows 正式便携版从 `toolchain.lock.json` 锁定的公开上游准备: + +| 组件 | 当前版本/来源 | 许可 | +| --- | --- | --- | +| Java、keytool | Eclipse Temurin 21,经 `jlink` 生成 | GPL-2.0-only WITH Classpath-exception-2.0;各模块可能另有随附许可 | +| HDC | OpenHarmony 公共 SDK 6.1.0.31 | 以 SDK NOTICE 和上游仓库为准,主体为 Apache-2.0 | +| hap-sign-tool | OpenHarmony 公共 SDK 6.1.0.31 | Apache-2.0 及 JAR 内随附第三方许可 | +| libusb_shared | OpenHarmony 公共 SDK 内的 libusb 1.0.28 | LGPL-2.1-or-later | + +构建脚本会保留: + +- `resources/toolchain//runtime/legal/`; +- OpenHarmony 公共 SDK 的完整 `NOTICE.txt`; +- 下载地址、版本及 SHA-256 的 `PROVENANCE.txt` 和 `toolchain.lock.json`; +- `licenses/libusb-source/` 中与 DLL 对应的 OpenHarmony libusb 完整源码快照、 + 补丁、构建配置和 LGPL 文本。 + +HapSign 的 MIT 许可只覆盖本项目自身代码,不覆盖这些独立组件。接收者可以按各组件 +许可证替换或重新构建它们。源码快照的固定来源和哈希见 +`third_party/libusb/README.md`。 + +`--allow-deveco-toolchain` 仍可为兼容排障从本机 DevEco 复制工具;此模式的 +`PROVENANCE.txt` 会标识本机来源,不属于上述可审计正式构建,不应直接上传公开 +Release。`--skip-toolchain` 则完全不包含这些工具。 + +详细发布门禁见 [docs/OPEN_SOURCE_RELEASE.md](docs/OPEN_SOURCE_RELEASE.md)。 + +## 参考实现 + +HAP 签名块格式、HDC 行为和 hap-sign-tool 调用方式参考或对齐了 OpenHarmony 的 +`developtools_hapsigner` 与 `developtools_hdc` 项目。上游项目以各自仓库中的 +LICENSE 为准,主要采用 Apache-2.0: + +- +- + +如果后续从这些或其他开源仓库复制、移植了具体代码,而不只是依据公开格式重新实现, +必须在提交时记录来源文件、commit、修改内容和许可证。不得把不兼容许可证覆盖的代码 +直接标成 HapSign 的 MIT 代码。 + +## 商标与非隶属关系 + +HarmonyOS、OpenHarmony、HUAWEI、DevEco Studio、Qt、Python、Chromium、 +Microsoft Edge 和 Google Chrome 是各自权利人的商标或名称。本项目对这些名称的 +使用仅用于描述兼容性和依赖关系,不表示隶属、授权、认可或背书。 diff --git a/bundle/hapsign.manifest b/bundle/hapsign.manifest new file mode 100644 index 0000000..2c54a80 --- /dev/null +++ b/bundle/hapsign.manifest @@ -0,0 +1,23 @@ + + + + + + + + + + + + + true/pm + + + PerMonitorV2,PerMonitor + + + true + + + + diff --git a/bundle/hapsign.spec b/bundle/hapsign.spec new file mode 100644 index 0000000..a736778 --- /dev/null +++ b/bundle/hapsign.spec @@ -0,0 +1,71 @@ +# -*- mode: python ; coding: utf-8 -*- + +from pathlib import Path +import os +import sys +from PyInstaller.utils.hooks import collect_all + +project_root = Path(SPECPATH).parent +app_manifest = ( + str(project_root / "bundle" / "hapsign.manifest") + if sys.platform == "win32" + else None +) +playwright_datas, playwright_binaries, playwright_hiddenimports = collect_all( + "playwright" +) +if os.environ.get("HAPSIGN_BUNDLE_CHROMIUM", "0") != "1": + playwright_datas = [ + item for item in playwright_datas if ".local-browsers" not in item[0] + ] + playwright_binaries = [ + item for item in playwright_binaries if ".local-browsers" not in item[0] + ] + +analysis = Analysis( + [str(project_root / "hapsign" / "gui.py")], + pathex=[str(project_root)], + binaries=playwright_binaries, + datas=playwright_datas, + hiddenimports=playwright_hiddenimports, + hookspath=[str(project_root / "bundle" / "hooks")], + hooksconfig={}, + runtime_hooks=[], + excludes=[ + "OpenSSL", + "cryptography", + "pytest", + "pytest_cov", + "ruff", + "setuptools", + "wheel", + ], + noarchive=False, + optimize=1, +) +pyz = PYZ(analysis.pure) + +exe = EXE( + pyz, + analysis.scripts, + [], + exclude_binaries=True, + name="HapSign", + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=False, + console=False, + disable_windowed_traceback=False, + manifest=app_manifest, +) + +collect = COLLECT( + exe, + analysis.binaries, + analysis.datas, + strip=False, + upx=False, + upx_exclude=[], + name="HapSign", +) diff --git a/bundle/hooks/hook-playwright.async_api.py b/bundle/hooks/hook-playwright.async_api.py new file mode 100644 index 0000000..a085b1c --- /dev/null +++ b/bundle/hooks/hook-playwright.async_api.py @@ -0,0 +1,9 @@ +"""按便携构建模式收集 Playwright 数据。""" + +import os + +from PyInstaller.utils.hooks import collect_data_files + +datas = collect_data_files("playwright") +if os.environ.get("HAPSIGN_BUNDLE_CHROMIUM", "0") != "1": + datas = [item for item in datas if ".local-browsers" not in item[0]] diff --git a/bundle/hooks/hook-playwright.sync_api.py b/bundle/hooks/hook-playwright.sync_api.py new file mode 100644 index 0000000..a085b1c --- /dev/null +++ b/bundle/hooks/hook-playwright.sync_api.py @@ -0,0 +1,9 @@ +"""按便携构建模式收集 Playwright 数据。""" + +import os + +from PyInstaller.utils.hooks import collect_data_files + +datas = collect_data_files("playwright") +if os.environ.get("HAPSIGN_BUNDLE_CHROMIUM", "0") != "1": + datas = [item for item in datas if ".local-browsers" not in item[0]] diff --git a/docs/OPEN_SOURCE_RELEASE.md b/docs/OPEN_SOURCE_RELEASE.md new file mode 100644 index 0000000..3b4b854 --- /dev/null +++ b/docs/OPEN_SOURCE_RELEASE.md @@ -0,0 +1,57 @@ +# 开源发布门禁 + +本文区分“公开源代码”和“公开预编译便携包”。仓库采用 MIT License,并不自动赋予 +发布者重新分发第三方二进制的权利。 + +## 源码仓库 + +公开前应确认: + +- `LICENSE`、`README.md`、`THIRD_PARTY_NOTICES.md`、`PRIVACY.md`、 + `SECURITY.md`、`CONTRIBUTING.md` 和 `CODE_OF_CONDUCT.md` 存在; +- README 明确非官方性质、在线接口兼容性风险、数据流和第三方许可边界; +- 对所有“参考/移植自开源实现”的代码完成来源审计,记录上游仓库、commit 和许可; + 如果存在 GPL 或其他与整体 MIT 声明不兼容的复制代码,先解决许可和标注,不能仅在 + README 写一句致谢; +- Git 历史及当前工作区不含 HAP、token、UDID、证书、Profile、私钥、日志、 + 本机配置或反编译产物; +- CI 能在无账号、无设备、无 DevEco 环境下完成 lint、format 和单元测试; +- 发布版本有 changelog、版本号、tag 和可复现的构建命令; +- 仓库设置已启用 GitHub Private vulnerability reporting,或 SECURITY 文件提供 + 一个真实可用的私密安全联系方式。 + +## 预编译包 + +每个公开二进制包还必须满足: + +- 根目录包含 HapSign `LICENSE`、隐私说明、第三方声明和构建说明; +- `licenses/python/` 包含冻结依赖随附的许可和 NOTICE; +- Temurin runtime 的 `legal/` 未被裁掉,OpenHarmony SDK 的 `NOTICE.txt` 已保留; +- `licenses/libusb-source/` 包含与 `libusb_shared` 对应的完整源码快照; +- 记录 Python、Qt、Playwright、Temurin、HDC、hap-sign-tool 和可选 Chromium 的准确 + 版本、来源及 SHA-256; +- 工具链来自 `toolchain.lock.json` 锁定的公共上游,实际产物哈希与 + `PROVENANCE.txt` 一致; +- Qt 以可替换的共享库形式分发,并按选择的 LGPL-3.0 路径提供相应通知和许可文本; +- 兼容包保留 Chromium/Playwright 的第三方许可文件; +- 解压目录不含 `hapsign-config.json`、`logs/`、`signing_files/`、`signed_haps/` + 或任何构建者/测试者数据; +- 对最终 ZIP 运行恶意软件扫描、签名(若有代码签名证书)和 SHA-256 校验。 + +Windows 正式构建必须先运行 `python scripts/prepare_toolchain.py`,再运行 +`python scripts/build_portable.py`。准备脚本锁定并校验 OpenHarmony 公共 SDK 与 +Eclipse Temurin,只提取运行所需文件,并附带 libusb 对应源代码。 + +使用 `--allow-deveco-toolchain` 生成的是本机兼容/排障包,不通过公开发布门禁; +使用 `--skip-toolchain` 生成的是 GUI-only 包,也不能作为完整便携版发布。 + +## GitHub 发布建议 + +1. 整理并审查未提交改动。 +2. 执行 `python -m ruff format --check .`、`python -m ruff check .` 和 + `python -m pytest --cov`。 +3. 运行敏感文件扫描和 `git diff --check`。 +4. 更新 `hapsign.__version__` 与 `CHANGELOG.md`。 +5. 创建签名 tag。 +6. 只上传已通过本页许可门禁的产物,并在 Release notes 中列出 SHA-256、支持平台、 + 已知限制和第三方工具链来源。 diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md new file mode 100644 index 0000000..ae06a54 --- /dev/null +++ b/docs/PACKAGING.md @@ -0,0 +1,282 @@ +# HapSign 便携版打包指南 + +HapSign 使用 PyInstaller 的 `onedir` 模式生成桌面程序目录,再把当前平台的 +Java、keytool、`hap-sign-tool.jar` 和 HDC 一并复制后压缩成 ZIP。目标电脑解压 +即可运行,不要求预装 Python 或 DevEco Studio。 + +## 1. 构建模型 + +- Windows、macOS、Linux 产物必须分别在对应操作系统构建,不能交叉生成。 +- 当前经过完整构建和启动验证的是 64 位 Windows 版本。 +- 仓库不保存大型 SDK/JDK 二进制;`toolchain.lock.json` 固定公共上游地址、版本、 + 大小和 SHA-256,准备结果放在被 Git 忽略的 `build/`。 +- 默认由 Playwright 控制系统 Edge/Chrome,因此继续拥有本地网络权限和回调控制, + 但不携带 Chromium;可另外生成包含内置 Chromium 的兼容包。 +- PyInstaller 使用 `onedir` 而不是单文件模式,启动更快,也便于检查和替换 + Java/HDC 等资源。 + +## 2. 构建机要求 + +完整便携包需要: + +1. Python 3.11 或更高版本。 +2. Windows 正式构建需要网络下载锁定的 OpenHarmony 公共 SDK(约 2.5 GB)和 + Temurin JDK(约 205 MB),或由构建者提供已下载的相同文件。 +3. 首次准备建议至少 6 GB 可用磁盘空间;工具链生成后可删除研究/下载副本, + `build/toolchain-prepared/windows` 约 66 MiB。 + +DevEco Studio 不是正式便携构建的依赖。它仍可用于源码运行和兼容排障;只有显式 +传入 `--allow-deveco-toolchain` 时,构建脚本才会从本机发现并复制它的工具。 + +## 3. 安装构建依赖 + +建议使用独立虚拟环境: + +```powershell +python -m venv .venv +.\.venv\Scripts\Activate.ps1 +python -m pip install --upgrade pip +python -m pip install -e ".[gui,bundle]" +``` + +开发检查还需要: + +```powershell +python -m pip install -e ".[dev]" +``` + +## 4. 准备公开工具链 + +在仓库根目录执行: + +```powershell +python scripts/prepare_toolchain.py +``` + +准备脚本会: + +1. 校验 `toolchain.lock.json`; +2. 下载并校验 OpenHarmony 6.1 公共 SDK 和 Eclipse Temurin 21; +3. 只提取 Windows HDC、配套 libusb、hap-sign-tool 及完整 NOTICE; +4. 用 `jlink` 生成只含签名器和 keytool 所需模块的 Java runtime; +5. 附带 libusb 1.0.28 对应的 OpenHarmony 源码快照; +6. 实际运行 Java、EC keytool、hap-sign-tool 和 HDC 版本自检。 + +已有官方归档时可以避免重复下载;文件仍会严格按锁文件验证: + +```powershell +python scripts/prepare_toolchain.py ` + --sdk-archive D:\cache\ohos-sdk-windows_linux-public.tar.gz ` + --jdk-archive D:\cache\OpenJDK21U-jdk_x64_windows_hotspot_21.0.12_8.zip +``` + +默认输出是 `build/toolchain-prepared/windows/`。要重建现有输出,显式添加 +`--force`。 + +## 5. 生成完整便携包 + +在仓库根目录执行: + +```powershell +python scripts/build_portable.py +``` + +脚本会依次: + +1. 检查构建机系统 Edge/Chrome 可由 Playwright 正常控制。 +2. 使用 `bundle/hapsign.spec` 构建 `dist/HapSign/`;默认不收集 Chromium。 +3. 复制已校验的公开工具链及其许可、来源和对应源码。 +4. 复制便携版使用说明和本打包指南。 +5. 运行冻结应用自检,并清除自检生成的日志、配置和运行数据。 +6. 生成 `dist/HapSign-portable-.zip`。 + +Windows 正式构建使用 Temurin `jlink` runtime 并复用系统 Edge/Chrome。每次构建 +都会实际启动冻结版受控系统浏览器,并运行 Java、hap-sign-tool 和 keytool 密钥 +生成自检;任一失败便终止构建。 + +仅在排查新版 DevEco 兼容性时可使用本机回退: + +```powershell +python scripts/build_portable.py --allow-deveco-toolchain +python scripts/build_portable.py --allow-deveco-toolchain --keep-full-jbr +``` + +这两种产物都不是锁定的公开构建,不得直接上传 Release。 + +生成包含内置 Chromium 的兼容包: + +```powershell +$env:PLAYWRIGHT_BROWSERS_PATH = "0" +python -m playwright install --no-shell chromium +python scripts/build_portable.py --keep-bundled-browser +``` + +Windows 的主要输出结构: + +```text +dist/ +├── HapSign/ +│ ├── HapSign.exe +│ ├── _internal/ # Python、Qt 和应用依赖 +│ │ └── playwright/ # 浏览器控制驱动 +│ ├── resources/toolchain/windows/ +│ │ ├── runtime/ # Temurin jlink runtime +│ │ ├── lib/hap-sign-tool.jar +│ │ ├── bin/hdc.exe +│ │ ├── NOTICE.txt +│ │ ├── PROVENANCE.txt +│ │ ├── toolchain.lock.json +│ │ └── licenses/libusb-source/ +│ ├── README.md +│ └── BUILDING.md +└── HapSign-portable-windows.zip +``` + +## 6. GUI-only 快速构建 + +仅验证界面和 PyInstaller 配置时可以跳过约数百 MiB 的工具链复制: + +```powershell +python scripts/build_portable.py --skip-toolchain +``` + +GUI-only 包仍能启动,但目标电脑若没有可发现的外部工具链,就不能检测设备、 +签名或安装。不要把它当作正式便携版发布。 + +## 7. 构建后验证 + +先运行自动检查: + +```powershell +python -m ruff check hapsign tests scripts +python -m pytest --cov +``` + +验证窗口程序能够启动并自动退出: + +```powershell +.\dist\HapSign\HapSign.exe --smoke-test +.\dist\HapSign\HapSign.exe --system-browser-smoke-test +``` + +兼容包还应执行 `HapSign.exe --browser-smoke-test`。 + +验证内置工具: + +```powershell +.\dist\HapSign\resources\toolchain\windows\runtime\bin\java.exe -version +.\dist\HapSign\resources\toolchain\windows\runtime\bin\keytool.exe -help +.\dist\HapSign\resources\toolchain\windows\bin\hdc.exe -v +``` + +构建脚本会在 ZIP 旁自动生成标准 SHA-256 sidecar,例如 +`HapSign-portable-windows.zip.sha256`。仍可手动复核: + +```powershell +Get-FileHash -Algorithm SHA256 .\dist\HapSign-portable-windows.zip +``` + +GUI 改动还应至少人工检查一次: + +- 100%、150%、200% 缩放下文字和控件没有位图模糊。 +- 拖入、重新选择和移除 HAP 正常。 +- 选择 HAP 后文件卡片始终位于拖放区右侧,不会覆盖拖放区。 +- 受控系统 Edge/Chrome 能打开登录页,授权回调后程序继续并跳转成功页。 +- 进度条按流程阶段递增,不显示无限循环动画。 +- 登录等待、签名或安装过程中点击“取消”能回到可重试状态。 +- 任务运行时关闭窗口会询问是否中断;确认后清理并正常退出。 +- 日志滚动条、长文件名和错误提示显示正常。 +- “检测设备”及安装前自动检查都能识别未授权、无设备和正常设备。 +- Java、keytool、HDC 执行期间没有命令行窗口闪现。 +- 无预先运行的 HDC server 时,任务结束后不残留 `hdc` 进程。 +- DevEco 已经运行 HDC server 时,任务结束后该既有进程仍然保留。 + +## 8. 签名文件位置 + +桌面/便携版默认把运行数据放在可执行文件旁: + +```text +HapSign/ +├── HapSign.exe +├── signing_files/ + ├── .token_cache.json + └── / +└── signed_haps/ # 最新一个签名 HAP,与材料目录设置无关 +``` + +因此便携目录必须可写。源码 GUI 默认使用项目根目录,源码 CLI 默认使用当前工作 +目录下的 `signing_files//`。可以用 `HAPSIGN_DATA_DIR` 覆盖桌面版 +数据根目录,CLI 则可使用 `--work-dir`。已签名 HAP 会跳过签名流程,因此不会 +产生新的 `.p12`、`.cer`、`.p7b` 或签名后 HAP。 + +GUI 设置也可选择用户 `AppData Local` 或自定义签名目录。程序目录下的 +`hapsign-config.json` 保存这些设置,`logs/hapsign.log` 保存滚动诊断日志; +程序目录不可写时日志回退到用户本地数据目录。敏感诊断默认关闭,开启后配合 +DEBUG 级别可记录 token、用户标识和完整 API 请求/响应,但密钥库密码永不记录。 +最终签名 HAP 固定使用程序目录的 `signed_haps/`,默认只保留最新一个 HapSign +清单记录的产物,不会清理未记录的用户 HAP,当前输入文件也会受到保护;设置关闭 +保留后,任务使用系统临时目录并在结束时清理。 + +## 9. 跨平台注意事项 + +- `runtime.py` 已集中处理平台目录和可执行文件名;`toolchain.lock.json` 当前只 + 锁定并实测 Windows x64。 +- 新平台应增加独立锁定项、对应平台 Temurin 归档、OpenHarmony 工具包与真实设备 + 测试,不能复用 Windows 二进制。 +- macOS 发布通常还需要应用包、代码签名和 notarization;当前脚本只生成目录 + 和 ZIP。 +- Linux 需要在目标发行版或兼容的较旧发行版构建,并验证 Qt、USB 权限和 HDC。 +- 不要把一个平台的 Java runtime 或 HDC 复制进另一个平台的产物。 + +## 10. 发布前清单 + +- 工作区不包含 token、`.p12`、`.cer`、`.p7b`、UDID 或用户 HAP。 +- 发布 ZIP 不包含构建自检生成的 `logs/`、配置或运行数据目录。 +- 包根目录包含 `LICENSE`、`PRIVACY.md`、`THIRD_PARTY_NOTICES.md` 和 + `BUILDING.md`,`licenses/python/` 中存在冻结依赖的随附许可。 +- 工具链目录包含 `PROVENANCE.txt`、锁文件、OpenHarmony NOTICE、Temurin legal + 和 `licenses/libusb-source/`。 +- 全部测试和 Ruff 检查通过。 +- 正式包不是 `--skip-toolchain` 产物。 +- EXE 冒烟、Java、keytool 和 HDC 版本命令通过。 +- 至少用一台真实设备验证检测、签名和安装。 +- 确认 Temurin、HDC、签名工具和 libusb 的再分发条件。 +- 按 `docs/OPEN_SOURCE_RELEASE.md` 记录第三方组件的准确版本、来源和 SHA-256; +- 正式包不是 `--allow-deveco-toolchain` 回退产物。 +- 记录 ZIP 大小和 SHA-256。 + +## 11. 常见问题 + +### 提示工具链缺失 + +先运行 `python scripts/prepare_toolchain.py`。若已有下载,使用 +`--sdk-archive`/`--jdk-archive` 指向文件;校验失败时不要绕过锁文件。 + +### 包很大 + +主要空间来自 Playwright 浏览器控制驱动、Qt 和 Python 冻结运行时。不要直接删除 +DLL;应使用 +经过验证的精简方案,并重新执行完整登录、签名和安装测试。 + +替换前的 Windows 精简构建未压缩目录约 390 MiB、ZIP 约 187 MiB。公开工具链 +将 Java 从约 170 MiB JBR 换成约 48 MiB Temurin runtime,完整工具链约 66 MiB; +最终 ZIP 大小以本次构建结果为准。替换后的主要组成大致为: + +- Playwright 浏览器控制驱动:约 103 MiB +- PySide6/Qt:约 73 MiB +- Temurin `jlink` runtime:约 48 MiB +- hap-sign-tool:约 12 MiB +- HDC 及配套文件:约 5.5 MiB +- OpenHarmony NOTICE、libusb 对应源码及来源记录:约 1 MiB +- Python 运行时、requests 和应用代码等其余部分:约 28 MiB + +这些数字会随 Playwright、Qt、Temurin 和 OpenHarmony 版本变化。精简包通过复用 +系统 Edge/Chrome,兼容包则额外携带 Chromium。两种模式都由 +Playwright 控制浏览器并授予本地回调权限,非受控系统默认浏览器仅作为备用。 + +### 4K 屏幕字体模糊 + +Windows 包包含 Per-Monitor V2 manifest,GUI 也在创建 `QApplication` 前启用 +Qt 高 DPI 小数缩放。界面字体使用整数逻辑像素、Microsoft YaHei UI 和完整 +hinting,避免 200% 缩放下出现分数物理像素。若仍模糊,检查 `HapSign.exe` +兼容性设置中是否被手动启用了“替代高 DPI 缩放行为”,并关闭该覆盖。 diff --git a/hapsign/api/cert_api.py b/hapsign/api/cert_api.py index e402977..ba70019 100644 --- a/hapsign/api/cert_api.py +++ b/hapsign/api/cert_api.py @@ -21,7 +21,6 @@ API_CERT_LIST, API_REAPPLY_URL, API_USER_TEAM, - BASE_URL, HEADER_ACCESS_TOKEN, ) @@ -60,16 +59,17 @@ def get_team_id(self) -> str: def sign_agreement(self) -> bool: """签署未实名用户的开发者协议。 - POST {API_AGREEMENT},header 中携带 accessToken。 - 使用登录域名(BASE_URL),不是签名 API 域名。 + POST {API_AGREEMENT},header 中携带 accessToken(只发这一个认证头)。 + 走统一客户端封装(_build_url 的 /authrouter/ 路由 → 登录域名 BASE_URL, + 非签名 API 域名),从而获得取消检查、超时、脱敏诊断、HTTP 错误和 + token 失效(HTTP 401 / code=4000 → TokenExpiredError)处理。 Returns: 签署成功返回 True。 """ - url = f"{BASE_URL}{API_AGREEMENT}" headers = {HEADER_ACCESS_TOKEN: self._client.access_token} - resp = requests.post(url, headers=headers, timeout=30) - resp.raise_for_status() + text = self._client._do_post_form_text(API_AGREEMENT, headers) + logger.debug("agreement response: %s", text[:200]) return True def add_certificate( @@ -219,7 +219,9 @@ def download_file( raise ValueError(f"reapply URL 中未找到 newUrl: {urls_info}") # 第二步:直接下载(不需要认证 header) - resp = requests.get(new_url, timeout=60) + self._client._check_cancelled() + resp = requests.get(new_url, timeout=(5, 20)) + self._client._check_cancelled() resp.raise_for_status() with open(save_path, "wb") as f: diff --git a/hapsign/api/client.py b/hapsign/api/client.py index fae70ba..ccac48e 100644 --- a/hapsign/api/client.py +++ b/hapsign/api/client.py @@ -7,20 +7,29 @@ - code=4000 或 HTTP 401 表示 token 失效 """ +import logging +import threading from typing import Any +from urllib.parse import urlparse import requests +from hapsign.cancellation import raise_if_cancelled from hapsign.config import ( + ACCEPT_LANGUAGE, BASE_URL, CLOUD_BASE_URL, ERR_TOKEN_INVALID_CODE, - HEADER_ACCEPT_LANG, + HEADER_ACCEPT_LANGUAGE, HEADER_OAUTH2_TOKEN, HEADER_TEAM_ID, HEADER_UID, HEADER_USER_AGENT, + USER_AGENT, ) +from hapsign.diagnostics import sensitive_logging_enabled + +logger = logging.getLogger(__name__) class TokenExpiredError(Exception): @@ -38,6 +47,7 @@ def __init__( access_token: str, uid: str, base_url: str = CLOUD_BASE_URL, + cancel_event: threading.Event | None = None, ): """初始化客户端。 @@ -49,6 +59,10 @@ def __init__( self.access_token = access_token self.uid = uid self.base_url = base_url.rstrip("/") + self.cancel_event = cancel_event + + def _check_cancelled(self) -> None: + raise_if_cancelled(self.cancel_event) def _get_headers(self, team_id: str | None = None) -> dict[str, str]: """构造请求头。 @@ -62,8 +76,8 @@ def _get_headers(self, team_id: str | None = None) -> dict[str, str]: headers = { HEADER_OAUTH2_TOKEN: self.access_token, HEADER_UID: self.uid, - HEADER_USER_AGENT: "Chrome/49.0.2623.75", - HEADER_ACCEPT_LANG: "zh-CN", + HEADER_USER_AGENT: USER_AGENT, + HEADER_ACCEPT_LANGUAGE: ACCEPT_LANGUAGE, } if team_id is not None: headers[HEADER_TEAM_ID] = team_id @@ -110,6 +124,52 @@ def _check_response(self, resp: requests.Response) -> None: except (ValueError, AttributeError): pass + def _log_http( + self, + method: str, + url: str, + response: requests.Response, + *, + headers: dict[str, str], + payload: object = None, + ) -> None: + """记录足以定位接口变更的元数据;完整载荷由敏感开关控制。""" + parsed = urlparse(url) + response_keys: list[str] = [] + business_code: object = None + try: + decoded = response.json() + if isinstance(decoded, dict): + response_keys = sorted(str(key) for key in decoded) + ret = decoded.get("ret") + business_code = ( + ret.get("code") if isinstance(ret, dict) else decoded.get("code") + ) + except (ValueError, AttributeError): + pass + content = getattr(response, "content", b"") + response_size = len(content) if isinstance(content, (bytes, str)) else -1 + logger.debug( + "[api] %s %s://%s%s status=%d bytes=%d keys=%s code=%r", + method, + parsed.scheme, + parsed.netloc, + parsed.path, + response.status_code, + response_size, + response_keys, + business_code, + ) + if sensitive_logging_enabled(): + logger.debug( + "[api] sensitive %s %s headers=%r payload=%r response=%r", + method, + url, + headers, + payload, + response.text, + ) + def _do_get( self, url: str, @@ -118,7 +178,10 @@ def _do_get( ) -> dict[str, Any]: """执行 GET 请求并返回解析后的 JSON。""" full_url = self._build_url(url) - resp = requests.get(full_url, headers=headers, params=params, timeout=30) + self._check_cancelled() + resp = requests.get(full_url, headers=headers, params=params, timeout=(5, 15)) + self._check_cancelled() + self._log_http("GET", full_url, resp, headers=headers, payload=params) self._check_response(resp) resp.raise_for_status() return resp.json() @@ -131,7 +194,10 @@ def _do_post_form( ) -> dict[str, Any]: """执行 POST form-encoded 请求并返回解析后的 JSON。""" full_url = self._build_url(url) - resp = requests.post(full_url, headers=headers, data=params, timeout=30) + self._check_cancelled() + resp = requests.post(full_url, headers=headers, data=params, timeout=(5, 15)) + self._check_cancelled() + self._log_http("POST", full_url, resp, headers=headers, payload=params) self._check_response(resp) resp.raise_for_status() return resp.json() @@ -144,7 +210,10 @@ def _do_post_json( ) -> dict[str, Any]: """执行 POST JSON 请求并返回解析后的 JSON。""" full_url = self._build_url(url) - resp = requests.post(full_url, headers=headers, json=data, timeout=30) + self._check_cancelled() + resp = requests.post(full_url, headers=headers, json=data, timeout=(5, 15)) + self._check_cancelled() + self._log_http("POST", full_url, resp, headers=headers, payload=data) self._check_response(resp) resp.raise_for_status() return resp.json() @@ -157,7 +226,10 @@ def _do_delete( ) -> dict[str, Any]: """执行 DELETE 请求(带 JSON body)并返回解析后的 JSON。""" full_url = self._build_url(url) - resp = requests.delete(full_url, headers=headers, json=data, timeout=30) + self._check_cancelled() + resp = requests.delete(full_url, headers=headers, json=data, timeout=(5, 15)) + self._check_cancelled() + self._log_http("DELETE", full_url, resp, headers=headers, payload=data) self._check_response(resp) resp.raise_for_status() return resp.json() @@ -174,7 +246,10 @@ def _do_post_form_text( Java 通过 responseContent.contains('"code":0') 检查成功。 """ full_url = self._build_url(url) - resp = requests.post(full_url, headers=headers, data=params, timeout=30) + self._check_cancelled() + resp = requests.post(full_url, headers=headers, data=params, timeout=(5, 15)) + self._check_cancelled() + self._log_http("POST", full_url, resp, headers=headers, payload=params) self._check_response(resp) resp.raise_for_status() return resp.text @@ -187,7 +262,10 @@ def _do_delete_text( ) -> str: """执行 DELETE 请求(带 JSON body)并返回原始响应文本。""" full_url = self._build_url(url) - resp = requests.delete(full_url, headers=headers, json=data, timeout=30) + self._check_cancelled() + resp = requests.delete(full_url, headers=headers, json=data, timeout=(5, 15)) + self._check_cancelled() + self._log_http("DELETE", full_url, resp, headers=headers, payload=data) self._check_response(resp) resp.raise_for_status() return resp.text @@ -204,7 +282,10 @@ def _do_post_json_text( Java 通过 responseContent.contains('"code":0') 检查成功。 """ full_url = self._build_url(url) - resp = requests.post(full_url, headers=headers, json=data, timeout=30) + self._check_cancelled() + resp = requests.post(full_url, headers=headers, json=data, timeout=(5, 15)) + self._check_cancelled() + self._log_http("POST", full_url, resp, headers=headers, payload=data) self._check_response(resp) resp.raise_for_status() return resp.text diff --git a/hapsign/api/device_api.py b/hapsign/api/device_api.py index 7c0ec18..aa12a93 100644 --- a/hapsign/api/device_api.py +++ b/hapsign/api/device_api.py @@ -8,7 +8,32 @@ from typing import Any from hapsign.api.client import HuaweiSignClient -from hapsign.config import API_DEVICE_ADD, API_DEVICE_LIST, ERR_DEVICE_DUPLICATE +from hapsign.config import ( + API_DEVICE_ADD, + API_DEVICE_LIST, + ERR_DEVICE_DUPLICATE, + ERR_DEVICE_UDID_DUPLICATE, +) + +_DEVICE_ALREADY_EXISTS_CODES = { + str(ERR_DEVICE_DUPLICATE), + str(ERR_DEVICE_UDID_DUPLICATE), +} + + +def _is_device_already_exists(value: Any) -> bool: + text = str(value) + return any(code in text for code in _DEVICE_ALREADY_EXISTS_CODES) + + +def _extract_response_code(data: Any) -> Any: + """从签名 API 响应提取业务 code(顶层或 ret 嵌套)。""" + if not isinstance(data, dict): + return None + ret = data.get("ret") + if isinstance(ret, dict) and "code" in ret: + return ret.get("code") + return data.get("code") class DeviceAPI: @@ -34,7 +59,8 @@ def add_device( POST form {API_DEVICE_ADD} 参数:deviceName(自动生成)、udid(设备唯一标识)、deviceType(设备类型码)。 - 重复设备(错误码 205389857)视为成功。 + 设备名称重复(205389857)或 UDID 已注册(205389858)视为成功, + 后续从设备列表复用已有记录。 Args: udid: 设备 UDID(64 位十六进制字符串)。 @@ -61,14 +87,20 @@ def add_device( "deviceType": device_type, } try: - self._client._do_post_form(API_DEVICE_ADD, headers, params) + data = self._client._do_post_form(API_DEVICE_ADD, headers, params) except Exception as exc: - err_msg = str(exc) - if str(ERR_DEVICE_DUPLICATE) in err_msg: + # HTTP 层失败时仍兼容逆向场景:错误信息里可能直接带业务码 + if _is_device_already_exists(exc): return True raise - return True + # 200 响应也可能返回业务错误码(与 DevEco 行为一致) + code = _extract_response_code(data) + if code in (None, 0, "0"): + return True + if _is_device_already_exists(code): + return True + raise RuntimeError(f"add_device failed: {data}") def get_device_list(self, team_id: str) -> list[dict[str, Any]]: """查询已注册的设备列表。 diff --git a/hapsign/cancellation.py b/hapsign/cancellation.py new file mode 100644 index 0000000..671da78 --- /dev/null +++ b/hapsign/cancellation.py @@ -0,0 +1,15 @@ +"""跨线程任务取消原语。""" + +from __future__ import annotations + +import threading + + +class OperationCancelled(Exception): + """用户主动取消当前操作。""" + + +def raise_if_cancelled(cancel_event: threading.Event | None) -> None: + """取消信号已设置时立即中断当前流程。""" + if cancel_event is not None and cancel_event.is_set(): + raise OperationCancelled("操作已取消") diff --git a/hapsign/cli.py b/hapsign/cli.py index 482e75c..e97f794 100644 --- a/hapsign/cli.py +++ b/hapsign/cli.py @@ -42,7 +42,11 @@ def build_parser() -> argparse.ArgumentParser: hapsign --hap app.hap --bundle-name com.example.myapp """, ) - parser.add_argument("--hap", required=True, help="未签名的 HAP 文件路径") + parser.add_argument( + "--hap", + required=True, + help="HAP 文件路径(未签名则自动签名;已签名则直接安装)", + ) parser.add_argument( "--bundle-name", default=None, diff --git a/hapsign/config.py b/hapsign/config.py index 6fa5ace..8081068 100644 --- a/hapsign/config.py +++ b/hapsign/config.py @@ -4,6 +4,8 @@ import os import sys +from hapsign.runtime import discover_toolchain + # ── 域名 ────────────────────────────────────────────────────── # 登录/认证域名(从 IdeSystem.properties 逆向获得) BASE_URL = "https://devecostudio.huawei.com" @@ -39,6 +41,11 @@ # ── 登录参数 ────────────────────────────────────────────────── APP_ID = "1007" +# 登录协议兼容版本:temptoken/check 请求里的 version 参数。 +# 逆向自 DevEco Studio 5.0.5 的请求,服务端可能校验该字段;在没有真实接口 +# 回归证据前不要直接改成 hapsign.__version__(升级策略见 todo P1-9)。 +LOGIN_PROTOCOL_VERSION = "5.0.5" + # 国家码映射(从 HiAiLoginService 逆向获得) # siteId → countryCode: 1→CN, 5→SG, 7→DE, 8→RU SITE_ID_MAP = {"CN": "1", "SG": "5", "DE": "7", "RU": "8"} @@ -69,13 +76,14 @@ ERR_CERT_EXCEED_LIMIT = 205389872 ERR_DEVICE_EXCEED_LIMIT = 205389859 ERR_DEVICE_DUPLICATE = 205389857 +ERR_DEVICE_UDID_DUPLICATE = 205389858 ERR_PROVISION_EXCEED_LIMIT = 205389938 ERR_PROVISION_EXCEED_LIMIT_2 = 205389845 ERR_APP_ID_INVALID = 205389959 ERR_TOKEN_INVALID_CODE = 4000 # 响应 code=4000 表示 token 失效 -# ── 本机 SDK 路径(可通过环境变量覆盖)────────────────────── +# ── 本机 SDK 路径兼容解析(可通过环境变量覆盖)────────────── def default_deveco_home(platform: str | None = None) -> str: """返回当前平台的 DevEco Studio 默认安装根目录。""" plat = sys.platform if platform is None else platform @@ -120,7 +128,12 @@ def resolve_sdk_paths( _DEVECO_HOME = os.environ.get("DEVECO_HOME") or default_deveco_home() -DEVECO_JBR, HAP_SIGN_TOOL, HDC_PATH, KEYTOOL_PATH = resolve_sdk_paths(_DEVECO_HOME) +# ── 本机 / 便携版工具链路径(可通过环境变量覆盖)──────────── +_TOOLCHAIN = discover_toolchain() +DEVECO_JBR = str(_TOOLCHAIN.java) +KEYTOOL_PATH = str(_TOOLCHAIN.keytool) +HAP_SIGN_TOOL = str(_TOOLCHAIN.hap_sign_tool) +HDC_PATH = str(_TOOLCHAIN.hdc) # ── HTTP header 常量 ────────────────────────────────────────── HEADER_OAUTH2_TOKEN = "oauth2Token" @@ -129,8 +142,11 @@ def resolve_sdk_paths( HEADER_ACCESS_TOKEN = "accessToken" HEADER_JWT_TOKEN = "jwtToken" HEADER_REFRESH = "refresh" -HEADER_USER_AGENT = "Chrome/49.0.2623.75" -HEADER_ACCEPT_LANG = "zh-CN" +HEADER_USER_AGENT = "User-Agent" +HEADER_ACCEPT_LANGUAGE = "Accept-Language" +# DevEco 逆向请求里使用的 UA / Accept-Language 值 +USER_AGENT = "Chrome/49.0.2623.75" +ACCEPT_LANGUAGE = "zh-CN" # ── ACL 权限白名单(从 DevEco 6.1 生成的 p7b allowed-acls 提取)──── # 仅这些权限可通过 aclPermissionList 预授权,其余权限会被服务端拒绝 diff --git a/hapsign/diagnostics.py b/hapsign/diagnostics.py new file mode 100644 index 0000000..58cdfb2 --- /dev/null +++ b/hapsign/diagnostics.py @@ -0,0 +1,125 @@ +"""持久化诊断日志。""" + +from __future__ import annotations + +import logging +import re +from logging.handlers import RotatingFileHandler +from pathlib import Path + +from hapsign import __version__ +from hapsign.runtime import platform_tag +from hapsign.settings import AppSettings, log_directory, user_local_data_dir + +_HANDLER_MARKER = "_hapsign_file_handler" +_sensitive_logging_enabled = False + +_SENSITIVE_ASSIGNMENT_RE = re.compile( + r"(?i)(?P\b(?:temp[_-]?token|access[_-]?token|refresh[_-]?token|" + r"jwt[_-]?token|oauth2[_-]?token|keystore[_-]?password|password|passwd|" + r"secret|token|code)\b)" + r"(?P\s*['\"]?\s*[:=]\s*['\"]?)" + r"(?P[^&,\s}'\"\]\)]+)" +) +_AUTHORIZATION_RE = re.compile( + r"(?i)(?P\bauthorization\b)" + r"(?P\s*['\"]?\s*[:=]\s*['\"]?)" + r"(?PBearer\s+)?" + r"(?P[^&,\s}'\"\]\)]+)" +) +_BEARER_TOKEN_RE = re.compile(r"(?i)(?P\bBearer\s+)(?P[^\s,}'\"\]\)]+)") + + +def set_sensitive_logging(enabled: bool) -> None: + """控制是否允许诊断日志包含 token 及完整网络载荷。""" + global _sensitive_logging_enabled + _sensitive_logging_enabled = bool(enabled) + + +def sensitive_logging_enabled() -> bool: + """返回敏感诊断开关状态。""" + return _sensitive_logging_enabled + + +def redact_sensitive_text(value: object) -> str: + """脱敏异常文本中的令牌、密码和授权参数。 + + 敏感诊断开关是显式选择,开启后保留原始文本以便定位协议问题;默认日志 + 必须避免把认证信息写入控制台、文件或 GUI 日志窗口。 + """ + text = str(value) + if sensitive_logging_enabled(): + return text + + text = _AUTHORIZATION_RE.sub( + lambda match: ( + f"{match.group('key')}{match.group('separator')}" + f"{match.group('scheme') or ''}" + ), + text, + ) + text = _SENSITIVE_ASSIGNMENT_RE.sub( + lambda match: f"{match.group('key')}{match.group('separator')}", + text, + ) + return _BEARER_TOKEN_RE.sub( + lambda match: f"{match.group('prefix')}", + text, + ) + + +def configure_file_logging(settings: AppSettings) -> Path: + """配置滚动日志;程序目录不可写时回退到用户本地目录。""" + set_sensitive_logging(settings.log_sensitive_data) + root_logger = logging.getLogger() + for handler in list(root_logger.handlers): + if getattr(handler, _HANDLER_MARKER, False): + root_logger.removeHandler(handler) + handler.close() + + preferred = log_directory() + try: + preferred.mkdir(parents=True, exist_ok=True) + log_path = preferred / "hapsign.log" + handler = RotatingFileHandler( + log_path, + maxBytes=4 * 1024 * 1024, + backupCount=3, + encoding="utf-8", + ) + except OSError: + fallback = user_local_data_dir() / "logs" + fallback.mkdir(parents=True, exist_ok=True) + log_path = fallback / "hapsign.log" + handler = RotatingFileHandler( + log_path, + maxBytes=4 * 1024 * 1024, + backupCount=3, + encoding="utf-8", + ) + + setattr(handler, _HANDLER_MARKER, True) + handler.setLevel(getattr(logging, settings.log_level)) + handler.setFormatter( + logging.Formatter( + "%(asctime)s.%(msecs)03d %(levelname)-7s " + "[%(threadName)s] %(name)s: %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + ) + root_logger.addHandler(handler) + # 由各 handler 决定输出级别;根 logger 保留 DEBUG 才能让文件级别即时生效。 + root_logger.setLevel(logging.DEBUG) + logging.captureWarnings(True) + logging.getLogger(__name__).info( + "HapSign 启动:version=%s platform=%s log_level=%s sensitive=%s", + __version__, + platform_tag(), + settings.log_level, + settings.log_sensitive_data, + ) + if settings.log_sensitive_data: + logging.getLogger(__name__).warning( + "敏感诊断已开启:日志可能包含 token、用户标识及完整 API 请求/响应" + ) + return log_path diff --git a/hapsign/gui.py b/hapsign/gui.py new file mode 100644 index 0000000..ef4bcfe --- /dev/null +++ b/hapsign/gui.py @@ -0,0 +1,1474 @@ +"""HapSign 桌面应用。 + +桌面层只负责交互与后台调度,签名逻辑继续复用 :class:`SignPipeline`。 +""" + +from __future__ import annotations + +import logging +import os +import sys +import threading +from pathlib import Path + +# Qt 6 默认支持高 DPI;在导入 Qt 前显式启用并保留小数缩放比例, +# 避免 Windows 在 125%/150%/4K 屏幕上退回位图拉伸。 +os.environ.setdefault("QT_ENABLE_HIGHDPI_SCALING", "1") +os.environ.setdefault("QT_SCALE_FACTOR_ROUNDING_POLICY", "PassThrough") + +from PySide6.QtCore import ( + QObject, + QPointF, + QRectF, + QSize, + Qt, + QThread, + QTimer, + QUrl, + Signal, + Slot, +) +from PySide6.QtGui import ( + QColor, + QDesktopServices, + QDragEnterEvent, + QDropEvent, + QFont, + QFontDatabase, + QIcon, + QLinearGradient, + QMouseEvent, + QPainter, + QPainterPath, + QPen, + QPixmap, +) +from PySide6.QtWidgets import ( + QApplication, + QCheckBox, + QComboBox, + QDialog, + QDialogButtonBox, + QFileDialog, + QFormLayout, + QFrame, + QHBoxLayout, + QLabel, + QLineEdit, + QMainWindow, + QMessageBox, + QPlainTextEdit, + QProgressBar, + QProxyStyle, + QPushButton, + QSizePolicy, + QSpacerItem, + QStyle, + QStyledItemDelegate, + QStyleOptionViewItem, + QVBoxLayout, + QWidget, +) + +from hapsign import __version__ +from hapsign.cancellation import OperationCancelled +from hapsign.cli import detect_bundle_name +from hapsign.diagnostics import configure_file_logging +from hapsign.login.browser_login import playwright_browser_smoke_test +from hapsign.pipeline import SignPipeline +from hapsign.runtime import ( + discover_toolchain, + platform_tag, +) +from hapsign.settings import ( + AppSettings, + config_file_path, + load_settings, + save_settings, + signed_haps_dir, + signing_files_dir, +) +from hapsign.signing.hap_inspect import is_hap_signed +from hapsign.signing.installer import Installer + +LOGGER = logging.getLogger(__name__) + +WINDOW_STYLE = """ +QWidget { + color: #18212f; + font-size: 14px; +} +QWidget#root { + background: #f4f6fa; +} +QFrame#card { + background: #ffffff; + border: 1px solid #e6eaf0; + border-radius: 18px; +} +QFrame#dropZone { + background: #fafbfe; + border: 2px dashed #cbd3df; + border-radius: 16px; +} +QFrame#dropZone:hover, QFrame#dropZone[active="true"] { + background: #f3f6ff; + border-color: #5a6ff0; +} +QLabel#title { + color: #111827; + font-size: 28px; + font-weight: 700; +} +QLabel#subtitle { + color: #657084; + font-size: 14px; +} +QLabel#dropTitle { + color: #18212f; + font-size: 18px; + font-weight: 650; +} +QLabel#muted, QLabel#fileMeta, QLabel#logTitle { + color: #778195; + font-size: 13px; +} +QLabel#fileName { + color: #172033; + font-size: 16px; + font-weight: 650; +} +QLabel#status { + color: #536078; + font-size: 14px; +} +QLabel#chip { + color: #526075; + background: #e9edf5; + border-radius: 11px; + padding: 3px 9px; + font-size: 12px; + font-weight: 600; +} +QLabel#signedChip { + color: #237353; + background: #e6f6ee; + border-radius: 11px; + padding: 3px 9px; + font-size: 12px; + font-weight: 650; +} +QLabel#unsignedChip { + color: #8a5b14; + background: #fff2d9; + border-radius: 11px; + padding: 3px 9px; + font-size: 12px; + font-weight: 650; +} +QPushButton { + min-height: 42px; + border-radius: 11px; + padding: 0 18px; + font-weight: 650; +} +QPushButton#primaryButton { + color: #ffffff; + background: #4459dc; + border: 1px solid #4459dc; +} +QPushButton#primaryButton:hover { + background: #384cc9; + border-color: #384cc9; +} +QPushButton#primaryButton:pressed { + background: #3043b7; +} +QPushButton#primaryButton:disabled { + color: #a8afbc; + background: #e6e9ef; + border-color: #e6e9ef; +} +QPushButton#secondaryButton { + color: #334155; + background: #ffffff; + border: 1px solid #d9dee8; +} +QPushButton#secondaryButton:hover { + background: #f7f8fb; + border-color: #c8cfdb; +} +QPushButton#cancelButton { + color: #a33c48; + background: #fff7f8; + border: 1px solid #efcbd0; +} +QPushButton#cancelButton:hover { + color: #8f2f3b; + background: #fdecef; + border-color: #e9aeb7; +} +QPushButton#removeButton { + color: #7b8495; + background: transparent; + border: none; + border-radius: 16px; + min-width: 32px; + max-width: 32px; + min-height: 32px; + max-height: 32px; + padding: 0; + font-size: 24px; + font-weight: 400; +} +QPushButton#removeButton:hover { + color: #b33f4a; + background: #fdecef; +} +QPushButton#removeButton:pressed { + background: #f9dce1; +} +QProgressBar { + min-height: 18px; + max-height: 18px; + border: none; + border-radius: 9px; + background: #e8ebf2; + color: #ffffff; + font-size: 12px; + font-weight: 650; + text-align: center; +} +QProgressBar::chunk { + border-radius: 9px; + background: #5367e8; +} +QPlainTextEdit { + color: #445066; + background: #f7f8fb; + border: 1px solid #e7eaf0; + border-radius: 11px; + padding: 10px; + font-size: 13px; + selection-background-color: #cdd5ff; +} +QDialog { + background: #f7f8fb; +} +QDialog QLabel#settingsTitle { + color: #111827; + font-size: 22px; + font-weight: 700; +} +QDialog QLabel#settingsSubtitle { + color: #778195; + font-size: 13px; +} +QFrame#settingsSection { + background: #ffffff; + border: 1px solid #e5e9f0; + border-radius: 14px; +} +QLabel#sectionTitle { + color: #263247; + font-size: 14px; + font-weight: 700; +} +QLabel#warning { + color: #966114; + background: #fff8e8; + border: 1px solid #f1dfb4; + border-radius: 9px; + padding: 7px 10px; + font-size: 13px; +} +QComboBox, QLineEdit { + min-height: 40px; + color: #243047; + background: #ffffff; + border: 1px solid #d9dee8; + border-radius: 10px; + padding: 0 13px; + selection-background-color: #dfe4ff; +} +QComboBox:hover, QLineEdit:hover { + border-color: #aeb9ca; +} +QComboBox:focus, QLineEdit:focus { + border: 1px solid #6072e8; +} +QComboBox { + padding-right: 48px; +} +QComboBox::drop-down { + subcontrol-origin: padding; + subcontrol-position: top right; + width: 42px; + background: #f7f8fc; + border: none; + border-left: 1px solid #e2e6ee; + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; +} +QComboBox::drop-down:hover { + background: #eef1fb; +} +QComboBox::down-arrow { + image: none; + width: 16px; + height: 16px; +} +QComboBox QAbstractItemView { + color: #243047; + background: #ffffff; + border: 1px solid #d8dee9; + border-radius: 10px; + padding: 6px; + outline: none; + selection-color: #263baf; + selection-background-color: #edf0ff; +} +QComboBox QAbstractItemView::item { + min-height: 38px; + padding: 0 12px; + border-radius: 7px; +} +QComboBox QAbstractItemView::item:selected { + color: #263baf; + background: #edf0ff; + border: none; +} +QLineEdit:disabled { + color: #98a2b3; + background: #f3f5f8; + border-color: #e4e8ef; +} +QCheckBox { + min-height: 24px; + spacing: 10px; +} +QCheckBox::indicator { + width: 18px; + height: 18px; +} +QDialogButtonBox QPushButton { + min-width: 92px; +} +QScrollBar:vertical { + background: transparent; + width: 12px; + margin: 5px 2px; +} +QScrollBar::handle:vertical { + background: #c7cedb; + min-height: 32px; + border-radius: 4px; +} +QScrollBar::handle:vertical:hover { + background: #9da8bb; +} +QScrollBar::handle:vertical:pressed { + background: #7e8aa0; +} +QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0; + background: transparent; +} +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: transparent; +} +QScrollBar:horizontal { + background: transparent; + height: 12px; + margin: 2px 5px; +} +QScrollBar::handle:horizontal { + background: #c7cedb; + min-width: 32px; + border-radius: 4px; +} +QScrollBar::handle:horizontal:hover { + background: #9da8bb; +} +QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { + width: 0; + background: transparent; +} +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { + background: transparent; +} +""" + + +def _human_size(size: int) -> str: + value = float(size) + for unit in ("B", "KB", "MB", "GB"): + if value < 1024 or unit == "GB": + if unit == "B": + return f"{int(value)} {unit}" + return f"{value:.1f} {unit}" + value /= 1024 + return f"{size} B" + + +def _make_app_icon(size: int = 128) -> QIcon: + pixmap = QPixmap(size, size) + pixmap.fill(Qt.GlobalColor.transparent) + + painter = QPainter(pixmap) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + rect = QRectF(8, 8, size - 16, size - 16) + gradient = QLinearGradient(rect.topLeft(), rect.bottomRight()) + gradient.setColorAt(0.0, QColor("#6477f2")) + gradient.setColorAt(1.0, QColor("#3548c7")) + painter.setBrush(gradient) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawRoundedRect(rect, 28, 28) + + path = QPainterPath() + path.moveTo(size * 0.31, size * 0.36) + path.lineTo(size * 0.50, size * 0.25) + path.lineTo(size * 0.69, size * 0.36) + path.lineTo(size * 0.69, size * 0.64) + path.lineTo(size * 0.50, size * 0.75) + path.lineTo(size * 0.31, size * 0.64) + path.closeSubpath() + painter.setBrush(QColor("#ffffff")) + painter.drawPath(path) + + painter.setPen(QPen(QColor("#5367e8"), max(2, size // 32))) + painter.drawLine( + QPointF(size * 0.39, size * 0.47), + QPointF(size * 0.61, size * 0.47), + ) + painter.drawLine( + QPointF(size * 0.39, size * 0.56), + QPointF(size * 0.56, size * 0.56), + ) + painter.end() + return QIcon(pixmap) + + +class HapSignStyle(QProxyStyle): + """在 Fusion 基础上绘制与界面一致的箭头和复选框。""" + + def drawPrimitive(self, element, option, painter, widget=None) -> None: + if element == QStyle.PrimitiveElement.PE_IndicatorArrowDown: + painter.save() + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + center = option.rect.center() + pen = QPen(QColor("#667085"), 1.8) + pen.setCapStyle(Qt.PenCapStyle.RoundCap) + pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) + painter.setPen(pen) + path = QPainterPath() + path.moveTo(center.x() - 4.5, center.y() - 2) + path.lineTo(center.x(), center.y() + 2.5) + path.lineTo(center.x() + 4.5, center.y() - 2) + painter.drawPath(path) + painter.restore() + return + + if element == QStyle.PrimitiveElement.PE_IndicatorCheckBox: + painter.save() + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + rect = QRectF(option.rect).adjusted(1, 1, -1, -1) + enabled = bool(option.state & QStyle.StateFlag.State_Enabled) + checked = bool(option.state & QStyle.StateFlag.State_On) + hovered = bool(option.state & QStyle.StateFlag.State_MouseOver) + if checked: + fill = QColor("#4459dc" if enabled else "#aab3dd") + border = QColor("#4459dc" if enabled else "#aab3dd") + else: + fill = QColor("#f8f9fc" if enabled else "#f0f2f5") + border = QColor("#aeb8c8" if hovered else "#c7ced9") + painter.setBrush(fill) + painter.setPen(QPen(border, 1.2)) + painter.drawRoundedRect(rect, 4, 4) + if checked: + pen = QPen(QColor("#ffffff"), 1.8) + pen.setCapStyle(Qt.PenCapStyle.RoundCap) + pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) + painter.setPen(pen) + check = QPainterPath() + check.moveTo(rect.left() + 4.0, rect.center().y()) + check.lineTo(rect.left() + 7.2, rect.bottom() - 4.2) + check.lineTo(rect.right() - 3.5, rect.top() + 4.1) + painter.drawPath(check) + painter.restore() + return + + super().drawPrimitive(element, option, painter, widget) + + +def _application_font() -> QFont: + """选择高质量中文 UI 字体,并让笔画尽量贴合物理像素网格。""" + available = set(QFontDatabase.families()) + preferred = ( + ("Microsoft YaHei UI", "Microsoft YaHei", "Segoe UI") + if platform_tag() == "windows" + else ("PingFang SC", "Noto Sans CJK SC", "Noto Sans") + ) + family = next( + (candidate for candidate in preferred if candidate in available), + QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont).family(), + ) + font = QFont(family) + # 整数逻辑像素避免 10pt -> 13.333px 在 200% DPI 下形成分数物理像素。 + font.setPixelSize(14) + font.setHintingPreference(QFont.HintingPreference.PreferFullHinting) + font.setStyleStrategy( + QFont.StyleStrategy.PreferAntialias | QFont.StyleStrategy.PreferQuality + ) + return font + + +def _fixed_width_font() -> QFont: + available = set(QFontDatabase.families()) + family = next( + ( + candidate + for candidate in ("Cascadia Mono", "Consolas", "Microsoft YaHei UI") + if candidate in available + ), + QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont).family(), + ) + font = QFont(family) + font.setPixelSize(13) + font.setHintingPreference(QFont.HintingPreference.PreferFullHinting) + font.setStyleStrategy( + QFont.StyleStrategy.PreferAntialias | QFont.StyleStrategy.PreferQuality + ) + return font + + +class ComboItemDelegate(QStyledItemDelegate): + def paint(self, painter, option, index) -> None: + clean_option = QStyleOptionViewItem(option) + clean_option.state &= ~QStyle.StateFlag.State_HasFocus + super().paint(painter, clean_option, index) + + +class StyledComboBox(QComboBox): + """使用程序自己的抗锯齿箭头,避免平台原生方框样式混入。""" + + def __init__(self, parent: QWidget | None = None) -> None: + super().__init__(parent) + self.setItemDelegate(ComboItemDelegate(self)) + + def paintEvent(self, event) -> None: + super().paintEvent(event) + painter = QPainter(self) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) + center = QPointF(self.width() - 21.0, self.height() / 2.0) + pen = QPen(QColor("#667085"), 1.8) + pen.setCapStyle(Qt.PenCapStyle.RoundCap) + pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) + painter.setPen(pen) + path = QPainterPath() + path.moveTo(center.x() - 4.5, center.y() - 2.0) + path.lineTo(center.x(), center.y() + 2.5) + path.lineTo(center.x() + 4.5, center.y() - 2.0) + painter.drawPath(path) + + +class DropZone(QFrame): + """支持点击与 HAP 文件拖放的选择区域。""" + + choose_requested = Signal() + file_dropped = Signal(str) + + def __init__(self) -> None: + super().__init__() + self.setObjectName("dropZone") + self.setAcceptDrops(True) + self.setCursor(Qt.CursorShape.PointingHandCursor) + self.setMinimumHeight(190) + self.setSizePolicy( + QSizePolicy.Policy.Expanding, + QSizePolicy.Policy.Fixed, + ) + + layout = QVBoxLayout(self) + layout.setContentsMargins(28, 25, 28, 25) + layout.setSpacing(8) + layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + + badge = QLabel("HAP") + badge.setAlignment(Qt.AlignmentFlag.AlignCenter) + badge.setFixedSize(58, 58) + badge.setStyleSheet( + "color: #4459dc; background: #e9edff; border-radius: 16px;" + "font-size: 14px; font-weight: 750;" + ) + title = QLabel("拖入 HAP 文件") + title.setObjectName("dropTitle") + title.setAlignment(Qt.AlignmentFlag.AlignCenter) + hint = QLabel("或点击这里从电脑中选择") + hint.setObjectName("muted") + hint.setAlignment(Qt.AlignmentFlag.AlignCenter) + + layout.addWidget(badge, 0, Qt.AlignmentFlag.AlignCenter) + layout.addWidget(title) + layout.addWidget(hint) + + def _set_active(self, active: bool) -> None: + self.setProperty("active", active) + self.style().unpolish(self) + self.style().polish(self) + + def mousePressEvent(self, event: QMouseEvent) -> None: + if event.button() == Qt.MouseButton.LeftButton: + self.choose_requested.emit() + super().mousePressEvent(event) + + def dragEnterEvent(self, event: QDragEnterEvent) -> None: + urls = event.mimeData().urls() + if any(url.toLocalFile().lower().endswith(".hap") for url in urls): + event.acceptProposedAction() + self._set_active(True) + return + event.ignore() + + def dragLeaveEvent(self, event) -> None: + self._set_active(False) + super().dragLeaveEvent(event) + + def dropEvent(self, event: QDropEvent) -> None: + self._set_active(False) + for url in event.mimeData().urls(): + path = url.toLocalFile() + if path.lower().endswith(".hap"): + self.file_dropped.emit(path) + event.acceptProposedAction() + return + event.ignore() + + +class PipelineLogHandler(logging.Handler): + """将后台线程日志转发到 Qt 信号。""" + + def __init__(self, callback) -> None: + super().__init__(logging.INFO) + self.callback = callback + self.last_error = "" + + def emit(self, record: logging.LogRecord) -> None: + message = record.getMessage() + if record.levelno >= logging.ERROR: + self.last_error = message + self.callback(message) + + +class PipelineWorker(QObject): + """在后台线程运行签名安装流程。""" + + log_message = Signal(str) + progress_changed = Signal(int, str) + finished = Signal(bool, bool, str) + + def __init__( + self, + hap_path: str, + state_dir: Path, + browser_mode: str, + keep_signed_hap: bool, + ) -> None: + super().__init__() + self.hap_path = hap_path + self.state_dir = state_dir + self.browser_mode = browser_mode + self.keep_signed_hap = keep_signed_hap + self.cancel_event = threading.Event() + + def request_cancel(self) -> None: + """可从 GUI 线程直接调用;Event 是线程安全的。""" + self.cancel_event.set() + + @Slot() + def run(self) -> None: + root_logger = logging.getLogger() + handler = PipelineLogHandler(self.log_message.emit) + root_logger.addHandler(handler) + + try: + bundle_name = detect_bundle_name(self.hap_path) + signed = is_hap_signed(self.hap_path) + toolchain = discover_toolchain() + missing = toolchain.missing(require_signing=not signed) + if missing: + details = "\n".join(f"• {item}" for item in missing) + raise RuntimeError(f"便携工具链不完整:\n{details}") + + self.log_message.emit(f"应用包:{bundle_name}") + self.log_message.emit(f"工具来源:{toolchain.source}") + LOGGER.info( + "开始桌面流程:hap=%s state_dir=%s browser=%s", + self.hap_path, + self.state_dir, + self.browser_mode, + ) + pipeline = SignPipeline( + hap_path=self.hap_path, + bundle_name=bundle_name, + state_dir=str(self.state_dir), + browser_mode=self.browser_mode, + signed_output_dir=str(signed_haps_dir()), + keep_signed_hap=self.keep_signed_hap, + cancel_event=self.cancel_event, + progress_callback=self.progress_changed.emit, + ) + if pipeline.run(): + self.finished.emit(True, False, "HAP 已成功安装到设备") + elif self.cancel_event.is_set(): + self.finished.emit(False, True, "任务已取消") + else: + message = handler.last_error or "签名或安装没有完成,请查看运行记录" + self.finished.emit(False, False, message) + except OperationCancelled: + self.log_message.emit("任务已取消,清理工作已完成") + self.finished.emit(False, True, "任务已取消") + except Exception as exc: + if self.cancel_event.is_set(): + self.finished.emit(False, True, "任务已取消") + else: + LOGGER.exception("桌面流程失败") + self.finished.emit(False, False, str(exc)) + finally: + root_logger.removeHandler(handler) + + +class DeviceCheckWorker(QObject): + """在后台确认 HDC 工具和设备连接状态。""" + + progress_changed = Signal(int, str) + finished = Signal(bool, bool, str) + + def __init__(self) -> None: + super().__init__() + self.cancel_event = threading.Event() + + def request_cancel(self) -> None: + self.cancel_event.set() + + @Slot() + def run(self) -> None: + try: + self.progress_changed.emit(15, "正在检查 HDC 工具") + toolchain = discover_toolchain() + missing = toolchain.missing(require_signing=False) + if missing: + details = "\n".join(f"• {item}" for item in missing) + raise RuntimeError(f"HDC 工具不可用:\n{details}") + self.progress_changed.emit(55, "正在查询连接设备") + with Installer(cancel_event=self.cancel_event) as installer: + udid = installer.get_udid() + self.finished.emit( + True, + False, + f"设备连接正常(UDID 尾号 {udid[-6:]})", + ) + except OperationCancelled: + self.finished.emit(False, True, "设备检测已取消") + except Exception as exc: + if self.cancel_event.is_set(): + self.finished.emit(False, True, "设备检测已取消") + else: + self.finished.emit(False, False, str(exc)) + + +def _open_local_directory(parent: QWidget, path: Path, title: str) -> None: + try: + path.mkdir(parents=True, exist_ok=True) + except OSError as exc: + QMessageBox.warning(parent, f"无法打开{title}", str(exc)) + return + if not QDesktopServices.openUrl(QUrl.fromLocalFile(str(path.resolve()))): + QMessageBox.warning(parent, f"无法打开{title}", f"目录位置:\n{path}") + + +class SettingsDialog(QDialog): + """桌面版持久化设置。""" + + def __init__( + self, + settings: AppSettings, + log_path: Path, + parent: QWidget | None = None, + ) -> None: + super().__init__(parent) + self._settings = settings + self._log_path = log_path + self.setWindowTitle("HapSign 设置") + self.setMinimumWidth(610) + + layout = QVBoxLayout(self) + layout.setContentsMargins(24, 20, 24, 18) + layout.setSpacing(11) + + title = QLabel("设置") + title.setObjectName("settingsTitle") + layout.addWidget(title) + basic_section = QFrame() + basic_section.setObjectName("settingsSection") + basic_layout = QVBoxLayout(basic_section) + basic_layout.setContentsMargins(16, 13, 16, 15) + basic_layout.setSpacing(9) + basic_title = QLabel("登录与签名材料") + basic_title.setObjectName("sectionTitle") + basic_layout.addWidget(basic_title) + form = QFormLayout() + form.setHorizontalSpacing(18) + form.setVerticalSpacing(9) + form.setLabelAlignment( + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter + ) + form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) + + self.browser_combo = StyledComboBox() + self.browser_combo.addItem( + "系统 Edge / Chrome(受控,推荐)", + "system_controlled", + ) + self.browser_combo.addItem("内置 Chromium(兼容模式)", "playwright") + self.browser_combo.addItem("系统默认浏览器(非受控备用)", "system") + self.browser_combo.setSizePolicy( + QSizePolicy.Policy.Expanding, + QSizePolicy.Policy.Fixed, + ) + self._select_data(self.browser_combo, settings.browser_mode) + form.addRow("登录浏览器", self.browser_combo) + + self.storage_combo = StyledComboBox() + self.storage_combo.addItem("程序目录(便携)", "program") + self.storage_combo.addItem("用户 AppData Local", "appdata") + self.storage_combo.addItem("自定义目录", "custom") + self.storage_combo.setSizePolicy( + QSizePolicy.Policy.Expanding, + QSizePolicy.Policy.Fixed, + ) + self._select_data(self.storage_combo, settings.signing_storage) + self.storage_combo.currentIndexChanged.connect(self._update_custom_state) + form.addRow("签名文件位置", self.storage_combo) + + custom_row = QWidget() + custom_layout = QHBoxLayout(custom_row) + custom_layout.setContentsMargins(0, 0, 0, 0) + custom_layout.setSpacing(8) + self.custom_path = QLineEdit(settings.custom_signing_dir) + self.custom_path.setPlaceholderText("选择一个用于保存签名材料的目录") + self.custom_browse_button = QPushButton("浏览") + self.custom_browse_button.setObjectName("secondaryButton") + self.custom_browse_button.clicked.connect(self._choose_custom_directory) + custom_layout.addWidget(self.custom_path, 1) + custom_layout.addWidget(self.custom_browse_button) + form.addRow("自定义目录", custom_row) + basic_layout.addLayout(form) + layout.addWidget(basic_section) + + diagnostic_section = QFrame() + diagnostic_section.setObjectName("settingsSection") + diagnostic_layout = QVBoxLayout(diagnostic_section) + diagnostic_layout.setContentsMargins(16, 13, 16, 14) + diagnostic_layout.setSpacing(8) + diagnostic_title = QLabel("日志与签名产物") + diagnostic_title.setObjectName("sectionTitle") + diagnostic_layout.addWidget(diagnostic_title) + log_row = QHBoxLayout() + log_row.setSpacing(18) + log_label = QLabel("日志级别") + log_label.setMinimumWidth(104) + self.log_level_combo = StyledComboBox() + for level in ("DEBUG", "INFO", "WARNING", "ERROR"): + self.log_level_combo.addItem(level, level) + self._select_data(self.log_level_combo, settings.log_level) + log_row.addWidget(log_label) + log_row.addWidget(self.log_level_combo, 1) + diagnostic_layout.addLayout(log_row) + + self.sensitive_check = QCheckBox( + "记录敏感诊断信息(可能包含 token、用户标识及完整 API 请求/响应)" + ) + self.sensitive_check.setChecked(settings.log_sensitive_data) + diagnostic_layout.addWidget(self.sensitive_check) + warning = QLabel("仅 DEBUG 记录敏感内容;签名库密码永不记录,排障后请关闭。") + warning.setObjectName("warning") + warning.setWordWrap(True) + diagnostic_layout.addWidget(warning) + self.keep_signed_check = QCheckBox("保留最新一个签名后的 HAP") + self.keep_signed_check.setChecked(settings.keep_signed_hap) + diagnostic_layout.addWidget(self.keep_signed_check) + signed_hint = QLabel( + "目录:程序目录 / signed_haps;新文件成功后自动清理旧 HAP。" + ) + signed_hint.setObjectName("muted") + signed_hint.setWordWrap(True) + signed_hint.setToolTip(str(signed_haps_dir())) + diagnostic_layout.addWidget(signed_hint) + layout.addWidget(diagnostic_section) + + data_section = QFrame() + data_section.setObjectName("settingsSection") + data_layout = QVBoxLayout(data_section) + data_layout.setContentsMargins(16, 13, 16, 14) + data_layout.setSpacing(8) + data_title = QLabel("数据位置") + data_title.setObjectName("sectionTitle") + data_layout.addWidget(data_title) + directory_actions = QHBoxLayout() + directory_actions.setSpacing(9) + open_signing = QPushButton("打开签名目录") + open_signing.setObjectName("secondaryButton") + open_signing.setToolTip(str(signing_files_dir(settings))) + open_signing.clicked.connect(self._open_selected_signing_directory) + open_logs = QPushButton("打开日志目录") + open_logs.setObjectName("secondaryButton") + open_logs.setToolTip(str(self._log_path.parent)) + open_logs.clicked.connect( + lambda: _open_local_directory(self, self._log_path.parent, "日志目录") + ) + open_signed = QPushButton("打开签名 HAP 目录") + open_signed.setObjectName("secondaryButton") + open_signed.setToolTip(str(signed_haps_dir())) + open_signed.clicked.connect( + lambda: _open_local_directory( + self, + signed_haps_dir(), + "签名 HAP 目录", + ) + ) + directory_actions.addWidget(open_signing) + directory_actions.addWidget(open_signed) + directory_actions.addWidget(open_logs) + data_layout.addLayout(directory_actions) + data_section.setToolTip(f"配置文件:{config_file_path()}") + layout.addWidget(data_section) + + buttons = QDialogButtonBox( + QDialogButtonBox.StandardButton.Save + | QDialogButtonBox.StandardButton.Cancel + ) + save_button = buttons.button(QDialogButtonBox.StandardButton.Save) + save_button.setText("保存") + save_button.setObjectName("primaryButton") + cancel_button = buttons.button(QDialogButtonBox.StandardButton.Cancel) + cancel_button.setText("取消") + cancel_button.setObjectName("secondaryButton") + buttons.accepted.connect(self._validate_and_accept) + buttons.rejected.connect(self.reject) + layout.addWidget(buttons) + self._update_custom_state() + + @staticmethod + def _select_data(combo: QComboBox, value: str) -> None: + index = combo.findData(value) + combo.setCurrentIndex(max(0, index)) + + @Slot() + def _update_custom_state(self) -> None: + enabled = self.storage_combo.currentData() == "custom" + self.custom_path.setEnabled(enabled) + self.custom_browse_button.setEnabled(enabled) + + @Slot() + def _choose_custom_directory(self) -> None: + initial = self.custom_path.text().strip() or str(Path.home()) + selected = QFileDialog.getExistingDirectory( + self, + "选择签名文件目录", + initial, + ) + if selected: + self.custom_path.setText(selected) + + def result_settings(self) -> AppSettings: + return AppSettings( + log_level=str(self.log_level_combo.currentData()), + signing_storage=str(self.storage_combo.currentData()), + custom_signing_dir=self.custom_path.text().strip(), + browser_mode=str(self.browser_combo.currentData()), + log_sensitive_data=self.sensitive_check.isChecked(), + keep_signed_hap=self.keep_signed_check.isChecked(), + ) + + @Slot() + def _open_selected_signing_directory(self) -> None: + settings = self.result_settings() + if settings.signing_storage == "custom" and not settings.custom_signing_dir: + QMessageBox.warning(self, "尚未选择目录", "请先选择一个自定义目录。") + return + _open_local_directory( + self, + signing_files_dir(settings), + "签名目录", + ) + + @Slot() + def _validate_and_accept(self) -> None: + if ( + self.storage_combo.currentData() == "custom" + and not self.custom_path.text().strip() + ): + QMessageBox.warning(self, "无法保存", "自定义签名目录不能为空。") + return + self.accept() + + +class MainWindow(QMainWindow): + """HapSign 主窗口。""" + + def __init__( + self, + settings: AppSettings | None = None, + log_path: Path | None = None, + ) -> None: + super().__init__() + self.settings = settings or load_settings() + self.log_path = log_path or configure_file_logging(self.settings) + self.selected_path: Path | None = None + self.worker_thread: QThread | None = None + self.worker: QObject | None = None + self._close_after_cancel = False + self._cancel_requested = False + + self.setWindowTitle("HapSign") + self.setWindowIcon(_make_app_icon()) + self.resize(820, 690) + self.setMinimumSize(QSize(720, 620)) + + root = QWidget() + root.setObjectName("root") + self.setCentralWidget(root) + outer = QVBoxLayout(root) + outer.setContentsMargins(42, 34, 42, 34) + outer.setSpacing(22) + outer.addLayout(self._build_header()) + + card = QFrame() + card.setObjectName("card") + card_layout = QVBoxLayout(card) + card_layout.setContentsMargins(26, 26, 26, 24) + card_layout.setSpacing(18) + + file_layout = QHBoxLayout() + file_layout.setSpacing(14) + self.drop_zone = DropZone() + self.drop_zone.choose_requested.connect(self._choose_file) + self.drop_zone.file_dropped.connect(self._load_file) + file_layout.addWidget(self.drop_zone, 1) + + self.file_panel = self._build_file_panel() + self.file_panel.hide() + file_layout.addWidget(self.file_panel) + card_layout.addLayout(file_layout) + + action_layout = QHBoxLayout() + action_layout.setSpacing(10) + self.choose_button = QPushButton("选择 HAP") + self.choose_button.setObjectName("secondaryButton") + self.choose_button.clicked.connect(self._choose_file) + self.check_button = QPushButton("检测设备") + self.check_button.setObjectName("secondaryButton") + self.check_button.clicked.connect(self._check_device) + self.start_button = QPushButton("开始签名并安装") + self.start_button.setObjectName("primaryButton") + self.start_button.setEnabled(False) + self.start_button.clicked.connect(self._start) + self.cancel_button = QPushButton("取消") + self.cancel_button.setObjectName("cancelButton") + self.cancel_button.clicked.connect(self._cancel_active_task) + self.cancel_button.hide() + action_layout.addWidget(self.choose_button) + action_layout.addWidget(self.check_button) + action_layout.addWidget(self.start_button, 1) + action_layout.addWidget(self.cancel_button) + card_layout.addLayout(action_layout) + + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + self.progress_bar.setFormat("%p%") + self.progress_bar.setTextVisible(True) + self.progress_bar.hide() + card_layout.addWidget(self.progress_bar) + + self.status_label = QLabel("选择一个 HAP 文件即可开始") + self.status_label.setObjectName("status") + card_layout.addWidget(self.status_label) + + self.log_title = QLabel("运行记录") + self.log_title.setObjectName("logTitle") + self.log_title.hide() + card_layout.addWidget(self.log_title) + self.log_view = QPlainTextEdit() + self.log_view.setReadOnly(True) + self.log_view.setMaximumBlockCount(300) + self.log_view.setMinimumHeight(120) + self.log_view.setFont(_fixed_width_font()) + self.log_view.hide() + card_layout.addWidget(self.log_view) + outer.addWidget(card) + outer.addItem( + QSpacerItem( + 0, + 0, + QSizePolicy.Policy.Minimum, + QSizePolicy.Policy.Expanding, + ) + ) + + def _build_header(self) -> QHBoxLayout: + header = QHBoxLayout() + header.setSpacing(15) + + icon_label = QLabel() + icon_label.setPixmap(_make_app_icon(92).pixmap(56, 56)) + icon_label.setFixedSize(56, 56) + header.addWidget(icon_label) + + text_layout = QVBoxLayout() + text_layout.setSpacing(2) + title = QLabel("HapSign") + title.setObjectName("title") + subtitle = QLabel("签名并安装 HarmonyOS 应用") + subtitle.setObjectName("subtitle") + text_layout.addWidget(title) + text_layout.addWidget(subtitle) + header.addLayout(text_layout) + header.addStretch(1) + + self.settings_button = QPushButton("设置") + self.settings_button.setObjectName("secondaryButton") + self.settings_button.clicked.connect(self._open_settings) + header.addWidget(self.settings_button) + + platform_chip = QLabel(f"{platform_tag().title()} · v{__version__}") + platform_chip.setObjectName("chip") + platform_chip.setAlignment(Qt.AlignmentFlag.AlignCenter) + header.addWidget(platform_chip) + return header + + def _build_file_panel(self) -> QFrame: + panel = QFrame() + panel.setStyleSheet("QFrame { background: #f7f8fb; border-radius: 12px; }") + panel.setFixedWidth(245) + panel.setMinimumHeight(190) + layout = QVBoxLayout(panel) + layout.setContentsMargins(15, 12, 15, 12) + layout.setSpacing(8) + + top_layout = QHBoxLayout() + file_badge = QLabel("HAP") + file_badge.setAlignment(Qt.AlignmentFlag.AlignCenter) + file_badge.setFixedSize(42, 42) + file_badge.setStyleSheet( + "color: #4459dc; background: #e7ebff; border-radius: 10px;" + "font-size: 12px; font-weight: 750;" + ) + top_layout.addWidget(file_badge) + top_layout.addStretch(1) + self.remove_button = QPushButton("×") + self.remove_button.setObjectName("removeButton") + self.remove_button.setToolTip("移除当前 HAP") + self.remove_button.setAccessibleName("移除当前 HAP") + self.remove_button.clicked.connect(self._clear_file) + top_layout.addWidget(self.remove_button) + layout.addLayout(top_layout) + + self.file_name = QLabel() + self.file_name.setObjectName("fileName") + self.file_name.setWordWrap(True) + self.file_meta = QLabel() + self.file_meta.setObjectName("fileMeta") + layout.addWidget(self.file_name) + layout.addWidget(self.file_meta) + layout.addStretch(1) + + self.signature_chip = QLabel() + self.signature_chip.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.signature_chip, 0, Qt.AlignmentFlag.AlignLeft) + return panel + + @Slot() + def _open_settings(self) -> None: + if self.worker_thread is not None: + return + dialog = SettingsDialog(self.settings, self.log_path, self) + if dialog.exec() != QDialog.DialogCode.Accepted: + return + updated = dialog.result_settings() + try: + save_settings(updated) + self.log_path = configure_file_logging(updated) + except OSError as exc: + QMessageBox.warning(self, "无法保存设置", str(exc)) + return + self.settings = updated + self.status_label.setStyleSheet("color: #237353; font-weight: 650;") + self.status_label.setText("设置已保存,将用于下一次任务") + + @Slot() + def _choose_file(self) -> None: + path, _ = QFileDialog.getOpenFileName( + self, + "选择 HAP 文件", + str(Path.home()), + "HarmonyOS HAP (*.hap);;所有文件 (*)", + ) + if path: + self._load_file(path) + + @Slot(str) + def _load_file(self, raw_path: str) -> None: + path = Path(raw_path).expanduser().resolve() + try: + if path.suffix.lower() != ".hap": + raise ValueError("请选择扩展名为 .hap 的文件") + if not path.is_file(): + raise ValueError("文件不存在或无法读取") + bundle_name = detect_bundle_name(str(path)) + signed = is_hap_signed(path) + except Exception as exc: + QMessageBox.warning(self, "无法载入 HAP", str(exc)) + return + + self.selected_path = path + self.status_label.setStyleSheet("") + self.start_button.setText("开始签名并安装") + self.file_name.setText(path.name) + self.file_name.setToolTip(str(path)) + self.file_meta.setText(f"{bundle_name} · {_human_size(path.stat().st_size)}") + self.signature_chip.setText("已签名" if signed else "待签名") + self.signature_chip.setObjectName("signedChip" if signed else "unsignedChip") + self.signature_chip.style().unpolish(self.signature_chip) + self.signature_chip.style().polish(self.signature_chip) + self.file_panel.show() + self.start_button.setEnabled(True) + self.status_label.setText( + "已签名,将直接安装" if signed else "未签名,将自动完成签名后安装" + ) + + @Slot() + def _clear_file(self) -> None: + if self.worker_thread is not None: + return + self.selected_path = None + self.file_name.clear() + self.file_name.setToolTip("") + self.file_meta.clear() + self.signature_chip.clear() + self.file_panel.hide() + self.start_button.setText("开始签名并安装") + self.start_button.setEnabled(False) + self.progress_bar.hide() + self.log_title.hide() + self.log_view.clear() + self.log_view.hide() + self.status_label.setStyleSheet("") + self.status_label.setText("HAP 已移除,可重新选择或拖入文件") + + @Slot() + def _check_device(self) -> None: + if self.worker_thread is not None: + return + + self.status_label.setStyleSheet("") + self.status_label.setText("正在检测设备连接…") + self.choose_button.setEnabled(False) + self.check_button.setEnabled(False) + self.start_button.setEnabled(False) + self.remove_button.setEnabled(False) + self.drop_zone.setEnabled(False) + self.settings_button.setEnabled(False) + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(5) + self.progress_bar.show() + self.cancel_button.show() + self._cancel_requested = False + + thread = QThread(self) + worker = DeviceCheckWorker() + worker.moveToThread(thread) + thread.started.connect(worker.run) + worker.progress_changed.connect(self._set_progress) + worker.finished.connect(self._finish_device_check) + worker.finished.connect(thread.quit) + worker.finished.connect(worker.deleteLater) + thread.finished.connect(thread.deleteLater) + thread.finished.connect(self._thread_finished) + self.worker_thread = thread + self.worker = worker + thread.start() + + @Slot(bool, bool, str) + def _finish_device_check( + self, success: bool, cancelled: bool, message: str + ) -> None: + self.progress_bar.setValue(100 if success else self.progress_bar.value()) + self.status_label.setText(message) + if success: + self.status_label.setStyleSheet("color: #237353; font-weight: 650;") + elif cancelled: + self.progress_bar.setValue(0) + self.status_label.setStyleSheet("color: #657084; font-weight: 650;") + else: + self.status_label.setStyleSheet("color: #b54646; font-weight: 650;") + QMessageBox.warning(self, "未检测到可用设备", message) + + @Slot() + def _start(self) -> None: + if self.selected_path is None or self.worker_thread is not None: + return + + self.status_label.setStyleSheet("") + self.start_button.setEnabled(False) + self.choose_button.setEnabled(False) + self.check_button.setEnabled(False) + self.remove_button.setEnabled(False) + self.drop_zone.setEnabled(False) + self.settings_button.setEnabled(False) + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + self.progress_bar.show() + self.cancel_button.show() + self._cancel_requested = False + self.log_title.show() + self.log_view.clear() + self.log_view.show() + self.status_label.setText("正在准备,请保持设备连接…") + + thread = QThread(self) + worker = PipelineWorker( + str(self.selected_path), + signing_files_dir(self.settings), + self.settings.browser_mode, + self.settings.keep_signed_hap, + ) + worker.moveToThread(thread) + thread.started.connect(worker.run) + worker.log_message.connect(self._append_log) + worker.progress_changed.connect(self._set_progress) + worker.finished.connect(self._finish) + worker.finished.connect(thread.quit) + worker.finished.connect(worker.deleteLater) + thread.finished.connect(thread.deleteLater) + thread.finished.connect(self._thread_finished) + self.worker_thread = thread + self.worker = worker + thread.start() + + @Slot(str) + def _append_log(self, message: str) -> None: + self.log_view.appendPlainText(message) + self.status_label.setText(message) + + @Slot(int, str) + def _set_progress(self, value: int, message: str) -> None: + self.progress_bar.setValue(max(0, min(100, value))) + if message: + self.status_label.setText(message) + + @Slot() + def _cancel_active_task(self) -> None: + if self.worker is None or self._cancel_requested: + return + self._cancel_requested = True + self.cancel_button.setEnabled(False) + self.status_label.setStyleSheet("color: #657084; font-weight: 650;") + self.status_label.setText("正在取消并清理,请稍候…") + request_cancel = getattr(self.worker, "request_cancel", None) + if request_cancel is not None: + request_cancel() + + @Slot(bool, bool, str) + def _finish(self, success: bool, cancelled: bool, message: str) -> None: + self.progress_bar.setValue(100 if success else self.progress_bar.value()) + self.status_label.setText(message) + if success: + self.status_label.setStyleSheet("color: #237353; font-weight: 650;") + self.start_button.setText("再次安装") + elif cancelled: + self.progress_bar.setValue(0) + self.status_label.setStyleSheet("color: #657084; font-weight: 650;") + self.start_button.setText("重新开始") + else: + self.status_label.setStyleSheet("color: #b54646; font-weight: 650;") + self.start_button.setText("重试") + QMessageBox.critical(self, "未能完成安装", message) + + @Slot() + def _thread_finished(self) -> None: + self.worker_thread = None + self.worker = None + self.cancel_button.hide() + self.cancel_button.setEnabled(True) + self._cancel_requested = False + self.start_button.setEnabled(self.selected_path is not None) + self.choose_button.setEnabled(True) + self.check_button.setEnabled(True) + self.remove_button.setEnabled(self.selected_path is not None) + self.drop_zone.setEnabled(True) + self.settings_button.setEnabled(True) + if self._close_after_cancel: + self._close_after_cancel = False + QTimer.singleShot(0, self.close) + + def closeEvent(self, event) -> None: + if self.worker_thread is not None: + if self._close_after_cancel: + event.ignore() + return + answer = QMessageBox.question( + self, + "中断并退出?", + "任务仍在执行。是否中断当前任务、完成清理后退出程序?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if answer == QMessageBox.StandardButton.Yes: + self._close_after_cancel = True + self._cancel_active_task() + event.ignore() + return + event.accept() + + +def main() -> int: + """启动桌面应用。""" + settings = load_settings() + log_path = configure_file_logging(settings) + browser_smoke_mode = None + if "--browser-smoke-test" in sys.argv: + browser_smoke_mode = "playwright" + elif "--system-browser-smoke-test" in sys.argv: + browser_smoke_mode = "system_controlled" + if browser_smoke_mode is not None: + try: + playwright_browser_smoke_test(browser_smoke_mode) + except Exception: + LOGGER.exception("受控浏览器自检失败:%s", browser_smoke_mode) + return 1 + LOGGER.info("受控浏览器自检成功:%s", browser_smoke_mode) + return 0 + + QApplication.setHighDpiScaleFactorRoundingPolicy( + Qt.HighDpiScaleFactorRoundingPolicy.PassThrough + ) + app = QApplication(sys.argv) + app.setApplicationName("HapSign") + app.setOrganizationName("HapSign") + app.setWindowIcon(_make_app_icon()) + app.setStyle(HapSignStyle("Fusion")) + app.setStyleSheet(WINDOW_STYLE) + + app.setFont(_application_font()) + + window = MainWindow(settings, log_path) + window.show() + if "--smoke-test" in sys.argv: + QTimer.singleShot(250, app.quit) + return app.exec() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/hapsign/login/browser_login.py b/hapsign/login/browser_login.py index b40016a..133c4c7 100644 --- a/hapsign/login/browser_login.py +++ b/hapsign/login/browser_login.py @@ -4,22 +4,30 @@ 通过本地 HTTP 服务拦截回调拿到 tempToken。 流程(逆向自 DevEco Studio HiAiLoginService): - 1. 找空闲端口,生成 CSRF code (UUID 去横线) - 2. 启动本地 HTTP 服务等待 POST /callback - 3. Playwright 浏览器打开登录页 ?port={port}&appid=1007&code={uuid} + 1. 生成 CSRF code (UUID 去横线),在 loopback 上绑定临时端口 + 2. 启动本地 HTTP 服务等待授权回调 + 3. 系统浏览器打开登录页 ?port={port}&appid=1007&code={uuid} 4. 用户在浏览器中手动输入账号密码并登录 5. 华为 OAuth 服务端通过浏览器回调本地 HTTP 服务,携带 tempToken, siteId, code 6. 校验 code(CSRF),返回 tempToken """ +import json import logging -import socket +import os import threading +import time import uuid +import webbrowser +from email import policy +from email.parser import BytesParser from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path from urllib.parse import parse_qs, urlparse -from hapsign.config import APP_ID, BASE_URL, LOGIN_AUTH_PATH +from hapsign.cancellation import OperationCancelled, raise_if_cancelled +from hapsign.config import APP_ID, BASE_URL, LOGIN_AUTH_PATH, LOGIN_SUCCESS_PATH +from hapsign.diagnostics import sensitive_logging_enabled logger = logging.getLogger(__name__) @@ -27,16 +35,69 @@ _CALLBACK_TIMEOUT = 300 _CALLBACK_HOST = "127.0.0.1" _MAX_CALLBACK_BODY_SIZE = 64 * 1024 +_CONTROLLED_SYSTEM_CHANNELS = ("msedge", "chrome") + + +def _parse_multipart(body: bytes, content_type: str) -> dict[str, str]: + """解析浏览器回调的 multipart 表单,兼容 DevEco 的 Netty 解码行为。""" + message = BytesParser(policy=policy.default).parsebytes( + b"Content-Type: " + + content_type.encode("ascii", errors="ignore") + + b"\r\nMIME-Version: 1.0\r\n\r\n" + + body + ) + if not message.is_multipart(): + return {} + params: dict[str, str] = {} + for part in message.iter_parts(): + name = part.get_param("name", header="content-disposition") + if not name: + continue + value = part.get_payload(decode=True) or b"" + charset = part.get_content_charset() or "utf-8" + params[name] = value.decode(charset, errors="replace") + return params + + +def _flatten_params(params: dict) -> dict[str, str]: + return { + str(key): str(value[-1] if isinstance(value, list) else value) + for key, value in params.items() + } + + +def _decode_callback_params(body: bytes, content_type: str) -> dict[str, str]: + """按 Content-Type 解码,并对浏览器省略/误报类型的情况进行安全回退。""" + media_type = content_type.lower() + params: dict = {} + + if "application/json" in media_type: + try: + decoded = json.loads(body.decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError, TypeError): + decoded = {} + if isinstance(decoded, dict): + nested = decoded.get("data") + params = nested if isinstance(nested, dict) else decoded + elif "multipart/form-data" in media_type: + params = _parse_multipart(body, content_type) + else: + try: + params = parse_qs(body.decode("utf-8")) + except UnicodeDecodeError: + params = {} + + # 某些授权页会漏掉 multipart Content-Type;从首行恢复 boundary 后重试。 + if not {"tempToken", "code"}.intersection(params) and body.startswith(b"--"): + first_line = body.splitlines()[0].strip() + if first_line.startswith(b"--") and len(first_line) > 2: + boundary = first_line[2:].decode("ascii", errors="ignore") + params = _parse_multipart( + body, + f'multipart/form-data; boundary="{boundary}"', + ) -# 登录成功重定向路径 -_LOGIN_SUCCESS_PATH = "console/DevEcoIDE/loginSuccess" - - -def _find_free_port() -> int: - """找本机空闲 TCP 端口。""" - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind((_CALLBACK_HOST, 0)) - return s.getsockname()[1] + return _flatten_params(params) def _make_callback_handler( @@ -44,66 +105,150 @@ def _make_callback_handler( ): """创建回调 HTTP 请求处理器类(闭包注入共享状态)。 - 华为 OAuth 服务端在用户登录成功后,通过浏览器向本地回调服务器 - POST 表单数据:tempToken, siteId, code。 + 华为 OAuth 服务端在用户登录成功后,通过浏览器向本地回调服务器发送 + tempToken、siteId 和 code。兼容 DevEco 使用的 multipart 表单及其他版本。 """ class _CallbackHandler(BaseHTTPRequestHandler): + def _send_headers(self, status: int, content_type: str = "text/plain") -> None: + self.send_response(status) + self.send_header("Content-Type", f"{content_type}; charset=utf-8") + origin = self.headers.get("Origin", "") + self.send_header("Access-Control-Allow-Origin", origin or "*") + if origin: + self.send_header("Access-Control-Allow-Credentials", "true") + self.send_header("Vary", "Origin") + self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + requested_headers = self.headers.get( + "Access-Control-Request-Headers", "Content-Type" + ) + self.send_header("Access-Control-Allow-Headers", requested_headers) + # Chromium 对 HTTPS 页面访问 loopback 可能发起 Private Network + # Access 预检;允许后才会真正发送带 token 的 POST。 + self.send_header("Access-Control-Allow-Private-Network", "true") + self.send_header("Cache-Control", "no-store") + self.end_headers() + + def _send_text( + self, status: int, message: str, content_type: str = "text/plain" + ) -> None: + self._send_headers(status, content_type) + try: + self.wfile.write(message.encode("utf-8")) + except (BrokenPipeError, ConnectionResetError): + logger.debug("[callback] client closed before reading response") + + def _send_success_redirect(self) -> None: + self.send_response(302) + self.send_header("Location", f"{BASE_URL}/{LOGIN_SUCCESS_PATH}") + self.send_header("Cache-Control", "no-store") + self.end_headers() + def _process_params(self, params: dict) -> None: """处理回调参数,校验 CSRF code 并保存 tempToken。""" # 回调中含有 tempToken。日志只记录字段名,避免用户分享日志时泄漏凭据。 logger.debug("[callback] fields received: %s", sorted(params)) + if sensitive_logging_enabled(): + logger.debug("[callback] sensitive params=%r", params) received_code = params.get("code", "") if received_code != expected_code: logger.warning("[callback] CSRF code mismatch") - self.send_response(400) - self.end_headers() - self.wfile.write(b"invalid csrf code") + self._send_text(400, "invalid csrf code") + return + + temp_token = params.get("tempToken", "") + if not temp_token: + denied = params.get("quit", "") + callback_data["error"] = ( + "用户取消了华为账号授权" + if denied in {"quit", "access_denied"} + else "登录回调缺少 tempToken" + ) + callback_event.set() + logger.warning("[callback] authorization did not return tempToken") + self._send_text(400, callback_data["error"], "text/html") return - callback_data["tempToken"] = params.get("tempToken", "") + callback_data["tempToken"] = temp_token callback_data["siteId"] = params.get("siteId", "") callback_data["code"] = params.get("code", "") callback_event.set() - - redirect_url = f"{BASE_URL}/{_LOGIN_SUCCESS_PATH}" - self.send_response(302) - self.send_header("Location", redirect_url) - self.end_headers() + logger.info("[callback] 授权回调校验成功") + self._send_success_redirect() def do_POST(self): - content_length = int(self.headers.get("Content-Length", 0)) + try: + content_length = int(self.headers.get("Content-Length", 0)) + except ValueError: + self._send_text(400, "invalid content length") + return + if content_length < 0: + self._send_text(400, "invalid content length") + return if content_length > _MAX_CALLBACK_BODY_SIZE: - self.send_response(413) - self.end_headers() + self._send_text(413, "request body too large") return - body = self.rfile.read(content_length).decode("utf-8") - logger.debug("[callback] POST path=%s", self.path) - params = parse_qs(body) - params = {k: v[0] if isinstance(v, list) else v for k, v in params.items()} + body = self.rfile.read(content_length) + parsed_path = urlparse(self.path) + content_type = self.headers.get("Content-Type", "") + params = _decode_callback_params(body, content_type) + query_params = parse_qs(urlparse(self.path).query) + for key, value in _flatten_params(query_params).items(): + params.setdefault(key, value) + logger.info( + "[callback] 收到 POST:path=%s, type=%s, bytes=%d, fields=%s", + parsed_path.path, + content_type.split(";", 1)[0] or "(missing)", + len(body), + sorted(params), + ) + if sensitive_logging_enabled(): + logger.debug( + "[callback] sensitive headers=%r body=%r", + dict(self.headers.items()), + body.decode("utf-8", errors="replace"), + ) self._process_params(params) def do_GET(self): parsed = urlparse(self.path) - logger.debug("[callback] GET path=%s", parsed.path) - if parsed.path != "/callback": - self.send_response(200) - self.end_headers() - self.wfile.write(b"ok") + params = _flatten_params(parse_qs(parsed.query)) + logger.info( + "[callback] 收到 GET:path=%s, fields=%s", + parsed.path, + sorted(params), + ) + if sensitive_logging_enabled(): + logger.debug( + "[callback] sensitive headers=%r query=%r", + dict(self.headers.items()), + params, + ) + # DevEco/Huawei 的不同版本可能回调 /、/callback 或其他本地路径。 + # 只要携带了授权字段,就按回调处理,避免授权成功后永远等待。 + if "tempToken" not in params and "code" not in params: + self._send_text(200, "HapSign callback server is ready") return - params = parse_qs(parsed.query) - params = {k: v[0] if isinstance(v, list) else v for k, v in params.items()} self._process_params(params) + def do_OPTIONS(self): + logger.info( + "[callback] 收到浏览器预检:origin=%s, private-network=%s", + self.headers.get("Origin", "(missing)"), + self.headers.get("Access-Control-Request-Private-Network", "false"), + ) + self._send_headers(204) + def log_message(self, fmt, *args): - logger.debug("[http] %s - %s", self.client_address[0], fmt % args) + # BaseHTTPRequestHandler 的默认请求行可能包含 tempToken,不记录。 + logger.debug("[callback] loopback request handled") return _CallbackHandler class BrowserLogin: - """Playwright 浏览器登录。 + """系统浏览器或 Playwright 浏览器登录。 使用示例:: @@ -111,6 +256,16 @@ class BrowserLogin: temp_token = login.login("CN") """ + def __init__( + self, + browser_mode: str | None = None, + cancel_event: threading.Event | None = None, + ): + self.browser_mode = ( + browser_mode or os.environ.get("HAPSIGN_BROWSER", "system_controlled") + ).lower() + self.cancel_event = cancel_event + def login(self, country: str = "CN") -> str: """打开华为登录页,用户手动登录,拦截回调拿 tempToken。 @@ -123,40 +278,43 @@ def login(self, country: str = "CN") -> str: Raises: RuntimeError: 任意步骤失败时抛出 """ - # ── 1. 找空闲端口 ── - port = _find_free_port() - - # ── 2. 生成 CSRF code(UUID 去横线,与原始插件一致) ── + # ── 1. 生成 CSRF code(UUID 去横线,与原始插件一致) ── csrf_code = uuid.uuid4().hex - - # ── 3. 构建登录 URL ── - login_url = ( - f"{BASE_URL}/{LOGIN_AUTH_PATH}?port={port}&appid={APP_ID}&code={csrf_code}" - ) - - logger.info("[LOGIN] callback port=%d", port) - - # ── 4. 启动本地 HTTP 回调服务 ── callback_data: dict = {} callback_event = threading.Event() - handler_class = _make_callback_handler(csrf_code, callback_data, callback_event) - server = ThreadingHTTPServer((_CALLBACK_HOST, port), handler_class) + # 直接让 HTTPServer 绑定临时端口,消除“先探测、后绑定”的端口竞争窗口。 + server = ThreadingHTTPServer((_CALLBACK_HOST, 0), handler_class) + server.daemon_threads = True + server.block_on_close = False + port = server.server_address[1] + login_url = ( + f"{BASE_URL}/{LOGIN_AUTH_PATH}?port={port}&appid={APP_ID}&code={csrf_code}" + ) server_thread = threading.Thread(target=server.serve_forever, daemon=True) server_thread.start() + logger.info("[LOGIN] callback port=%d", port) logger.info("[LOGIN] callback server listening on %s:%d", _CALLBACK_HOST, port) + logger.info("[LOGIN] browser mode=%s", self.browser_mode) + if sensitive_logging_enabled(): + logger.debug("[LOGIN] sensitive login_url=%s", login_url) try: - # ── 5. 启动 Playwright 浏览器,等待回调 ── + # ── 5. 启动浏览器,等待回调 ── self._browser_login_and_wait(login_url, callback_event) finally: # ── 6. 关闭 HTTP 服务 ── server.shutdown() + server.server_close() + server_thread.join(timeout=2) # ── 7. 校验并返回 tempToken ── temp_token = callback_data.get("tempToken", "") if not temp_token: + callback_error = callback_data.get("error") + if callback_error: + raise RuntimeError(callback_error) raise RuntimeError( "Callback did not contain tempToken. Login may have been cancelled." ) @@ -167,6 +325,54 @@ def _browser_login_and_wait( self, login_url: str, callback_event: threading.Event, + ) -> None: + """按配置启动系统浏览器或 Playwright。""" + if self.browser_mode == "system": + self._system_browser_login_and_wait(login_url, callback_event) + return + if self.browser_mode in {"playwright", "system_controlled"}: + self._playwright_login_and_wait(login_url, callback_event) + return + raise RuntimeError( + f"Unsupported browser mode: {self.browser_mode}. " + "Use 'system_controlled', 'playwright' or 'system'." + ) + + def _wait_for_callback(self, callback_event: threading.Event) -> None: + deadline = time.monotonic() + _CALLBACK_TIMEOUT + while not callback_event.wait(timeout=0.1): + raise_if_cancelled(self.cancel_event) + if time.monotonic() >= deadline: + raise RuntimeError( + "Login timed out: no callback received within " + f"{_CALLBACK_TIMEOUT}s. " + "Please check your network and complete login in the browser." + ) + + def _system_browser_login_and_wait( + self, + login_url: str, + callback_event: threading.Event, + ) -> None: + """使用系统默认浏览器完成登录,不引入 Chromium 运行时。""" + raise_if_cancelled(self.cancel_event) + if not webbrowser.open(login_url, new=1, autoraise=True): + raise RuntimeError( + "Unable to open the system browser. " + "Set HAPSIGN_BROWSER=playwright to use the optional backend." + ) + logger.info("[login] 登录页面已在系统浏览器中打开") + logger.info( + "[login] 正在等待登录回调(最多 %s 秒,可处理验证码或二次验证)", + _CALLBACK_TIMEOUT, + ) + raise_if_cancelled(self.cancel_event) + self._wait_for_callback(callback_event) + + def _playwright_login_and_wait( + self, + login_url: str, + callback_event: threading.Event, ) -> None: """启动 Playwright 浏览器,导航到登录页,等待回调。 @@ -179,6 +385,8 @@ def _browser_login_and_wait( Raises: RuntimeError: Playwright 未安装、浏览器启动失败或回调超时 """ + # 打包时浏览器放在 playwright 包内,源码运行也遵循同一路径。 + os.environ.setdefault("PLAYWRIGHT_BROWSERS_PATH", "0") try: from playwright.sync_api import TimeoutError as PwTimeout from playwright.sync_api import sync_playwright @@ -191,10 +399,12 @@ def _browser_login_and_wait( try: with sync_playwright() as pw: - browser = pw.chromium.launch( - headless=False, - args=["--disable-blink-features=AutomationControlled"], + browser, runtime_name = _launch_controlled_browser( + pw, + self.browser_mode, + allow_fallback=True, ) + logger.info("[login] 受控浏览器已启动:%s", runtime_name) context = browser.new_context( viewport={"width": 1280, "height": 800}, locale="zh-CN", @@ -204,31 +414,126 @@ def _browser_login_and_wait( "Chrome/120.0.0.0 Safari/537.36" ), ) + try: + context.grant_permissions( + ["local-network-access"], + origin=BASE_URL, + ) + logger.info("[login] 已授予登录页访问本地回调服务的权限") + except Exception as exc: + # 旧版 Chromium 不认识该权限;回调服务器仍提供 PNA 预检响应。 + logger.debug("[login] 无法预授予本地网络权限:%s", exc) page = context.new_page() + def log_request_failure(request) -> None: + parsed = urlparse(request.url) + safe_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}" + detail = request.url if sensitive_logging_enabled() else safe_url + logger.warning( + "[login] 浏览器请求失败:%s %s (%s)", + request.method, + detail, + request.failure, + ) + + page.on("requestfailed", log_request_failure) + page.on( + "pageerror", + lambda error: logger.warning("[login] 页面脚本错误:%s", error), + ) + # 使用 domcontentloaded,避免持续网络请求让 networkidle 超时。 try: page.goto(login_url, wait_until="domcontentloaded", timeout=60000) except PwTimeout as exc: raise RuntimeError("Login page load timed out") from exc - print("[LOGIN] Page loaded") - print("[LOGIN] Please login in the browser window...") - - print(f"[LOGIN] Waiting for callback (timeout={_CALLBACK_TIMEOUT}s)...") - print("[LOGIN] (handle captcha/2FA in browser if needed)") - - # 等待回调(浏览器保持打开,让用户手动登录) - if not callback_event.wait(timeout=_CALLBACK_TIMEOUT): - raise RuntimeError( - "Login timed out: no callback received within " - f"{_CALLBACK_TIMEOUT}s. " - f"Please check your network and complete login in the browser." - ) + logger.info("[login] 登录页面已打开,请在浏览器中完成登录") + logger.info( + "[login] 正在等待登录回调(最多 %s 秒,可处理验证码或二次验证)", + _CALLBACK_TIMEOUT, + ) + self._wait_for_callback(callback_event) browser.close() - except RuntimeError: + except (RuntimeError, OperationCancelled): raise except Exception as e: raise RuntimeError(f"Browser operation failed: {e}") from e + + +def _launch_controlled_browser( + playwright, + preferred_mode: str, + *, + allow_fallback: bool, + smoke_test: bool = False, +): + """启动受控浏览器;系统 Edge/Chrome 与内置 Chromium 可互相回退。""" + system_candidates = [ + (channel, {"channel": channel}) for channel in _CONTROLLED_SYSTEM_CHANNELS + ] + bundled_candidate = [("bundled-chromium", {})] + if preferred_mode == "system_controlled": + candidates = system_candidates + if allow_fallback: + candidates += bundled_candidate + elif preferred_mode == "playwright": + candidates = bundled_candidate + if allow_fallback: + candidates += system_candidates + else: + raise RuntimeError(f"Unsupported controlled browser mode: {preferred_mode}") + + failures: list[str] = [] + for runtime_name, launch_options in candidates: + if runtime_name == "bundled-chromium": + executable = Path(playwright.chromium.executable_path) + if not executable.is_file(): + failures.append("bundled-chromium: runtime not bundled") + continue + args = ["--disable-blink-features=AutomationControlled"] + if smoke_test: + args.append("--headless=new") + try: + browser = playwright.chromium.launch( + headless=False, + args=args, + **launch_options, + ) + if runtime_name != ( + "bundled-chromium" + if preferred_mode == "playwright" + else _CONTROLLED_SYSTEM_CHANNELS[0] + ): + logger.warning( + "[login] 首选浏览器不可用,已回退到 %s", + runtime_name, + ) + return browser, runtime_name + except Exception as exc: + failures.append(f"{runtime_name}: {exc}") + + raise RuntimeError( + "无法启动受控浏览器。请安装 Microsoft Edge/Google Chrome," + "或使用包含内置 Chromium 的兼容包。详情:" + " | ".join(failures) + ) + + +def playwright_browser_smoke_test(browser_mode: str = "playwright") -> None: + """严格启动指定受控浏览器,用于验证便携包运行时。""" + os.environ.setdefault("PLAYWRIGHT_BROWSERS_PATH", "0") + try: + from playwright.sync_api import sync_playwright + except ImportError as exc: + raise RuntimeError("Playwright 未安装") from exc + + with sync_playwright() as pw: + browser, _ = _launch_controlled_browser( + pw, + browser_mode, + allow_fallback=False, + smoke_test=True, + ) + browser.close() diff --git a/hapsign/pipeline.py b/hapsign/pipeline.py index f423d0e..40738b8 100644 --- a/hapsign/pipeline.py +++ b/hapsign/pipeline.py @@ -15,7 +15,11 @@ import json import logging import os +import tempfile +import threading +import traceback import zipfile +from collections.abc import Callable from datetime import date from hapsign.api.capability_api import CapabilityAPI @@ -23,23 +27,28 @@ from hapsign.api.client import HuaweiSignClient, TokenExpiredError from hapsign.api.device_api import DeviceAPI from hapsign.api.provision_api import ProvisionAPI +from hapsign.cancellation import OperationCancelled, raise_if_cancelled from hapsign.config import ( ACL_PERMISSION_WHITELIST, DEVICE_TYPE_PHONE, KEY_ALIAS, KEYSTORE_PASSWORD, ) +from hapsign.diagnostics import redact_sensitive_text from hapsign.login.browser_login import BrowserLogin from hapsign.models import AppBriefInfo, CertResult, ProvisionResult, TokenInfo +from hapsign.signing.hap_inspect import is_hap_signed from hapsign.signing.hap_signer import HapSigner from hapsign.signing.installer import Installer from hapsign.signing.keytool_util import KeytoolUtil +from hapsign.token import secure_token_cache from hapsign.token.token_exchange import TokenExchange logger = logging.getLogger(__name__) # 签名文件根目录(相对于项目根) SIGNING_FILES_DIR = "signing_files" +SIGNED_HAP_MANIFEST = ".hapsign-signed-haps.json" class SignPipeline: @@ -52,6 +61,10 @@ class SignPipeline: bundle_name="com.example.myapp", ) pipeline.run() + + 生命周期约定:SignPipeline 是一次性对象,一个实例只应调用一次 ``run()``; + 再次执行请新建实例。运行期状态(_team_id、_app_info、_cert_result、 + 签名材料路径等)在构造函数中显式初始化。 """ def __init__( @@ -61,9 +74,15 @@ def __init__( country: str = "CN", device_type: str = DEVICE_TYPE_PHONE, work_dir: str = "", + state_dir: str = "", enable_capability: bool = False, force_refresh_token: bool = False, force_refresh_signing: bool = False, + browser_mode: str = "system", + signed_output_dir: str = "", + keep_signed_hap: bool = True, + cancel_event: threading.Event | None = None, + progress_callback: Callable[[int, str], None] | None = None, ): self.hap_path = hap_path self.bundle_name = bundle_name @@ -72,16 +91,23 @@ def __init__( self.enable_capability = enable_capability self.force_refresh_token = force_refresh_token self.force_refresh_signing = force_refresh_signing + self.browser_mode = browser_mode + self.keep_signed_hap = keep_signed_hap + self._temporary_signed_dir: tempfile.TemporaryDirectory | None = None + self.cancel_event = cancel_event + self.progress_callback = progress_callback + self.state_dir = state_dir or SIGNING_FILES_DIR if work_dir: self.work_dir = work_dir else: - self.work_dir = os.path.join(SIGNING_FILES_DIR, bundle_name) + self.work_dir = os.path.join(self.state_dir, bundle_name) + self.signed_output_dir = signed_output_dir or self.work_dir os.makedirs(self.work_dir, exist_ok=True) self.keystore_password = KEYSTORE_PASSWORD self._metadata_path = os.path.join(self.work_dir, "metadata.json") - self._token_cache_path = os.path.join(SIGNING_FILES_DIR, ".token_cache.json") + self._token_cache_path = os.path.join(self.state_dir, ".token_cache.json") - self._token_exchange = TokenExchange() + self._token_exchange = TokenExchange(cancel_event=cancel_event) self._token_info: TokenInfo | None = None self._client: HuaweiSignClient | None = None self._cert_api: CertAPI | None = None @@ -89,27 +115,82 @@ def __init__( self._provision_api: ProvisionAPI | None = None self._capability_api: CapabilityAPI | None = None self._token_from_cache = False + self._udid = "" + self._installer: Installer | None = None + + # 运行期状态:全部显式初始化,避免 hasattr 探测和未初始化属性。 + self._team_id = "" + self._app_info: AppBriefInfo | None = None + self._cert_result: CertResult | None = None + self._p12_path = "" + self._cer_path = "" + self._p7b_path = "" + self._csr_path = "" + self._csr_content = "" + self._signed_hap_path = "" + + def _check_cancelled(self) -> None: + raise_if_cancelled(self.cancel_event) + + def _emit_progress(self, value: int, label: str) -> None: + if self.progress_callback is not None: + self.progress_callback(value, label) # ── Token 缓存 ────────────────────────────────────────────── def _load_token_cache(self) -> dict | None: """加载当天的 token 缓存。 - 条件:缓存存在、creation_date 是今天。 + 条件:缓存存在、creation_date 是今天;缓存可能是 DPAPI 加密格式或旧版 + 明文 JSON。明文缓存首次读取时安全迁移为加密格式;解密失败视为无缓存, + 回退重新登录。 """ if not os.path.exists(self._token_cache_path): return None try: - with open(self._token_cache_path, encoding="utf-8") as f: - cache = json.load(f) - except (json.JSONDecodeError, OSError): + with open(self._token_cache_path, "rb") as f: + raw = f.read() + except OSError: + return None + + if secure_token_cache.is_encrypted(raw): + try: + payload = secure_token_cache.decrypt(raw) + except secure_token_cache.DecryptError as exc: + logger.warning( + "[cache] token 缓存解密失败,将重新登录: %s", + redact_sensitive_text(exc), + ) + return None + try: + cache = json.loads(payload) + except (json.JSONDecodeError, UnicodeDecodeError, TypeError): + logger.warning("[cache] token 缓存内容损坏,将重新登录") + return None + legacy = False + else: + try: + cache = json.loads(raw) + except (json.JSONDecodeError, UnicodeDecodeError, TypeError): + return None + legacy = True + + if not isinstance(cache, dict): + logger.warning("[cache] token 缓存格式无效,将重新登录") return None if cache.get("creation_date") != date.today().isoformat(): logger.info("[cache] token 缓存非今日,跳过") return None - if not cache.get("access_token") or not cache.get("user_id"): + if ( + not cache.get("access_token") + or not cache.get("user_id") + or not cache.get("jwt_token") + ): return None + if legacy: + # 旧版明文缓存:安全迁移为加密格式,失败不影响本次使用。 + self._write_token_cache(cache) return cache def _save_token_cache(self) -> None: @@ -126,13 +207,36 @@ def _save_token_cache(self) -> None: "real_name": self._token_info.real_name, "jwt_token": self._token_info.jwt_token, } - os.makedirs(SIGNING_FILES_DIR, exist_ok=True) - with open(self._token_cache_path, "w", encoding="utf-8") as f: - json.dump(cache, f, indent=2, ensure_ascii=False) + self._write_token_cache(cache) + + def _write_token_cache(self, cache: dict) -> None: + """把 token 缓存写入磁盘(DPAPI 加密;原子替换,失败保留原缓存)。""" + os.makedirs(self.state_dir, exist_ok=True) + tmp_path = self._token_cache_path + ".tmp" try: - os.chmod(self._token_cache_path, 0o600) + payload = json.dumps(cache, ensure_ascii=False).encode("utf-8") + data = secure_token_cache.protect(payload) + with open(tmp_path, "wb") as f: + f.write(data) + try: + os.chmod(tmp_path, 0o600) + except OSError as exc: + logger.debug( + "无法限制 token 缓存文件权限: %s", + redact_sensitive_text(exc), + ) + os.replace(tmp_path, self._token_cache_path) except OSError as exc: - logger.debug("无法限制 token 缓存文件权限: %s", exc) + # 写入或替换失败时保留原缓存,不留下会被当成正式缓存读取的半截文件。 + try: + os.remove(tmp_path) + except OSError: + pass + logger.warning( + "保存 token 缓存失败,保留原缓存: %s", + redact_sensitive_text(exc), + ) + return logger.info("[cache] token 缓存已保存: %s", self._token_cache_path) def _init_client_from_cache(self, cache: dict) -> None: @@ -149,6 +253,7 @@ def _init_client_from_cache(self, cache: dict) -> None: self._client = HuaweiSignClient( access_token=self._token_info.access_token, uid=self._token_info.user_id, + cancel_event=self.cancel_event, ) self._cert_api = CertAPI(self._client) self._device_api = DeviceAPI(self._client) @@ -172,7 +277,11 @@ def _load_cached_metadata(self) -> dict | None: try: with open(self._metadata_path, encoding="utf-8") as f: meta = json.load(f) - except (json.JSONDecodeError, OSError): + except (json.JSONDecodeError, UnicodeDecodeError, OSError): + return None + + if not isinstance(meta, dict): + logger.warning("[cache] 签名文件元数据格式无效,将重新申请") return None if meta.get("creation_date") != date.today().isoformat(): @@ -214,9 +323,22 @@ def _save_metadata( # ── 主流程 ────────────────────────────────────────────────── def run(self) -> bool: + """执行流程,并清理由本次任务启动的 HDC server。""" + try: + self._check_cancelled() + self._emit_progress(2, "正在准备") + result = self._run_pipeline() + if result: + self._emit_progress(100, "安装完成") + return result + finally: + self._close_installer() + self._cleanup_temporary_signed_hap() + + def _run_pipeline(self) -> bool: """执行签名安装流程,优先使用缓存。 - 强制刷新逻辑: + 强制刷新逻辑(全部是本次运行的局部决策,不改写构造参数): - force_refresh_token: 清除 token 缓存,强制重新登录 - force_refresh_signing: 清除签名文件缓存,强制重新申请 - 刷新 token 时自动连带刷新签名文件(否则只刷新 token 无意义) @@ -224,14 +346,26 @@ def run(self) -> bool: Returns: 全部步骤成功返回 True,任一步骤失败返回 False。 """ - # 强制刷新 token 时连带刷新签名文件 + # 所有路径都先确认设备可用,避免登录、申请证书或签名完成后才发现 + # HDC 无法安装。检测得到的 UDID 也会在注册设备时直接复用。 + if not self._run_steps([("检测设备连接", self._step_check_device)]): + return False + + # 已签名包:跳过登录 / 申请 / 签名,直接安装原文件 + if is_hap_signed(self.hap_path): + logger.info("[sign] 检测到已签名 HAP,跳过签名流程,直接安装") + self._signed_hap_path = self.hap_path + steps = [("安装 hap 到设备", self._step_install)] + return self._run_steps(steps) + + # 强制刷新 token 时连带刷新签名文件(仅本次运行,不改写实例属性) + refresh_signing = self.force_refresh_signing if self.force_refresh_token: self._clear_token_cache() logger.info("[cache] 强制刷新 token 缓存") - # token 刷新后签名文件也必须重新申请 - self.force_refresh_signing = True + refresh_signing = True - if not self.force_refresh_signing: + if not refresh_signing: signing_cached = self._load_cached_metadata() else: signing_cached = None @@ -258,10 +392,18 @@ def run(self) -> bool: else: # 没有缓存 token,需要登录 try: + self._emit_progress(18, "等待华为账号授权") self._step_login() + self._emit_progress(30, "正在完成登录") self._step_exchange_token() + except OperationCancelled: + raise except Exception as e: - logger.error("x 登录失败: %s", e) + logger.error("x 登录失败: %s", redact_sensitive_text(e)) + logger.debug( + "登录失败调用栈:\n%s", + redact_sensitive_text(traceback.format_exc()), + ) return False steps = [ @@ -275,28 +417,65 @@ def run(self) -> bool: if self.enable_capability: steps.insert(0, ("查询应用信息", self._step_get_app_info)) + return self._run_steps(steps) + + def _run_steps(self, steps: list) -> bool: + """按顺序执行步骤,处理 token 失效重试。""" + progress_by_step = { + "检测设备连接": 7, + "查询应用信息": 38, + "生成密钥对和 CSR": 40, + "申请证书": 52, + "注册调试设备": 64, + "创建调试 Profile": 75, + "签名 hap": 86, + "安装 hap 到设备": 95, + } for name, step in steps: + self._check_cancelled() + self._emit_progress(progress_by_step.get(name, 10), name) logger.info("> %s ...", name) try: step() + self._check_cancelled() except TokenExpiredError as e: # 只有 token 真正失效才回退到重新登录 logger.warning( "x %s failed: token 已失效 (%s),回退到重新登录", name, - e, + redact_sensitive_text(e), ) self._token_from_cache = False self._clear_token_cache() try: + self._emit_progress(18, "登录已失效,等待重新授权") self._step_login() + self._emit_progress(30, "正在刷新登录") self._step_exchange_token() step() + except OperationCancelled: + raise except Exception as retry_err: - logger.error("x %s failed after retry: %s", name, retry_err) + logger.error( + "x %s failed after retry: %s", + name, + redact_sensitive_text(retry_err), + ) + logger.debug( + "%s 重试失败调用栈:\n%s", + name, + redact_sensitive_text(traceback.format_exc()), + ) return False + except OperationCancelled: + raise except Exception as e: - logger.error("x %s failed: %s", name, e) + logger.error("x %s failed: %s", name, redact_sensitive_text(e)) + logger.debug( + "%s 失败调用栈:\n%s", + name, + redact_sensitive_text(traceback.format_exc()), + ) return False logger.info("+ %s done", name) return True @@ -305,7 +484,10 @@ def run(self) -> bool: def _step_login(self) -> None: """Playwright 浏览器登录,用户手动输入账号密码,获取 tempToken。""" - login = BrowserLogin() + login = BrowserLogin( + browser_mode=self.browser_mode, + cancel_event=self.cancel_event, + ) self._temp_token = login.login(self.country) def _step_exchange_token(self) -> None: @@ -320,6 +502,7 @@ def _step_exchange_token(self) -> None: self._client = HuaweiSignClient( access_token=token_info.access_token, uid=token_info.user_id, + cancel_event=self.cancel_event, ) self._cert_api = CertAPI(self._client) self._device_api = DeviceAPI(self._client) @@ -342,7 +525,7 @@ def _step_get_app_info(self) -> None: self._team_id = team_id logger.info("Team ID: %s", team_id) - self._app_info: AppBriefInfo | None = self._with_refresh( + self._app_info = self._with_refresh( self._capability_api.get_app_brief_info, team_id, self.bundle_name ) if self._app_info is None: @@ -376,7 +559,7 @@ def _step_generate_keypair(self) -> None: self.work_dir, f"auto_debug_{self.bundle_name}.csr" ) - keytool = KeytoolUtil() + keytool = KeytoolUtil(cancel_event=self.cancel_event) keytool.generate_keypair(self._p12_path, KEY_ALIAS, self.keystore_password) self._csr_content = keytool.generate_csr( self._p12_path, KEY_ALIAS, self.keystore_password, self._csr_path @@ -393,13 +576,15 @@ def _step_add_certificate(self) -> None: 5. 下载 .cer 文件 """ assert self._cert_api is not None - if not hasattr(self, "_team_id") or self._team_id is None: + if not self._team_id: self._team_id = self._with_refresh(self._cert_api.get_team_id) team_id = self._team_id logger.info("Team ID: %s", team_id) try: self._with_refresh(self._cert_api.sign_agreement) + except OperationCancelled: + raise except Exception: logger.debug("Agreement signing skipped (may already be signed)") @@ -414,6 +599,8 @@ def _step_add_certificate(self) -> None: logger.info("Deleted old certificate (id=%s)", old_id) except ValueError: logger.debug("No existing certificate to delete") + except OperationCancelled: + raise except Exception: logger.debug("Delete old certificate failed, continuing") @@ -447,9 +634,8 @@ def _step_add_certificate(self) -> None: def _step_register_device(self) -> None: """获取设备 UDID 并注册到华为平台,然后查询设备 ID。""" assert self._device_api is not None - installer = Installer() - self._udid = installer.get_udid() - logger.info("已读取设备 UDID") + if not self._udid: + self._udid = self._get_installer().get_udid() self._with_refresh( self._device_api.add_device, @@ -525,8 +711,13 @@ def _step_create_provision(self) -> None: result.provision_id, ) logger.info("已删除远端 Profile (id=%s)", result.provision_id) + except OperationCancelled: + raise except Exception as e: - logger.debug("删除远端 Profile 失败(不影响流程): %s", e) + logger.debug( + "删除远端 Profile 失败(不影响流程): %s", + redact_sensitive_text(e), + ) self._save_metadata( p12_path=self._p12_path, @@ -540,29 +731,149 @@ def _step_create_provision(self) -> None: def _step_sign_hap(self) -> None: """用 hap-sign-tool 对 hap 包签名。""" hap_basename = os.path.splitext(os.path.basename(self.hap_path))[0] - self._signed_hap_path = os.path.join( - self.work_dir, f"{hap_basename}_signed.hap" - ) + if self.keep_signed_hap: + os.makedirs(self.signed_output_dir, exist_ok=True) + output_path = os.path.join( + self.signed_output_dir, + f".{hap_basename}.signing.tmp.hap", + ) + else: + self._temporary_signed_dir = tempfile.TemporaryDirectory( + prefix="hapsign-signed-" + ) + output_path = os.path.join( + self._temporary_signed_dir.name, + f"{hap_basename}_signed.hap", + ) - signer = HapSigner() - signer.sign_hap( - self.hap_path, - self._cer_path, - self._p7b_path, - self._p12_path, - KEY_ALIAS, - self.keystore_password, - self._signed_hap_path, - ) + signer = HapSigner(cancel_event=self.cancel_event) + try: + signer.sign_hap( + self.hap_path, + self._cer_path, + self._p7b_path, + self._p12_path, + KEY_ALIAS, + self.keystore_password, + output_path, + ) + except Exception: + if self.keep_signed_hap: + try: + os.remove(output_path) + except FileNotFoundError: + pass + raise + if self.keep_signed_hap: + final_path = os.path.join( + self.signed_output_dir, + f"{hap_basename}_signed.hap", + ) + # 原子发布新文件;签名失败不会破坏上一份同名产物或其他 HAP。 + os.replace(output_path, final_path) + self._cleanup_previous_signed_haps(final_path) + self._save_signed_hap_manifest(final_path) + self._signed_hap_path = final_path + else: + self._signed_hap_path = output_path logger.info("签名后 hap: %s", self._signed_hap_path) + def _cleanup_previous_signed_haps(self, final_path: str) -> None: + """只清理 manifest 记录的旧产物,绝不按扩展名删除用户文件。""" + manifest_path = os.path.join(self.signed_output_dir, SIGNED_HAP_MANIFEST) + try: + with open(manifest_path, encoding="utf-8") as manifest_file: + manifest = json.load(manifest_file) + except (json.JSONDecodeError, UnicodeDecodeError, OSError): + return + + if not isinstance(manifest, dict): + return + generated_haps = manifest.get("generated_haps") + if not isinstance(generated_haps, list): + return + + final_resolved = os.path.normcase(os.path.realpath(os.path.abspath(final_path))) + input_resolved = os.path.normcase( + os.path.realpath(os.path.abspath(self.hap_path)) + ) + for name in generated_haps: + if ( + not isinstance(name, str) + or os.path.basename(name) != name + or os.path.splitext(name)[1].lower() != ".hap" + ): + continue + candidate = os.path.join(self.signed_output_dir, name) + candidate_resolved = os.path.normcase( + os.path.realpath(os.path.abspath(candidate)) + ) + if candidate_resolved in {final_resolved, input_resolved}: + continue + if not os.path.isfile(candidate): + continue + try: + os.remove(candidate) + except OSError as exc: + logger.warning( + "无法删除旧的签名 HAP %s:%s", + candidate, + redact_sensitive_text(exc), + ) + + def _save_signed_hap_manifest(self, final_path: str) -> None: + """原子保存本次生成的签名 HAP 清单。""" + manifest_path = os.path.join(self.signed_output_dir, SIGNED_HAP_MANIFEST) + temporary_path = f"{manifest_path}.tmp" + manifest = { + "version": 1, + "generated_haps": [os.path.basename(final_path)], + } + try: + with open(temporary_path, "w", encoding="utf-8") as manifest_file: + json.dump(manifest, manifest_file, indent=2, ensure_ascii=False) + os.replace(temporary_path, manifest_path) + except OSError as exc: + try: + os.remove(temporary_path) + except OSError: + pass + logger.warning( + "无法保存签名 HAP 清单 %s:%s", + manifest_path, + redact_sensitive_text(exc), + ) + def _step_install(self) -> None: """hdc install 安装签名后的 hap 到设备。""" - installer = Installer() - installer.install(self._signed_hap_path) + self._get_installer().install(self._signed_hap_path) + + def _step_check_device(self) -> None: + """确认有且仅有一台已授权、可通过 HDC 访问的设备。""" + self._udid = self._get_installer().get_udid() + logger.info("已检测到可用设备(UDID 尾号 %s)", self._udid[-6:]) # ── 工具方法 ──────────────────────────────────────────────── + def _get_installer(self) -> Installer: + if self._installer is None: + self._installer = Installer(cancel_event=self.cancel_event) + return self._installer + + def _cleanup_temporary_signed_hap(self) -> None: + if self._temporary_signed_dir is None: + return + try: + self._temporary_signed_dir.cleanup() + finally: + self._temporary_signed_dir = None + + def _close_installer(self) -> None: + if self._installer is None: + return + self._installer.close() + self._installer = None + def _extract_permissions(self) -> list[str]: """从 hap 的 module.json 提取 requestPermissions,按 ACL 白名单过滤。 @@ -589,14 +900,19 @@ def _extract_permissions(self) -> list[str]: "非 ACL 权限(普通权限,由系统授予): %s", skipped ) return filtered + except OperationCancelled: + raise except Exception as e: - logger.debug("提取权限列表失败: %s", e) + logger.debug("提取权限列表失败: %s", redact_sensitive_text(e)) return [] def _with_refresh(self, func, *args, **kwargs): """执行 API 调用,token 失效时自动刷新并重试一次。""" + self._check_cancelled() try: - return func(*args, **kwargs) + result = func(*args, **kwargs) + self._check_cancelled() + return result except TokenExpiredError: logger.warning("Token 失效,尝试刷新...") new_token = self._token_exchange.refresh_access_token( @@ -605,4 +921,7 @@ def _with_refresh(self, func, *args, **kwargs): self._client.access_token = new_token self._token_info.access_token = new_token self._save_token_cache() - return func(*args, **kwargs) + self._check_cancelled() + result = func(*args, **kwargs) + self._check_cancelled() + return result diff --git a/hapsign/runtime.py b/hapsign/runtime.py new file mode 100644 index 0000000..bed98e6 --- /dev/null +++ b/hapsign/runtime.py @@ -0,0 +1,184 @@ +"""运行目录、用户数据目录与外部工具链发现。 + +该模块不依赖 GUI,CLI、桌面版和 PyInstaller 便携版共用同一套规则。 +所有平台差异集中在这里,避免业务流程散落 Windows 专用路径。 +""" + +from __future__ import annotations + +import os +import platform +import sys +from dataclasses import dataclass +from pathlib import Path + +APP_NAME = "HapSign" + + +def platform_tag() -> str: + """返回便携资源目录使用的平台标识。""" + system = platform.system().lower() + if system == "darwin": + return "macos" + if system == "windows": + return "windows" + return "linux" + + +def application_dir() -> Path: + """返回应用所在目录;冻结后为可执行文件目录。""" + if getattr(sys, "frozen", False): + return Path(sys.executable).resolve().parent + return Path(__file__).resolve().parent.parent + + +def resource_dir() -> Path: + """返回便携版资源根目录,可通过环境变量覆盖。""" + override = os.environ.get("HAPSIGN_RESOURCE_DIR") + if override: + return Path(override).expanduser().resolve() + return application_dir() / "resources" + + +def app_data_dir() -> Path: + """返回应用数据根目录;默认与程序可执行文件位于同一目录。""" + override = os.environ.get("HAPSIGN_DATA_DIR") + if override: + return Path(override).expanduser().resolve() + return application_dir() + + +@dataclass(frozen=True) +class ToolchainPaths: + """签名与安装所需外部工具路径。""" + + java: Path + keytool: Path + hap_sign_tool: Path + hdc: Path + source: str + + def missing(self, *, require_signing: bool = True) -> list[str]: + """返回缺少的工具;已签名 HAP 只要求 HDC。""" + required = [("HDC", self.hdc)] + if require_signing: + required.extend( + [ + ("Java", self.java), + ("keytool", self.keytool), + ("hap-sign-tool.jar", self.hap_sign_tool), + ] + ) + return [f"{name}: {path}" for name, path in required if not path.is_file()] + + +def _executable_name(name: str) -> str: + return f"{name}.exe" if platform.system() == "Windows" else name + + +def _from_portable_resources() -> ToolchainPaths: + root = resource_dir() / "toolchain" / platform_tag() + # 新版公开工具链使用中性的 runtime/;旧便携包仍兼容 jbr/。 + runtime_root = root / "runtime" + if not runtime_root.is_dir(): + runtime_root = root / "jbr" + return ToolchainPaths( + java=runtime_root / "bin" / _executable_name("java"), + keytool=runtime_root / "bin" / _executable_name("keytool"), + hap_sign_tool=root / "lib" / "hap-sign-tool.jar", + hdc=root / "bin" / _executable_name("hdc"), + source="portable", + ) + + +def _from_deveco_home(home: Path, source: str) -> ToolchainPaths: + toolchains = home / "sdk" / "default" / "openharmony" / "toolchains" + # DevEco Studio 的 macOS 应用包把 JBR 放在 Contents/Home 下;Windows + # 和 Linux 的发行版则直接使用 jbr/bin。 + runtime_root = home / "jbr" + if platform.system() == "Darwin": + runtime_root = runtime_root / "Contents" / "Home" + return ToolchainPaths( + java=runtime_root / "bin" / _executable_name("java"), + keytool=runtime_root / "bin" / _executable_name("keytool"), + hap_sign_tool=toolchains / "lib" / "hap-sign-tool.jar", + hdc=toolchains / _executable_name("hdc"), + source=source, + ) + + +def _deveco_home_candidates() -> list[tuple[Path, str]]: + candidates: list[tuple[Path, str]] = [] + configured = os.environ.get("DEVECO_HOME") + if configured: + candidates.append((Path(configured).expanduser(), "DEVECO_HOME")) + + system = platform.system() + if system == "Windows": + program_files = Path(os.environ.get("ProgramFiles", r"C:\Program Files")) + candidates.extend( + [ + (program_files / "Huawei" / "DevEco Studio", "DevEco Studio"), + ( + Path(r"D:\Program Files\Huawei\DevEco Studio"), + "DevEco Studio", + ), + ] + ) + elif system == "Darwin": + candidates.extend( + [ + ( + Path("/Applications/DevEco-Studio.app/Contents"), + "DevEco Studio", + ), + ( + Path("/Applications/DevEco Studio.app/Contents"), + "DevEco Studio", + ), + ] + ) + else: + candidates.extend( + [ + (Path("/opt/DevEco-Studio"), "DevEco Studio"), + (Path.home() / "DevEco-Studio", "DevEco Studio"), + ] + ) + return candidates + + +def _with_direct_overrides(paths: ToolchainPaths) -> ToolchainPaths: + def override(name: str, current: Path) -> Path: + value = os.environ.get(name) + return Path(value).expanduser() if value else current + + return ToolchainPaths( + java=override("HAPSIGN_JAVA", paths.java), + keytool=override("HAPSIGN_KEYTOOL", paths.keytool), + hap_sign_tool=override("HAPSIGN_HAP_SIGN_TOOL", paths.hap_sign_tool), + hdc=override("HAPSIGN_HDC", paths.hdc), + source=paths.source, + ) + + +def discover_toolchain() -> ToolchainPaths: + """优先发现便携资源,其次查找本机 DevEco Studio。""" + candidates = [_from_portable_resources()] + candidates.extend( + _from_deveco_home(home, source) for home, source in _deveco_home_candidates() + ) + + overridden = [_with_direct_overrides(item) for item in candidates] + complete = next((item for item in overridden if not item.missing()), None) + if complete is not None: + return complete + + # 未完整安装时返回现存文件最多的一组,让错误信息指向最可能的安装位置。 + return max( + overridden, + key=lambda item: sum( + path.is_file() + for path in (item.java, item.keytool, item.hap_sign_tool, item.hdc) + ), + ) diff --git a/hapsign/settings.py b/hapsign/settings.py new file mode 100644 index 0000000..e8b9b11 --- /dev/null +++ b/hapsign/settings.py @@ -0,0 +1,115 @@ +"""HapSign 桌面配置与数据目录解析。""" + +from __future__ import annotations + +import json +import os +import platform +from dataclasses import asdict, dataclass +from pathlib import Path + +from hapsign.runtime import APP_NAME, application_dir + +LOG_LEVELS = {"DEBUG", "INFO", "WARNING", "ERROR"} +STORAGE_MODES = {"program", "appdata", "custom"} +BROWSER_MODES = {"system_controlled", "playwright", "system"} + + +@dataclass(frozen=True) +class AppSettings: + """可持久化的桌面设置。""" + + log_level: str = "INFO" + signing_storage: str = "program" + custom_signing_dir: str = "" + browser_mode: str = "system_controlled" + log_sensitive_data: bool = False + keep_signed_hap: bool = True + + +def config_file_path() -> Path: + """配置文件默认与可执行文件放在一起,保持便携语义。""" + override = os.environ.get("HAPSIGN_CONFIG_FILE") + if override: + return Path(override).expanduser().resolve() + return application_dir() / "hapsign-config.json" + + +def user_local_data_dir() -> Path: + """返回当前平台的用户本地应用数据目录。""" + system = platform.system() + if system == "Windows": + root = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local")) + return root / APP_NAME + if system == "Darwin": + return Path.home() / "Library" / "Application Support" / APP_NAME + root = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share")) + return root / APP_NAME + + +def signing_files_dir(settings: AppSettings) -> Path: + """按设置返回签名材料、缓存和签名后 HAP 的目录。""" + override = os.environ.get("HAPSIGN_DATA_DIR") + if override: + return Path(override).expanduser().resolve() / "signing_files" + if settings.signing_storage == "appdata": + return user_local_data_dir() / "signing_files" + if settings.signing_storage == "custom" and settings.custom_signing_dir: + return Path(settings.custom_signing_dir).expanduser().resolve() + return application_dir() / "signing_files" + + +def log_directory() -> Path: + """日志固定优先写入程序目录,便于便携迁移和问题定位。""" + return application_dir() / "logs" + + +def signed_haps_dir() -> Path: + """最终签名 HAP 始终位于程序目录,不随签名材料设置变化。""" + return application_dir() / "signed_haps" + + +def _validated(data: dict) -> AppSettings: + log_level = str(data.get("log_level", "INFO")).upper() + if log_level not in LOG_LEVELS: + log_level = "INFO" + storage = str(data.get("signing_storage", "program")).lower() + if storage not in STORAGE_MODES: + storage = "program" + browser_mode = str(data.get("browser_mode", "system_controlled")).lower() + if browser_mode not in BROWSER_MODES: + browser_mode = "system_controlled" + keep_signed_hap = data.get("keep_signed_hap", True) + if not isinstance(keep_signed_hap, bool): + keep_signed_hap = True + return AppSettings( + log_level=log_level, + signing_storage=storage, + custom_signing_dir=str(data.get("custom_signing_dir", "")), + browser_mode=browser_mode, + log_sensitive_data=data.get("log_sensitive_data", False) is True, + keep_signed_hap=keep_signed_hap, + ) + + +def load_settings() -> AppSettings: + """读取 JSON 设置;缺失或损坏时使用安全默认值。""" + path = config_file_path() + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return AppSettings() + return _validated(data) if isinstance(data, dict) else AppSettings() + + +def save_settings(settings: AppSettings) -> Path: + """原子保存 JSON 设置并返回文件路径。""" + path = config_file_path() + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_suffix(path.suffix + ".tmp") + temporary.write_text( + json.dumps(asdict(settings), ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + os.replace(temporary, path) + return path diff --git a/hapsign/signing/__init__.py b/hapsign/signing/__init__.py index 9d73cde..7872dcf 100644 --- a/hapsign/signing/__init__.py +++ b/hapsign/signing/__init__.py @@ -1,7 +1,8 @@ """Signing 模块 —— 密钥生成、HAP 签名与安装。""" +from .hap_inspect import is_hap_signed from .hap_signer import HapSigner from .installer import Installer from .keytool_util import KeytoolUtil -__all__ = ["KeytoolUtil", "HapSigner", "Installer"] +__all__ = ["KeytoolUtil", "HapSigner", "Installer", "is_hap_signed"] diff --git a/hapsign/signing/hap_inspect.py b/hapsign/signing/hap_inspect.py new file mode 100644 index 0000000..07eef2e --- /dev/null +++ b/hapsign/signing/hap_inspect.py @@ -0,0 +1,188 @@ +"""检测 HAP 是否已包含签名块。 + +对齐 OpenHarmony ``developtools_hapsigner``: +- Java ``ZipUtils.findEocdInHap`` / ``HapUtils.findHapSigningBlock`` +- C++ ``HapSigningBlockUtils::FindHapSigningBlock`` / ``CheckSignBlockHead`` + +仅做 presence 检测(结构上是否存在 Signing Block),不做 CMS / 证书链验签。 +完整验签请使用 ``hap-sign-tool verify-app``,不适合 pipeline 门禁。 +""" + +from __future__ import annotations + +import struct +from pathlib import Path +from typing import BinaryIO + +# HapUtils / HapSignerBlockUtils 常量 +HAP_SIGN_SCHEME_V3_BLOCK_VERSION = 3 +HAP_SIG_BLOCK_MAGIC_LO_V2 = 0x2067695320504148 +HAP_SIG_BLOCK_MAGIC_HI_V2 = 0x3234206B636F6C42 +HAP_SIG_BLOCK_MAGIC_LO_V3 = 0x676973207061683C +HAP_SIG_BLOCK_MAGIC_HI_V3 = 0x3E6B636F6C62206E +HAP_SIG_BLOCK_HEADER_SIZE = 32 +OPTIONAL_SUB_BLOCK_HEADER_SIZE = 12 +# C++ HapSigningBlockUtils +MAX_BLOCK_COUNT = 10 +MAX_HAP_SIGN_BLOCK_SIZE = 1024 * 1024 * 1024 +# Java HapUtils.verifySignBlock: size <= Integer.MAX_VALUE - 8 +_JAVA_MAX_SIG_BLOCK_SIZE = 0x7FFFFFFF - 8 + +# ZipUtils +ZIP_EOCD_SEGMENT_MIN_SIZE = 22 +ZIP_EOCD_COMMENT_LENGTH_OFFSET = 20 +ZIP_CD_SIZE_OFFSET_IN_EOCD = 12 +ZIP_CD_OFFSET_IN_EOCD = 16 +ZIP_EOCD_MAX_COMMENT_SIZE = 0xFFFF +ZIP64_EOCD_LOCATOR_SIZE = 20 +ZIP64_EOCD_LOCATOR_SIG = 0x07064B50 +ZIP64_UINT32_SENTINEL = 0xFFFFFFFF +_EOCD_SIGNATURE = b"PK\x05\x06" + + +def _find_eocd_in_window(window: bytes) -> int | None: + """在搜索窗口内定位 EOCD,用 comment length 自洽性排除伪签名。""" + search_size = len(window) + if search_size < ZIP_EOCD_SEGMENT_MIN_SIZE: + return None + relative = window.rfind(_EOCD_SIGNATURE) + while relative >= 0: + if relative + ZIP_EOCD_SEGMENT_MIN_SIZE <= search_size: + comment_size = struct.unpack_from( + " int | None: + """按给定最大 comment 长度从文件末尾搜索 EOCD。""" + if file_size < ZIP_EOCD_SEGMENT_MIN_SIZE: + return None + if max_comment_size < 0 or max_comment_size > ZIP_EOCD_MAX_COMMENT_SIZE: + raise ValueError(f"max_comment_size out of range: {max_comment_size}") + + final_max_comment = min(max_comment_size, file_size - ZIP_EOCD_SEGMENT_MIN_SIZE) + search_size = final_max_comment + ZIP_EOCD_SEGMENT_MIN_SIZE + search_start = file_size - search_size + hap_file.seek(search_start) + window = hap_file.read(search_size) + if len(window) != search_size: + return None + relative = _find_eocd_in_window(window) + if relative is None: + return None + return search_start + relative + + +def _find_eocd_offset_two_phase(hap_file: BinaryIO, file_size: int) -> int | None: + """对齐 ZipUtils.findEocdInHap:先假设无 comment,再扩大到 65535。""" + eocd = _find_eocd_offset(hap_file, file_size, 0) + if eocd is not None: + return eocd + return _find_eocd_offset(hap_file, file_size, ZIP_EOCD_MAX_COMMENT_SIZE) + + +def _has_zip64_eocd_locator(hap_file: BinaryIO, eocd_offset: int) -> bool: + """对齐 ZipUtils.checkZip64EoCDLocatorIsPresent。""" + locator_pos = eocd_offset - ZIP64_EOCD_LOCATOR_SIZE + if locator_pos < 0: + return False + hap_file.seek(locator_pos) + sig = hap_file.read(4) + if len(sig) != 4: + return False + return struct.unpack(" bool: + """校验 32 字节签名块 footer:magic / version / size / blockCount。""" + if len(header) != HAP_SIG_BLOCK_HEADER_SIZE: + return False + block_count, block_size, magic_lo, magic_hi, version = struct.unpack( + "= HAP_SIGN_SCHEME_V3_BLOCK_VERSION: + magic_ok = ( + magic_lo == HAP_SIG_BLOCK_MAGIC_LO_V3 + and magic_hi == HAP_SIG_BLOCK_MAGIC_HI_V3 + ) + else: + magic_ok = ( + magic_lo == HAP_SIG_BLOCK_MAGIC_LO_V2 + and magic_hi == HAP_SIG_BLOCK_MAGIC_HI_V2 + ) + if not magic_ok: + return False + + # 真实签名包至少有一个 sub-block;0/负数视为非法 + if block_count <= 0 or block_count > MAX_BLOCK_COUNT: + return False + + max_size = min( + central_directory_offset, + MAX_HAP_SIGN_BLOCK_SIZE, + _JAVA_MAX_SIG_BLOCK_SIZE, + ) + if not HAP_SIG_BLOCK_HEADER_SIZE <= block_size <= max_size: + return False + + # sub-block header 数组必须能放进签名块主体(footer 之外) + block_array_size = block_size - HAP_SIG_BLOCK_HEADER_SIZE + if block_count * OPTIONAL_SUB_BLOCK_HEADER_SIZE > block_array_size: + return False + return True + + +def is_hap_signed(hap_path: str | Path) -> bool: + """判断 HAP 是否已包含 Hap Signing Block。 + + 仅检测签名块是否存在,不做证书链或 Profile 校验。 + 文件不存在、ZIP64、非 ZIP、或结构非法时返回 False(宁可漏判也不误判)。 + """ + path = Path(hap_path) + try: + with path.open("rb") as hap_file: + hap_file.seek(0, 2) + file_size = hap_file.tell() + eocd_offset = _find_eocd_offset_two_phase(hap_file, file_size) + if eocd_offset is None: + return False + + # ZIP64:上游 verify 路径直接拒绝;我们同样不做深入解析 + if _has_zip64_eocd_locator(hap_file, eocd_offset): + return False + + hap_file.seek(eocd_offset) + eocd = hap_file.read(ZIP_EOCD_SEGMENT_MIN_SIZE) + if len(eocd) != ZIP_EOCD_SEGMENT_MIN_SIZE: + return False + + central_directory_size = struct.unpack_from( + " None: + self.cancel_event = cancel_event + def sign_hap( self, hap_path: str, @@ -15,7 +19,7 @@ def sign_hap( p7b_path: str, p12_path: str, alias: str = config.KEY_ALIAS, - password: str = "123456", + password: str = config.KEYSTORE_PASSWORD, output_path: str = "", ) -> bool: """使用 hap-sign-tool sign-app 命令签名 hap。 @@ -67,7 +71,12 @@ def sign_hap( "-signCode", "1", ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = run_process( + cmd, + capture_output=True, + text=True, + cancel_event=self.cancel_event, + ) if result.returncode != 0: raise RuntimeError( f"hap-sign-tool sign-app 失败 (code={result.returncode}): " diff --git a/hapsign/signing/installer.py b/hapsign/signing/installer.py index 4342094..cc5308e 100644 --- a/hapsign/signing/installer.py +++ b/hapsign/signing/installer.py @@ -1,14 +1,282 @@ """HDC 工具封装 —— 获取设备 UDID 和安装 hap。""" +from __future__ import annotations + +import datetime +import logging +import os import re import subprocess +import threading +import time from hapsign import config +from hapsign.subprocess_utils import no_window_kwargs, run_process + +logger = logging.getLogger(__name__) + +_HDC_SERVER_HOST = "127.0.0.1" +_HDC_SERVER_PORT = 8710 + +# hdc start 调用时刻与监听进程创建时刻比较时允许的时钟偏差(秒) +_CLOCK_SKEW_SECONDS = 2.0 + +# hdc start 返回后确认监听进程出现的轮询参数(HDC 启动可能略慢) +_LISTENER_POLL_ATTEMPTS = 8 +_LISTENER_POLL_INTERVAL = 0.25 + +# hdc install 的失败标记:非零退出码之外,仅当输出含这些真实失败指示才判定失败。 +# - [Fail] 状态标记、INSTALL_FAILED_ 前缀可在任意位置出现(HDC 的错误码前缀); +# - error: 只匹配状态行行首,避免普通输出里的类似字符串误判。 +_FATAL_INSTALL_MARKERS = re.compile( + r"(?:\[fail\]|install_failed_|^error:)", re.IGNORECASE | re.MULTILINE +) + + +def _listener_pid() -> int | None: + """返回监听 ``127.0.0.1:8710`` 的进程 PID;无监听或无法识别时返回 None。""" + if os.name == "nt": + return _listener_pid_windows() + return _listener_pid_posix() + + +def _listener_pid_windows() -> int | None: + """解析 netstat 输出,取本地监听 8710 的 PID。""" + try: + result = subprocess.run( + ["netstat", "-ano", "-p", "TCP"], + capture_output=True, + text=True, + timeout=10, + **no_window_kwargs(), + ) + except (OSError, subprocess.TimeoutExpired): + return None + # 监听套接字的远端地址恒为 0.0.0.0:0,状态列文本在不同语言环境可能不同 + pattern = re.compile(r"TCP\s+127\.0\.0\.1:8710\s+0\.0\.0\.0:0\s+\S+\s+(\d+)\s*$") + for line in result.stdout.splitlines(): + match = pattern.search(line) + if match: + return int(match.group(1)) + return None + + +def _listener_pid_posix() -> int | None: + """POSIX 平台回退使用 lsof 定位监听 8710 的 PID。""" + try: + result = subprocess.run( + ["lsof", "-nP", f"-iTCP:{_HDC_SERVER_PORT}", "-sTCP:LISTEN"], + capture_output=True, + text=True, + timeout=5, + ) + except (OSError, subprocess.TimeoutExpired): + return None + for line in result.stdout.splitlines()[1:]: # 跳过表头 + parts = line.split() + if len(parts) >= 2: + try: + return int(parts[1]) + except ValueError: + continue + return None + + +def _process_start_time(pid: int) -> float | None: + """返回进程启动时刻的 epoch 秒;无法读取时返回 None。""" + if os.name == "nt": + return _process_start_time_windows(pid) + return _process_start_time_posix(pid) + + +def _process_start_time_windows(pid: int) -> float | None: + """用 GetProcessTimes 读取进程创建时间(epoch 秒)。""" + import ctypes + from ctypes import wintypes + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.OpenProcess.restype = wintypes.HANDLE + kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD] + kernel32.GetProcessTimes.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(wintypes.FILETIME), + ctypes.POINTER(wintypes.FILETIME), + ctypes.POINTER(wintypes.FILETIME), + ctypes.POINTER(wintypes.FILETIME), + ] + kernel32.CloseHandle.argtypes = [wintypes.HANDLE] + + process = kernel32.OpenProcess( + 0x1000, + False, + pid, # PROCESS_QUERY_LIMITED_INFORMATION + ) + if not process: + return None + try: + creation = wintypes.FILETIME() + exit_time = wintypes.FILETIME() + kernel_time = wintypes.FILETIME() + user_time = wintypes.FILETIME() + if not kernel32.GetProcessTimes( + process, + ctypes.byref(creation), + ctypes.byref(exit_time), + ctypes.byref(kernel_time), + ctypes.byref(user_time), + ): + return None + raw = (creation.dwHighDateTime << 32) | creation.dwLowDateTime + # FILETIME 从 1601-01-01 UTC 起以 100ns 计,换算为 Unix epoch 秒 + return (raw - 116444736000000000) / 10_000_000.0 + finally: + kernel32.CloseHandle(process) + + +def _process_start_time_posix(pid: int) -> float | None: + try: + result = subprocess.run( + ["ps", "-o", "lstart=", "-p", str(pid)], + capture_output=True, + text=True, + timeout=5, + ) + except (OSError, subprocess.TimeoutExpired): + return None + text = result.stdout.strip() + if not text: + return None + try: + parsed = datetime.datetime.strptime(text, "%a %b %d %H:%M:%S %Y") + except ValueError: + return None + return time.mktime(parsed.timetuple()) class Installer: """使用 hdc 获取设备 UDID 并安装 hap 包。""" + def __init__(self, cancel_event: threading.Event | None = None) -> None: + self._hdc = config.HDC_PATH + self.cancel_event = cancel_event + # 本任务确认创建、close 时应清理的 HDC server 监听 PID;None 表示不归属 + self._owned_server_pid: int | None = None + self._server_checked = False + self._closed = False + + def __enter__(self) -> Installer: + return self + + def __exit__(self, *_exc_info) -> None: + self.close() + + def _ensure_server(self) -> None: + """确保 HDC server 可用,并确认本任务是否创建了它。仅首次使用时执行。 + + - 已存在监听进程(既有 HDC 或非 HDC 占用):不启动、不接管;端口被非 + HDC 占用的情况由后续 hdc 命令失败自然暴露。 + - 无监听进程:显式 ``hdc start``;随后短轮询等待监听进程出现(HDC 启动 + 可能略慢),仅当出现的监听进程“创建时刻不早于本次调用”才确认归属并 + 记录 PID。 + - 启动失败或归属无法确认:记录警告,``close`` 不做清理。 + """ + if self._server_checked: + return + self._server_checked = True + + pre = _listener_pid() + if pre is not None: + logger.info("检测到既有 HDC server (PID %s),本次任务不接管", pre) + return + + started_at = time.time() + try: + result = subprocess.run( + [self._hdc, "start"], + capture_output=True, + text=True, + timeout=10, + **no_window_kwargs(), + ) + except (OSError, subprocess.TimeoutExpired) as exc: + logger.warning("启动 HDC 后台服务失败: %s", exc) + return + if result.returncode != 0: + output = (result.stderr or result.stdout or "").strip() + logger.warning( + "启动 HDC 后台服务失败 (code=%s): %s", + result.returncode, + output, + ) + return + + post = None + for attempt in range(_LISTENER_POLL_ATTEMPTS): + post = _listener_pid() + if post is not None: + break + if attempt + 1 < _LISTENER_POLL_ATTEMPTS: + time.sleep(_LISTENER_POLL_INTERVAL) + if post is None: + logger.warning("hdc start 未发现监听进程,归属不明,close 时不清理") + return + created = _process_start_time(post) + if created is None or created < started_at - _CLOCK_SKEW_SECONDS: + # 监听进程早于本次 hdc start 创建 → 外部 HDC 抢先启动,不接管 + logger.warning( + "监听进程 PID %s 非本次启动(可能外部抢先),不接管清理", post + ) + return + self._owned_server_pid = post + logger.info("本任务已启动 HDC 后台服务 (PID %s)", post) + + def close(self) -> None: + """仅关闭本实例确认启动的 HDC server。 + + 先复核监听 PID 未易主,再调用全局 ``hdc kill`` 优雅关闭(官方命令, + 由守护进程自身结束其进程树);归属不明或已易主时不清理。 + """ + if self._closed: + return + self._closed = True + if self._owned_server_pid is None: + return + + current = _listener_pid() + if current != self._owned_server_pid: + if current is None: + logger.info("HDC server 已不在监听,跳过清理") + else: + logger.warning( + "HDC server 监听 PID 已从 %s 变为 %s,视为外部接管,不执行清理", + self._owned_server_pid, + current, + ) + return + + try: + result = subprocess.run( + [self._hdc, "kill"], + capture_output=True, + text=True, + timeout=5, + **no_window_kwargs(), + ) + except (OSError, subprocess.TimeoutExpired) as exc: + logger.warning("关闭 HDC 后台服务失败: %s", exc) + return + if result.returncode == 0: + logger.info( + "已关闭本次启动的 HDC 后台服务 (PID %s)", self._owned_server_pid + ) + else: + output = (result.stderr or result.stdout or "").strip() + logger.warning( + "关闭 HDC 后台服务失败 (code=%s): %s", + result.returncode, + output, + ) + def get_udid(self) -> str: """获取已连接设备的 UDID。 @@ -26,14 +294,20 @@ def get_udid(self) -> str: Raises: RuntimeError: 没有可用设备或无法获取 UDID 时抛出。 """ - hdc = config.HDC_PATH + self._ensure_server() commands = [ - [hdc, "shell", "bm", "get", "-u"], - [hdc, "shell", "param", "get", "const.product.udid"], + [self._hdc, "shell", "bm", "get", "-u"], + [self._hdc, "shell", "param", "get", "const.product.udid"], ] for cmd in commands: try: - result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + result = run_process( + cmd, + capture_output=True, + text=True, + timeout=15, + cancel_event=self.cancel_event, + ) except subprocess.TimeoutExpired: continue if result.returncode == 0: @@ -41,7 +315,9 @@ def get_udid(self) -> str: match = re.search(r"\b([0-9A-Fa-f]{64})\b", result.stdout) if match: return match.group(1) - raise RuntimeError("无法获取设备 UDID: 请确认已连接设备且 hdc 可用") + raise RuntimeError( + "未检测到可用设备:请确认设备已连接、已授权 USB 调试,且当前只连接一台设备" + ) def install(self, hap_path: str) -> bool: """使用 hdc install 安装 hap 包到已连接设备。 @@ -55,13 +331,20 @@ def install(self, hap_path: str) -> bool: Raises: RuntimeError: hdc install 执行失败时抛出。 """ - hdc = config.HDC_PATH - cmd = [hdc, "install", hap_path] - result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + self._ensure_server() + cmd = [self._hdc, "install", hap_path] + result = run_process( + cmd, + capture_output=True, + text=True, + timeout=60, + cancel_event=self.cancel_event, + ) output = (result.stdout or "") + (result.stderr or "") - # hdc install 即使失败也可能返回 0,需要检查输出内容 - failed = "error:" in output.lower() or "failed" in output.lower() - if result.returncode != 0 or failed: + # hdc install 即使失败也可能返回 0,仅按真实失败标记识别: + # 非零退出码、[Fail] 状态行、INSTALL_FAILED_ 前缀或明确的 error: 行。 + failed = result.returncode != 0 or bool(_FATAL_INSTALL_MARKERS.search(output)) + if failed: raise RuntimeError( f"hdc install 失败 (code={result.returncode}): {output.strip()}" ) diff --git a/hapsign/signing/keytool_util.py b/hapsign/signing/keytool_util.py index 1498768..47f23d9 100644 --- a/hapsign/signing/keytool_util.py +++ b/hapsign/signing/keytool_util.py @@ -1,24 +1,28 @@ """keytool 工具封装 —— 生成密钥对和 CSR。""" import os -import subprocess +import threading from hapsign import config +from hapsign.subprocess_utils import run_process class KeytoolUtil: """封装 keytool 命令,用于生成 EC 密钥对和 CSR。""" + def __init__(self, cancel_event: threading.Event | None = None) -> None: + self.cancel_event = cancel_event + @staticmethod def _get_keytool_path() -> str: - """返回与当前平台匹配的 keytool 路径。""" + """返回运行时发现的 keytool 路径。""" return config.KEYTOOL_PATH def generate_keypair( self, keystore_path: str, alias: str = config.KEY_ALIAS, - password: str = "123456", + password: str = config.KEYSTORE_PASSWORD, ) -> bool: """使用 keytool -genkeypair 生成 EC 256 密钥对。 @@ -58,7 +62,12 @@ def generate_keypair( "-dname", config.KEY_DNAME, ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = run_process( + cmd, + capture_output=True, + text=True, + cancel_event=self.cancel_event, + ) if result.returncode != 0: raise RuntimeError( f"keytool -genkeypair 失败 (code={result.returncode}): " @@ -70,7 +79,7 @@ def generate_csr( self, keystore_path: str, alias: str = config.KEY_ALIAS, - password: str = "123456", + password: str = config.KEYSTORE_PASSWORD, csr_path: str = "", ) -> str: """使用 keytool -certreq 生成 CSR 并返回其内容。 @@ -102,7 +111,12 @@ def generate_csr( "-sigalg", config.SIGN_ALG, ] - result = subprocess.run(cmd, capture_output=True, text=True) + result = run_process( + cmd, + capture_output=True, + text=True, + cancel_event=self.cancel_event, + ) if result.returncode != 0: raise RuntimeError( f"keytool -certreq 失败 (code={result.returncode}): " diff --git a/hapsign/subprocess_utils.py b/hapsign/subprocess_utils.py new file mode 100644 index 0000000..4f686d0 --- /dev/null +++ b/hapsign/subprocess_utils.py @@ -0,0 +1,259 @@ +"""跨平台外部进程启动选项。""" + +from __future__ import annotations + +import os +import platform +import signal +import subprocess +import threading +import time +from typing import Any + +from hapsign.cancellation import OperationCancelled, raise_if_cancelled + +_CREATE_NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0x08000000) + + +def no_window_kwargs() -> dict[str, int]: + """Windows 下禁止控制台工具创建一闪而过的命令行窗口。""" + if platform.system() == "Windows": + return {"creationflags": _CREATE_NO_WINDOW} + return {} + + +# ── Windows Job Object(进程树终止) ───────────────────────────────── + +_JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000 +_JOB_OBJECT_EXTENDED_LIMIT_INFORMATION_CLASS = 9 + + +def _create_job_object(process: subprocess.Popen) -> Any | None: + """为 Windows 子进程创建带 KILL_ON_JOB_CLOSE 的 Job Object。 + + 创建或绑定失败(例如已处于禁止嵌套的 Job 中)时返回 None,调用方回退为 + 只终止直接子进程。 + """ + try: + import ctypes + from ctypes import wintypes + + class _BasicLimit(ctypes.Structure): + _fields_ = [ + ("PerProcessUserTimeLimit", wintypes.LARGE_INTEGER), + ("PerJobUserTimeLimit", wintypes.LARGE_INTEGER), + ("LimitFlags", wintypes.DWORD), + ("MinimumWorkingSetSize", ctypes.c_size_t), + ("MaximumWorkingSetSize", ctypes.c_size_t), + ("ActiveProcessLimit", wintypes.DWORD), + ("Affinity", ctypes.c_size_t), + ("PriorityClass", wintypes.DWORD), + ("SchedulingClass", wintypes.DWORD), + ] + + class _IOCounters(ctypes.Structure): + _fields_ = [ + ("ReadOperationCount", ctypes.c_ulonglong), + ("WriteOperationCount", ctypes.c_ulonglong), + ("OtherOperationCount", ctypes.c_ulonglong), + ("ReadTransferCount", ctypes.c_ulonglong), + ("WriteTransferCount", ctypes.c_ulonglong), + ("OtherTransferCount", ctypes.c_ulonglong), + ] + + class _ExtendedLimit(ctypes.Structure): + _fields_ = [ + ("BasicLimitInformation", _BasicLimit), + ("IoInfo", _IOCounters), + ("ProcessMemoryLimit", ctypes.c_size_t), + ("JobMemoryLimit", ctypes.c_size_t), + ("PeakProcessMemoryUsed", ctypes.c_size_t), + ("PeakJobMemoryUsed", ctypes.c_size_t), + ] + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.CreateJobObjectW.restype = wintypes.HANDLE + kernel32.CreateJobObjectW.argtypes = [wintypes.LPVOID, wintypes.LPCWSTR] + kernel32.SetInformationJobObject.argtypes = [ + wintypes.HANDLE, + ctypes.c_int, + wintypes.LPVOID, + wintypes.DWORD, + ] + kernel32.AssignProcessToJobObject.argtypes = [ + wintypes.HANDLE, + wintypes.HANDLE, + ] + + job = kernel32.CreateJobObjectW(None, None) + if not job: + return None + info = _ExtendedLimit() + info.BasicLimitInformation.LimitFlags = _JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + if not kernel32.SetInformationJobObject( + job, + _JOB_OBJECT_EXTENDED_LIMIT_INFORMATION_CLASS, + ctypes.byref(info), + ctypes.sizeof(info), + ): + kernel32.CloseHandle(job) + return None + if not kernel32.AssignProcessToJobObject(job, process._handle): + kernel32.CloseHandle(job) + return None + return job + except (OSError, AttributeError): + return None + + +def _terminate_job(job: Any) -> None: + try: + import ctypes + from ctypes import wintypes + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.TerminateJobObject.argtypes = [wintypes.HANDLE, wintypes.UINT] + kernel32.TerminateJobObject(job, 1) + except OSError: + pass + + +def _close_job(job: Any) -> None: + try: + import ctypes + from ctypes import wintypes + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.CloseHandle.argtypes = [wintypes.HANDLE] + kernel32.CloseHandle(job) + except OSError: + pass + + +# ── 进程树终止 ───────────────────────────────────────────────────── + + +def _terminate_process_group(process: subprocess.Popen) -> None: + """POSIX:向子进程所在进程组发送 SIGTERM(进程以新会话/组启动)。""" + try: + os.killpg(os.getpgid(process.pid), signal.SIGTERM) + except (ProcessLookupError, PermissionError, OSError): + pass + + +def _terminate_windows_tree(pid: int) -> None: + """Windows 兜底:taskkill /T 终止整棵进程树。 + + Job Object 只能覆盖绑定时刻之后创建的进程,对绑定前已存在的后代进程 + 无能为力;taskkill /T 在终止时重新遍历进程树,可补齐这部分进程。 + """ + try: + subprocess.run( + ["taskkill", "/F", "/T", "/PID", str(pid)], + capture_output=True, + text=True, + **no_window_kwargs(), + ) + except (OSError, ValueError): + pass + + +def _stop_process( + process: subprocess.Popen, + job: Any | None = None, +) -> None: + """终止子进程及其后代进程树。 + + Windows 依次使用 Job Object 与 taskkill /T 兜底;POSIX 使用进程组; + 最后等待最多 2 秒,仍存活则强制 kill。 + """ + if process.poll() is not None: + return + if job is not None: + _terminate_job(job) + if os.name == "nt": + _terminate_windows_tree(process.pid) + elif job is None: + _terminate_process_group(process) + try: + process.wait(timeout=2) + except subprocess.TimeoutExpired: + process.kill() + process.wait(timeout=2) + + +def _popen_process_tree_options() -> dict[str, Any]: + """Popen 需要额外传入的进程树参数:POSIX 下独立进程组。""" + if os.name == "nt": + return {} + return {"start_new_session": True} + + +def run_process( + command: list[str], + *, + cancel_event: threading.Event | None = None, + timeout: float | None = None, + capture_output: bool = False, + text: bool = False, + **kwargs: Any, +) -> subprocess.CompletedProcess: + """运行外部命令;有取消信号或超时要求时,终止子进程及整棵进程树。 + + 仅当既无取消信号也无超时时走 subprocess.run 快路径;否则使用 Popen + + Job Object / 进程组,确保取消或超时能终止整棵进程树。 + """ + options = no_window_kwargs() + options.update(kwargs) + if cancel_event is None and timeout is None: + return subprocess.run( + command, + capture_output=capture_output, + text=text, + **options, + ) + + raise_if_cancelled(cancel_event) + popen_options = dict(options) + popen_options.update(_popen_process_tree_options()) + process = subprocess.Popen( + command, + stdout=subprocess.PIPE if capture_output else None, + stderr=subprocess.PIPE if capture_output else None, + text=text, + **popen_options, + ) + job = _create_job_object(process) if os.name == "nt" else None + try: + deadline = None if timeout is None else time.monotonic() + timeout + while True: + if cancel_event is not None and cancel_event.is_set(): + _stop_process(process, job) + process.communicate() + raise OperationCancelled("操作已取消") + wait_timeout = 0.1 + if deadline is not None: + remaining = deadline - time.monotonic() + if remaining <= 0: + _stop_process(process, job) + stdout, stderr = process.communicate() + raise subprocess.TimeoutExpired( + command, + timeout, + output=stdout, + stderr=stderr, + ) + wait_timeout = min(wait_timeout, remaining) + try: + stdout, stderr = process.communicate(timeout=wait_timeout) + except subprocess.TimeoutExpired: + continue + return subprocess.CompletedProcess( + command, + process.returncode, + stdout, + stderr, + ) + finally: + if job is not None: + _close_job(job) diff --git a/hapsign/token/secure_token_cache.py b/hapsign/token/secure_token_cache.py new file mode 100644 index 0000000..fbcc82f --- /dev/null +++ b/hapsign/token/secure_token_cache.py @@ -0,0 +1,157 @@ +"""Token 缓存静态加密。 + +Windows 使用当前用户作用域的 DPAPI(CryptProtectData / CryptUnprotectData), +无第三方依赖;其他平台退化为受限权限(0o600)的明文 JSON,并在日志与文档中 +明确告警。缓存文件单行存储: + +- 加密格式:``hapsign-token-v1:`` +- 旧版明文 JSON 首次读取时自动安全迁移为加密格式。 + +任何路径都不得把加密前的 token、密钥或完整缓存内容写入日志。 +""" + +from __future__ import annotations + +import base64 +import logging +import os + +logger = logging.getLogger(__name__) + +_HEADER = "hapsign-token-v1:" +_HEADER_BYTES = _HEADER.encode("ascii") + +# CRYPTPROTECT_UI_FORBIDDEN:禁止弹出任何 UI,失败即抛错。 +_CRYPTPROTECT_UI_FORBIDDEN = 0x1 + + +class DecryptError(Exception): + """缓存无法解密(密钥不可用、内容损坏或非加密格式)。""" + + +def is_encrypted(data: bytes) -> bool: + """判断缓存字节是否为加密格式。""" + return data.startswith(_HEADER_BYTES) + + +def protect(payload: bytes) -> bytes: + """加密缓存载荷,返回可直接写入磁盘的字节。 + + 非 Windows 平台无 DPAPI,按约定退化为原样明文(由调用方限制文件权限), + 并记录明确告警。Windows 上 CryptProtectData 失败时抛出 OSError,由调用方 + 决定降级策略(当前策略:放弃保存并告警,绝不把 token 明文落盘)。 + """ + if os.name != "nt": + logger.warning( + "当前平台没有 DPAPI,token 缓存将以受限权限的明文存储;" + "请勿在共享电脑或云同步目录中使用本工具" + ) + return payload + return _HEADER_BYTES + base64.b64encode(_dpapi_protect(payload)) + + +def decrypt(data: bytes) -> bytes: + """解密缓存字节,返回原始 JSON 载荷。 + + Raises: + DecryptError: 非加密格式、非 Windows 平台、base64 无效或 DPAPI 解密失败。 + """ + if not is_encrypted(data): + raise DecryptError("缓存不是加密格式") + if os.name != "nt": + # 加密缓存由 Windows DPAPI 生成,跨平台无法解密。显式报错而不是 + # 去调用仅 Windows 可用的 crypt32,避免 AttributeError 泄漏。 + raise DecryptError( + "缓存文件是 Windows DPAPI 加密格式,当前平台无法解密;" + "请删除缓存文件后重新登录" + ) + try: + blob = base64.b64decode(data[len(_HEADER_BYTES) :]) + except ValueError as exc: + raise DecryptError("缓存头有效但 base64 编码无效") from exc + try: + return _dpapi_unprotect(blob) + except OSError as exc: + raise DecryptError(f"DPAPI 解密失败: {exc}") from exc + + +def _dpapi_protect(data: bytes) -> bytes: + """调用 CryptProtectData(当前用户作用域)加密字节串。""" + import ctypes + from ctypes import wintypes + + class DATA_BLOB(ctypes.Structure): + _fields_ = [("cbData", wintypes.DWORD), ("pbData", ctypes.c_void_p)] + + crypt32 = ctypes.WinDLL("crypt32", use_last_error=True) + crypt32.CryptProtectData.restype = wintypes.BOOL + crypt32.CryptProtectData.argtypes = [ + ctypes.POINTER(DATA_BLOB), + wintypes.LPCWSTR, + ctypes.POINTER(DATA_BLOB), + ctypes.c_void_p, + ctypes.c_void_p, + wintypes.DWORD, + ctypes.POINTER(DATA_BLOB), + ] + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.LocalFree.argtypes = [wintypes.HANDLE] + + buffer = ctypes.create_string_buffer(data) + blob_in = DATA_BLOB(len(data), ctypes.cast(buffer, ctypes.c_void_p)) + blob_out = DATA_BLOB() + if not crypt32.CryptProtectData( + ctypes.byref(blob_in), + None, + None, + None, + None, + _CRYPTPROTECT_UI_FORBIDDEN, + ctypes.byref(blob_out), + ): + raise OSError(f"CryptProtectData 失败: {ctypes.get_last_error()}") + try: + return ctypes.string_at(blob_out.pbData, blob_out.cbData) + finally: + kernel32.LocalFree(blob_out.pbData) + + +def _dpapi_unprotect(data: bytes) -> bytes: + """调用 CryptUnprotectData(当前用户作用域)解密字节串。""" + import ctypes + from ctypes import wintypes + + class DATA_BLOB(ctypes.Structure): + _fields_ = [("cbData", wintypes.DWORD), ("pbData", ctypes.c_void_p)] + + crypt32 = ctypes.WinDLL("crypt32", use_last_error=True) + crypt32.CryptUnprotectData.restype = wintypes.BOOL + crypt32.CryptUnprotectData.argtypes = [ + ctypes.POINTER(DATA_BLOB), + ctypes.POINTER(wintypes.LPWSTR), + ctypes.POINTER(DATA_BLOB), + ctypes.c_void_p, + ctypes.c_void_p, + wintypes.DWORD, + ctypes.POINTER(DATA_BLOB), + ] + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.LocalFree.argtypes = [wintypes.HANDLE] + + buffer = ctypes.create_string_buffer(data) + blob_in = DATA_BLOB(len(data), ctypes.cast(buffer, ctypes.c_void_p)) + blob_out = DATA_BLOB() + if not crypt32.CryptUnprotectData( + ctypes.byref(blob_in), + None, + None, + None, + None, + _CRYPTPROTECT_UI_FORBIDDEN, + ctypes.byref(blob_out), + ): + raise OSError(f"CryptUnprotectData 失败: {ctypes.get_last_error()}") + try: + return ctypes.string_at(blob_out.pbData, blob_out.cbData) + finally: + kernel32.LocalFree(blob_out.pbData) diff --git a/hapsign/token/token_exchange.py b/hapsign/token/token_exchange.py index 150852c..d71c116 100644 --- a/hapsign/token/token_exchange.py +++ b/hapsign/token/token_exchange.py @@ -9,35 +9,77 @@ import base64 import json +import logging +import threading +from urllib.parse import urlparse import requests +from hapsign.cancellation import raise_if_cancelled from hapsign.config import ( APP_ID, BASE_URL, HEADER_JWT_TOKEN, HEADER_REFRESH, JWT_TOKEN_CHECK_PATH, + LOGIN_PROTOCOL_VERSION, TEMP_TOKEN_CHECK_PATH, ) +from hapsign.diagnostics import sensitive_logging_enabled from hapsign.models import TokenInfo +logger = logging.getLogger(__name__) + + +def _log_response( + operation: str, + url: str, + response: requests.Response, + *, + request_data: object, +) -> None: + parsed = urlparse(url) + content = getattr(response, "content", b"") + response_size = len(content) if isinstance(content, (bytes, str)) else -1 + logger.debug( + "[token] %s response=%s://%s%s status=%d bytes=%d", + operation, + parsed.scheme, + parsed.netloc, + parsed.path, + response.status_code, + response_size, + ) + if sensitive_logging_enabled(): + logger.debug( + "[token] sensitive %s request=%r response=%r", + operation, + request_data, + response.text, + ) + class TokenExchange: """Token 交换工具类。""" + def __init__(self, cancel_event: threading.Event | None = None) -> None: + self.cancel_event = cancel_event + + def _check_cancelled(self) -> None: + raise_if_cancelled(self.cancel_event) + def exchange_temp_token( self, temp_token: str, site: str = "CN", - version: str = "5.0.5", + version: str = LOGIN_PROTOCOL_VERSION, ) -> str: """用 tempToken 换取 jwtToken。 Args: temp_token: 浏览器回调获得的临时令牌。 site: 站点代码(CN / SG / DE / RU)。 - version: DevEco Studio 版本号。 + version: 登录协议版本(默认取 config.LOGIN_PROTOCOL_VERSION)。 Returns: jwtToken 字符串。 @@ -52,7 +94,10 @@ def exchange_temp_token( "version": version, "appid": APP_ID, } - resp = requests.get(url, params=params, timeout=30) + self._check_cancelled() + resp = requests.get(url, params=params, timeout=(5, 15)) + self._check_cancelled() + _log_response("temp-token-check", url, resp, request_data=params) resp.raise_for_status() # 响应正文即为 jwtToken 字符串(非 JSON) return resp.text @@ -72,7 +117,10 @@ def get_access_token(self, jwt_token: str) -> TokenInfo: """ url = f"{BASE_URL}/{JWT_TOKEN_CHECK_PATH}" headers = {HEADER_JWT_TOKEN: jwt_token, HEADER_REFRESH: "false"} - resp = requests.get(url, headers=headers, timeout=30) + self._check_cancelled() + resp = requests.get(url, headers=headers, timeout=(5, 15)) + self._check_cancelled() + _log_response("jwt-token-check", url, resp, request_data=headers) resp.raise_for_status() data = resp.json() @@ -126,7 +174,10 @@ def refresh_access_token(self, jwt_token: str) -> str: """ url = f"{BASE_URL}/{JWT_TOKEN_CHECK_PATH}" headers = {HEADER_JWT_TOKEN: jwt_token, HEADER_REFRESH: "true"} - resp = requests.get(url, headers=headers, timeout=30) + self._check_cancelled() + resp = requests.get(url, headers=headers, timeout=(5, 15)) + self._check_cancelled() + _log_response("refresh-token", url, resp, request_data=headers) resp.raise_for_status() data = resp.json() diff --git a/pyproject.toml b/pyproject.toml index a42e949..88f09b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,11 +23,20 @@ classifiers = [ "Topic :: Software Development :: Build Tools", ] dependencies = [ - "playwright>=1.40,<2", "requests>=2.31,<3", ] [project.optional-dependencies] +playwright = [ + "playwright>=1.40,<2", +] +gui = [ + "PySide6-Essentials>=6.8,<7", + "playwright>=1.40,<2", +] +bundle = [ + "PyInstaller>=6.14,<7", +] dev = [ "pre-commit>=4,<5", "pytest>=8,<9", @@ -38,9 +47,15 @@ dev = [ [project.scripts] hapsign = "hapsign.cli:main" +[project.gui-scripts] +hapsign-app = "hapsign.gui:main" + [project.urls] Homepage = "https://github.com/guantw/HapSign" +Source = "https://github.com/guantw/HapSign" Issues = "https://github.com/guantw/HapSign/issues" +Changelog = "https://github.com/guantw/HapSign/blob/master/CHANGELOG.md" +Security = "https://github.com/guantw/HapSign/security" [tool.setuptools.dynamic] version = {attr = "hapsign.__version__"} @@ -55,6 +70,7 @@ testpaths = ["tests"] [tool.coverage.run] branch = true source = ["hapsign"] +omit = ["hapsign/gui.py"] [tool.coverage.report] fail_under = 58 diff --git a/scripts/build_portable.py b/scripts/build_portable.py new file mode 100644 index 0000000..ac01dd7 --- /dev/null +++ b/scripts/build_portable.py @@ -0,0 +1,631 @@ +"""构建当前平台的 HapSign 便携版目录和 ZIP。 + +构建机需要 Python、项目 bundle 依赖以及 prepare_toolchain 生成的公开工具链。 +DevEco 仅作为显式启用的本机兼容回退。 +""" + +from __future__ import annotations + +import argparse +import hashlib +import importlib.util +import os +import re +import shutil +import subprocess +import sys +import tempfile +from importlib import metadata +from pathlib import Path + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +DIST_DIR = PROJECT_ROOT / "dist" +PREPARED_TOOLCHAIN_ROOT = PROJECT_ROOT / "build" / "toolchain-prepared" +# 实际冻结进便携包、需要随包附许可的 Python 运行时依赖。 +# 门禁 _validate_runtime_license_coverage 会校验:本清单必须 ⊇ 实际冻结闭包。 +PYTHON_RUNTIME_DISTRIBUTIONS = ( + "PySide6-Essentials", + "shiboken6", + "playwright", + "greenlet", # playwright 的运行时传递依赖 + "pyee", # playwright 的运行时传递依赖 + "requests", + "urllib3", + "certifi", + "charset-normalizer", + "idna", + "PyInstaller", +) +# 仅构建期使用的工具:其 bootloader 嵌入冻结产物故保留许可记录,但自身与其传递 +# 依赖并不随便携包分发,因此不计入门禁的“实际冻结闭包”。 +BUILD_TIME_ONLY_DISTRIBUTIONS = ("PyInstaller",) + + +def _write_sha256_file(archive_path: Path) -> Path: + """为发布归档生成可被常见校验工具读取的 SHA-256 sidecar。""" + + digest = hashlib.sha256() + with archive_path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(chunk) + checksum_path = archive_path.with_suffix(archive_path.suffix + ".sha256") + checksum_path.write_text( + f"{digest.hexdigest()} {archive_path.name}\n", + encoding="ascii", + ) + return checksum_path + + +# 以下排除项只用于显式启用的 DevEco JBR 回退;正式构建使用 jlink runtime。 +# 这里仅排除能够由构建后工具链自检证明无关的 JCEF 原生资源;Java modules、 +# 字体、安全配置和其他 DLL 全部保留,后续精简需单独增加测试后再进行。 +WINDOWS_JCEF_BIN_FILES = { + "chrome_elf.dll", + "d3dcompiler_47.dll", + "dxcompiler.dll", + "dxil.dll", + "icudtl.dat", + "jcef.dll", + "jcef_helper.dll", + "jcef_helper.exe", + "jogl_desktop.dll", + "jogl_mobile.dll", + "libcef.dll", + "libegl.dll", + "libglesv2.dll", + "snapshot_blob.bin", + "v8_context_snapshot.bin", + "vk_swiftshader.dll", + "vulkan-1.dll", +} +WINDOWS_JCEF_LIB_FILES = { + "chrome_100_percent.pak", + "chrome_200_percent.pak", + "resources.pak", + "vk_swiftshader_icd.json", +} +WINDOWS_JCEF_LIB_DIRS = {"locales"} + + +def _require_build_modules() -> None: + missing = [ + name + for name in ("PyInstaller", "PySide6", "playwright") + if importlib.util.find_spec(name) is None + ] + if missing: + packages = " ".join(missing) + raise RuntimeError( + "缺少构建依赖:" + f"{', '.join(missing)}\n" + f"请先执行:{sys.executable} -m pip install -e .[gui,bundle]\n" + f"检测名称:{packages}" + ) + + +def _require_playwright_browser() -> None: + from playwright.sync_api import sync_playwright + + with sync_playwright() as pw: + executable = Path(pw.chromium.executable_path) + if executable.is_file(): + return + raise RuntimeError( + "缺少 Playwright Chromium 浏览器。\n" + "请在 PowerShell 中执行:\n" + '$env:PLAYWRIGHT_BROWSERS_PATH="0"\n' + f"{sys.executable} -m playwright install --no-shell chromium" + ) + + +def _run(command: list[str], *, env: dict[str, str]) -> None: + print("+", subprocess.list2cmdline(command)) + subprocess.run(command, cwd=PROJECT_ROOT, env=env, check=True) + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest().upper() + + +def _runtime_requirement_names(distribution_name: str) -> set[str]: + """返回 distribution 在当前环境生效的核心运行时依赖名(排除 extras 条件依赖)。 + + `metadata.requires` 返回的字符串形如 `greenlet>=3.1.1 ; python_version >= '3.9'` + 或 `pyee (>=12.0.0) ; extra == 'gui'`;这里只解析名字部分,并丢弃带 + `extra ==` 标记的条件依赖。 + """ + names: set[str] = set() + try: + raw_requirements = metadata.requires(distribution_name) + except metadata.PackageNotFoundError: + return names + for raw in raw_requirements or (): + if "extra ==" in raw: + continue + name_part = raw.split(";", 1)[0].strip() + if not name_part: + continue + # 只取名字部分:截断到版本比较符、逗号或空白。 + # 例如 "greenlet>=3.1.1" → "greenlet";"pyee (>=12.0.0)" → "pyee"。 + name = re.split(r"[<>=!~,\s]+", name_part, maxsplit=1)[0].strip() + if name: + names.add(name) + return names + + +def _normalize_distribution_name(name: str) -> str: + """PEP 503 规范化:小写并把 `-`/`_`/`.` 合并为 `-`。 + + 等价于 importlib.metadata.normalize_name(Python 3.13+ 才公开), + 3.11/3.12 构建机这里自行实现。 + """ + return re.sub(r"[-_.]+", "-", name).lower() + + +def _frozen_runtime_closure(roots: tuple[str, ...]) -> set[str]: + """从实际冻结的根依赖出发,解析传递的运行时依赖闭包(规范化名称)。 + + 只跟随当前环境实际生效的核心运行时依赖;构建期工具不在 roots 中, + 其依赖也不会被误收。 + """ + closure: set[str] = set() + pending = [_normalize_distribution_name(root) for root in roots] + while pending: + current = pending.pop() + if current in closure: + continue + closure.add(current) + for dep in _runtime_requirement_names(current): + normalized = _normalize_distribution_name(dep) + if normalized not in closure: + pending.append(normalized) + return closure + + +def _validate_runtime_license_coverage() -> None: + """门禁:许可清单必须覆盖实际冻结闭包,缺项直接让构建失败。 + + 断言方向为「清单 ⊇ 闭包」:只允许闭包比清单多而报错,不允许按清单反向 + 裁剪实际冻结内容,否则构建机与发布环境不一致时差距会被悄悄掩盖。 + """ + roots = tuple( + name + for name in PYTHON_RUNTIME_DISTRIBUTIONS + if name not in BUILD_TIME_ONLY_DISTRIBUTIONS + ) + listed = { + _normalize_distribution_name(name) for name in PYTHON_RUNTIME_DISTRIBUTIONS + } + closure = _frozen_runtime_closure(roots) + missing = sorted(name for name in closure if name not in listed) + if missing: + raise RuntimeError( + "第三方许可清单缺少实际冻结的运行时依赖:" + f"{', '.join(missing)}\n" + "请在 PYTHON_RUNTIME_DISTRIBUTIONS 中补全后再构建。" + ) + + +def _copy_python_license_files(portable_root: Path) -> None: + """复制冻结依赖随 wheel 安装的许可,并记录精确构建版本。""" + license_root = portable_root / "licenses" / "python" + license_root.mkdir(parents=True, exist_ok=True) + manifest = [ + "Python distributions frozen into this build", + "===========================================", + "", + "Generated from the active build environment.", + "", + ] + + for distribution_name in PYTHON_RUNTIME_DISTRIBUTIONS: + try: + distribution = metadata.distribution(distribution_name) + except metadata.PackageNotFoundError as exc: + raise RuntimeError( + f"无法生成第三方许可清单,缺少 distribution: {distribution_name}" + ) from exc + + canonical_name = distribution.metadata.get("Name", distribution_name) + version = distribution.version + license_expression = ( + distribution.metadata.get("License-Expression") + or distribution.metadata.get("License") + or "(not declared in package metadata)" + ) + manifest.append(f"- {canonical_name} {version}: {license_expression}") + + destination = license_root / f"{canonical_name}-{version}" + copied = 0 + for entry in distribution.files or (): + normalized = str(entry).replace("\\", "/") + lowered = normalized.lower() + filename = Path(normalized).name.lower() + is_license_name = filename.startswith( + ("license", "copying", "notice", "thirdpartynotice") + ) + if ".dist-info/licenses/" not in lowered and not ( + ".dist-info/" in lowered and is_license_name + ): + continue + source = Path(distribution.locate_file(entry)) + if not source.is_file(): + continue + target = destination / Path(normalized).name + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source, target) + copied += 1 + + if copied == 0: + manifest.append( + f" No standalone license file was present in the installed " + f"{canonical_name} distribution; see THIRD_PARTY_NOTICES.md." + ) + + python_license = Path(sys.base_prefix) / "LICENSE.txt" + if not python_license.is_file(): + raise RuntimeError(f"无法找到 Python 运行时许可文件: {python_license}") + shutil.copy2(python_license, license_root / "PYTHON-LICENSE.txt") + + (license_root / "DISTRIBUTIONS.txt").write_text( + "\n".join(manifest) + "\n", + encoding="utf-8", + ) + + +def _copy_release_documents(portable_root: Path) -> None: + """把源码许可、隐私和构建说明放到可执行包根目录。""" + documents = { + "PORTABLE.md": "README.md", + "LICENSE": "LICENSE", + "PRIVACY.md": "PRIVACY.md", + "THIRD_PARTY_NOTICES.md": "THIRD_PARTY_NOTICES.md", + "docs/PACKAGING.md": "BUILDING.md", + "docs/OPEN_SOURCE_RELEASE.md": "OPEN_SOURCE_RELEASE.md", + } + for source_name, target_name in documents.items(): + shutil.copy2(PROJECT_ROOT / source_name, portable_root / target_name) + _copy_python_license_files(portable_root) + + +def _jbr_ignore(source_root: Path): + bin_dir = (source_root / "bin").resolve() + lib_dir = (source_root / "lib").resolve() + + def ignore(directory: str, names: list[str]) -> set[str]: + current = Path(directory).resolve() + lowered = {name.lower(): name for name in names} + if current == bin_dir: + return {lowered[name] for name in WINDOWS_JCEF_BIN_FILES if name in lowered} + if current == lib_dir: + excluded = WINDOWS_JCEF_LIB_FILES | WINDOWS_JCEF_LIB_DIRS + return {lowered[name] for name in excluded if name in lowered} + return set() + + return ignore + + +def _smoke_test_toolchain(portable_root: Path) -> None: + from hapsign.runtime import platform_tag + + root = portable_root / "resources" / "toolchain" / platform_tag() + suffix = ".exe" if sys.platform == "win32" else "" + runtime = root / "runtime" + if not runtime.is_dir(): + runtime = root / "jbr" + java = runtime / "bin" / f"java{suffix}" + keytool = runtime / "bin" / f"keytool{suffix}" + signer = root / "lib" / "hap-sign-tool.jar" + + checks = [ + [str(java), "-version"], + [str(java), "-jar", str(signer)], + ] + with tempfile.TemporaryDirectory(prefix="hapsign-toolchain-smoke-") as temp: + keystore = Path(temp) / "smoke.p12" + checks.append( + [ + str(keytool), + "-genkeypair", + "-alias", + "hapsign-smoke", + "-keyalg", + "EC", + "-groupname", + "secp256r1", + "-keystore", + str(keystore), + "-storetype", + "PKCS12", + "-storepass", + "hapsign-smoke-password", + "-keypass", + "hapsign-smoke-password", + "-dname", + "CN=HapSign Build Smoke", + "-validity", + "1", + "-noprompt", + ] + ) + for command in checks: + result = subprocess.run( + command, + cwd=PROJECT_ROOT, + capture_output=True, + text=True, + timeout=30, + check=False, + ) + if result.returncode != 0: + details = result.stderr.strip() or result.stdout.strip() + raise RuntimeError( + "精简后的便携 Java 工具链自检失败:" + f"{subprocess.list2cmdline(command)}\n{details}" + ) + + +def _copy_local_toolchain(portable_root: Path, *, keep_full_jbr: bool) -> None: + """从本机安装复制工具,仅供兼容和排障,不用于公开 Release。""" + sys.path.insert(0, str(PROJECT_ROOT)) + from hapsign.runtime import discover_toolchain, platform_tag + + toolchain = discover_toolchain() + missing = toolchain.missing() + if missing: + details = "\n".join(f"- {item}" for item in missing) + raise RuntimeError(f"无法生成完整便携版,工具链缺失:\n{details}") + + jbr_root = toolchain.java.parent.parent + target = portable_root / "resources" / "toolchain" / platform_tag() + target.mkdir(parents=True, exist_ok=True) + copy_options = {"dirs_exist_ok": True} + if sys.platform == "win32" and not keep_full_jbr: + copy_options["ignore"] = _jbr_ignore(jbr_root) + shutil.copytree(jbr_root, target / "jbr", **copy_options) + + lib_dir = target / "lib" + bin_dir = target / "bin" + lib_dir.mkdir(exist_ok=True) + bin_dir.mkdir(exist_ok=True) + shutil.copy2(toolchain.hap_sign_tool, lib_dir / "hap-sign-tool.jar") + shutil.copy2(toolchain.hdc, bin_dir / toolchain.hdc.name) + + # Windows HDC 与同目录的 libusb_shared.dll 配套分发。 + libusb = toolchain.hdc.parent / "libusb_shared.dll" + if libusb.is_file(): + shutil.copy2(libusb, bin_dir / libusb.name) + notice = toolchain.hdc.parent / "NOTICE.txt" + if notice.is_file(): + shutil.copy2(notice, target / "NOTICE.txt") + + # DevEco 自身的综合 NOTICE 可补充 JBR/SDK 安装包中的许可文本。它不能代替 + # 发布者核对具体版本的再分发权,但公开二进制不得把随附声明裁掉。 + deveco_notice = jbr_root.parent / "license" / "NOTICE.txt" + if deveco_notice.is_file(): + notice_target = portable_root / "licenses" / "deveco-studio" + notice_target.mkdir(parents=True, exist_ok=True) + shutil.copy2(deveco_notice, notice_target / "NOTICE.txt") + + manifest_files = { + "java": target / "jbr" / "bin" / toolchain.java.name, + "keytool": target / "jbr" / "bin" / toolchain.keytool.name, + "hap-sign-tool": target / "lib" / "hap-sign-tool.jar", + "hdc": target / "bin" / toolchain.hdc.name, + } + manifest_lines = [ + "Bundled toolchain provenance", + "============================", + "", + f"Detected source type: {toolchain.source}", + "The source installation path is intentionally omitted for privacy.", + "A local build does not by itself grant public redistribution rights.", + "", + ] + for name, path in manifest_files.items(): + manifest_lines.append( + f"{name}: {path.relative_to(portable_root).as_posix()} " + f"sha256={_sha256(path)}" + ) + (target / "PROVENANCE.txt").write_text( + "\n".join(manifest_lines) + "\n", + encoding="utf-8", + ) + _smoke_test_toolchain(portable_root) + + +def _copy_toolchain( + portable_root: Path, + *, + keep_full_jbr: bool, + allow_local_toolchain: bool, +) -> None: + """优先复制已锁定的公开工具链;本机 DevEco 回退必须显式启用。""" + sys.path.insert(0, str(PROJECT_ROOT)) + from hapsign.runtime import platform_tag + + prepared = PREPARED_TOOLCHAIN_ROOT / platform_tag() + target = portable_root / "resources" / "toolchain" / platform_tag() + required = ( + prepared / "PROVENANCE.txt", + prepared / "toolchain.lock.json", + prepared / "NOTICE.txt", + ) + if prepared.is_dir() and all(path.is_file() for path in required): + shutil.copytree(prepared, target, dirs_exist_ok=True) + _smoke_test_toolchain(portable_root) + return + + if not allow_local_toolchain: + raise RuntimeError( + "尚未准备可公开分发的锁定工具链。\n" + f"请先执行:{sys.executable} scripts/prepare_toolchain.py\n" + "仅为本机排障时可显式传入 --allow-deveco-toolchain;" + "该回退产物不应上传公开 Release。" + ) + _copy_local_toolchain(portable_root, keep_full_jbr=keep_full_jbr) + + +def _prune_playwright_extras( + portable_root: Path, + *, + keep_bundled_browser: bool, +) -> None: + """按构建模式清理不需要的 Playwright 浏览器资源。""" + internal = portable_root / "_internal" + browsers = internal.glob("playwright/**/.local-browsers/*") + for browser in browsers: + if not browser.is_dir(): + continue + if browser.name.startswith("ffmpeg-") or ( + browser.name.startswith("chromium-") and not keep_bundled_browser + ): + shutil.rmtree(browser) + + +def _smoke_test_frozen_app( + portable_root: Path, + *, + keep_bundled_browser: bool, +) -> None: + executable_name = "HapSign.exe" if sys.platform == "win32" else "HapSign" + executable = portable_root / executable_name + checks = ["--system-browser-smoke-test", "--smoke-test"] + if keep_bundled_browser: + checks.insert(1, "--browser-smoke-test") + for argument in checks: + result = subprocess.run( + [str(executable), argument], + cwd=portable_root, + timeout=30, + check=False, + ) + if result.returncode != 0: + raise RuntimeError( + f"冻结应用自检失败:{executable.name} {argument} " + f"(code={result.returncode})" + ) + + +def _remove_smoke_artifacts(portable_root: Path) -> None: + """避免把构建自检生成的本地日志或运行数据收入发布包。""" + config = portable_root / "hapsign-config.json" + if config.is_file(): + config.unlink() + for name in ("logs", "signing_files", "signed_haps"): + directory = portable_root / name + if directory.is_dir(): + shutil.rmtree(directory) + + +def build( + *, + skip_toolchain: bool, + keep_full_jbr: bool, + keep_bundled_browser: bool, + allow_local_toolchain: bool, +) -> Path: + _require_build_modules() + _validate_runtime_license_coverage() + os.environ.setdefault("PLAYWRIGHT_BROWSERS_PATH", "0") + if keep_bundled_browser: + _require_playwright_browser() + env = os.environ.copy() + env["HAPSIGN_BUNDLE_CHROMIUM"] = "1" if keep_bundled_browser else "0" + + _run( + [ + sys.executable, + "-m", + "PyInstaller", + "--noconfirm", + "--clean", + "--distpath", + str(DIST_DIR), + "--workpath", + str(PROJECT_ROOT / "build" / "pyinstaller"), + str(PROJECT_ROOT / "bundle" / "hapsign.spec"), + ], + env=env, + ) + + portable_root = DIST_DIR / "HapSign" + _prune_playwright_extras( + portable_root, + keep_bundled_browser=keep_bundled_browser, + ) + if not skip_toolchain: + _copy_toolchain( + portable_root, + keep_full_jbr=keep_full_jbr, + allow_local_toolchain=allow_local_toolchain, + ) + _copy_release_documents(portable_root) + _smoke_test_frozen_app( + portable_root, + keep_bundled_browser=keep_bundled_browser, + ) + _remove_smoke_artifacts(portable_root) + + sys.path.insert(0, str(PROJECT_ROOT)) + from hapsign.runtime import platform_tag + + archive_variant = "-compat" if keep_bundled_browser else "" + archive_base = DIST_DIR / (f"HapSign-portable-{platform_tag()}{archive_variant}") + archive_path = archive_base.with_suffix(".zip") + if archive_path.is_file(): + archive_path.unlink() + shutil.make_archive( + str(archive_base), + "zip", + root_dir=portable_root.parent, + base_dir=portable_root.name, + ) + _write_sha256_file(archive_path) + return archive_path + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--skip-toolchain", + action="store_true", + help="只构建 GUI,不复制签名与设备工具链", + ) + parser.add_argument( + "--keep-full-jbr", + action="store_true", + help="使用 DevEco 回退时保留完整 JBR 的 JCEF 资源", + ) + parser.add_argument( + "--allow-deveco-toolchain", + action="store_true", + help="公开工具链未准备好时允许复制本机 DevEco 工具;产物不可直接公开发布", + ) + parser.add_argument( + "--keep-bundled-browser", + action="store_true", + help="额外包含 Playwright Chromium;默认复用系统 Edge/Chrome 以缩小体积", + ) + args = parser.parse_args() + + try: + archive = build( + skip_toolchain=args.skip_toolchain, + keep_full_jbr=args.keep_full_jbr, + keep_bundled_browser=args.keep_bundled_browser, + allow_local_toolchain=args.allow_deveco_toolchain, + ) + except (RuntimeError, subprocess.CalledProcessError) as exc: + print(f"构建失败:{exc}", file=sys.stderr) + return 1 + print(f"便携版已生成:{archive}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/prepare_toolchain.py b/scripts/prepare_toolchain.py new file mode 100644 index 0000000..a3c6d27 --- /dev/null +++ b/scripts/prepare_toolchain.py @@ -0,0 +1,517 @@ +"""从可审计的公开上游准备 HapSign 便携工具链。 + +下载内容、大小和 SHA-256 固定在 ``toolchain.lock.json``。Windows 当前使用: + +* OpenHarmony 6.1 公共 SDK 中的 HDC、libusb 和 hap-sign-tool; +* Eclipse Temurin 21 JDK,经 jlink 缩成仅供 HapSign 使用的运行时。 + +缓存和输出默认都位于被 Git 忽略的 ``build/``,不会把大型 SDK 加进源码仓库。 +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import platform +import shutil +import subprocess +import sys +import tarfile +import tempfile +import time +import urllib.parse +import urllib.request +import zipfile +from pathlib import Path, PurePosixPath +from typing import Any + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +LOCK_PATH = PROJECT_ROOT / "toolchain.lock.json" +DEFAULT_CACHE_DIR = PROJECT_ROOT / "build" / "toolchain-cache" +DEFAULT_OUTPUT_ROOT = PROJECT_ROOT / "build" / "toolchain-prepared" +DOWNLOAD_CHUNK_SIZE = 4 * 1024 * 1024 + + +def _platform_tag() -> str: + system = platform.system().lower() + if system == "darwin": + return "macos" + if system == "windows": + return "windows" + return "linux" + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(DOWNLOAD_CHUNK_SIZE), b""): + digest.update(chunk) + return digest.hexdigest().lower() + + +def _verify_file(path: Path, metadata: dict[str, Any], *, label: str) -> None: + expected_size = metadata.get("size") + if expected_size is not None and path.stat().st_size != expected_size: + raise RuntimeError( + f"{label} 大小校验失败:期望 {expected_size},实际 {path.stat().st_size}," + f"文件:{path}" + ) + expected_hash = metadata.get("sha256") + if expected_hash is not None: + actual_hash = _sha256(path) + if actual_hash != expected_hash.lower(): + raise RuntimeError( + f"{label} SHA-256 校验失败:\n" + f"期望 {expected_hash.lower()}\n实际 {actual_hash}\n文件:{path}" + ) + + +def _download( + metadata: dict[str, Any], + destination: Path, + *, + label: str, +) -> Path: + """下载并校验锁定文件;使用 .part 支持可靠的断点续传。""" + destination.parent.mkdir(parents=True, exist_ok=True) + if destination.is_file(): + _verify_file(destination, metadata, label=label) + print(f"使用已校验缓存:{destination}") + return destination + + partial = destination.with_name(f"{destination.name}.part") + expected_size = int(metadata["size"]) + offset = partial.stat().st_size if partial.is_file() else 0 + if offset > expected_size: + raise RuntimeError( + f"下载缓存大于锁定文件,请删除后重试:{partial} " + f"({offset} > {expected_size})" + ) + + headers = {"User-Agent": "HapSign-toolchain-preparer/1"} + if offset: + headers["Range"] = f"bytes={offset}-" + request = urllib.request.Request(metadata["url"], headers=headers) + print(f"下载 {label}:{metadata['url']}") + if offset: + print(f"从 {offset} 字节继续") + + with urllib.request.urlopen(request, timeout=60) as response: + status = getattr(response, "status", response.getcode()) + mode = "ab" + if offset and status != 206: + print("服务器未接受断点续传,改为从头下载") + offset = 0 + mode = "wb" + elif not offset: + mode = "wb" + + downloaded = offset + last_report = time.monotonic() + with partial.open(mode) as target: + while True: + chunk = response.read(DOWNLOAD_CHUNK_SIZE) + if not chunk: + break + target.write(chunk) + downloaded += len(chunk) + now = time.monotonic() + if now - last_report >= 5: + percent = downloaded / expected_size * 100 + print( + f"\r {downloaded / 1024**2:.1f} MiB / " + f"{expected_size / 1024**2:.1f} MiB ({percent:.1f}%)", + end="", + flush=True, + ) + last_report = now + print() + _verify_file(partial, metadata, label=label) + os.replace(partial, destination) + return destination + + +def _archive_name(url: str) -> str: + name = PurePosixPath(urllib.parse.urlparse(url).path).name + if not name: + raise RuntimeError(f"下载地址没有文件名:{url}") + return name + + +def _extract_nested_toolchains( + sdk_archive: Path, + *, + expected_name: str, + destination: Path, +) -> Path: + """从公共 SDK tar.gz 中只提取目标平台 toolchains ZIP。""" + destination.parent.mkdir(parents=True, exist_ok=True) + with tarfile.open(sdk_archive, mode="r:gz") as archive: + matches = [ + member + for member in archive.getmembers() + if member.isfile() and PurePosixPath(member.name).name == expected_name + ] + if len(matches) != 1: + names = ", ".join(item.name for item in matches) or "(未找到)" + raise RuntimeError( + f"公共 SDK 中应当恰好有一个 {expected_name},实际:{names}" + ) + source = archive.extractfile(matches[0]) + if source is None: + raise RuntimeError(f"无法读取公共 SDK 成员:{matches[0].name}") + with source, destination.open("wb") as target: + shutil.copyfileobj(source, target, length=DOWNLOAD_CHUNK_SIZE) + return destination + + +def _normalized_zip_name(name: str) -> str: + return str(PurePosixPath(name.replace("\\", "/"))) + + +def _find_zip_member(archive: zipfile.ZipFile, suffix: str) -> zipfile.ZipInfo: + normalized_suffix = _normalized_zip_name(suffix).lower() + matches = [ + item + for item in archive.infolist() + if not item.is_dir() + and _normalized_zip_name(item.filename).lower().endswith(normalized_suffix) + ] + if len(matches) != 1: + names = ", ".join(item.filename for item in matches) or "(未找到)" + raise RuntimeError(f"toolchains ZIP 中应当恰好有一个 *{suffix},实际:{names}") + return matches[0] + + +def _extract_openharmony_files( + toolchains_zip: Path, + output: Path, + files: dict[str, dict[str, Any]], +) -> None: + with zipfile.ZipFile(toolchains_zip) as archive: + for target_name, metadata in files.items(): + member = _find_zip_member(archive, metadata["archive_suffix"]) + target = output / Path(target_name) + target.parent.mkdir(parents=True, exist_ok=True) + with archive.open(member) as source, target.open("wb") as destination: + shutil.copyfileobj(source, destination, length=DOWNLOAD_CHUNK_SIZE) + _verify_file(target, metadata, label=target_name) + + +def _safe_extract_zip(archive_path: Path, destination: Path) -> None: + destination_resolved = destination.resolve() + with zipfile.ZipFile(archive_path) as archive: + for member in archive.infolist(): + target = (destination / _normalized_zip_name(member.filename)).resolve() + if ( + target != destination_resolved + and destination_resolved not in target.parents + ): + raise RuntimeError(f"ZIP 包含越界路径:{member.filename}") + archive.extractall(destination) + + +def _find_jdk_root(extracted: Path) -> Path: + executable = "jlink.exe" if os.name == "nt" else "jlink" + matches = list(extracted.glob(f"*/bin/{executable}")) + if len(matches) != 1: + names = ", ".join(str(item) for item in matches) or "(未找到)" + raise RuntimeError(f"Temurin ZIP 中无法唯一定位 {executable}:{names}") + return matches[0].parent.parent + + +def _create_java_runtime( + jdk_archive: Path, + output: Path, + *, + modules: list[str], +) -> None: + with tempfile.TemporaryDirectory(prefix="hapsign-temurin-") as temporary: + extracted = Path(temporary) + _safe_extract_zip(jdk_archive, extracted) + jdk_root = _find_jdk_root(extracted) + executable = "jlink.exe" if os.name == "nt" else "jlink" + command = [ + str(jdk_root / "bin" / executable), + "--add-modules", + ",".join(modules), + "--strip-debug", + "--no-man-pages", + "--no-header-files", + "--compress=zip-6", + "--output", + str(output / "runtime"), + ] + print("+", subprocess.list2cmdline(command)) + subprocess.run(command, check=True) + + license_dir = output / "licenses" / "temurin" + license_dir.mkdir(parents=True, exist_ok=True) + for name in ( + "NOTICE", + "LICENSE", + "ADDITIONAL_LICENSE_INFO", + "ASSEMBLY_EXCEPTION", + ): + source = jdk_root / name + if source.is_file(): + shutil.copy2(source, license_dir / name) + + +def _copy_libusb_source(output: Path, metadata: dict[str, Any]) -> None: + source = PROJECT_ROOT / metadata["path"] + _verify_file(source, metadata, label="libusb 对应源代码") + destination = output / "licenses" / "libusb-source" + destination.mkdir(parents=True, exist_ok=True) + shutil.copy2(source, destination / source.name) + readme = source.parent / "README.md" + if readme.is_file(): + shutil.copy2(readme, destination / "README.md") + + +def _smoke_test(output: Path) -> None: + suffix = ".exe" if os.name == "nt" else "" + java = output / "runtime" / "bin" / f"java{suffix}" + keytool = output / "runtime" / "bin" / f"keytool{suffix}" + signer = output / "lib" / "hap-sign-tool.jar" + hdc = output / "bin" / f"hdc{suffix}" + + commands = [ + [str(java), "-version"], + [str(java), "-jar", str(signer)], + [str(hdc), "-v"], + ] + with tempfile.TemporaryDirectory(prefix="hapsign-toolchain-smoke-") as temporary: + keystore = Path(temporary) / "smoke.p12" + commands.append( + [ + str(keytool), + "-genkeypair", + "-alias", + "hapsign-smoke", + "-keyalg", + "EC", + "-groupname", + "secp256r1", + "-keystore", + str(keystore), + "-storetype", + "PKCS12", + "-storepass", + "hapsign-smoke-password", + "-keypass", + "hapsign-smoke-password", + "-dname", + "CN=HapSign Toolchain Smoke", + "-validity", + "1", + "-noprompt", + ] + ) + for command in commands: + result = subprocess.run( + command, + capture_output=True, + text=True, + timeout=60, + check=False, + ) + if result.returncode != 0: + details = result.stderr.strip() or result.stdout.strip() + raise RuntimeError( + f"工具链自检失败:{subprocess.list2cmdline(command)}\n{details}" + ) + + +def _write_provenance( + output: Path, + *, + platform_config: dict[str, Any], +) -> None: + openharmony = platform_config["openharmony"] + java = platform_config["java"] + files = { + "java": output + / "runtime" + / "bin" + / ("java.exe" if os.name == "nt" else "java"), + "keytool": output + / "runtime" + / "bin" + / ("keytool.exe" if os.name == "nt" else "keytool"), + "hap-sign-tool": output / "lib" / "hap-sign-tool.jar", + "hdc": output / "bin" / ("hdc.exe" if os.name == "nt" else "hdc"), + } + lines = [ + "HapSign verified public toolchain", + "=================================", + "", + f"OpenHarmony SDK: {openharmony['release']} / {openharmony['version']}", + f"OpenHarmony archive: {openharmony['archive']['url']}", + f"OpenHarmony archive SHA-256: {openharmony['archive']['sha256']}", + f"Java: {java['distribution']} {java['version']}", + f"Java archive: {java['archive']['url']}", + f"Java archive SHA-256: {java['archive']['sha256']}", + "Java runtime: generated with jlink from the locked JDK archive", + "", + "Bundled files:", + ] + for name, path in files.items(): + lines.append( + f"- {name}: {path.relative_to(output).as_posix()} sha256={_sha256(path)}" + ) + (output / "PROVENANCE.txt").write_text( + "\n".join(lines) + "\n", + encoding="utf-8", + ) + shutil.copy2(LOCK_PATH, output / "toolchain.lock.json") + + +def prepare( + *, + target_platform: str, + cache_dir: Path, + output: Path, + sdk_archive_override: Path | None, + jdk_archive_override: Path | None, + force: bool, +) -> Path: + lock = json.loads(LOCK_PATH.read_text(encoding="utf-8")) + if lock.get("schema") != 1: + raise RuntimeError(f"不支持的工具链锁文件版本:{lock.get('schema')}") + try: + platform_config = lock["platforms"][target_platform] + except KeyError as exc: + supported = ", ".join(sorted(lock["platforms"])) + raise RuntimeError( + f"尚未锁定 {target_platform} 工具链;当前支持:{supported}" + ) from exc + if target_platform != _platform_tag(): + raise RuntimeError( + f"jlink 不能跨平台生成运行时:目标 {target_platform}," + f"当前主机 {_platform_tag()}" + ) + + if output.exists() and not force: + raise RuntimeError(f"输出目录已存在;确认后使用 --force 重建:{output}") + output.parent.mkdir(parents=True, exist_ok=True) + + openharmony = platform_config["openharmony"] + java = platform_config["java"] + sdk_archive = sdk_archive_override + if sdk_archive is None: + sdk_archive = cache_dir / _archive_name(openharmony["archive"]["url"]) + sdk_archive = _download( + openharmony["archive"], + sdk_archive, + label="OpenHarmony 公共 SDK", + ) + else: + sdk_archive = sdk_archive.resolve() + _verify_file(sdk_archive, openharmony["archive"], label="OpenHarmony 公共 SDK") + + jdk_archive = jdk_archive_override + if jdk_archive is None: + jdk_archive = cache_dir / _archive_name(java["archive"]["url"]) + jdk_archive = _download( + java["archive"], jdk_archive, label="Eclipse Temurin JDK" + ) + else: + jdk_archive = jdk_archive.resolve() + _verify_file(jdk_archive, java["archive"], label="Eclipse Temurin JDK") + + cache_dir.mkdir(parents=True, exist_ok=True) + toolchains_zip = cache_dir / openharmony["toolchains_member"] + if not toolchains_zip.is_file(): + print(f"从公共 SDK 提取:{openharmony['toolchains_member']}") + _extract_nested_toolchains( + sdk_archive, + expected_name=openharmony["toolchains_member"], + destination=toolchains_zip, + ) + _verify_file( + toolchains_zip, + {"sha256": openharmony["toolchains_sha256"]}, + label="OpenHarmony Windows toolchains", + ) + + staging = Path(tempfile.mkdtemp(prefix=f".{output.name}-", dir=str(output.parent))) + try: + _extract_openharmony_files( + toolchains_zip, + staging, + openharmony["files"], + ) + _create_java_runtime( + jdk_archive, + staging, + modules=java["modules"], + ) + _copy_libusb_source(staging, openharmony["libusb_source"]) + _write_provenance(staging, platform_config=platform_config) + _smoke_test(staging) + if output.exists(): + shutil.rmtree(output) + staging.replace(output) + except BaseException: + shutil.rmtree(staging, ignore_errors=True) + raise + print(f"已生成并验证公开工具链:{output}") + return output + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--platform", + default=_platform_tag(), + choices=("windows", "macos", "linux"), + help="目标平台;jlink 要求与当前主机一致", + ) + parser.add_argument( + "--cache-dir", + type=Path, + default=DEFAULT_CACHE_DIR, + help="下载缓存目录", + ) + parser.add_argument( + "--output-dir", + type=Path, + help="输出目录;默认 build/toolchain-prepared/", + ) + parser.add_argument( + "--sdk-archive", + type=Path, + help="使用已下载的 OpenHarmony 公共 SDK,同时仍执行锁定校验", + ) + parser.add_argument( + "--jdk-archive", + type=Path, + help="使用已下载的 Temurin JDK ZIP,同时仍执行锁定校验", + ) + parser.add_argument( + "--force", + action="store_true", + help="删除并重建已存在的目标工具链目录", + ) + args = parser.parse_args() + output = args.output_dir or DEFAULT_OUTPUT_ROOT / args.platform + try: + prepare( + target_platform=args.platform, + cache_dir=args.cache_dir.resolve(), + output=output.resolve(), + sdk_archive_override=args.sdk_archive, + jdk_archive_override=args.jdk_archive, + force=args.force, + ) + except (OSError, RuntimeError, subprocess.SubprocessError, tarfile.TarError) as exc: + print(f"准备工具链失败:{exc}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_api.py b/tests/test_api.py index 7c18388..75bad76 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -4,17 +4,26 @@ from unittest.mock import Mock import pytest +import requests from hapsign.api.cert_api import CertAPI +from hapsign.api.client import TokenExpiredError from hapsign.api.device_api import DeviceAPI from hapsign.api.provision_api import ProvisionAPI -from hapsign.config import API_REAL_PROVISION_ADD, API_TEST_PROVISION_ADD +from hapsign.cancellation import OperationCancelled +from hapsign.config import ( + API_AGREEMENT, + API_REAL_PROVISION_ADD, + API_TEST_PROVISION_ADD, + HEADER_ACCESS_TOKEN, +) @pytest.fixture def client() -> Mock: value = Mock() value._get_headers.return_value = {"teamId": "team"} + value.access_token = "tok" return value @@ -61,6 +70,29 @@ def test_provision_rejects_error_response(client) -> None: ProvisionAPI(client).add_test_provision("team", "bundle", [], []) +@pytest.mark.parametrize("duplicate_code", [205389857, 205389858, "205389858"]) +def test_add_device_treats_duplicate_code_as_success(client, duplicate_code) -> None: + client._do_post_form.return_value = {"ret": {"code": duplicate_code}} + + assert DeviceAPI(client).add_device("udid", "4", "team", "name") is True + + +@pytest.mark.parametrize("duplicate_code", [205389857, 205389858]) +def test_add_device_treats_duplicate_in_http_error_as_success( + client, duplicate_code +) -> None: + client._do_post_form.side_effect = RuntimeError(f"request failed: {duplicate_code}") + + assert DeviceAPI(client).add_device("udid", "4", "team", "name") is True + + +def test_add_device_raises_on_other_business_error(client) -> None: + client._do_post_form.return_value = {"ret": {"code": 1, "msg": "failed"}} + + with pytest.raises(RuntimeError, match="add_device failed"): + DeviceAPI(client).add_device("udid", "4", "team", "name") + + def test_find_device_id(client) -> None: client._do_get.return_value = {"list": [{"id": 7, "udid": "wanted"}]} @@ -82,3 +114,35 @@ def test_find_certificate(client) -> None: result = CertAPI(client).find_cert("team", "debug.cer") assert result["certObjectId"] == "object" + + +def test_sign_agreement_uses_unified_client_with_only_access_token(client) -> None: + client._do_post_form_text.return_value = "ok" + + assert CertAPI(client).sign_agreement() is True + + url, headers = client._do_post_form_text.call_args.args + assert url == API_AGREEMENT + # 只保留该接口特殊的 accessToken 头,不误加签名 API 的其他认证头。 + assert headers == {HEADER_ACCESS_TOKEN: "tok"} + + +def test_sign_agreement_propagates_http_error(client) -> None: + client._do_post_form_text.side_effect = requests.HTTPError("500 Server Error") + + with pytest.raises(requests.HTTPError): + CertAPI(client).sign_agreement() + + +def test_sign_agreement_propagates_token_expiry(client) -> None: + client._do_post_form_text.side_effect = TokenExpiredError("Token 失效") + + with pytest.raises(TokenExpiredError): + CertAPI(client).sign_agreement() + + +def test_sign_agreement_propagates_cancellation(client) -> None: + client._do_post_form_text.side_effect = OperationCancelled("cancelled") + + with pytest.raises(OperationCancelled): + CertAPI(client).sign_agreement() diff --git a/tests/test_browser_login.py b/tests/test_browser_login.py index 75cd760..3775759 100644 --- a/tests/test_browser_login.py +++ b/tests/test_browser_login.py @@ -4,12 +4,16 @@ import logging import threading from http.server import ThreadingHTTPServer +from unittest.mock import Mock from urllib.parse import urlencode +from hapsign.cancellation import OperationCancelled +from hapsign.diagnostics import set_sensitive_logging from hapsign.login import browser_login def test_callback_accepts_token_without_logging_it(caplog) -> None: + set_sensitive_logging(False) callback_data = {} callback_event = threading.Event() handler = browser_login._make_callback_handler( @@ -45,3 +49,342 @@ def test_callback_accepts_token_without_logging_it(caplog) -> None: def test_callback_host_is_loopback() -> None: assert browser_login._CALLBACK_HOST == "127.0.0.1" + + +def test_callback_accepts_get_on_root_path() -> None: + callback_data = {} + callback_event = threading.Event() + handler = browser_login._make_callback_handler( + "expected-code", callback_data, callback_event + ) + server = ThreadingHTTPServer((browser_login._CALLBACK_HOST, 0), handler) + server_thread = threading.Thread(target=server.serve_forever, daemon=True) + server_thread.start() + + try: + connection = http.client.HTTPConnection(*server.server_address, timeout=5) + query = urlencode( + {"tempToken": "root-token", "siteId": "CN", "code": "expected-code"} + ) + connection.request("GET", f"/?{query}") + response = connection.getresponse() + response.read() + connection.close() + + assert response.status == 302 + assert response.getheader("Location").endswith( + "/console/DevEcoIDE/loginSuccess" + ) + assert callback_event.wait(timeout=2) + assert callback_data["tempToken"] == "root-token" + finally: + server.shutdown() + server.server_close() + + +def test_callback_accepts_multipart_form() -> None: + callback_data = {} + callback_event = threading.Event() + handler = browser_login._make_callback_handler( + "expected-code", callback_data, callback_event + ) + server = ThreadingHTTPServer((browser_login._CALLBACK_HOST, 0), handler) + server_thread = threading.Thread(target=server.serve_forever, daemon=True) + server_thread.start() + boundary = "----HapSignBoundary" + fields = { + "tempToken": "multipart-token", + "siteId": "1", + "code": "expected-code", + } + body = "".join( + f"--{boundary}\r\n" + f'Content-Disposition: form-data; name="{name}"\r\n\r\n' + f"{value}\r\n" + for name, value in fields.items() + ) + body += f"--{boundary}--\r\n" + + try: + connection = http.client.HTTPConnection(*server.server_address, timeout=5) + connection.request( + "POST", + "/callback", + body.encode("utf-8"), + {"Content-Type": f"multipart/form-data; boundary={boundary}"}, + ) + response = connection.getresponse() + response.read() + connection.close() + + assert response.status == 302 + assert callback_event.wait(timeout=2) + assert callback_data["tempToken"] == "multipart-token" + assert callback_data["siteId"] == "1" + finally: + server.shutdown() + server.server_close() + + +def test_callback_infers_multipart_when_content_type_is_missing() -> None: + boundary = "----MissingHeaderBoundary" + body = ( + f"--{boundary}\r\n" + 'Content-Disposition: form-data; name="tempToken"\r\n\r\n' + "inferred-token\r\n" + f"--{boundary}\r\n" + 'Content-Disposition: form-data; name="code"\r\n\r\n' + "expected-code\r\n" + f"--{boundary}--\r\n" + ).encode() + + assert browser_login._decode_callback_params(body, "") == { + "tempToken": "inferred-token", + "code": "expected-code", + } + + +def test_callback_json_accepts_nested_data() -> None: + body = b'{"data":{"tempToken":"json-token","code":"expected-code"}}' + + assert browser_login._decode_callback_params(body, "application/json") == { + "tempToken": "json-token", + "code": "expected-code", + } + + +def test_denied_authorization_finishes_callback_wait() -> None: + callback_data = {} + callback_event = threading.Event() + handler = browser_login._make_callback_handler( + "expected-code", callback_data, callback_event + ) + server = ThreadingHTTPServer((browser_login._CALLBACK_HOST, 0), handler) + server_thread = threading.Thread(target=server.serve_forever, daemon=True) + server_thread.start() + + try: + connection = http.client.HTTPConnection(*server.server_address, timeout=5) + body = urlencode({"quit": "access_denied", "code": "expected-code"}) + connection.request( + "POST", + "/callback", + body, + {"Content-Type": "application/x-www-form-urlencoded"}, + ) + response = connection.getresponse() + response.read() + connection.close() + + assert response.status == 400 + assert callback_event.wait(timeout=2) + assert "取消" in callback_data["error"] + finally: + server.shutdown() + server.server_close() + + +def test_post_body_takes_precedence_over_query_params() -> None: + callback_data = {} + callback_event = threading.Event() + handler = browser_login._make_callback_handler( + "expected-code", callback_data, callback_event + ) + server = ThreadingHTTPServer((browser_login._CALLBACK_HOST, 0), handler) + server_thread = threading.Thread(target=server.serve_forever, daemon=True) + server_thread.start() + + try: + connection = http.client.HTTPConnection(*server.server_address, timeout=5) + body = urlencode({"tempToken": "body-token", "code": "expected-code"}) + connection.request( + "POST", + "/callback?code=wrong-query-code", + body, + {"Content-Type": "application/x-www-form-urlencoded"}, + ) + response = connection.getresponse() + response.read() + connection.close() + + assert response.status == 302 + assert callback_event.wait(timeout=2) + assert callback_data["tempToken"] == "body-token" + finally: + server.shutdown() + server.server_close() + + +def test_private_network_preflight_is_allowed() -> None: + handler = browser_login._make_callback_handler( + "expected-code", {}, threading.Event() + ) + server = ThreadingHTTPServer((browser_login._CALLBACK_HOST, 0), handler) + server_thread = threading.Thread(target=server.serve_forever, daemon=True) + server_thread.start() + + try: + connection = http.client.HTTPConnection(*server.server_address, timeout=5) + connection.request( + "OPTIONS", + "/callback", + headers={ + "Origin": "https://devecostudio.huawei.com", + "Access-Control-Request-Headers": "content-type, x-requested-with", + "Access-Control-Request-Private-Network": "true", + }, + ) + response = connection.getresponse() + response.read() + connection.close() + + assert response.status == 204 + assert ( + response.getheader("Access-Control-Allow-Origin") + == "https://devecostudio.huawei.com" + ) + assert response.getheader("Access-Control-Allow-Private-Network") == "true" + assert "x-requested-with" in response.getheader("Access-Control-Allow-Headers") + finally: + server.shutdown() + server.server_close() + + +def test_system_browser_waits_for_callback(monkeypatch) -> None: + opened = Mock(return_value=True) + monkeypatch.setattr(browser_login.webbrowser, "open", opened) + callback_event = threading.Event() + callback_event.set() + + login = browser_login.BrowserLogin(browser_mode="system") + login._browser_login_and_wait("https://example.invalid/login", callback_event) + + opened.assert_called_once_with( + "https://example.invalid/login", + new=1, + autoraise=True, + ) + + +def test_default_browser_mode_is_controlled_system_browser(monkeypatch) -> None: + monkeypatch.delenv("HAPSIGN_BROWSER", raising=False) + assert browser_login.BrowserLogin().browser_mode == "system_controlled" + + +def test_controlled_system_browser_prefers_edge() -> None: + browser = Mock() + chromium = Mock() + chromium.launch.return_value = browser + playwright = Mock(chromium=chromium) + + result, runtime = browser_login._launch_controlled_browser( + playwright, + "system_controlled", + allow_fallback=False, + smoke_test=True, + ) + + assert result is browser + assert runtime == "msedge" + chromium.launch.assert_called_once() + assert chromium.launch.call_args.kwargs["channel"] == "msedge" + assert "--headless=new" in chromium.launch.call_args.kwargs["args"] + + +def test_controlled_system_browser_falls_back_to_chrome() -> None: + browser = Mock() + chromium = Mock() + chromium.launch.side_effect = [RuntimeError("edge missing"), browser] + playwright = Mock(chromium=chromium) + + result, runtime = browser_login._launch_controlled_browser( + playwright, + "system_controlled", + allow_fallback=False, + ) + + assert result is browser + assert runtime == "chrome" + assert [call.kwargs["channel"] for call in chromium.launch.call_args_list] == [ + "msedge", + "chrome", + ] + + +def test_missing_bundled_chromium_falls_back_for_existing_settings( + tmp_path, +) -> None: + browser = Mock() + chromium = Mock() + chromium.executable_path = str(tmp_path / "missing-chromium.exe") + chromium.launch.return_value = browser + playwright = Mock(chromium=chromium) + + result, runtime = browser_login._launch_controlled_browser( + playwright, + "playwright", + allow_fallback=True, + ) + + assert result is browser + assert runtime == "msedge" + chromium.launch.assert_called_once() + assert chromium.launch.call_args.kwargs["channel"] == "msedge" + + +def test_sensitive_callback_logging_requires_explicit_switch(caplog) -> None: + callback_data = {} + callback_event = threading.Event() + handler = browser_login._make_callback_handler( + "expected-code", callback_data, callback_event + ) + server = ThreadingHTTPServer((browser_login._CALLBACK_HOST, 0), handler) + server_thread = threading.Thread(target=server.serve_forever, daemon=True) + server_thread.start() + secret = "explicit-sensitive-token" + + try: + set_sensitive_logging(True) + caplog.set_level(logging.DEBUG) + connection = http.client.HTTPConnection(*server.server_address, timeout=5) + body = urlencode({"tempToken": secret, "code": "expected-code"}) + connection.request( + "POST", + "/callback", + body, + {"Content-Type": "application/x-www-form-urlencoded"}, + ) + response = connection.getresponse() + response.read() + connection.close() + + assert response.status == 302 + assert secret in caplog.text + finally: + set_sensitive_logging(False) + server.shutdown() + server.server_close() + + +def test_unknown_browser_mode_is_rejected() -> None: + login = browser_login.BrowserLogin(browser_mode="unknown") + + try: + login._browser_login_and_wait("https://example.invalid", threading.Event()) + except RuntimeError as exc: + assert "Unsupported browser mode" in str(exc) + else: + raise AssertionError("unsupported browser mode should fail") + + +def test_callback_wait_can_be_cancelled() -> None: + cancel_event = threading.Event() + cancel_event.set() + login = browser_login.BrowserLogin(browser_mode="system", cancel_event=cancel_event) + + try: + login._wait_for_callback(threading.Event()) + except OperationCancelled: + pass + else: + raise AssertionError("cancelled callback wait should stop") diff --git a/tests/test_build_portable.py b/tests/test_build_portable.py new file mode 100644 index 0000000..debdfdc --- /dev/null +++ b/tests/test_build_portable.py @@ -0,0 +1,367 @@ +"""便携版保守精简规则测试。""" + +import hashlib + +import pytest + +from scripts import build_portable + + +def test_write_sha256_file_uses_standard_sidecar_format(tmp_path) -> None: + archive = tmp_path / "HapSign-portable-windows.zip" + archive.write_bytes(b"portable archive") + + checksum = build_portable._write_sha256_file(archive) + + expected = hashlib.sha256(b"portable archive").hexdigest() + assert checksum.name == "HapSign-portable-windows.zip.sha256" + assert checksum.read_text(encoding="ascii") == f"{expected} {archive.name}\n" + + +def test_jbr_pruning_only_targets_known_jcef_resources(tmp_path) -> None: + jbr = tmp_path / "jbr" + bin_dir = jbr / "bin" + lib_dir = jbr / "lib" + bin_dir.mkdir(parents=True) + lib_dir.mkdir() + ignore = build_portable._jbr_ignore(jbr) + + ignored_bin = ignore( + str(bin_dir), + ["java.exe", "keytool.exe", "libcef.dll", "awt.dll"], + ) + ignored_lib = ignore( + str(lib_dir), + ["modules", "security", "locales", "resources.pak"], + ) + + assert ignored_bin == {"libcef.dll"} + assert ignored_lib == {"locales", "resources.pak"} + assert ignore(str(jbr / "conf"), ["security"]) == set() + + +def _playwright_browser_fixture(tmp_path): + portable = tmp_path / "HapSign" + browsers = ( + portable / "_internal" / "playwright" / "driver" / "package" / ".local-browsers" + ) + chromium = browsers / "chromium-1" + ffmpeg = browsers / "ffmpeg-1" + chromium.mkdir(parents=True) + ffmpeg.mkdir() + (chromium / "chrome.exe").write_bytes(b"chrome") + (ffmpeg / "ffmpeg.exe").write_bytes(b"ffmpeg") + return portable, chromium, ffmpeg + + +def test_compat_build_keeps_chromium_but_removes_ffmpeg(tmp_path) -> None: + portable, chromium, ffmpeg = _playwright_browser_fixture(tmp_path) + + build_portable._prune_playwright_extras( + portable, + keep_bundled_browser=True, + ) + + assert chromium.is_dir() + assert not ffmpeg.exists() + + +def test_slim_build_removes_all_bundled_browser_resources(tmp_path) -> None: + portable, chromium, ffmpeg = _playwright_browser_fixture(tmp_path) + + build_portable._prune_playwright_extras( + portable, + keep_bundled_browser=False, + ) + + assert not chromium.exists() + assert not ffmpeg.exists() + + +def test_smoke_artifact_cleanup_preserves_application_files(tmp_path) -> None: + portable = tmp_path / "HapSign" + portable.mkdir() + executable = portable / "HapSign.exe" + executable.write_bytes(b"app") + (portable / "hapsign-config.json").write_text("{}", encoding="utf-8") + for name in ("logs", "signing_files", "signed_haps"): + directory = portable / name + directory.mkdir() + (directory / "private-data").write_text("secret", encoding="utf-8") + + build_portable._remove_smoke_artifacts(portable) + + assert executable.is_file() + assert not (portable / "hapsign-config.json").exists() + assert not (portable / "logs").exists() + assert not (portable / "signing_files").exists() + assert not (portable / "signed_haps").exists() + + +def test_python_dependency_licenses_are_copied(tmp_path, monkeypatch) -> None: + portable = tmp_path / "HapSign" + portable.mkdir() + monkeypatch.setattr( + build_portable, + "PYTHON_RUNTIME_DISTRIBUTIONS", + ("requests",), + ) + + build_portable._copy_python_license_files(portable) + + license_root = portable / "licenses" / "python" + assert (license_root / "PYTHON-LICENSE.txt").is_file() + assert (license_root / "DISTRIBUTIONS.txt").is_file() + assert ( + "requests" + in (license_root / "DISTRIBUTIONS.txt").read_text(encoding="utf-8").lower() + ) + assert any( + path.name.lower().startswith(("license", "notice")) + for path in license_root.rglob("*") + if path.is_file() + ) + + +def test_runtime_license_list_covers_playwright_core_dependencies() -> None: + listed = { + build_portable._normalize_distribution_name(name) + for name in build_portable.PYTHON_RUNTIME_DISTRIBUTIONS + } + for name in ("greenlet", "pyee"): + assert build_portable._normalize_distribution_name(name) in listed + + +def _fake_requirement_map() -> dict[str, set[str]]: + return { + "playwright": {"greenlet", "pyee"}, + "greenlet": set(), + "pyee": set(), + "requests": {"urllib3", "certifi"}, + "urllib3": set(), + "certifi": set(), + } + + +def test_runtime_requirement_names_parses_versions_and_markers(monkeypatch) -> None: + # 不模拟 _runtime_requirement_names,走真实解析:版本比较符/括号/条件标记必须剥离。 + raw = { + "playwright": [ + "greenlet>=3.1.1 ; python_version >= '3.9'", + "pyee (>=12.0.0) ; python_version >= '3.9'", + "requests ; extra == 'test'", + ], + } + monkeypatch.setattr( + build_portable, + "metadata", + type( + "FakeMetadata", + (), + {"requires": lambda name: raw.get(name)}, + ), + ) + + assert build_portable._runtime_requirement_names("playwright") == { + "greenlet", + "pyee", + } + + +def test_frozen_runtime_closure_is_transitive_and_skips_extras(monkeypatch) -> None: + deps = _fake_requirement_map() + # 模拟带 extra 条件依赖的原始字符串,验证解析层会排除它们。 + raw = { + "playwright": [ + "greenlet>=3.1.1 ; python_version >= '3.9'", + "pyee (>=12.0.0) ; python_version >= '3.9'", + "requests ; extra == 'test'", + ], + } + monkeypatch.setattr( + build_portable, + "metadata", + type( + "FakeMetadata", + (), + {"requires": lambda name: raw.get(name)}, + ), + ) + monkeypatch.setattr( + build_portable, + "_runtime_requirement_names", + lambda name: deps.get(name, set()), + ) + + closure = build_portable._frozen_runtime_closure(("playwright", "requests")) + + assert closure == { + build_portable._normalize_distribution_name(name) + for name in ("playwright", "greenlet", "pyee", "requests", "urllib3", "certifi") + } + + +def test_normalize_distribution_name_follows_pep503() -> None: + assert ( + build_portable._normalize_distribution_name("PySide6-Essentials") + == "pyside6-essentials" + ) + assert build_portable._normalize_distribution_name("charset_normalizer") == ( + "charset-normalizer" + ) + + +def test_license_gate_passes_when_closure_is_covered(monkeypatch) -> None: + deps = _fake_requirement_map() + monkeypatch.setattr( + build_portable, + "_runtime_requirement_names", + lambda name: deps.get(name, set()), + ) + monkeypatch.setattr( + build_portable, + "PYTHON_RUNTIME_DISTRIBUTIONS", + ("playwright", "greenlet", "pyee", "requests", "urllib3", "certifi"), + ) + monkeypatch.setattr( + build_portable, "BUILD_TIME_ONLY_DISTRIBUTIONS", ("PyInstaller",) + ) + + build_portable._validate_runtime_license_coverage() + + +def test_license_gate_fails_when_closure_misses_manifest(monkeypatch) -> None: + deps = _fake_requirement_map() + monkeypatch.setattr( + build_portable, + "_runtime_requirement_names", + lambda name: deps.get(name, set()), + ) + # playwright 的传递依赖 greenlet/pyee 不在清单中 → 门禁必须失败。 + monkeypatch.setattr( + build_portable, + "PYTHON_RUNTIME_DISTRIBUTIONS", + ("playwright",), + ) + monkeypatch.setattr(build_portable, "BUILD_TIME_ONLY_DISTRIBUTIONS", ()) + + with pytest.raises(RuntimeError, match="greenlet"): + build_portable._validate_runtime_license_coverage() + + +def test_license_gate_ignores_build_time_only_distributions(monkeypatch) -> None: + deps = _fake_requirement_map() + # PyInstaller 及其传递依赖不会被冻结,不要求进入清单。 + monkeypatch.setattr( + build_portable, + "_runtime_requirement_names", + lambda name: deps.get(name, set()), + ) + monkeypatch.setattr( + build_portable, + "PYTHON_RUNTIME_DISTRIBUTIONS", + ("playwright", "greenlet", "pyee", "PyInstaller"), + ) + monkeypatch.setattr( + build_portable, + "BUILD_TIME_ONLY_DISTRIBUTIONS", + ("PyInstaller",), + ) + + build_portable._validate_runtime_license_coverage() + + +def test_release_documents_include_open_source_notices( + tmp_path, + monkeypatch, +) -> None: + project = tmp_path / "project" + portable = tmp_path / "HapSign" + (project / "docs").mkdir(parents=True) + portable.mkdir() + sources = { + "PORTABLE.md": "portable", + "LICENSE": "MIT", + "PRIVACY.md": "privacy", + "THIRD_PARTY_NOTICES.md": "notices", + "docs/PACKAGING.md": "building", + "docs/OPEN_SOURCE_RELEASE.md": "release", + } + for name, content in sources.items(): + path = project / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + + monkeypatch.setattr(build_portable, "PROJECT_ROOT", project) + monkeypatch.setattr( + build_portable, + "_copy_python_license_files", + lambda root: None, + ) + + build_portable._copy_release_documents(portable) + + for name in ( + "README.md", + "LICENSE", + "PRIVACY.md", + "THIRD_PARTY_NOTICES.md", + "BUILDING.md", + "OPEN_SOURCE_RELEASE.md", + ): + assert (portable / name).is_file() + + +def test_copy_toolchain_prefers_verified_prepared_directory( + tmp_path, + monkeypatch, +) -> None: + project = tmp_path / "project" + prepared_root = project / "build" / "toolchain-prepared" + prepared = prepared_root / "windows" + portable = tmp_path / "HapSign" + for path in ( + prepared / "runtime" / "bin" / "java.exe", + prepared / "runtime" / "bin" / "keytool.exe", + prepared / "lib" / "hap-sign-tool.jar", + prepared / "bin" / "hdc.exe", + prepared / "PROVENANCE.txt", + prepared / "toolchain.lock.json", + prepared / "NOTICE.txt", + ): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"verified") + + monkeypatch.setattr(build_portable, "PROJECT_ROOT", project) + monkeypatch.setattr(build_portable, "PREPARED_TOOLCHAIN_ROOT", prepared_root) + monkeypatch.setattr("hapsign.runtime.platform_tag", lambda: "windows") + monkeypatch.setattr(build_portable, "_smoke_test_toolchain", lambda root: None) + + build_portable._copy_toolchain( + portable, + keep_full_jbr=False, + allow_local_toolchain=False, + ) + + target = portable / "resources" / "toolchain" / "windows" + assert (target / "runtime" / "bin" / "java.exe").read_bytes() == b"verified" + assert (target / "PROVENANCE.txt").is_file() + + +def test_copy_toolchain_requires_explicit_deveco_fallback( + tmp_path, + monkeypatch, +) -> None: + monkeypatch.setattr( + build_portable, + "PREPARED_TOOLCHAIN_ROOT", + tmp_path / "missing", + ) + monkeypatch.setattr("hapsign.runtime.platform_tag", lambda: "windows") + + with pytest.raises(RuntimeError, match="prepare_toolchain.py"): + build_portable._copy_toolchain( + tmp_path / "HapSign", + keep_full_jbr=False, + allow_local_toolchain=False, + ) diff --git a/tests/test_client.py b/tests/test_client.py index 0277b54..616db43 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,12 +1,22 @@ """HTTP 客户端响应处理测试。""" +import logging from unittest.mock import Mock import pytest from hapsign.api import client as client_module from hapsign.api.client import HuaweiSignClient, TokenExpiredError -from hapsign.config import HEADER_OAUTH2_TOKEN, HEADER_TEAM_ID, HEADER_UID +from hapsign.config import ( + ACCEPT_LANGUAGE, + HEADER_ACCEPT_LANGUAGE, + HEADER_OAUTH2_TOKEN, + HEADER_TEAM_ID, + HEADER_UID, + HEADER_USER_AGENT, + USER_AGENT, +) +from hapsign.diagnostics import set_sensitive_logging def test_headers_include_credentials_and_optional_team() -> None: @@ -17,6 +27,12 @@ def test_headers_include_credentials_and_optional_team() -> None: assert headers[HEADER_OAUTH2_TOKEN] == "access-token" assert headers[HEADER_UID] == "user-id" assert headers[HEADER_TEAM_ID] == "team-id" + assert headers[HEADER_USER_AGENT] == USER_AGENT + assert headers[HEADER_ACCEPT_LANGUAGE] == ACCEPT_LANGUAGE + assert HEADER_USER_AGENT == "User-Agent" + assert HEADER_ACCEPT_LANGUAGE == "Accept-Language" + assert "Chrome/" in headers[HEADER_USER_AGENT] + assert USER_AGENT not in headers # 值不能被误当成 header 名 @pytest.mark.parametrize( @@ -80,3 +96,44 @@ def test_request_helpers(monkeypatch, method_name, request_name, expected) -> No assert result == expected response.raise_for_status.assert_called_once() + + +def test_http_diagnostics_require_sensitive_switch(caplog) -> None: + response = Mock( + status_code=200, + content=b'{"secret":"response-secret"}', + text='{"secret":"response-secret"}', + ) + response.json.return_value = {"ret": {"code": 0}, "secret": "response-secret"} + client = HuaweiSignClient("access-token", "user-id") + caplog.set_level(logging.DEBUG) + + try: + set_sensitive_logging(False) + client._log_http( + "POST", + "https://example.invalid/path?secret=query-secret", + response, + headers={"oauth2Token": "header-secret"}, + payload={"token": "payload-secret"}, + ) + assert "header-secret" not in caplog.text + assert "payload-secret" not in caplog.text + assert "response-secret" not in caplog.text + assert "query-secret" not in caplog.text + + caplog.clear() + set_sensitive_logging(True) + client._log_http( + "POST", + "https://example.invalid/path?secret=query-secret", + response, + headers={"oauth2Token": "header-secret"}, + payload={"token": "payload-secret"}, + ) + assert "header-secret" in caplog.text + assert "payload-secret" in caplog.text + assert "response-secret" in caplog.text + assert "query-secret" in caplog.text + finally: + set_sensitive_logging(False) diff --git a/tests/test_diagnostics.py b/tests/test_diagnostics.py new file mode 100644 index 0000000..3a4beed --- /dev/null +++ b/tests/test_diagnostics.py @@ -0,0 +1,66 @@ +"""持久诊断日志配置测试。""" + +import logging + +from hapsign import diagnostics +from hapsign.settings import AppSettings + + +def test_file_logging_reconfigures_single_handler_and_sensitive_switch( + tmp_path, monkeypatch +) -> None: + monkeypatch.setattr(diagnostics, "log_directory", lambda: tmp_path / "logs") + root = logging.getLogger() + previous_level = root.level + + try: + first_path = diagnostics.configure_file_logging( + AppSettings(log_level="DEBUG", log_sensitive_data=False) + ) + logging.getLogger("hapsign.test").warning("diagnostic marker") + handlers = [ + handler + for handler in root.handlers + if getattr(handler, diagnostics._HANDLER_MARKER, False) + ] + for handler in handlers: + handler.flush() + + assert first_path == tmp_path / "logs" / "hapsign.log" + assert "diagnostic marker" in first_path.read_text(encoding="utf-8") + assert len(handlers) == 1 + assert diagnostics.sensitive_logging_enabled() is False + + diagnostics.configure_file_logging( + AppSettings(log_level="ERROR", log_sensitive_data=True) + ) + handlers = [ + handler + for handler in root.handlers + if getattr(handler, diagnostics._HANDLER_MARKER, False) + ] + assert len(handlers) == 1 + assert handlers[0].level == logging.ERROR + assert diagnostics.sensitive_logging_enabled() is True + finally: + for handler in list(root.handlers): + if getattr(handler, diagnostics._HANDLER_MARKER, False): + root.removeHandler(handler) + handler.close() + root.setLevel(previous_level) + diagnostics.set_sensitive_logging(False) + + +def test_redact_sensitive_text_hides_tokens_and_bearer_headers() -> None: + diagnostics.set_sensitive_logging(False) + + text = diagnostics.redact_sensitive_text( + "tempToken=TEMP accessToken=ACCESS Authorization: Bearer BEARER" + ) + + assert "TEMP" not in text + assert "ACCESS" not in text + assert "BEARER" not in text + assert "tempToken=" in text + assert "accessToken=" in text + assert "Authorization: Bearer " in text diff --git a/tests/test_gui_style.py b/tests/test_gui_style.py new file mode 100644 index 0000000..839d78b --- /dev/null +++ b/tests/test_gui_style.py @@ -0,0 +1,50 @@ +"""桌面样式的高 DPI 与设置页回归测试。""" + +import os + +import pytest + +os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") + +pytest.importorskip("PySide6") + +from PySide6.QtWidgets import QApplication, QDialogButtonBox + +from hapsign import gui +from hapsign.settings import AppSettings + + +@pytest.fixture(scope="module") +def qt_app(): + app = QApplication.instance() or QApplication([]) + app.setStyle(gui.HapSignStyle("Fusion")) + app.setStyleSheet(gui.WINDOW_STYLE) + app.setFont(gui._application_font()) + return app + + +def test_styles_use_integer_logical_pixels(qt_app) -> None: + assert "font-size:" in gui.WINDOW_STYLE + assert "pt;" not in gui.WINDOW_STYLE + assert gui._application_font().pixelSize() == 14 + assert gui._fixed_width_font().pixelSize() == 13 + + +def test_settings_controls_have_consistent_custom_style(qt_app, tmp_path) -> None: + dialog = gui.SettingsDialog( + AppSettings(), + tmp_path / "logs" / "hapsign.log", + ) + dialog.ensurePolished() + + assert dialog.browser_combo.currentData() == "system_controlled" + assert dialog.browser_combo.sizeHint().height() >= 42 + assert dialog.storage_combo.sizeHint().height() >= 42 + assert dialog.log_level_combo.sizeHint().height() >= 42 + assert dialog.sensitive_check.sizeHint().height() >= 24 + + buttons = dialog.findChild(QDialogButtonBox) + save = buttons.button(QDialogButtonBox.StandardButton.Save) + cancel = buttons.button(QDialogButtonBox.StandardButton.Cancel) + assert save.objectName() == "primaryButton" + assert cancel.objectName() == "secondaryButton" diff --git a/tests/test_hap_inspect.py b/tests/test_hap_inspect.py new file mode 100644 index 0000000..2947c60 --- /dev/null +++ b/tests/test_hap_inspect.py @@ -0,0 +1,227 @@ +"""HAP 签名块检测测试。""" + +import struct +import zipfile +from pathlib import Path + +from hapsign.signing.hap_inspect import ( + HAP_SIG_BLOCK_HEADER_SIZE, + HAP_SIG_BLOCK_MAGIC_HI_V2, + HAP_SIG_BLOCK_MAGIC_HI_V3, + HAP_SIG_BLOCK_MAGIC_LO_V2, + HAP_SIG_BLOCK_MAGIC_LO_V3, + HAP_SIGN_SCHEME_V3_BLOCK_VERSION, + MAX_BLOCK_COUNT, + ZIP64_EOCD_LOCATOR_SIG, + ZIP64_EOCD_LOCATOR_SIZE, + is_hap_signed, +) + + +def _write_unsigned_hap(path: Path, *, comment: bytes = b"") -> None: + with zipfile.ZipFile(path, "w") as archive: + archive.writestr( + "module.json", + '{"app":{"bundleName":"com.example.app"}}', + ) + archive.comment = comment + + +def _find_eocd_offset(data: bytes) -> int: + """定位测试包的真实 EOCD,忽略 comment 内的伪签名。""" + relative = data.rfind(b"PK\x05\x06") + while relative >= 0: + if relative + 22 <= len(data): + comment_size = struct.unpack_from(" None: + """在 ZIP Central Directory 前插入伪造的 HAP Signing Block header。""" + data = bytearray(path.read_bytes()) + eocd_offset = _find_eocd_offset(data) + cd_offset = struct.unpack_from(" None: + hap = tmp_path / "app.hap" + _write_unsigned_hap(hap) + + assert is_hap_signed(str(hap)) is False + + +def test_v3_signing_block_is_detected(tmp_path: Path) -> None: + hap = tmp_path / "app.hap" + _write_unsigned_hap(hap) + _inject_signing_block( + hap, + magic_lo=HAP_SIG_BLOCK_MAGIC_LO_V3, + magic_hi=HAP_SIG_BLOCK_MAGIC_HI_V3, + version=HAP_SIGN_SCHEME_V3_BLOCK_VERSION, + block_size=64, + ) + + assert is_hap_signed(str(hap)) is True + + +def test_v2_signing_block_is_detected(tmp_path: Path) -> None: + hap = tmp_path / "app.hap" + _write_unsigned_hap(hap) + _inject_signing_block( + hap, + magic_lo=HAP_SIG_BLOCK_MAGIC_LO_V2, + magic_hi=HAP_SIG_BLOCK_MAGIC_HI_V2, + version=2, + block_size=48, + ) + + assert is_hap_signed(str(hap)) is True + + +def test_invalid_magic_is_not_signed(tmp_path: Path) -> None: + hap = tmp_path / "app.hap" + _write_unsigned_hap(hap) + _inject_signing_block( + hap, + magic_lo=0, + magic_hi=0, + version=HAP_SIGN_SCHEME_V3_BLOCK_VERSION, + block_size=64, + ) + + assert is_hap_signed(str(hap)) is False + + +def test_eocd_signature_inside_comment_is_ignored(tmp_path: Path) -> None: + hap = tmp_path / "comment.hap" + _write_unsigned_hap(hap, comment=b"contains-PK\x05\x06-marker") + _inject_signing_block( + hap, + magic_lo=HAP_SIG_BLOCK_MAGIC_LO_V3, + magic_hi=HAP_SIG_BLOCK_MAGIC_HI_V3, + version=HAP_SIGN_SCHEME_V3_BLOCK_VERSION, + block_size=64, + ) + + assert is_hap_signed(hap) is True + + +def test_invalid_central_directory_layout_is_not_signed(tmp_path: Path) -> None: + hap = tmp_path / "invalid-directory.hap" + _write_unsigned_hap(hap) + _inject_signing_block( + hap, + magic_lo=HAP_SIG_BLOCK_MAGIC_LO_V3, + magic_hi=HAP_SIG_BLOCK_MAGIC_HI_V3, + version=HAP_SIGN_SCHEME_V3_BLOCK_VERSION, + block_size=64, + ) + data = bytearray(hap.read_bytes()) + eocd_offset = _find_eocd_offset(data) + struct.pack_into(" None: + hap = tmp_path / "zero-blocks.hap" + _write_unsigned_hap(hap) + _inject_signing_block( + hap, + magic_lo=HAP_SIG_BLOCK_MAGIC_LO_V3, + magic_hi=HAP_SIG_BLOCK_MAGIC_HI_V3, + version=HAP_SIGN_SCHEME_V3_BLOCK_VERSION, + block_size=64, + block_count=0, + ) + + assert is_hap_signed(hap) is False + + +def test_block_count_must_fit_inside_signing_block(tmp_path: Path) -> None: + hap = tmp_path / "too-many-blocks.hap" + _write_unsigned_hap(hap) + _inject_signing_block( + hap, + magic_lo=HAP_SIG_BLOCK_MAGIC_LO_V3, + magic_hi=HAP_SIG_BLOCK_MAGIC_HI_V3, + version=HAP_SIGN_SCHEME_V3_BLOCK_VERSION, + block_size=64, + block_count=3, + ) + + assert is_hap_signed(hap) is False + + +def test_block_count_above_max_is_not_signed(tmp_path: Path) -> None: + hap = tmp_path / "max-blocks.hap" + _write_unsigned_hap(hap) + block_count = MAX_BLOCK_COUNT + 1 + block_size = HAP_SIG_BLOCK_HEADER_SIZE + block_count * 12 + 64 + _inject_signing_block( + hap, + magic_lo=HAP_SIG_BLOCK_MAGIC_LO_V3, + magic_hi=HAP_SIG_BLOCK_MAGIC_HI_V3, + version=HAP_SIGN_SCHEME_V3_BLOCK_VERSION, + block_size=block_size, + block_count=block_count, + ) + + assert is_hap_signed(hap) is False + + +def test_zip64_locator_bytes_before_eocd_are_rejected(tmp_path: Path) -> None: + """对齐 VerifyHap:EOCD 前 20 字节若为 ZIP64 Locator 则不支持。""" + hap = tmp_path / "zip64-locator.hap" + _write_unsigned_hap(hap) + _inject_signing_block( + hap, + magic_lo=HAP_SIG_BLOCK_MAGIC_LO_V3, + magic_hi=HAP_SIG_BLOCK_MAGIC_HI_V3, + version=HAP_SIGN_SCHEME_V3_BLOCK_VERSION, + block_size=64, + ) + data = bytearray(hap.read_bytes()) + eocd_offset = _find_eocd_offset(data) + locator_pos = eocd_offset - ZIP64_EOCD_LOCATOR_SIZE + assert locator_pos >= 0 + struct.pack_into(" None: + assert is_hap_signed(str(tmp_path / "missing.hap")) is False diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index 05dd312..68e3cb8 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -1,16 +1,24 @@ """签名流程中的本地、无网络逻辑测试。""" +import base64 import json +import logging +import os +import threading import zipfile from datetime import date, timedelta +from types import SimpleNamespace from unittest.mock import Mock import pytest from hapsign.api.client import TokenExpiredError +from hapsign.cancellation import OperationCancelled from hapsign.config import ACL_PERMISSION_WHITELIST +from hapsign.diagnostics import set_sensitive_logging from hapsign.models import TokenInfo from hapsign.pipeline import SignPipeline +from hapsign.token import secure_token_cache def _pipeline(tmp_path, monkeypatch) -> SignPipeline: @@ -22,7 +30,29 @@ def _pipeline(tmp_path, monkeypatch) -> SignPipeline: ) +def _fake_dpapi(monkeypatch) -> None: + """用可逆的确定性伪加密替代真实 DPAPI。 + + 使加密格式相关测试不依赖系统用户配置(沙箱/服务账户可能没有可用的 + 用户配置,CryptProtectData 会失败),任何平台都能稳定运行。 + """ + monkeypatch.setattr( + secure_token_cache, "_dpapi_protect", lambda data: base64.b64encode(data) + ) + + def _unprotect(data: bytes) -> bytes: + try: + return base64.b64decode(data) + except ValueError as exc: + raise OSError(f"DPAPI 解密失败(测试伪实现): {exc}") from None + + monkeypatch.setattr(secure_token_cache, "_dpapi_unprotect", _unprotect) + # 让 protect/decrypt 的跨平台守卫走 DPAPI 分支;测试结束后由 monkeypatch 恢复。 + monkeypatch.setattr(secure_token_cache.os, "name", "nt") + + def test_token_cache_round_trip(tmp_path, monkeypatch) -> None: + _fake_dpapi(monkeypatch) pipeline = _pipeline(tmp_path, monkeypatch) pipeline._token_info = TokenInfo( access_token="access", @@ -34,6 +64,96 @@ def test_token_cache_round_trip(tmp_path, monkeypatch) -> None: pipeline._save_token_cache() assert pipeline._load_token_cache()["access_token"] == "access" + assert not (tmp_path / "signing_files" / ".token_cache.json.tmp").exists() + + +def test_token_cache_write_failure_keeps_old_file(tmp_path, monkeypatch) -> None: + pipeline = _pipeline(tmp_path, monkeypatch) + cache_path = tmp_path / "signing_files" / ".token_cache.json" + cache_path.parent.mkdir() + cache_path.write_text('{"old": true}', encoding="utf-8") + pipeline._token_info = TokenInfo( + access_token="access", + user_id="user", + jwt_token="jwt", + ) + + def _broken_dumps(*args, **kwargs): + raise OSError("disk full") + + monkeypatch.setattr("hapsign.pipeline.json.dumps", _broken_dumps) + + pipeline._save_token_cache() + + assert cache_path.read_text(encoding="utf-8") == '{"old": true}' + assert not (tmp_path / "signing_files" / ".token_cache.json.tmp").exists() + + +def test_token_cache_replace_failure_keeps_old_file(tmp_path, monkeypatch) -> None: + pipeline = _pipeline(tmp_path, monkeypatch) + cache_path = tmp_path / "signing_files" / ".token_cache.json" + cache_path.parent.mkdir() + cache_path.write_text('{"old": true}', encoding="utf-8") + pipeline._token_info = TokenInfo( + access_token="access", + user_id="user", + jwt_token="jwt", + ) + + def _broken_replace(src, dst): + raise OSError("replace failed") + + monkeypatch.setattr("hapsign.pipeline.os.replace", _broken_replace) + + pipeline._save_token_cache() + + assert cache_path.read_text(encoding="utf-8") == '{"old": true}' + assert not (tmp_path / "signing_files" / ".token_cache.json.tmp").exists() + + +def test_token_cache_stale_tmp_does_not_break_load(tmp_path, monkeypatch) -> None: + _fake_dpapi(monkeypatch) + pipeline = _pipeline(tmp_path, monkeypatch) + cache_path = tmp_path / "signing_files" / ".token_cache.json" + cache_path.parent.mkdir() + # 残留的半截 tmp 文件不应被当成正式缓存。 + cache_path.with_suffix(cache_path.suffix + ".tmp").write_text( + '{"access_tok', encoding="utf-8" + ) + + assert pipeline._load_token_cache() is None + + pipeline._token_info = TokenInfo( + access_token="access", + user_id="user", + jwt_token="jwt", + ) + pipeline._save_token_cache() + + assert pipeline._load_token_cache()["access_token"] == "access" + assert not cache_path.with_suffix(cache_path.suffix + ".tmp").exists() + + +def test_custom_state_dir_owns_token_cache(tmp_path, monkeypatch) -> None: + _fake_dpapi(monkeypatch) + monkeypatch.chdir(tmp_path) + state_dir = tmp_path / "app-data" + pipeline = SignPipeline( + hap_path="app.hap", + bundle_name="com.example.app", + work_dir=str(tmp_path / "work"), + state_dir=str(state_dir), + ) + pipeline._token_info = TokenInfo( + access_token="access", + user_id="user", + jwt_token="jwt", + ) + + pipeline._save_token_cache() + + assert (state_dir / ".token_cache.json").is_file() + assert not (tmp_path / "signing_files" / ".token_cache.json").exists() def test_expired_token_cache_is_ignored(tmp_path, monkeypatch) -> None: @@ -46,6 +166,7 @@ def test_expired_token_cache_is_ignored(tmp_path, monkeypatch) -> None: "creation_date": (date.today() - timedelta(days=1)).isoformat(), "access_token": "access", "user_id": "user", + "jwt_token": "jwt", } ), encoding="utf-8", @@ -54,6 +175,181 @@ def test_expired_token_cache_is_ignored(tmp_path, monkeypatch) -> None: assert pipeline._load_token_cache() is None +def test_token_cache_without_jwt_is_ignored(tmp_path, monkeypatch) -> None: + pipeline = _pipeline(tmp_path, monkeypatch) + cache_path = tmp_path / "signing_files" / ".token_cache.json" + cache_path.parent.mkdir() + cache_path.write_text( + json.dumps( + { + "creation_date": date.today().isoformat(), + "access_token": "access", + "user_id": "user", + } + ), + encoding="utf-8", + ) + + assert pipeline._load_token_cache() is None + + +def test_token_cache_encrypted_on_disk(tmp_path, monkeypatch) -> None: + _fake_dpapi(monkeypatch) + pipeline = _pipeline(tmp_path, monkeypatch) + pipeline._token_info = TokenInfo( + access_token="access", + refresh_token="refresh", + user_id="user", + jwt_token="jwt", + ) + + pipeline._save_token_cache() + + raw = (tmp_path / "signing_files" / ".token_cache.json").read_bytes() + assert raw.startswith(secure_token_cache._HEADER.encode("ascii")) + assert b"access" not in raw + + +def test_token_cache_migrates_legacy_plaintext(tmp_path, monkeypatch) -> None: + _fake_dpapi(monkeypatch) + pipeline = _pipeline(tmp_path, monkeypatch) + cache_path = tmp_path / "signing_files" / ".token_cache.json" + cache_path.parent.mkdir() + cache_path.write_text( + json.dumps( + { + "creation_date": date.today().isoformat(), + "access_token": "access", + "refresh_token": "refresh", + "user_id": "user", + "jwt_token": "jwt", + } + ), + encoding="utf-8", + ) + + cache = pipeline._load_token_cache() + + assert cache is not None + assert cache["access_token"] == "access" + # 迁移后磁盘上应为加密格式。 + raw = cache_path.read_bytes() + assert raw.startswith(secure_token_cache._HEADER.encode("ascii")) + assert b"access" not in raw + assert pipeline._load_token_cache()["access_token"] == "access" + + +def test_token_cache_bad_base64_falls_back_to_relogin(tmp_path, monkeypatch) -> None: + pipeline = _pipeline(tmp_path, monkeypatch) + cache_path = tmp_path / "signing_files" / ".token_cache.json" + cache_path.parent.mkdir() + cache_path.write_bytes(b"hapsign-token-v1:!!!not-base64!!!") + + assert pipeline._load_token_cache() is None + + +def test_token_cache_corrupt_payload_falls_back_to_relogin( + tmp_path, monkeypatch +) -> None: + _fake_dpapi(monkeypatch) + pipeline = _pipeline(tmp_path, monkeypatch) + cache_path = tmp_path / "signing_files" / ".token_cache.json" + cache_path.parent.mkdir() + # 头 + 合法 base64 但非 DPAPI blob:解密必须失败而不是崩溃。 + cache_path.write_bytes(b"hapsign-token-v1:" + base64.b64encode(b"not a dpapi blob")) + + assert pipeline._load_token_cache() is None + + +@pytest.mark.parametrize("payload", ["[]", "null", '"not-an-object"']) +def test_token_cache_non_object_falls_back_to_relogin( + tmp_path, monkeypatch, payload +) -> None: + pipeline = _pipeline(tmp_path, monkeypatch) + cache_path = tmp_path / "signing_files" / ".token_cache.json" + cache_path.parent.mkdir() + cache_path.write_text(payload, encoding="utf-8") + + assert pipeline._load_token_cache() is None + + +@pytest.mark.parametrize("payload", ["[]", "null", '"not-an-object"']) +def test_metadata_non_object_is_ignored(tmp_path, monkeypatch, payload) -> None: + pipeline = _pipeline(tmp_path, monkeypatch) + pipeline._metadata_path = str(tmp_path / "signing" / "metadata.json") + with open(pipeline._metadata_path, "w", encoding="utf-8") as metadata: + metadata.write(payload) + + assert pipeline._load_cached_metadata() is None + + +def test_pipeline_failure_logs_redacted_exception( + caplog, tmp_path, monkeypatch +) -> None: + set_sensitive_logging(False) + pipeline = _pipeline(tmp_path, monkeypatch) + secret = "secret-temp-token" + caplog.set_level(logging.DEBUG, logger="hapsign.pipeline") + + result = pipeline._run_steps( + [ + ( + "登录", + Mock( + side_effect=RuntimeError( + "request failed: " + f"/authrouter/auth/api/temptoken/check?tempToken={secret}" + ) + ), + ) + ] + ) + + assert result is False + assert secret not in caplog.text + assert "tempToken=" in caplog.text + + +def test_token_cache_protect_failure_keeps_old_file(tmp_path, monkeypatch) -> None: + _fake_dpapi(monkeypatch) + monkeypatch.setattr( + secure_token_cache, + "_dpapi_protect", + Mock(side_effect=OSError("CryptProtectData 失败: 无法加载用户配置")), + ) + pipeline = _pipeline(tmp_path, monkeypatch) + cache_path = tmp_path / "signing_files" / ".token_cache.json" + cache_path.parent.mkdir() + cache_path.write_bytes(b"hapsign-token-v1:" + base64.b64encode(b"old-cache")) + pipeline._token_info = TokenInfo( + access_token="new", + user_id="user", + jwt_token="jwt", + ) + + pipeline._save_token_cache() + + # 加密失败:保留原缓存,绝不落明文。 + raw = cache_path.read_bytes() + assert raw.startswith(secure_token_cache._HEADER.encode("ascii")) + assert b"new" not in raw + assert not (tmp_path / "signing_files" / ".token_cache.json.tmp").exists() + + +def test_token_cache_encrypted_cache_on_non_windows_ignored( + tmp_path, monkeypatch +) -> None: + _fake_dpapi(monkeypatch) + # 模拟加密缓存被带到非 Windows 平台:decrypt 必须显式失败而不是崩溃。 + monkeypatch.setattr(secure_token_cache.os, "name", "posix") + pipeline = _pipeline(tmp_path, monkeypatch) + cache_path = tmp_path / "signing_files" / ".token_cache.json" + cache_path.parent.mkdir() + cache_path.write_bytes(b"hapsign-token-v1:" + base64.b64encode(b"whatever")) + + assert pipeline._load_token_cache() is None + + def test_metadata_does_not_store_keystore_password(tmp_path, monkeypatch) -> None: pipeline = _pipeline(tmp_path, monkeypatch) @@ -112,6 +408,7 @@ def test_with_refresh_skips_refresh_on_success(tmp_path, monkeypatch) -> None: def test_with_refresh_refreshes_and_retries_on_token_expired( tmp_path, monkeypatch ) -> None: + _fake_dpapi(monkeypatch) pipeline = _pipeline_with_token(tmp_path, monkeypatch) func = Mock(side_effect=[TokenExpiredError("expired"), "ok"]) @@ -138,3 +435,327 @@ def test_with_refresh_propagates_persistent_token_expired( # 仍只刷新一次,重试时再次抛出不再二次刷新 pipeline._token_exchange.refresh_access_token.assert_called_once_with("jwt") + + +def test_run_installs_already_signed_hap_directly(tmp_path, monkeypatch) -> None: + from hapsign import pipeline as pipeline_module + + hap = tmp_path / "signed.hap" + hap.write_bytes(b"placeholder") + pipeline = SignPipeline( + hap_path=str(hap), + bundle_name="com.example.app", + work_dir=str(tmp_path / "signing"), + ) + monkeypatch.setattr(pipeline_module, "is_hap_signed", lambda _path: True) + install = Mock() + monkeypatch.setattr( + pipeline_module, + "Installer", + lambda **_kwargs: SimpleNamespace( + get_udid=lambda: "A" * 64, + install=install, + close=Mock(), + ), + ) + login = Mock(side_effect=AssertionError("login should be skipped")) + monkeypatch.setattr(pipeline, "_step_login", login) + monkeypatch.setattr(pipeline, "_step_sign_hap", Mock()) + + assert pipeline.run() is True + install.assert_called_once_with(str(hap)) + login.assert_not_called() + pipeline._step_sign_hap.assert_not_called() + + +def test_run_unsigned_hap_does_not_take_signed_shortcut(tmp_path, monkeypatch) -> None: + from hapsign import pipeline as pipeline_module + + hap = tmp_path / "unsigned.hap" + hap.write_bytes(b"placeholder") + pipeline = SignPipeline( + hap_path=str(hap), + bundle_name="com.example.app", + work_dir=str(tmp_path / "signing"), + ) + monkeypatch.setattr(pipeline_module, "is_hap_signed", lambda _path: False) + monkeypatch.setattr(pipeline, "_step_check_device", Mock()) + monkeypatch.setattr(pipeline, "_load_cached_metadata", lambda: None) + monkeypatch.setattr(pipeline, "_load_token_cache", lambda: None) + monkeypatch.setattr( + pipeline, + "_step_login", + Mock(side_effect=RuntimeError("stop before network")), + ) + + assert pipeline.run() is False + pipeline._step_check_device.assert_called_once() + pipeline._step_login.assert_called_once() + + +def test_run_stops_before_signing_when_device_is_unavailable( + tmp_path, monkeypatch +) -> None: + from hapsign import pipeline as pipeline_module + + pipeline = _pipeline(tmp_path, monkeypatch) + monkeypatch.setattr(pipeline_module, "is_hap_signed", Mock()) + monkeypatch.setattr( + pipeline, + "_step_check_device", + Mock(side_effect=RuntimeError("未检测到可用设备")), + ) + login = Mock() + monkeypatch.setattr(pipeline, "_step_login", login) + + assert pipeline.run() is False + pipeline_module.is_hap_signed.assert_not_called() + login.assert_not_called() + + +def test_run_always_closes_hdc_installer(tmp_path, monkeypatch) -> None: + pipeline = _pipeline(tmp_path, monkeypatch) + close = Mock() + pipeline._installer = SimpleNamespace(close=close) + monkeypatch.setattr( + pipeline, + "_run_pipeline", + Mock(side_effect=RuntimeError("pipeline failed")), + ) + + with pytest.raises(RuntimeError, match="pipeline failed"): + pipeline.run() + + close.assert_called_once() + assert pipeline._installer is None + + +def test_register_device_reuses_preflight_udid(tmp_path, monkeypatch) -> None: + pipeline = _pipeline(tmp_path, monkeypatch) + pipeline._udid = "B" * 64 + pipeline._team_id = "team" + pipeline._device_api = SimpleNamespace( + add_device=Mock(return_value=True), + find_device_id=Mock(return_value="device-id"), + ) + get_udid = Mock(side_effect=AssertionError("UDID should be reused")) + from hapsign import pipeline as pipeline_module + + monkeypatch.setattr( + pipeline_module, + "Installer", + lambda **_kwargs: SimpleNamespace(get_udid=get_udid), + ) + + pipeline._step_register_device() + + get_udid.assert_not_called() + pipeline._device_api.add_device.assert_called_once() + assert pipeline._device_id == "device-id" + + +def test_pipeline_runtime_state_initialized(tmp_path, monkeypatch) -> None: + pipeline = _pipeline(tmp_path, monkeypatch) + + assert pipeline._team_id == "" + assert pipeline._app_info is None + assert pipeline._cert_result is None + assert pipeline._p12_path == "" + assert pipeline._cer_path == "" + assert pipeline._p7b_path == "" + assert pipeline._csr_path == "" + assert pipeline._csr_content == "" + assert pipeline._signed_hap_path == "" + + +def test_force_refresh_signing_decision_does_not_leak(tmp_path, monkeypatch) -> None: + from hapsign import pipeline as pipeline_module + + pipeline = _pipeline(tmp_path, monkeypatch) + pipeline.force_refresh_token = True + pipeline.force_refresh_signing = False + monkeypatch.setattr(pipeline_module, "is_hap_signed", lambda _path: False) + monkeypatch.setattr(pipeline, "_step_check_device", Mock()) + clear_cache = Mock() + monkeypatch.setattr(pipeline, "_clear_token_cache", clear_cache) + load_token_cache = Mock() + monkeypatch.setattr(pipeline, "_load_token_cache", load_token_cache) + login = Mock() + monkeypatch.setattr(pipeline, "_step_login", login) + exchange = Mock() + monkeypatch.setattr(pipeline, "_step_exchange_token", exchange) + monkeypatch.setattr(pipeline, "_run_steps", Mock(return_value=True)) + + assert pipeline._run_pipeline() is True + + # 强制刷新 token 走重新登录路径(不再读取 token 缓存) + clear_cache.assert_called_once() + login.assert_called_once() + exchange.assert_called_once() + load_token_cache.assert_not_called() + # 局部决策不得泄漏到实例属性:复用实例再次运行不应永久强制刷新签名 + assert pipeline.force_refresh_signing is False + assert pipeline.force_refresh_token is True + + +def test_cancelled_pipeline_closes_installer_and_propagates( + tmp_path, monkeypatch +) -> None: + cancel_event = threading.Event() + pipeline = SignPipeline( + hap_path="app.hap", + bundle_name="com.example.app", + work_dir=str(tmp_path / "signing"), + cancel_event=cancel_event, + ) + close = Mock() + pipeline._installer = SimpleNamespace(close=close) + cancel_event.set() + + with pytest.raises(OperationCancelled): + pipeline.run() + + close.assert_called_once() + assert pipeline._installer is None + + +def test_pipeline_reports_stage_progress(tmp_path, monkeypatch) -> None: + values = [] + pipeline = SignPipeline( + hap_path="app.hap", + bundle_name="com.example.app", + work_dir=str(tmp_path / "signing"), + progress_callback=lambda value, label: values.append((value, label)), + ) + monkeypatch.setattr(pipeline, "_run_pipeline", Mock(return_value=True)) + + assert pipeline.run() is True + assert values[0][0] == 2 + assert values[-1] == (100, "安装完成") + + +def _prepare_sign_step(pipeline: SignPipeline) -> None: + pipeline._cer_path = "certificate.cer" + pipeline._p7b_path = "profile.p7b" + pipeline._p12_path = "keystore.p12" + + +def test_kept_signed_hap_preserves_unrelated_hap(tmp_path, monkeypatch) -> None: + from hapsign import pipeline as pipeline_module + + output_dir = tmp_path / "signed_haps" + output_dir.mkdir() + source = output_dir / "new-app.hap" + source.write_bytes(b"source") + unrelated = output_dir / "previous_signed.hap" + unrelated.write_bytes(b"old") + generated = output_dir / "old-app_signed.hap" + generated.write_bytes(b"generated-old") + (output_dir / ".hapsign-signed-haps.json").write_text( + json.dumps({"version": 1, "generated_haps": [generated.name, source.name]}), + encoding="utf-8", + ) + pipeline = SignPipeline( + hap_path=str(source), + bundle_name="com.example.app", + work_dir=str(tmp_path / "signing"), + signed_output_dir=str(output_dir), + keep_signed_hap=True, + ) + _prepare_sign_step(pipeline) + + class FakeSigner: + def __init__(self, **_kwargs) -> None: + pass + + def sign_hap(self, *_args) -> bool: + output_path = _args[-1] + with open(output_path, "wb") as output: + output.write(b"new") + return True + + monkeypatch.setattr(pipeline_module, "HapSigner", FakeSigner) + + pipeline._step_sign_hap() + + kept = list(output_dir.glob("*.hap")) + assert sorted(kept) == sorted( + [source, unrelated, output_dir / "new-app_signed.hap"] + ) + assert source.read_bytes() == b"source" + assert unrelated.read_bytes() == b"old" + signed_path = output_dir / "new-app_signed.hap" + assert signed_path.read_bytes() == b"new" + assert pipeline._signed_hap_path == str(signed_path) + assert json.loads( + (output_dir / ".hapsign-signed-haps.json").read_text(encoding="utf-8") + ) == {"version": 1, "generated_haps": [signed_path.name]} + + +def test_failed_signing_preserves_previous_output(tmp_path, monkeypatch) -> None: + from hapsign import pipeline as pipeline_module + + output_dir = tmp_path / "signed_haps" + output_dir.mkdir() + old = output_dir / "previous_signed.hap" + old.write_bytes(b"old") + pipeline = SignPipeline( + hap_path=str(tmp_path / "new-app.hap"), + bundle_name="com.example.app", + work_dir=str(tmp_path / "signing"), + signed_output_dir=str(output_dir), + keep_signed_hap=True, + ) + _prepare_sign_step(pipeline) + + class FailingSigner: + def __init__(self, **_kwargs) -> None: + pass + + def sign_hap(self, *_args) -> bool: + with open(_args[-1], "wb") as output: + output.write(b"partial") + raise RuntimeError("signing failed") + + monkeypatch.setattr(pipeline_module, "HapSigner", FailingSigner) + + with pytest.raises(RuntimeError, match="signing failed"): + pipeline._step_sign_hap() + + assert list(output_dir.glob("*.hap")) == [old] + assert old.read_bytes() == b"old" + + +def test_disabled_signed_hap_retention_uses_temporary_file( + tmp_path, monkeypatch +) -> None: + from hapsign import pipeline as pipeline_module + + output_dir = tmp_path / "signed_haps" + pipeline = SignPipeline( + hap_path=str(tmp_path / "new-app.hap"), + bundle_name="com.example.app", + work_dir=str(tmp_path / "signing"), + signed_output_dir=str(output_dir), + keep_signed_hap=False, + ) + _prepare_sign_step(pipeline) + + class FakeSigner: + def __init__(self, **_kwargs) -> None: + pass + + def sign_hap(self, *_args) -> bool: + with open(_args[-1], "wb") as output: + output.write(b"temporary") + return True + + monkeypatch.setattr(pipeline_module, "HapSigner", FakeSigner) + + pipeline._step_sign_hap() + temporary_path = pipeline._signed_hap_path + assert os.path.isfile(temporary_path) + assert not output_dir.exists() + + pipeline._cleanup_temporary_signed_hap() + assert not os.path.exists(temporary_path) diff --git a/tests/test_prepare_toolchain.py b/tests/test_prepare_toolchain.py new file mode 100644 index 0000000..f7c87fc --- /dev/null +++ b/tests/test_prepare_toolchain.py @@ -0,0 +1,90 @@ +"""公开工具链下载、校验和安全提取测试。""" + +from __future__ import annotations + +import hashlib +import io +import tarfile +import zipfile + +import pytest + +from scripts import prepare_toolchain + + +def test_verify_file_rejects_wrong_size_and_hash(tmp_path) -> None: + artifact = tmp_path / "artifact.bin" + artifact.write_bytes(b"locked") + + with pytest.raises(RuntimeError, match="大小校验失败"): + prepare_toolchain._verify_file( + artifact, + {"size": 5}, + label="test", + ) + + with pytest.raises(RuntimeError, match="SHA-256 校验失败"): + prepare_toolchain._verify_file( + artifact, + {"size": 6, "sha256": "0" * 64}, + label="test", + ) + + +def test_extract_nested_toolchains_selects_exact_basename(tmp_path) -> None: + sdk = tmp_path / "sdk.tar.gz" + expected = "toolchains-windows-x64-test.zip" + payload = b"toolchains" + with tarfile.open(sdk, mode="w:gz") as archive: + ignored = tarfile.TarInfo("sdk/linux/toolchains-linux.zip") + ignored.size = 5 + archive.addfile(ignored, io.BytesIO(b"linux")) + selected = tarfile.TarInfo(f"sdk/windows/{expected}") + selected.size = len(payload) + archive.addfile(selected, io.BytesIO(payload)) + + output = tmp_path / expected + prepare_toolchain._extract_nested_toolchains( + sdk, + expected_name=expected, + destination=output, + ) + + assert output.read_bytes() == payload + + +def test_extract_openharmony_files_checks_locked_hash(tmp_path) -> None: + archive_path = tmp_path / "toolchains.zip" + with zipfile.ZipFile(archive_path, mode="w") as archive: + archive.writestr("sdk/toolchains/hdc.exe", b"hdc") + archive.writestr("sdk/toolchains/lib/hap-sign-tool.jar", b"signer") + + output = tmp_path / "prepared" + prepare_toolchain._extract_openharmony_files( + archive_path, + output, + { + "bin/hdc.exe": { + "archive_suffix": "hdc.exe", + "sha256": hashlib.sha256(b"hdc").hexdigest(), + }, + "lib/hap-sign-tool.jar": { + "archive_suffix": "lib/hap-sign-tool.jar", + "sha256": hashlib.sha256(b"signer").hexdigest(), + }, + }, + ) + + assert (output / "bin" / "hdc.exe").read_bytes() == b"hdc" + assert (output / "lib" / "hap-sign-tool.jar").read_bytes() == b"signer" + + +def test_safe_extract_zip_rejects_parent_traversal(tmp_path) -> None: + archive_path = tmp_path / "unsafe.zip" + with zipfile.ZipFile(archive_path, mode="w") as archive: + archive.writestr("../escaped.txt", "unsafe") + + with pytest.raises(RuntimeError, match="越界路径"): + prepare_toolchain._safe_extract_zip(archive_path, tmp_path / "target") + + assert not (tmp_path / "escaped.txt").exists() diff --git a/tests/test_runtime.py b/tests/test_runtime.py new file mode 100644 index 0000000..727594e --- /dev/null +++ b/tests/test_runtime.py @@ -0,0 +1,86 @@ +"""跨平台运行目录与便携工具链发现测试。""" + +from hapsign import runtime + + +def test_app_data_dir_honors_override(tmp_path, monkeypatch) -> None: + target = tmp_path / "state" + monkeypatch.setenv("HAPSIGN_DATA_DIR", str(target)) + + assert runtime.app_data_dir() == target.resolve() + + +def test_app_data_dir_defaults_to_application_dir(tmp_path, monkeypatch) -> None: + monkeypatch.delenv("HAPSIGN_DATA_DIR", raising=False) + monkeypatch.setattr(runtime, "application_dir", lambda: tmp_path) + + assert runtime.app_data_dir() == tmp_path + + +def test_portable_toolchain_takes_precedence(tmp_path, monkeypatch) -> None: + resources = tmp_path / "resources" + monkeypatch.setenv("HAPSIGN_RESOURCE_DIR", str(resources)) + monkeypatch.setattr(runtime.platform, "system", lambda: "Windows") + root = resources / "toolchain" / "windows" + paths = [ + root / "jbr" / "bin" / "java.exe", + root / "jbr" / "bin" / "keytool.exe", + root / "lib" / "hap-sign-tool.jar", + root / "bin" / "hdc.exe", + ] + for path in paths: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"tool") + + toolchain = runtime.discover_toolchain() + + assert toolchain.source == "portable" + assert toolchain.missing() == [] + assert toolchain.hdc == paths[-1] + + +def test_portable_public_runtime_takes_precedence_over_legacy_jbr( + tmp_path, + monkeypatch, +) -> None: + resources = tmp_path / "resources" + monkeypatch.setenv("HAPSIGN_RESOURCE_DIR", str(resources)) + monkeypatch.setattr(runtime.platform, "system", lambda: "Windows") + root = resources / "toolchain" / "windows" + for name in ("java.exe", "keytool.exe"): + for runtime_name in ("runtime", "jbr"): + path = root / runtime_name / "bin" / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(runtime_name.encode()) + for path in ( + root / "lib" / "hap-sign-tool.jar", + root / "bin" / "hdc.exe", + ): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"tool") + + toolchain = runtime.discover_toolchain() + + assert toolchain.java == root / "runtime" / "bin" / "java.exe" + assert toolchain.keytool == root / "runtime" / "bin" / "keytool.exe" + + +def test_signed_hap_only_requires_hdc(tmp_path) -> None: + hdc = tmp_path / "hdc" + hdc.write_bytes(b"tool") + toolchain = runtime.ToolchainPaths( + java=tmp_path / "missing-java", + keytool=tmp_path / "missing-keytool", + hap_sign_tool=tmp_path / "missing.jar", + hdc=hdc, + source="test", + ) + + assert toolchain.missing(require_signing=False) == [] + assert len(toolchain.missing(require_signing=True)) == 3 + + +def test_platform_tag_normalizes_darwin(monkeypatch) -> None: + monkeypatch.setattr(runtime.platform, "system", lambda: "Darwin") + + assert runtime.platform_tag() == "macos" diff --git a/tests/test_settings.py b/tests/test_settings.py new file mode 100644 index 0000000..7537dff --- /dev/null +++ b/tests/test_settings.py @@ -0,0 +1,96 @@ +"""桌面版设置与目录规则测试。""" + +import json + +from hapsign import settings + + +def test_load_missing_config_uses_safe_defaults(tmp_path, monkeypatch) -> None: + monkeypatch.setenv("HAPSIGN_CONFIG_FILE", str(tmp_path / "missing.json")) + + loaded = settings.load_settings() + + assert loaded == settings.AppSettings() + assert loaded.browser_mode == "system_controlled" + assert loaded.signing_storage == "program" + assert loaded.log_sensitive_data is False + assert loaded.keep_signed_hap is True + + +def test_settings_round_trip(tmp_path, monkeypatch) -> None: + config = tmp_path / "config" / "hapsign.json" + monkeypatch.setenv("HAPSIGN_CONFIG_FILE", str(config)) + expected = settings.AppSettings( + log_level="DEBUG", + signing_storage="custom", + custom_signing_dir=str(tmp_path / "keys"), + browser_mode="system", + log_sensitive_data=True, + keep_signed_hap=False, + ) + + assert settings.save_settings(expected) == config + assert settings.load_settings() == expected + assert not config.with_suffix(".json.tmp").exists() + + +def test_invalid_values_fall_back_and_sensitive_requires_boolean( + tmp_path, monkeypatch +) -> None: + config = tmp_path / "hapsign.json" + monkeypatch.setenv("HAPSIGN_CONFIG_FILE", str(config)) + config.write_text( + json.dumps( + { + "log_level": "verbose", + "signing_storage": "unknown", + "browser_mode": "other", + "log_sensitive_data": "true", + "keep_signed_hap": "false", + } + ), + encoding="utf-8", + ) + + loaded = settings.load_settings() + + assert loaded.log_level == "INFO" + assert loaded.signing_storage == "program" + assert loaded.browser_mode == "system_controlled" + assert loaded.log_sensitive_data is False + assert loaded.keep_signed_hap is True + + +def test_signing_directory_modes(tmp_path, monkeypatch) -> None: + monkeypatch.setattr(settings, "application_dir", lambda: tmp_path / "app") + monkeypatch.setattr(settings, "user_local_data_dir", lambda: tmp_path / "local") + + assert settings.signing_files_dir(settings.AppSettings()) == ( + tmp_path / "app" / "signing_files" + ) + assert settings.signing_files_dir( + settings.AppSettings(signing_storage="appdata") + ) == (tmp_path / "local" / "signing_files") + assert settings.signing_files_dir( + settings.AppSettings( + signing_storage="custom", + custom_signing_dir=str(tmp_path / "custom"), + ) + ) == (tmp_path / "custom") + + +def test_data_directory_environment_override_wins(tmp_path, monkeypatch) -> None: + monkeypatch.setenv("HAPSIGN_DATA_DIR", str(tmp_path / "override")) + + assert settings.signing_files_dir( + settings.AppSettings(signing_storage="appdata") + ) == (tmp_path / "override" / "signing_files") + + +def test_signed_hap_directory_is_always_in_program_directory( + tmp_path, monkeypatch +) -> None: + monkeypatch.setattr(settings, "application_dir", lambda: tmp_path / "app") + monkeypatch.setenv("HAPSIGN_DATA_DIR", str(tmp_path / "elsewhere")) + + assert settings.signed_haps_dir() == tmp_path / "app" / "signed_haps" diff --git a/tests/test_signing.py b/tests/test_signing.py index ba94c5f..f18caca 100644 --- a/tests/test_signing.py +++ b/tests/test_signing.py @@ -1,5 +1,6 @@ """外部签名与安装命令的无设备测试。""" +import time from types import SimpleNamespace from unittest.mock import Mock @@ -10,7 +11,7 @@ def test_hap_signer_builds_subprocess_command(monkeypatch) -> None: run = Mock(return_value=SimpleNamespace(returncode=0, stdout="", stderr="")) - monkeypatch.setattr(hap_signer.subprocess, "run", run) + monkeypatch.setattr(hap_signer, "run_process", run) result = hap_signer.HapSigner().sign_hap( "in.hap", "cert.cer", "profile.p7b", "key.p12", output_path="out.hap" @@ -20,11 +21,33 @@ def test_hap_signer_builds_subprocess_command(monkeypatch) -> None: assert result is True assert command[command.index("-inFile") + 1] == "in.hap" assert command[command.index("-outFile") + 1] == "out.hap" + assert ( + command[command.index("-keystorePwd") + 1] + == hap_signer.config.KEYSTORE_PASSWORD + ) + + +def test_keytool_defaults_use_config_keystore_password(monkeypatch, tmp_path) -> None: + run = Mock(return_value=SimpleNamespace(returncode=0, stdout="", stderr="")) + monkeypatch.setattr(keytool_util, "run_process", run) + monkeypatch.setattr( + keytool_util.KeytoolUtil, + "_get_keytool_path", + staticmethod(lambda: "keytool"), + ) + keystore = tmp_path / "debug.p12" + + keytool_util.KeytoolUtil().generate_keypair(str(keystore)) + + command = run.call_args.args[0] + assert command[command.index("-storepass") + 1] == ( + keytool_util.config.KEYSTORE_PASSWORD + ) def test_hap_signer_reports_tool_failure(monkeypatch) -> None: result = SimpleNamespace(returncode=2, stdout="", stderr="sign failed") - monkeypatch.setattr(hap_signer.subprocess, "run", Mock(return_value=result)) + monkeypatch.setattr(hap_signer, "run_process", Mock(return_value=result)) with pytest.raises(RuntimeError, match="sign failed"): hap_signer.HapSigner().sign_hap("in.hap", "cert.cer", "profile.p7b", "key.p12") @@ -33,22 +56,246 @@ def test_hap_signer_reports_tool_failure(monkeypatch) -> None: def test_installer_extracts_udid(monkeypatch) -> None: udid = "A" * 64 run = Mock(return_value=SimpleNamespace(returncode=0, stdout=udid, stderr="")) - monkeypatch.setattr(installer.subprocess, "run", run) + monkeypatch.setattr(installer, "run_process", run) + monkeypatch.setattr(installer.Installer, "_ensure_server", lambda self: None) assert installer.Installer().get_udid() == udid -def test_installer_detects_failure_text(monkeypatch) -> None: - result = SimpleNamespace(returncode=0, stdout="Failure: failed", stderr="") - monkeypatch.setattr(installer.subprocess, "run", Mock(return_value=result)) +def test_installer_raises_on_fail_marker(monkeypatch) -> None: + # rc=0 但输出含 [Fail] 状态行 → 判定失败 + result = SimpleNamespace( + returncode=0, stdout="[Fail]install bundle failed", stderr="" + ) + monkeypatch.setattr(installer, "run_process", Mock(return_value=result)) + monkeypatch.setattr(installer.Installer, "_ensure_server", lambda self: None) + + with pytest.raises(RuntimeError, match="hdc install"): + installer.Installer().install("app.hap") + + +def test_installer_raises_on_install_failed_prefix(monkeypatch) -> None: + result = SimpleNamespace( + returncode=0, + stdout="INSTALL_FAILED_MSG_BUFFER_ERROR: install failed", + stderr="", + ) + monkeypatch.setattr(installer, "run_process", Mock(return_value=result)) + monkeypatch.setattr(installer.Installer, "_ensure_server", lambda self: None) with pytest.raises(RuntimeError, match="hdc install"): installer.Installer().install("app.hap") +def test_installer_raises_on_error_line(monkeypatch) -> None: + result = SimpleNamespace(returncode=0, stdout="Error: signature invalid", stderr="") + monkeypatch.setattr(installer, "run_process", Mock(return_value=result)) + monkeypatch.setattr(installer.Installer, "_ensure_server", lambda self: None) + + with pytest.raises(RuntimeError, match="hdc install"): + installer.Installer().install("app.hap") + + +def test_installer_accepts_error_text_not_at_line_start(monkeypatch) -> None: + # 普通输出里含 "error:" 但不在状态行行首 → 不得误判为失败 + result = SimpleNamespace( + returncode=0, + stdout="[Info]connect success: 127.0.0.1:8710 error: nothing to worry\n" + "[Info]install bundle successfully", + stderr="", + ) + monkeypatch.setattr(installer, "run_process", Mock(return_value=result)) + monkeypatch.setattr(installer.Installer, "_ensure_server", lambda self: None) + + assert installer.Installer().install("app.hap") is True + + +def test_installer_raises_on_nonzero_exit(monkeypatch) -> None: + result = SimpleNamespace(returncode=1, stdout="", stderr="") + monkeypatch.setattr(installer, "run_process", Mock(return_value=result)) + monkeypatch.setattr(installer.Installer, "_ensure_server", lambda self: None) + + with pytest.raises(RuntimeError, match="hdc install"): + installer.Installer().install("app.hap") + + +def test_installer_accepts_success_with_error_in_path(monkeypatch) -> None: + # 路径/包名含 error/failed 但安装成功 → 不得误判 + result = SimpleNamespace( + returncode=0, + stdout="[Info]install bundle successfully", + stderr="", + ) + monkeypatch.setattr(installer, "run_process", Mock(return_value=result)) + monkeypatch.setattr(installer.Installer, "_ensure_server", lambda self: None) + + hdc = installer.Installer() + assert hdc.install(r"C:\error\failed-app.hap") is True + assert hdc.install(r"C:\tmp\debug\failed_error.apk.hap") is True + + +def test_installer_closes_server_started_by_current_task(monkeypatch) -> None: + # 无既有 server → hdc start 启动 PID 47024 → close 只 kill 该 PID + listener_pids = [None, 47024, 47024] + monkeypatch.setattr(installer, "_listener_pid", lambda: listener_pids.pop(0)) + monkeypatch.setattr(installer, "_process_start_time", lambda pid: time.time()) + run = Mock(return_value=SimpleNamespace(returncode=0, stdout="", stderr="")) + monkeypatch.setattr(installer.subprocess, "run", run) + + hdc = installer.Installer() + hdc._ensure_server() + hdc.close() + hdc.close() + + commands = [call.args[0] for call in run.call_args_list] + assert commands == [[hdc._hdc, "start"], [hdc._hdc, "kill"]] + + +def test_installer_preserves_preexisting_server(monkeypatch) -> None: + monkeypatch.setattr(installer, "_listener_pid", lambda: 47024) + run = Mock() + monkeypatch.setattr(installer.subprocess, "run", run) + + with installer.Installer() as hdc: + hdc._ensure_server() + + run.assert_not_called() + + +def test_installer_does_not_take_over_non_hdc_listener(monkeypatch) -> None: + # 端口被非 HDC 进程占用:不启动、不接管、close 不清理 + monkeypatch.setattr(installer, "_listener_pid", lambda: 12345) + run = Mock() + monkeypatch.setattr(installer.subprocess, "run", run) + + with installer.Installer() as hdc: + hdc._ensure_server() + + run.assert_not_called() + + +def test_installer_refuses_external_server_taken_over_before_start( + monkeypatch, +) -> None: + # 探测后外部 HDC 抢先启动:监听进程创建早于本次 hdc start,不接管、不清理 + listener_pids = [None, 55555] + monkeypatch.setattr(installer, "_listener_pid", lambda: listener_pids.pop(0)) + monkeypatch.setattr(installer, "_process_start_time", lambda pid: time.time() - 30) + run = Mock(return_value=SimpleNamespace(returncode=0, stdout="", stderr="")) + monkeypatch.setattr(installer.subprocess, "run", run) + + hdc = installer.Installer() + hdc._ensure_server() + hdc.close() + + commands = [call.args[0] for call in run.call_args_list] + assert commands == [[hdc._hdc, "start"]] + + +def test_installer_start_failure_does_not_kill(monkeypatch) -> None: + monkeypatch.setattr(installer, "_listener_pid", lambda: None) + monkeypatch.setattr(installer, "_LISTENER_POLL_ATTEMPTS", 1) + run = Mock( + return_value=SimpleNamespace(returncode=1, stdout="", stderr="server error") + ) + monkeypatch.setattr(installer.subprocess, "run", run) + + hdc = installer.Installer() + hdc._ensure_server() + hdc.close() + + commands = [call.args[0] for call in run.call_args_list] + assert commands == [[hdc._hdc, "start"]] + + +def test_installer_skips_kill_after_server_pid_changes(monkeypatch) -> None: + # 启动成功后监听 PID 被外部接管:close 只核对不 kill + listener_pids = [None, 47024, 99999] + monkeypatch.setattr(installer, "_listener_pid", lambda: listener_pids.pop(0)) + monkeypatch.setattr(installer, "_process_start_time", lambda pid: time.time()) + run = Mock(return_value=SimpleNamespace(returncode=0, stdout="", stderr="")) + monkeypatch.setattr(installer.subprocess, "run", run) + + hdc = installer.Installer() + hdc._ensure_server() + hdc.close() + + commands = [call.args[0] for call in run.call_args_list] + assert commands == [[hdc._hdc, "start"]] + + +def test_installer_start_failure_does_not_own(monkeypatch) -> None: + # 本任务启动失败:hdc start 后无监听进程,不归属,close 不清理 + listener_pids = [None, None] + monkeypatch.setattr( + installer, + "_listener_pid", + lambda: listener_pids.pop(0) if listener_pids else None, + ) + monkeypatch.setattr(installer, "_LISTENER_POLL_ATTEMPTS", 1) + run = Mock(return_value=SimpleNamespace(returncode=0, stdout="", stderr="")) + monkeypatch.setattr(installer.subprocess, "run", run) + + hdc = installer.Installer() + hdc._ensure_server() + hdc.close() + + commands = [call.args[0] for call in run.call_args_list] + assert commands == [[hdc._hdc, "start"]] + + +def test_installer_polls_until_listener_appears(monkeypatch) -> None: + # hdc start 后监听进程稍晚出现:轮询直到确认 PID 再归属 + listener_pids = [None, None, None, 47024, 47024] + monkeypatch.setattr( + installer, + "_listener_pid", + lambda: listener_pids.pop(0) if listener_pids else None, + ) + monkeypatch.setattr(installer, "_LISTENER_POLL_INTERVAL", 0) + monkeypatch.setattr(installer, "_process_start_time", lambda pid: time.time()) + run = Mock(return_value=SimpleNamespace(returncode=0, stdout="", stderr="")) + monkeypatch.setattr(installer.subprocess, "run", run) + + hdc = installer.Installer() + hdc._ensure_server() + hdc.close() + + commands = [call.args[0] for call in run.call_args_list] + assert commands == [[hdc._hdc, "start"], [hdc._hdc, "kill"]] + + +def test_listener_pid_parses_netstat_output(monkeypatch) -> None: + output = ( + " TCP 127.0.0.1:8710 0.0.0.0:0 LISTENING 47024\n" + " TCP 127.0.0.1:8710 127.0.0.1:65140 TIME_WAIT 0\n" + " TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 1234\n" + ) + monkeypatch.setattr(installer.os, "name", "nt") + monkeypatch.setattr( + installer.subprocess, + "run", + Mock(return_value=SimpleNamespace(returncode=0, stdout=output, stderr="")), + ) + assert installer._listener_pid() == 47024 + + +def test_listener_pid_returns_none_when_port_free(monkeypatch) -> None: + output = ( + " TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 1234\n" + ) + monkeypatch.setattr(installer.os, "name", "nt") + monkeypatch.setattr( + installer.subprocess, + "run", + Mock(return_value=SimpleNamespace(returncode=0, stdout=output, stderr="")), + ) + assert installer._listener_pid() is None + + def test_generate_keypair_builds_command(monkeypatch, tmp_path) -> None: run = Mock(return_value=SimpleNamespace(returncode=0, stdout="", stderr="")) - monkeypatch.setattr(keytool_util.subprocess, "run", run) + monkeypatch.setattr(keytool_util, "run_process", run) keystore = tmp_path / "key.p12" result = keytool_util.KeytoolUtil().generate_keypair( @@ -69,8 +316,8 @@ def test_generate_keypair_removes_existing_keystore(monkeypatch, tmp_path) -> No removed = [] monkeypatch.setattr(keytool_util.os, "remove", removed.append) monkeypatch.setattr( - keytool_util.subprocess, - "run", + keytool_util, + "run_process", Mock(return_value=SimpleNamespace(returncode=0, stdout="", stderr="")), ) @@ -81,7 +328,7 @@ def test_generate_keypair_removes_existing_keystore(monkeypatch, tmp_path) -> No def test_generate_keypair_reports_failure(monkeypatch, tmp_path) -> None: result = SimpleNamespace(returncode=1, stdout="", stderr="genkey failed") - monkeypatch.setattr(keytool_util.subprocess, "run", Mock(return_value=result)) + monkeypatch.setattr(keytool_util, "run_process", Mock(return_value=result)) with pytest.raises(RuntimeError, match="genkeypair"): keytool_util.KeytoolUtil().generate_keypair(str(tmp_path / "key.p12")) @@ -97,7 +344,7 @@ def create_csr(command, **_kwargs): return SimpleNamespace(returncode=0, stdout="", stderr="") run = Mock(side_effect=create_csr) - monkeypatch.setattr(keytool_util.subprocess, "run", run) + monkeypatch.setattr(keytool_util, "run_process", run) content = keytool_util.KeytoolUtil().generate_csr( str(tmp_path / "key.p12"), "debugKey", "pw", str(csr) @@ -110,7 +357,7 @@ def create_csr(command, **_kwargs): def test_generate_csr_reports_failure(monkeypatch, tmp_path) -> None: result = SimpleNamespace(returncode=1, stdout="", stderr="certreq failed") - monkeypatch.setattr(keytool_util.subprocess, "run", Mock(return_value=result)) + monkeypatch.setattr(keytool_util, "run_process", Mock(return_value=result)) with pytest.raises(RuntimeError, match="certreq"): keytool_util.KeytoolUtil().generate_csr( @@ -120,8 +367,8 @@ def test_generate_csr_reports_failure(monkeypatch, tmp_path) -> None: def test_generate_csr_reports_read_failure(monkeypatch, tmp_path) -> None: monkeypatch.setattr( - keytool_util.subprocess, - "run", + keytool_util, + "run_process", Mock(return_value=SimpleNamespace(returncode=0, stdout="", stderr="")), ) diff --git a/tests/test_subprocess_utils.py b/tests/test_subprocess_utils.py new file mode 100644 index 0000000..7ec99a8 --- /dev/null +++ b/tests/test_subprocess_utils.py @@ -0,0 +1,182 @@ +"""外部进程窗口策略与进程树终止测试。""" + +import os +import subprocess +import sys +import threading +import time +from unittest.mock import Mock + +import pytest + +from hapsign import subprocess_utils +from hapsign.cancellation import OperationCancelled + + +def test_windows_commands_do_not_create_console_window(monkeypatch) -> None: + monkeypatch.setattr(subprocess_utils.platform, "system", lambda: "Windows") + + assert subprocess_utils.no_window_kwargs() == { + "creationflags": subprocess_utils._CREATE_NO_WINDOW + } + + +def test_other_platforms_do_not_receive_windows_flags(monkeypatch) -> None: + monkeypatch.setattr(subprocess_utils.platform, "system", lambda: "Linux") + + assert subprocess_utils.no_window_kwargs() == {} + + +def test_cancelled_command_does_not_start_process(monkeypatch) -> None: + cancel_event = threading.Event() + cancel_event.set() + popen = Mock() + monkeypatch.setattr(subprocess_utils, "no_window_kwargs", lambda: {}) + monkeypatch.setattr(subprocess_utils.subprocess, "Popen", popen) + + with pytest.raises(OperationCancelled): + subprocess_utils.run_process(["tool"], cancel_event=cancel_event) + + popen.assert_not_called() + + +def _spawning_child_code(out_file: str) -> str: + """生成子进程代码:拉起一个挂起 300s 的孙进程,并把其 PID 写入文件。 + + 孙进程 stdout/stderr 重定向到 DEVNULL,避免持有子进程的管道句柄导致 + 取消后 communicate 无法读到 EOF(若进程树终止失效,测试会因此卡住而非误报)。 + """ + grandchild = "import time; time.sleep(300)" + return ( + "import pathlib, subprocess, sys, time;" + f"g = subprocess.Popen([sys.executable, '-c', {grandchild!r}]," + " stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL);" + f"pathlib.Path({out_file!r}).write_text(str(g.pid));" + "time.sleep(300)" + ) + + +def _process_exists(pid: int) -> bool: + """判断进程是否存活。Windows 上用 OpenProcess(os.kill(pid,0) 在进程不存在 + 时会抛 SystemError),POSIX 上用 kill(pid, 0)。""" + if os.name == "nt": + import ctypes + from ctypes import wintypes + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.OpenProcess.restype = wintypes.HANDLE + kernel32.OpenProcess.argtypes = [ + wintypes.DWORD, + wintypes.BOOL, + wintypes.DWORD, + ] + kernel32.CloseHandle.argtypes = [wintypes.HANDLE] + handle = kernel32.OpenProcess(0x1000, False, pid) # QUERY_LIMITED_INFORMATION + if not handle: + return False + kernel32.CloseHandle(handle) + return True + try: + os.kill(pid, 0) + return True + except OSError: + return False + + +def _wait_pid_exit(pid: int, timeout: float = 5.0) -> bool: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if not _process_exists(pid): + return True + time.sleep(0.05) + return False + + +def test_cancel_terminates_whole_process_tree(tmp_path) -> None: + out = tmp_path / "grandchild.pid" + cancel_event = threading.Event() + + def cancel_after_grandchild_spawns() -> None: + deadline = time.monotonic() + 10 + while not out.exists() and time.monotonic() < deadline: + time.sleep(0.05) + cancel_event.set() + + threading.Thread(target=cancel_after_grandchild_spawns, daemon=True).start() + with pytest.raises(OperationCancelled): + subprocess_utils.run_process( + [sys.executable, "-c", _spawning_child_code(str(out))], + capture_output=True, + text=True, + cancel_event=cancel_event, + ) + + assert out.exists(), "子进程未启动孙进程" + grandchild_pid = int(out.read_text()) + assert _wait_pid_exit(grandchild_pid), "取消后孙进程仍存活" + + # 取消后下一次命令仍可正常运行 + result = subprocess_utils.run_process( + [sys.executable, "-c", "print('ok')"], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + + +def test_timeout_terminates_whole_process_tree(tmp_path) -> None: + out = tmp_path / "grandchild.pid" + + with pytest.raises(subprocess.TimeoutExpired): + subprocess_utils.run_process( + [sys.executable, "-c", _spawning_child_code(str(out))], + capture_output=True, + text=True, + timeout=3.0, + ) + + assert out.exists(), "子进程未在超时前启动孙进程" + grandchild_pid = int(out.read_text()) + assert _wait_pid_exit(grandchild_pid), "超时后孙进程仍存活" + + +def test_stop_process_terminates_tree_via_job(monkeypatch) -> None: + process = Mock() + process.poll.return_value = None + terminated = [] + tree_killed = [] + monkeypatch.setattr(subprocess_utils, "_terminate_job", terminated.append) + monkeypatch.setattr(subprocess_utils, "_terminate_windows_tree", tree_killed.append) + + subprocess_utils._stop_process(process, job="job") + + assert terminated == ["job"] + # 无论 job 是否命中,Windows 都会补一发 taskkill /T 兜底 + assert tree_killed == [process.pid] + process.wait.assert_called_once_with(timeout=2) + + +def test_stop_process_falls_back_to_process_group(monkeypatch) -> None: + process = Mock() + process.poll.return_value = None + killed = [] + monkeypatch.setattr(subprocess_utils, "_terminate_process_group", killed.append) + monkeypatch.setattr(subprocess_utils.os, "name", "posix") + + subprocess_utils._stop_process(process) + + assert killed == [process] + process.wait.assert_called_once_with(timeout=2) + + +def test_stop_process_falls_back_to_windows_tree_kill(monkeypatch) -> None: + process = Mock() + process.poll.return_value = None + tree_killed = [] + monkeypatch.setattr(subprocess_utils, "_terminate_windows_tree", tree_killed.append) + monkeypatch.setattr(subprocess_utils.os, "name", "nt") + + subprocess_utils._stop_process(process) + + assert tree_killed == [process.pid] + process.wait.assert_called_once_with(timeout=2) diff --git a/tests/test_token_exchange.py b/tests/test_token_exchange.py index f7cf5e3..added47 100644 --- a/tests/test_token_exchange.py +++ b/tests/test_token_exchange.py @@ -18,12 +18,28 @@ def _jwt(payload: dict) -> str: def test_exchange_temp_token(monkeypatch) -> None: response = Mock(text="jwt-token") - monkeypatch.setattr(token_exchange.requests, "get", Mock(return_value=response)) + get = Mock(return_value=response) + monkeypatch.setattr(token_exchange.requests, "get", get) result = token_exchange.TokenExchange().exchange_temp_token("temp", "CN", "1.0") assert result == "jwt-token" response.raise_for_status.assert_called_once() + assert get.call_args.kwargs["params"]["version"] == "1.0" + + +def test_exchange_temp_token_default_version(monkeypatch) -> None: + response = Mock(text="jwt-token") + get = Mock(return_value=response) + monkeypatch.setattr(token_exchange.requests, "get", get) + + token_exchange.TokenExchange().exchange_temp_token("temp") + + assert ( + get.call_args.kwargs["params"]["version"] + == token_exchange.LOGIN_PROTOCOL_VERSION + ) + assert token_exchange.LOGIN_PROTOCOL_VERSION == "5.0.5" def test_get_access_token_decodes_user(monkeypatch) -> None: diff --git a/third_party/libusb/README.md b/third_party/libusb/README.md new file mode 100644 index 0000000..242c297 --- /dev/null +++ b/third_party/libusb/README.md @@ -0,0 +1,15 @@ +# libusb 对应源代码 + +Windows 便携包中的 `libusb_shared.dll` 来自 OpenHarmony 公共 SDK,其 +`NOTICE.txt` 标识为 libusb 1.0.28、LGPL-2.1-or-later。为让接收者无需依赖仍然 +在线的第三方服务即可取得对应源代码,本目录保存 OpenHarmony 官方镜像的完整源码 +快照: + +- 上游: +- commit:`b982552012b9faf15db3bd5c714594da91df33b0` +- 归档:`openharmony-third_party_libusb-b982552.tar.gz` +- SHA-256: + `d6b7e36319a9aab07854e8bc89c458203697e7442ad66dd000c240df2a39a90c` + +该快照包含上游 libusb 1.0.28 源码压缩包、OpenHarmony 补丁、构建配置和 +LGPL-2.1 许可文本。源码仍按其自身许可证分发,不采用 HapSign 的 MIT License。 diff --git a/third_party/libusb/openharmony-third_party_libusb-b982552.tar.gz b/third_party/libusb/openharmony-third_party_libusb-b982552.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..f49dc691240c28655143ef6a03b8ef9a4046d0d5 GIT binary patch literal 475852 zcmV)0K+eA(iwFP!000001MGbXd?Zz!FoGfz4IZn47t}P)Ofu8y9Ft`3fu5u@X~`rp zoy_6Dw$0-L8nyT((8W=%E@j!HS#T)(Bd)EV9-4&6`W5pHr^IB0C*JH(7JU|cy z=KCM-y?WJ0jyZ7mTjnPw>8^Um|NZalmCb|c*`k{6@9Q`BKjhy) zpOJM-CCjO7%WT@%H!#rG)7zKXlsEIe1G&ulOl}~%0ZwC6f6m<0o6E0XpXq7)ORxFS zf5+~hTeh5n>6Kik)>SFkUM^iWy-F=zv@=yd(5tXdf2dGpf&x&?_v7y>*?#; zaKgYb+T()%{+HAL63>6fOmb{EHtQVG9L)dwTbzGyZ(nb3{rUIy_rmwbc&|WC|jNnFSwbCX*;$vYnUK+$~7Dh z9tO=Iu(styT=r@++R5#bKiGP_|gj;Y#G!MZ08+N#J!HjlkmL7OFmM1s5DO zN^Z{1<4=qBSFUD?wqJ-EIU7gMR4eeIj~`|&2j7C-bh{o7=Uc@h4r0RwXw$(O=`ozB zjKHl3aM9HB1-BI5kL}}V`Ksr@#1_4la{+ub?*&%2f}h|UdAC?}=W&l&*U8zqHGi`> z#cBB7%(!zFZHp1WaVvP>v=ls%a&Xe(E5BgE9x|2yA53mLIGU;L%)?~>RXG(K4x{XP zG;jTmB{ofuCXCd?$n>80RKiH6jLE5q-O1s^un~!;;O|J(*pr+dotT+6;EAdD`1C$w zV#J7#?=#L#jt@tT#NNrNL@H%WOyOwBU6W(U1bmzv9~zq(PLA(1cEGFS6Vt|6a#wO1 z#+{y^ImKwnL<+~-m6#eDg+Jpvl4Hr~eNh~1Bso2fBaci>8F6DWJ~f>jni-2v8Iv

hY9JP<-Y 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

nd-y`V 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

My4 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 `IXXYwg*ZoD9f2|`Rz{E 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⁣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

;;i7sMM) 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%0A%+?Y`)J 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

)A(ag`%FdwIvFn>HrBlZ7PMwT%e&iZLCq( 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 Oj*wEk7fuxPL!4cDGqzL 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

k2t=X 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 u^Z0rEJboTOkDtfS Date: Fri, 28 Aug 2026 15:14:09 +0800 Subject: [PATCH 2/2] test: make CI cases platform independent --- tests/test_build_portable.py | 5 +++++ tests/test_subprocess_utils.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/tests/test_build_portable.py b/tests/test_build_portable.py index debdfdc..67c2f7a 100644 --- a/tests/test_build_portable.py +++ b/tests/test_build_portable.py @@ -101,6 +101,11 @@ def test_smoke_artifact_cleanup_preserves_application_files(tmp_path) -> None: def test_python_dependency_licenses_are_copied(tmp_path, monkeypatch) -> None: portable = tmp_path / "HapSign" portable.mkdir() + python_root = tmp_path / "python" + python_root.mkdir() + (python_root / "LICENSE.txt").write_text("Python license\n", encoding="utf-8") + # 不依赖各平台 Python 发行版是否把 LICENSE.txt 放在 base_prefix 根目录。 + monkeypatch.setattr(build_portable.sys, "base_prefix", str(python_root)) monkeypatch.setattr( build_portable, "PYTHON_RUNTIME_DISTRIBUTIONS", diff --git a/tests/test_subprocess_utils.py b/tests/test_subprocess_utils.py index 7ec99a8..71c1385 100644 --- a/tests/test_subprocess_utils.py +++ b/tests/test_subprocess_utils.py @@ -145,6 +145,8 @@ def test_stop_process_terminates_tree_via_job(monkeypatch) -> None: process.poll.return_value = None terminated = [] tree_killed = [] + # 该用例覆盖 Windows 的 Job Object + taskkill 路径,不依赖 CI 主机系统。 + monkeypatch.setattr(subprocess_utils.os, "name", "nt") monkeypatch.setattr(subprocess_utils, "_terminate_job", terminated.append) monkeypatch.setattr(subprocess_utils, "_terminate_windows_tree", tree_killed.append)