【鸿蒙PC】三种证书:自签名 / 调试 / 二进制,到底差在哪【鸿蒙心迹】
摘要
上一篇《二进制签名实战:三类签名、沙箱权限矩阵,以及一张调试证书的坑》是踩坑视角:怎么签、报什么错、日志去哪看。这一篇只回答一个问题:这三种签名,本质上差在哪?
官方文档把它们并列成三张表(签名类型对比、沙箱权限矩阵、调试证书签名流程),照着做没问题,但表格是结果,不是原因。它告诉你"自签名不能声明权限",不告诉你为什么不能。
把 OpenHarmony 的 security_code_signature、developtools_hapsigner 和内核 kernel_linux_common_modules/code_sign 三个仓对着读完:
一句话结论:三种签名共用同一个 .codesign 段、同一个 fs-verity 描述符、同一个 0x3 版本号,区别只在 signature 字段——填描述符自己的摘要就是自签名,填调试证书链签出的 PKCS7 就是调试证书签名,填二进制证书链签出的 PKCS7 就是二进制证书签名。权限、分发范围、设备要求上的所有差异,都是从这一个字段推出来的。
阅读约定:文中标注 【源码】 的结论读自 OpenHarmony 开源仓,标注 【官方】 的引自华为文档与工单回复,其余是我的解释。
实测环境与上篇一致:HUAWEI MateBook Pro(OpenHarmony-7.0.0.105/ API 26 / 2in1)。
如果你还没确定自己该用哪种签名,先看这一篇:《二进制签名入门:证书从哪来、能要什么权限、该用哪一个》。
本文是它的"为什么"。
签名产物:一个三段式的 ELF
签名工具往 ELF 里加的不是一段而是最多三段。段名是源码里的常量 【源码】(developtools_hapsigner/binary_sign_tool/common/include/constant.h):
const std::string CODE_SIGN_SEC_NAME = ".codesign"; // 代码签名块
const std::string PROFILE_SEC_NAME = ".profile"; // Provision Profile(p7b)
const std::string PERMISSION_SEC_NAME = ".permission"; // module.json
| 段 | 内容 | 由谁写入 | 对应命令行参数 |
|---|---|---|---|
.codesign | fs-verity 描述符 + 签名 | 签名工具(总是会写) | 无(核心动作) |
.profile | Provision Profile(p7b) | 签名工具 | -profileFile |
.permission | requestPermissions / debug-info.device-ids | 签名工具 | -moduleFile |
display-sign 打印的就是这里的权限段与证书链。三种签名的差别,全在 .codesign 里。
.codesign 段的布局
内核侧的结构注释写得很清楚 【源码】(kernel_linux_common_modules/code_sign/code_sign_elf.h):
+-------------------------------------------------+
| |type (4 bytes)| code signing|
| |length (4 bytes)| block |
| |offset (4 bytes)| header |
| +---------------------------+-------------|
| |type (4 bytes)| profile |
| |length (4 bytes)| block |
| |offset (4 bytes)| header |
| +---------------------------+-------------|
| SIGN |type (4 bytes)| merkle |
| |length (4 bytes)| tree |
| DATA |merkle tree data (N bytes)| block |
| +---------------------------+-------------|
| |type (1 byte )| |
| |version (1 byte )| |
| |hash alg (1 byte )| |
| |log2blocksize (1 byte )| |
| |salt size (1 byte )| |
| |signature size (4 bytes)| code sign |
| |data size (8 bytes)| block |
| |root hash (64 bytes)| |
| |salt (32 bytes)| |
| |flags (4 bytes)| |
| |reserved (4 bytes)| |
| |tree offset (8 bytes)| |
| |reserved (127 bytes)| |
| |cs version (1 byte )| |
| |signature (N bytes)| ← 分水岭 |
|-------+---------------------------+-------------|
| | magic string (16 bytes)| |
| SIGN | version (4 bytes) | |
| | sign data size (4 bytes) | |
| HEAD | sign block num (4 bytes) | |
| | padding (4 bytes) | |
+-------+-----------------------------------------+
设备侧的解析结构 ElfSignInfo 与之一一对应 【源码】(security_code_signature/utils/include/elf_code_sign_block.h):
| 字段 | 大小 | 含义 | 校验约束 |
|---|---|---|---|
type | 4 | 数据类型 | 必须 = 0x1(fs-verity 描述符) |
length | 4 | 本块长度 | 落在合法区间 |
version | 1 | fs-verity 描述符版本 | 必须 = 1 |
hashAlgorithm | 1 | 哈希算法 | 1=SHA-256,2=SHA-512 |
logBlockSize | 1 | log₂(块大小) | 必须 = 12(4 KiB) |
saltSize | 1 | 盐长度 | ≤ 32 |
signSize | 4 | 签名字段长度 | ≠ 0 |
dataSize | 8 | 被签数据字节数 | — |
rootHash | 64 | fs-verity Merkle 树根哈希 | — |
salt | 32 | 盐 | — |
flags | 4 | 见下文 flags 一节 | — |
csVersion | 1 | ELF 代码签名块版本 | 必须 = 0x3 |
signature | N | 签名字段 | — |
两条对齐约束,解释了两个现象
ElfCodeSignBlock::ParseSignBlock 里有两条硬校验 【源码】:
if (secOff % PAGE_SIZE != 0) { /* codesign section offset is not aligned */ }
if (csBlockSize == 0 || csBlockSize % PAGE_SIZE != 0) { /* SECTION_SIZE */ }
if (fileSize < PAGE_SIZE) { /* file size is too small */ }
段的偏移和长度都必须是 4096 的整数倍,文件本身也不能小于 4 KiB。这两条约束顺手解释了两件事:
- 为什么
llvm-objcopy --remove-section .codesign之后可以重新签——段是自包含、按页对齐的,摘掉不影响别的段; - 为什么改完文件必须重签——
rootHash是整份文件的 Merkle 树根,动一个字节就全对不上。
顺带一条社区经验 【源码·他人实证】:预构建的
.so从构建机复制到另一台设备后可能不被接受,即便它带着看起来完好的.codesign;标准解是llvm-objcopy --remove-section .codesign→ 重新binary-sign-tool sign -selfSign 1→ 恢复权限位。参见 build_in_harmonyos issue #3349。
三种签名,是同一个字段的三种填法
签名工具的 CodeSigning::GetElfCodeSignBlock 一共就干四件事 【源码】(developtools_hapsigner/binary_sign_tool/codesigning/sign/src/code_signing.cpp):
const int FLAG_SELF_SIGN = 1 << 4;
int flags = 0;
if (m_selfSign) { flags = flags | FLAG_SELF_SIGN; }
// 1) 算整份文件的 fs-verity Merkle 树,拿到 rootHash
fsVerityGenerator->GenerateFsVerityDigest(inputstream, fileSize, flags);
// 2) 填 signature 字段 —— 唯一的岔路口
if (!m_selfSign) {
GetOwnerIdFromCert(ownerID); // 取叶子证书的 OU
std::vector<int8_t> fsVerityDigest = fsVerityGenerator->GetFsVerityDigest();
GenerateSignature(fsVerityDigest, ownerID, signature); // 证书链签出的 PKCS7
} else {
signature = fsVerityGenerator->GetDescriptorDigest(); // ← 描述符自己的摘要
}
// 3) 组描述符
fsdbuilder.SetFileSize(fileSize).SetHashAlgorithm(...).SetLog2BlockSize(12)
.SetSalt(...).SetSignSize(signature.size()).SetRawRootHash(...)
.SetFlags(flags).SetCsVersion(0x3);
// 4) 描述符 + 签名写进 .codesign
第 2 步里的分支是唯一的岔路口。自签名填进去的是 GetDescriptorDigest()——描述符自己算自己得到的摘要,不是任何第三方签出来的东西;证书分支填进去的是用证书链对这个描述符做的 PKCS7 签名,并且把叶子证书的 OU(organizationalUnitName) 作为 ownerid 一并塞进了 PKCS7。
"被签对象"到底是什么,内核头文件里定义得很精确 【源码】(kernel_linux_5.10/include/linux/code_sign.h):
The file measurement is the hash of this structure excluding the signature and with the
sig_sizefield set to 0, while version is replaced by code sign version.
也就是说,两条分支签的是同一个东西——把 sig_size 归零后算出的描述符摘要。区别只在于拿它怎么办:
| 自签名 | 证书签名 | |
|---|---|---|
| 对这个摘要做什么 | 直接把它当签名字段填回去 | 请证书链对同一个摘要做一次 PKCS7 |
| 性质 | 自证(self-referential) | 他证(third-party attested) |
签名到这里就分成两种:自证和他证。自证只能回答"文件还是原来那份吗",他证才能回答"这是谁签的"。
在签名工具眼里,两种证书签名是同一个动作
源码里 m_selfSign 是个 bool,没有第三个分支。也就是说:
- 调试证书签名和二进制证书签名在签名工具眼里是同一个动作——都是"用某张证书链 + 私钥对描述符做 PKCS7";
- 区别不在工具,在你递进去的是哪张证书,以及内核对这张证书所在的信任表怎么看。
所以你没法通过"换一个命令行参数"把调试签名变成正式签名——两者是同一条代码路径的两种输入。
flags 的另外两个位由设备侧设置
flags 字段里的位是分开的 【源码】:
| 位 | 名字 | 谁设置 | 含义 |
|---|---|---|---|
1 << 4 | FLAG_SELF_SIGN | 签名工具(-selfSign 1) | 这是自签名产物 |
1 << 5 | BINARY_CERT_FLAGS | 设备侧(IS_LOCAL_HSP_PLUGIN) | 原生库来自二进制证书签的 HSP |
1 << 6 | SIDE_LOADING_FLAGS | 设备侧(IS_SIDE_LOADING_APP) | 原生库来自侧载(调试安装)的应用 |
注意后两位在 security_code_signature/utils/include/code_sign_block.h 里,是设备侧解析 HAP 内嵌原生库时用的 【源码】:
if (flag & IS_LOCAL_HSP_PLUGIN) { arg.flags |= BINARY_CERT_FLAGS; }
if (flag & IS_SIDE_LOADING_APP) { arg.flags |= SIDE_LOADING_FLAGS; }
所以对独立 ELF 二进制来说,签名工具只会动 bit 4;bit 5/6 是应用内嵌原生库场景下的身份标记。这个区别在排查时有用:手搓的独立二进制不该看到 bit 5/6 被置位——那说明产物是在被包进 HSP/应用之后(或由设备侧)重新打过标的。
运行时谁在拦:exec → fs-verity → 信任表
签完只是把数据写进去了,能不能跑是另一回事。执行一个二进制的判定链大致是:
exec()
└─ code_protect(用户态服务,日志 TAG:CODE_SIGN / BSS)
├─ 调 EnforceCodeSignForFile → 解析 .codesign 段
│ └─ 解析失败:CS_CODE_SIGN_NOT_EXISTS / CS_ERR_SECTION_OFFSET / …
├─ 调试证书:先查 .permission 的 device-ids(VerityHdcStatusAndDeviceId)
└─ fsverity_ioctl_enable_code_sign(fd, arg)
└─ 内核 fs_security_verity
├─ 按 dataSize/rootHash/salt 算并比对 Merkle 树 ← 完整性
└─ code_sign_verify_certchain(PKCS7) ← 来源
└─ hmcrypt_rnd: PKCS7_signatureVerify
内核里的判定分支
code_sign_verify_certchain 的分支不多,但每一个都对应一条官方规则 【源码】(kernel_linux_common_modules/code_sign/verify_cert_chain.c):
pkcs7 = pkcs7_parse_message(raw_pkcs7, pkcs7_len);
if (!pkcs7->signed_infos) goto untrusted;
/* 没有证书链 —— 走"本机代码"分支 */
if (!pkcs7->certs) { *ret = MAY_LOCAL_CODE; goto exit; }
is_dev_mode = (get_developer_mode_state() == STATE_ON);
for (sinfo = pkcs7->signed_infos; sinfo; sinfo = sinfo->next) {
signer = sinfo->signer;
source = find_matched_source(signer, false); /* 先查发行信任表 */
if (!source) {
if (is_dev_mode) {
source = find_matched_source(signer, true); /* 再查调试信任表 */
if (!source) goto untrusted;
} else {
goto untrusted;
}
}
/* 校验链深度不超过 source->max_path_depth */
}
三条结论里,前两行读代码就能定,第三行对应官方文档那句"运行时要求设备处于开发者模式":
| 代码 | 含义 |
|---|---|
if (!pkcs7->certs) → MAY_LOCAL_CODE | PKCS7 里不含证书链时走这里:只能靠内核 keyring 里已有的证书兜底,否则按"本机代码"处理 |
find_matched_source(signer, false) 先匹配 | 发行信任表(二进制证书)始终参与匹配 |
if (is_dev_mode) → find_matched_source(signer, true) | "调试证书签名要求设备处于开发者模式"在代码里就是一个 if |
官方文档说"运行时要求设备处于开发者模式",读起来像一条策略约定;源码里它的实际形态是调试信任表是否参与匹配的开关——开发者模式一关,调试证书签的二进制就直接落到 untrusted。
一条边界说明。 上面这些
path_type是一组枚举:发行侧RELEASE_CODE_START(0x0) … RELEASE_CODE_END(0x5),
调试侧DEBUG_CODE_START(0x100) … DEBUG_CODE_END(0x106),另有MAY_LOCAL_CODE = 0x201。
在code_sign_elf.c里还有一层更严的validate_elf_source:它要求code_sign_verify_certchain
返回的类型落在调试区间内(且不等于DEBUG_DEVELOPER_CODE),否则直接-EKEYREJECTED。
这一条与"adhoc 和二进制证书签的 ELF 在我们的设备上都能跑"并不完全吻合——可能是开源仓master
与设备实际发行版本之间的差异,也可能实际走的是另一条校验路径。这里只陈述观察,不下结论。
验完之后,文件被打上 ownerid
过了信任表校验,内核会给文件打上 ownerid 【源码】(code_sign_ext.h / verify_cert_chain.c):
| ownerid | 标签常量 | 判定条件 |
|---|---|---|
FILE_OWNERID_SYSTEM | SYSTEM_LIB_ID | 系统库 |
FILE_OWNERID_DEBUG | DEBUG_LIB_ID | path_type 落在 DEBUG 区间,或 ownerid == DEBUG_LIB_ID |
FILE_OWNERID_SHARED | SHARED_LIB_ID | ownerid == SHARED_LIB_ID |
FILE_OWNERID_COMPAT | COMPAT_LIB_ID | ownerid 为空 |
FILE_OWNERID_APP | — | 其余情况,携带证书里的 ownerid |
而 ownerid 的原始来源,就是前面 GetElfCodeSignBlock 里那句 GetOwnerIdFromCert 取到的叶子证书 OU 【源码】:
VerifyCertOpensslUtils::GetTextFromX509Name(subject, NID_organizationalUnitName, ownerID);
整条权限体系都挂在"这个文件是谁签的"上,而这个"谁"最终落在证书的一个字符串字段里。
三种签名正面对照
现在可以正面回答了。下表每一行是一个能观察到的差异,每一列是一种签名:
| 维度 | 自签名(adhoc) | 调试证书签名 | 二进制证书签名 |
|---|---|---|---|
signature 字段内容 | 描述符自身摘要 | 调试证书链签出的 PKCS7 | 二进制证书链签出的 PKCS7 |
| 需要证书 / 密钥库 | 否 | 是(-appCertFile + -keystoreFile) | 是(同上) |
flags bit 4 | 1 | 0 | 0 |
| 内核对它的处理 | 只有完整性校验,没有可验证的签发主体 | 调试信任表(仅开发者模式) | 发行信任表(始终参与) |
ownerid 归类 | 无身份 | FILE_OWNERID_DEBUG | FILE_OWNERID_APP |
| 设备绑定 | 无 | .permission 的 debug-info.device-ids(UDID) | 无 |
| 官方要求的设备状态 | 无 | 开发者模式 | 无 |
| 能否声明权限 | ❌ 不生效 | ✅(共享库除外) | ✅ |
| 分发范围 | 无限制(但只对本机有意义) | 仅限 device-ids 列出的设备 | 无限制 |
| 获取门槛 | 零成本 | 个人开发者可自行申请 | 需华为认证,个人开发者基本拿不到 |
| 典型场景 | 本机编译自用 | 开发者内部调试 | 面向用户大范围分发 |
这张表从上往下读,除了第一行,其余每一行都能从第一行推出来。
为什么 adhoc 能跑,却不能声明权限
因为完整性和来源是两件事,而 adhoc 只解决了前一件。
signature 字段填的是描述符自身的摘要,它能证明的只有一件事:"这份文件从签名那一刻起,一个字节都没被改过。" 它证明不了"这是谁签的"——因为这个摘要不是任何主体签出来的,没有一个可以为之背书的身份。
而权限授予的对象恰恰是身份。回头看签名工具那段代码就很直白:自签名分支里根本没有证书参与,产出的也不是 PKCS7,只是一个自证的摘要。既然"签发者"这个信息从一开始就不存在,运行时自然也换不出任何身份——你不是"某个被信任的签发者",只是"一个自我校验通过的 ELF 文件"。
⚠️ 顺带纠一个常见误解:自签名不绑定设备。
.codesign段里只有文件自身的大小、
merkle 根哈希和摘要,没有任何设备标识,所以产物可以拷到别的鸿蒙 PC 上直接运行。
绑定设备的是调试证书——它走device-ids里的 UDID 白名单(见下一节)。
也就是说 adhoc 的"弱"在于没有身份,而不是"只在这台机器上有效"。
所以 【官方】"自签名的二进制(adhoc)权限策略段运行过程不会生效"这句话要这么理解:不是策略段没写进去,而是没有一个主体来认领它。 你就算把 -moduleFile 传进去、.permission 段也确实被写进了 ELF,运行时依然不会有任何权限上身。
为什么调试证书要求开发者模式 + 绑定 UDID
这两条是同一个设计意图的两道锁:把"开发期凭证"的影响半径压到开发者自己的机器上。
- UDID 锁:
.permission里必须写明debug-info.device-ids,运行前由VerityHdcStatusAndDeviceId校验。缺了就是[FAILED] verity deviceId for debug signer: udid not in module.json。这一道锁管的是哪些设备。 - 开发者模式锁:前面
code_sign_verify_certchain里那行if (is_dev_mode)。这一道锁管的是什么状态——同一台列在白名单里的设备,关掉开发者模式同样跑不了。
(另一个源码观察:内核 code_sign_elf.c 的 elf_file_enable_fs_verity 第一步就检查开发者模式,!= STATE_ON 直接返回 -EINVAL——这条路径与开发者模式是硬绑定的,不是建议。)
这是典型的能力与约束对偶:既然调试证书被授予了"声明权限"的能力,就必然要限制它能传播到哪里,否则调试证书就等价于正式证书了。二进制证书没有这两道锁,代价是它几乎拿不到。
为什么调试证书签的共享库不能声明权限
这条是 【官方】 明写的,但文档只给了结论、没给理由。下面这段是我的解释,不是源码事实:
权限的主体是进程/应用,而不是文件。.so 被 dlopen 进某个宿主进程之后,它在内核眼里并不是一个可以独立承担权限的实体——它藉由宿主进程的身份运行。所以"给一个 .so 授予权限"这件事本身缺少可挂靠的授权对象。
反过来说,可执行程序拥有自己的进程,因此拥有可被授权的身份。官方把这条单独拎出来强调,原因也在这里:它属于结构性限制。
签名之外还有第二道门:沙箱矩阵
签名决定"这个文件的身份是否可信",沙箱决定"这个文件能不能被某个应用拉起来"。两者是独立的两道关。
官方的强/弱沙箱定义 【官方】:
- 弱沙箱应用:具备
ohos.permission.CUSTOM_SANDBOX - 强沙箱应用:不具备该权限
| 应用类型 | ALLOW_EXTERNAL_NATIVE_CODE | normal bin | debug bin | adhoc bin |
|---|---|---|---|---|
| 弱沙箱 | 无 | ✅ | ✅ | ✅ |
| 弱沙箱 | 有 | ✅ | ✅ | ✅ |
| 强沙箱 | 无 | ❌ | ❌ | ❌ |
| 强沙箱 | 有 | ✅ | ✅ | ❌ |
这张表和前面"自证 / 他证"那条结论连起来看:
- 弱沙箱应用怎样都能拉起独立二进制——它本来就拿到了
CUSTOM_SANDBOX(一条 ACL 权限),等于自带"允许外部原生代码"的身份; - 强沙箱应用必须申请
ALLOW_EXTERNAL_NATIVE_CODE,而且即便如此也拉不起 adhoc bin——因为 adhoc 没有身份,无法参与"这个二进制是谁给的、能不能信"的判断。
再叠加一条 【官方】 重要说明:
使用 DevEco Studio 的 clang 编译的二进制属于自签名场景,二进制类型是
adhoc bin而不是debug bin(debug bin必须使用调试证书签名)。
也就是说:交叉编译出来的产物默认就是 adhoc。如果你的目标是"在强沙箱应用里拉起自编译的二进制",那么这条路从编译那一刻起就已经被堵死了——必须补一步调试证书签名,把它从 adhoc bin 变成 debug bin。
补充一个省事的办法 【官方·工具链】:ohos-sdk 的
ld.lld支持--code-sign,可以在链接期就把签名打好,省掉单独的binary-sign-tool步骤:
```bash
clang -Wl,--code-sign my_program.c -o my_program
llvm-readelf -S my_program # 末尾能看到 .codesign 段
```
不过它默认没开(macOS 上 lld 是默认开的),而且链接期没有证书参与,所以产物仍然落在自签名路径上。
和 macOS 对照一下
这套机制在设计上和 macOS 的代码签名是同构的,对照着看容易理解:
| macOS | HarmonyOS / 鸿蒙 PC | |
|---|---|---|
| 签名工具 | codesign | binary-sign-tool |
| 正式签名 | Developer ID / App Store | 二进制证书签名 |
| 开发签名 | Development 证书 | 调试证书签名 |
| 无身份签名 | Ad Hoc | 自签名(adhoc) |
| 链接期签名 | lld / Go 链接器默认打 adhoc | ld.lld --code-sign,需手动开启 |
| 完整性机制 | 代码目录哈希 | fs-verity Merkle 树 |
| 校验位置 | 内核 + amfid | 内核 fs_security_verity + 用户态 code_protect |
| 信任判断 | 系统信任库 | 内核证书来源信任表(发行表 / 调试表) |
差异最大的一格是"链接期签名":macOS 上 lld 默认就打 adhoc 签名,所以开发者几乎对签名无感;鸿蒙这边同一个能力存在但默认关闭,于是每个人都得显式补一步。
这套验签机制在开源仓里有完整实现,但"是否强制校验"取决于设备与发行版配置。 在鸿蒙 PC(2in1)上它是强制的——不签名就是 Permission denied,没有商量余地。
什么场景用哪种
选择其实只取决于两件事,前面几节都已经给出了答案:
- 权限要靠身份换。
signature里没有证书,就没有主体能把权限授给你,所以只要涉及声明权限,自签名就直接出局。 - 身份能管到哪,看你打算给谁跑。 调试证书的身份只在
device-ids列出的设备上、且开发者模式开着时成立;要跨出这个范围公开发布,就只剩二进制证书,而它要华为认证。
对应到具体场景:
| 你的情况 | 该用哪种 | 关键动作 |
|---|---|---|
| 本机自己写的小工具,不需要任何权限 | 自签名 | -selfSign 1 + chmod 755 |
| 需要声明权限(JIT、继承父权限、独立加载库…) | 调试证书签名 | 用自己申请的调试证书;.permission 写 UDID;开启开发者模式 |
| 要在应用里被拉起 | 看上面那张沙箱矩阵 | 注意交叉编译产物默认是 adhoc bin |
| 要分发给大量用户 | 二进制证书签名 | 需华为认证,个人开发者目前不适用 |
只想让 clang 一步到位 | 链接期签名 | -Wl,--code-sign,产物仍是 adhoc |
三条最容易踩的:
- 二进制签名一定要用自己申请的调试证书。 DevEco Studio 自动签名生成的那张不能用——它签出的 ELF 会在内核
PKCS7_signatureVerify处被拒,详见上一篇的完整证据链。 .so别指望声明权限,前面讲共享库那条是结构性限制。- 签名 ≠ 能跑:
write code sign data success只代表数据写进去了,判定发生在运行时的code_protect+ 内核里,证据在hilog/kmsg。
附录:命令速查
# ---- 自签名(adhoc)----
binary-sign-tool sign -inFile tool -outFile tool.signed -selfSign 1
chmod 755 tool.signed
# ---- 调试证书签名 ----
binary-sign-tool sign -keyAlias <alias> -signAlg SHA256withECDSA \
-appCertFile <cert.cer> -keystoreFile <key.p12> \
-keyPwd <pwd> -keystorePwd <pwd> \
-inFile tool -outFile tool.signed -moduleFile module.json
chmod 755 tool.signed
# ---- 链接期自签名(省掉单独一步)----
clang -Wl,--code-sign my_program.c -o my_program
# ---- 看产物:段与签名信息 ----
llvm-readelf -S tool.signed # 段表末尾应有 .codesign
binary-sign-tool display-sign -inFile tool.signed # 权限段 + 证书链
# ---- 摘掉签名重签(预构建 .so 落到本机不可用时)----
llvm-objcopy --remove-section .codesign in.so out.so
# ---- 实时看验签判定 ----
hdc shell "hilog -x | grep -E 'code_protect|CODE_SIGN'"
# ---- 打开内核日志后再复现(原因证据在这里)----
hdc shell "hilog -k on; hilog -w start -t kmsg"
hdc shell "hilog -t kmsg" | grep -E "fsverity|hmcrypt"
参考资料
- 华为《二进制签名工具》官方文档:developer.huawei.com/consumer/cn/doc/harmonyOS-guides/binary-sign-tool
- OpenHarmony 代码签名部件:security_code_signature
- OpenHarmony 签名工具仓(含《二进制签名工具》章节):developtools_hapsigner README_ZH
- 内核侧实现:kernel_linux_common_modules/code_sign
- 预构建
.so签名被拒的社区实证:build_in_harmonyos issue #3349 - 本博客踩坑视角的上一篇:《二进制签名实战:三类签名、沙箱权限矩阵,以及一张调试证书的坑》
说明:本文标注 【源码】 的结论读自上述开源仓(
master分支),标注 【官方】 的引自华为开发者文档与工单回复,
其余为我的解释,可能有误,欢迎指正。实测环境为 HUAWEI MateBook Pro(OpenHarmony-7.0.0.105,API 26,2in1)。
更多推荐

所有评论(0)