茶器艺科智造HarmonyOS应用实战-07-build-profile把证书路径和口令写进仓库,换台电脑为何立刻失效:拆分签名引用与本机材料
同一份 HarmonyOS 工程在原开发机上可以选择默认签名,交给另一位同事后却立刻提示证书文件不存在,最容易被误判成 DevEco Studio 版本不同。真正需要先看的,是项目根目录的 build-profile.json5:如果这里同时保存了绝对文件路径、证书库口令、私钥口令、别名、Profile 与证书路径,工程就把“可复用构建配置”和“只属于某台机器的材料”焊在了一起。Git 能复制文本,却不会复制盘符、凭据、账号授权和设备关系。
本文以当前源码快照 master@f671fcd 为依据。源码事实只描述字段与引用;所有秘密值统一写成 [REDACTED_SECRET],本机路径改成示意路径。文中不会给出真实口令,也不会公布能定位当前证书材料的真实目录。

本文解决四个具体问题:
- 从根配置识别可移植规则与本机材料;
- 解释换机失败发生在文件、解锁、身份还是用途层;
- 用安全模板、本机注入和流水线秘密库划清提交边界;
- 用不打印秘密的预检与验证矩阵留下可审计证据。


一、当前源码把四类本机条件集中在根配置
当前源码已存在的事实很明确:根 build-profile.json5 的 app.signingConfigs 数组包含名为 default 的 HarmonyOS 签名项;material 中出现 storeFile、storePassword、keyAlias、keyPassword、signAlg、profile、certpath。default 产品又通过 signingConfig 引用这个签名项。三条文件字段使用绝对路径,两个 password 字段在文件中有具体值。
下面只保留字段形态,不保留当前值:
{
"app": {
"signingConfigs": [{
"name": "default",
"type": "HarmonyOS",
"material": {
"storeFile": "D:/[LOCAL_CERT_DIR]/chaqi-signing.p12",
"storePassword": "[REDACTED_SECRET]",
"keyAlias": "local-signing-alias",
"keyPassword": "[REDACTED_SECRET]",
"signAlg": "SHA256withECDSA",
"profile": "D:/[LOCAL_CERT_DIR]/chaqi-debug-profile.p7b",
"certpath": "D:/[LOCAL_CERT_DIR]/chaqi-public-cert.cer"
}
}],
"products": [{
"name": "default",
"signingConfig": "default",
"targetSdkVersion": "6.0.2(22)",
"compatibleSdkVersion": "6.0.2(22)",
"runtimeOS": "HarmonyOS"
}]
}
}
这段结构能证明“配置引用已经存在”,不能证明另一台机器拥有相同材料,更不能证明这些材料适合 release。产品名、SDK 与签名项逻辑名属于工程规则;证书库、Profile、证书和口令则受机器、账号、设备、渠道与用途约束。
| signingConfig | default 产品引用 default | 名字仍可解析 | 可提交引用 |
| storeFile | 本机绝对路径 | 文件不存在或目录不同 | 本机/流水线材料 |
| storePassword | 文件中有具体值 | 即使复制证书库也不应扩散 | 秘密库 |
| keyAlias | 指向证书库条目 | 新证书库找不到同名项 | 环境元数据 |
| keyPassword | 文件中有具体值 | 私钥无法解锁 | 秘密库 |
| profile | 本机绝对路径 | 设备或应用身份可能不匹配 | 受控制品 |
| certpath | 本机绝对路径 | 文件缺失或证书链不一致 | 受控制品 |
二、换机失效是一条四层故障链
排查时不要只盯着第一条“file not found”。第一层是 定位失败:盘符与目录不一致,绝对路径只在原机器成立。第二层是 材料失败:复制 p12、p7b、cer 后,Profile 仍可能与新设备、应用标识或证书关系不符。第三层是 解锁失败:两个口令必须分别对应正确的证书库和私钥。第四层是 用途失败:能签本地 Debug 包的材料,不自动成为商店发布材料。
建议先定义一个不含秘密的预检回执:
type SigningPreflightCode =
| 'CONFIG_REFERENCE_MISSING'
| 'STORE_FILE_MISSING'
| 'PROFILE_FILE_MISSING'
| 'CERT_FILE_MISSING'
| 'SECRET_NOT_INJECTED'
| 'READY_FOR_BUILD';
interface SigningPreflightReceipt {
code: SigningPreflightCode;
signingConfigName: string;
checkedFileKinds: string[];
secretsReady: boolean;
// message 只能写问题类别,不能拼入口令或真实路径
message: string;
}
构建日志需要知道哪类输入没准备好,不需要知道输入值。发布台账需要知道使用了哪个逻辑签名策略,不应保存可直接解锁私钥的材料。这两个边界一旦混在一起,错误处理本身就可能成为第二条泄露通道。
三、仓库保存规则,本机保存可直接使用的材料
可提交内容的判断标准,是团队成员能否在不接触个人秘密时理解工程。modules、products、buildModeSet、SDK、签名算法要求、占位字段、准备脚本和操作说明都属于可复用规则。真实 p12、口令、账号约束的 Profile、机器绝对路径不属于这一层。
| modules、products、buildModeSet | 是 | 决定工程结构和模式 |
| 签名项逻辑名称 | 是 | 让产品引用稳定 |
| 安全模板与占位符 | 是 | 让缺失项可读、可阻断 |
| 真实 p12 与两个口令 | 否 | 具有直接签名能力 |
| 真实 p7b、cer | 默认否 | 与账号、设备、证书链及流程有关 |
| 绝对本机目录 | 否 | 不可移植,还暴露环境结构 |
| 预检脚本 | 是 | 在进入构建前阻断错误 |
| 制品摘要与 commit | 是或进入受控台账 | 可追溯且不含秘密 |
本文建议把本机生成结果和材料目录加入忽略清单。它不是当前源码已经具备的方案,落地前应先确认 DevEco Studio 与流水线究竟读取哪个配置文件。
#本文建议:本机渲染结果与材料目录
build-profile.local.json5
.local-signing/
*.p12
*.p7b
#cer 是否进入仓库取决于组织证书策略。
#若不提交,应由受控制品库分发。
忽略规则只阻止新的未跟踪文件加入,不能清除历史提交。若秘密曾进入 Git,简单删除当前行不足以完成处置;需要由账号和证书负责人判断吊销、轮换、历史治理与协作者重新同步范围。
四、安全模板只表达字段契约
一种可执行的迁移路径,是保留 build-profile.safe.json5 作为规则源,用明显占位符表达环境输入;准备脚本再生成只属于当前作业的工作副本。团队必须明确实际构建如何消费工作副本,不能假定 Hvigor 会自动读取任意文件名。
{
"app": {
"signingConfigs": [{
"name": "local",
"type": "HarmonyOS",
"material": {
"storeFile": "@@STORE_FILE@@",
"storePassword": "@@STORE_PASSWORD@@",
"keyAlias": "@@KEY_ALIAS@@",
"keyPassword": "@@KEY_PASSWORD@@",
"signAlg": "SHA256withECDSA",
"profile": "@@PROFILE_FILE@@",
"certpath": "@@CERT_FILE@@"
}
}],
"products": [{
"name": "default",
"signingConfig": "local",
"targetSdkVersion": "6.0.2(22)",
"compatibleSdkVersion": "6.0.2(22)",
"runtimeOS": "HarmonyOS"
}]
}
}
占位符不应换成空字符串。空值会把“输入缺失”拖到更晚的签名阶段,使错误远离根因。模板还要完整保留当前 modules 和 buildModeSet;示例为突出签名边界而省略的部分,不能在真实迁移时顺手删除。
五、输入脚本在写文件前失败,而且不回显值
本机可以从进程环境、Windows 凭据设施或团队允许的秘密管理方案读取值。无论来源是什么,脚本都应先核对键是否齐全,再核对三类文件是否存在,最后才渲染工作副本。
$required = @{
STORE_FILE = $env:CHAQI_STORE_FILE
STORE_PASSWORD = $env:CHAQI_STORE_PASSWORD
KEY_ALIAS = $env:CHAQI_KEY_ALIAS
KEY_PASSWORD = $env:CHAQI_KEY_PASSWORD
PROFILE_FILE = $env:CHAQI_PROFILE_FILE
CERT_FILE = $env:CHAQI_CERT_FILE
}
$missing = @($required.Keys | Where-Object {
[string]::IsNullOrWhiteSpace($required[$_])
})
if ($missing.Count -gt 0) {
throw "Signing inputs are missing: $($missing -join ', ')"
}
foreach ($fileKey in 'STORE_FILE', 'PROFILE_FILE', 'CERT_FILE') {
if (-not (Test-Path –LiteralPath $required[$fileKey] –PathType Leaf)) {
throw "Signing file is unavailable: $fileKey"
}
}
Write-Host 'Signing inputs are present; values are not printed.'
这段示例只证明输入存在,不证明口令正确、证书有效或用途匹配。真实渲染还应给临时文件设置合适权限,异常时删除半成品,并在作业结束后清理。绝不能为排错把 $required 整个输出。
六、路径只在边界处规范化,仓库不猜盘符
路径注入要处理三个细节:通过 GetFullPath 固定语义;只允许落在团队约定的本机材料根或作业临时目录;用 LiteralPath 读取,避免方括号等字符被解释成通配符。
$repoRoot = [IO.Path]::GetFullPath((Get-Location).Path)
$materialRoot = [IO.Path]::GetFullPath($env:CHAQI_SIGNING_HOME)
$storeFile = [IO.Path]::GetFullPath($env:CHAQI_STORE_FILE)
if (-not $storeFile.StartsWith(
$materialRoot,
[StringComparison]::OrdinalIgnoreCase
)) {
throw 'STORE_FILE must stay inside CHAQI_SIGNING_HOME.'
}
if ($storeFile.StartsWith(
$repoRoot,
[StringComparison]::OrdinalIgnoreCase
)) {
throw 'Signing material must not be stored inside the repository.'
}
目标不是让所有同事使用相同盘符,而是让 CHAQI_SIGNING_HOME 成为每台机器自己的契约。仓库认识环境键和材料类别,不认识某位开发者的磁盘布局。路径可以变化,产品对签名策略的逻辑引用保持稳定。
七、提交前同时扫描具体值、绝对路径与材料文件
只搜索 password 不够,绝对路径同样会破坏可移植性。建议的提交前扫描至少覆盖:敏感字段出现非占位值;签名文件字段含 Windows 盘符;p12、p7b 被加入暂存区;脚本日志打印完整 material 对象。
$profile = Get-Content –LiteralPath '.\\build-profile.safe.json5' –Raw
$problems = [System.Collections.Generic.List[string]]::new()
if ($profile -match '"(storePassword|keyPassword)"\\s*:\\s*"(?!@@)[^"]+"') {
$problems.Add('A password field contains a concrete value.')
}
if ($profile -match '"(storeFile|profile|certpath)"\\s*:\\s*"[A-Za-z]:[/\\\\]') {
$problems.Add('A signing path is machine absolute.')
}
$staged = git diff —cached —name-only
if ($staged -match '\\.(p12|p7b)$') {
$problems.Add('A signing material file is staged.')
}
if ($problems.Count -gt 0) {
$problems | ForEach-Object { Write-Error $_ }
exit 1
}
Write-Host 'Repository signing boundary is clean.'
扫描输出只列问题类别。不要为了方便而打印匹配行,因为匹配行可能正是秘密。还要记住:看起来像长串编码的 password 字段,只要能被当前工具直接使用,就仍应按秘密处理;不能以“肉眼不可读”为公开理由。
八、流水线材料必须有完整生命周期
CI 与开发机的差别只是材料来源,安全边界相同。流水线从秘密管理服务读取口令,从受控制品库取证书文件,写入作业专属临时目录,构建结束后清理。不要把 Base64 证书写回仓库变量文件,也不要把渲染后的完整 build-profile 当作普通制品上传。
steps:
– name: prepare–signing
run: |
create-job-private-directory
fetch-signing-files-with-access-control
inject-signing-secrets-with-masking
render-local-build-profile
run-signing-preflight
– name: build–release–candidate
run: |
invoke-approved-hvigor-command
– name: collect–safe–evidence
run: |
record-commit-and-artifact-digest
record-signing-config-logical-name
– name: cleanup
if: always()
run: |
remove-job-private-directory
remove-rendered-local-profile
这是平台无关的流程骨架,并未声称这些伪命令能直接运行。不同 CI 的秘密遮罩、临时目录和失败回调语义不同,需要在自己的平台核对。安全证据包含 commit、模式、SDK、逻辑签名名、制品 SHA-256 和时间;不包含口令、私钥或完整配置。
九、验证矩阵同时守住可移植性与保密性
迁移分支至少要覆盖以下场景。每项只保存通过状态或阻断原因,不保存输入值。
| 原开发机输入齐全 | 本机材料可读 | 预检通过,进入构建命令 | commit、逻辑签名名、退出码 |
| 新机器没有材料 | 只克隆仓库 | 构建前明确阻断 | 缺失键名 |
| 新机器路径写错 | 环境键指向不存在文件 | 报文件类别缺失 | STORE/PROFILE/CERT 类别 |
| 口令未注入 | 文件存在,secret 为空 | 渲染前阻断 | SECRET_NOT_INJECTED |
| 模板出现绝对路径 | 人为写入盘符 | 提交前扫描失败 | 规则编号与文件名 |
| 暂存 p12/p7b | 材料进入暂存区 | 提交前扫描失败 | 扩展名与暂存项 |
| Debug 材料用于 release | 用途选择错误 | 发布门禁阻断 | 用途策略编号 |
| 作业异常退出 | 构建中断 | cleanup 仍执行 | 临时目录已移除 |
| 制品归档 | 构建完成 | 只归档安全摘要 | SHA-256、模式、commit |
若没有第二台干净机器,可先用新的 Windows 账户或隔离目录模拟“没有原盘符与缓存”,但这仍不等价于真实协作者环境。可移植性最终要由独立机器实际跑通准备链路来确认。
十、排查表与证据边界
| storeFile 不存在 | 环境键与文件存在性 | 路径来自原机器 | 设置本机材料根,不改安全模板 |
| 证书库存在但不能签 | 秘密来源是否对应 | 两个口令或证书库不匹配 | 由材料负责人重新注入,日志不打印 |
| 找不到 keyAlias | 受控别名元数据 | 别名与证书库不一致 | 核对正确材料,避免公开遍历结果 |
| Profile 不接受设备 | Profile 用途与设备关系 | 复制他人的 Debug Profile | 为当前账号与设备准备 |
| 模板仍被扫描阻断 | 占位格式、绝对路径 | 遗留具体值 | 逐字段替换并复核 Git diff |
| 新机器仍需改跟踪文件 | 工作副本未接入构建入口 | 只拆文件,没统一命令 | 固定 prepare 与 build 入口 |
| CI 日志出现秘密 | 命令回显与异常对象 | 未遮罩或输出完整配置 | 停止扩散并轮换相关凭据 |
| 删除当前值仍有风险 | Git 历史与旧制品 | 只清理工作树 | 走吊销、轮换和历史治理 |
当前源码已存在: master@f671fcd 的根 build-profile.json5 包含 default 签名项;material 同时出现三条绝对文件路径、两个具体口令字段、别名与算法;default 产品引用该签名项。根配置还声明 API 22、HarmonyOS runtime、debug/release 模式以及 entry、libraryhsp、libraryhar 三个模块。
本文建议: 安全模板、本机或流水线注入、提交前扫描、临时材料目录与发布证据台账。示例没有写回项目。
尚未证明: 本文未修改项目源码,未运行 hvigorw,未验证任何口令,未生成 HAP/APP,未在真机安装,也未提交 AppGallery。静态源码核对只能证明风险形态,不能证明某套签名材料当前有效。真正改造时,应先由有权限的负责人处理凭据,再在独立机器按矩阵复核。
签名配置可移植的标志,是仓库在没有秘密时仍能完整表达规则,每个环境只在受控边界内补齐自己的材料,并且失败信息不会把秘密带进日志。




