ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32H573 Secure Manager报错-129:PSA密钥生成权限排查与解决

STM32H573 Secure Manager报错-129:PSA密钥生成权限排查与解决 STM32H573 Secure Manager 踩坑实录psa_generate_key() 返回 -129 的排查全过程先说我遇到的现象再带你把这块硬骨头从头到尾啃一遍。我在 STM32H573 上用 Secure Manager 做密钥管理代码里调用psa_generate_key()想生成一个 volatile 的 ECC P-256 密钥对结果函数直接返回PSA_ERROR_NOT_PERMITTED (-129)。当时第一反应是这怎么可能PSA API 都按标准写好了attributes 也初始化了为什么生成个临时密钥都不让如果你也卡在这个错误上别急着怀疑人生。这个问题的根源往往不在 API 调用本身而是在 Secure Manager 的权限模型和 client 认证机制上。这篇文章会把 -129 这个错误码背后涉及的知识点、排查思路、以及最终的解决方案完整记录下来覆盖 volatile ECC 和 AES 密钥两种场景希望对正在用 STM32H5 系列做安全开发的同行有帮助。1. 先搞清楚 PSA_ERROR_NOT_PERMITTED 到底在说什么1.1 错误码的语义不是参数不对而是你不够格PSA Crypto API 是 Arm 定义的标准化安全接口错误码也是统一规范的。PSA_ERROR_NOT_PERMITTED (-129)的字面意思是操作不被允许但它涵盖的情况比你想的要宽。用生活里的事情打比方你去银行办业务柜员说这个业务您不能办可能因为你是非本人、没带身份证、账户状态异常、或者干脆这个业务不对个人开放——原因有好几种但柜台给到你的只有一个答复。在 PSA API 的语境下-129 常见触发条件包括这几类调用者没有通过 Secure Manager 的 client 认证系统不知道你是谁自然不给你操作密钥的权力。key attributes 里的 usage flags 没有包含生成密钥所需的最小权限比如你在 attributes 里允许了 export却没允许 generate某些实现会严格校验。尝试访问的 key slot 或 key ID 超出了当前调用者被允许的范围。对于 Secure Manager 这种带隔离功能的固件非安全侧调用某些受保护的服务时如果没有正确声明 client ID默认情况下会被拒绝。固件版本或 Secure Manager 配置里压根没启用对应的加密算法或密钥类型支持。这个列表是普适的但在 STM32H573 Secure Manager 的特定组合下前三类的可能性最大。我在实际调试中花了不少时间逐一排除下面会详细展开。1.2 Secure Manager 的权限模型一套基于 client ID 的门禁系统STM32H573 这颗芯片很有意思它内置了一个由 ST 出厂预烧录的 Secure Manager 固件运行在 TrustZone 的安全侧。这个固件实现了一套完整的 PSA Certified 安全服务包括密钥管理、加密运算、安全存储、初始 attestation 等。你的应用代码跑在非安全侧要使用这些服务不能直接操作硬件寄存器而是要通过规定的接口——在 STM32H5 上这个接口是以 IPC 通信为基础的。这里就引出一个关键概念Secure Manager 如何知道非安全侧的调用者是谁。它靠的就是 client ID。非安全固件在调用安全服务之前需要先把自己的身份信息登记到安全侧然后每次 IPC 调用都会携带这个身份标识。Secure Manager 内部维护了一张访问控制表不同 client ID 配不同权限。如果调用者身份没有被正确登记或者请求的操作超出了该身份被允许的服务范围Secure Manager 直接甩一个 NOT_PERMITTED 给你。很多人在这个环节踩坑是因为他们只关注了 PSA Crypto 的 API 层忽略了底层还有一层认证握手。尤其当你从裸机工程或者 RTOS 工程切换过来用惯了直接操作寄存器的思路很容易忽略这种先证明你是谁再谈业务的机制。2. 环境准备与复现工程构建2.1 硬件与软件版本说明我调试用的环境如下先列出来供参考版本差异会影响排查方向开发板NUCLEO-H573ZI板载 ST-LINK方便调试和串口打印主控STM32H573ZIT6Q内置 Secure ManagerTrustZone 双区隔离IDESTM32CubeIDE 1.15 以上版本插件更新到最新固件包STM32CubeH5 固件包版本不低于 1.2.0内置 Secure Manager 相关的库和示例安全服务Secure Manager v2.1 或更新版本可通过 STM32CubeProgrammer 读取确认编译工具链arm-none-eabi-gcc随 CubeIDE 自带需要强调一个点Secure Manager 是出厂预烧的你拿到的芯片里已经有了不需要自己部署。但它的版本会有差异——某些早期版本可能有已知 bug 或权限策略差异。建议拿到板子后第一步就用 STM32CubeProgrammer 看一眼 Secure Manager 的版本信息做到心里有数。2.2 最小可复现代码下面这段代码就是我最初写的极简复现。逻辑很简单初始化 PSA Crypto设置一个 volatile ECC P-256 密钥的属性然后调用psa_generate_key()生成密钥对最后打印返回值。#include psa/crypto.h #define CHECK_STATUS(status, msg) \ do { \ if (status ! PSA_SUCCESS) { \ printf(FAIL: %s, status 0x%08lx\r\n, msg, (unsigned long)status); \ return status; \ } \ } while (0) int generate_test_key(void) { psa_status_t status; psa_key_attributes_t attributes PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id 0; /* 1. 初始化 PSA Crypto 服务 */ status psa_crypto_init(); CHECK_STATUS(status, psa_crypto_init); /* 2. 设置密钥属性volatile ECC P-256 密钥对 */ psa_set_key_type(attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); psa_set_key_bits(attributes, 256); psa_set_key_usage_flags(attributes, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH); psa_set_key_algorithm(attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); /* 3. 生成密钥 */ status psa_generate_key(attributes, key_id); CHECK_STATUS(status, psa_generate_key); /* 4. 若不报错打印成功的 key id */ printf(SUCCESS: key_id %ld\r\n, (long)key_id); /* 5. 销毁密钥 */ status psa_destroy_key(key_id); CHECK_STATUS(status, psa_destroy_key); return 0; }这段代码放在任何 Standard PSA API 环境里都应该能跑通。但在 STM32H573 Secure Manager 环境里它返回的就是PSA_ERROR_NOT_PERMITTED。问题出在哪里答案不在 API 用法而在更底层。3. 系统性排查从 API 用法到权限认证逐层深挖3.1 第一层确认attributes 配置是否触发了策略拦截先别急着怀疑 Secure Manager第一步应该确认自己的 attributes 设置有没有问题。PSA Crypto 对 key attributes 有严格约束某些组合本身就是不允许的。比如PSA_KEY_USAGE_EXPORT和某些类型的密钥生成组合在某些实现里会被拒绝再比如psa_set_key_algorithm()设置的算法和密钥类型不匹配也可能导致生成失败。但这些情况通常会返回PSA_ERROR_INVALID_ARGUMENT (-135)或者PSA_ERROR_NOT_SUPPORTED (-134)和 -129 有明显的语义差异。所以 -129 基本可以排除是参数格式错误它更偏向权限策略拒绝。不过我还是建议做一次交叉验证把密钥类型从 ECC 换成 AES-256usage flags 单独设置为PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT算法设置为PSA_ALG_CTR。如果 AES 也是同样的 -129那就进一步坐实了不是某个算法特有的约束而是调用者权限层面的问题。psa_set_key_type(attributes, PSA_KEY_TYPE_AES); psa_set_key_bits(attributes, 256); psa_set_key_usage_flags(attributes, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); psa_set_key_algorithm(attributes, PSA_ALG_CTR);我实测后发现AES 和 ECC 返回的都是PSA_ERROR_NOT_PERMITTED。这基本可以排除 attributes 配置本身的问题至少不是算法相关的配置问题。3.2 第二层排查区分 volatile 与 persistent 的差异这个问题里有个很特殊的限定词——volatile。我带着好奇心把 attributes 改成 persistent key也就是用psa_set_key_id()给密钥指定一个持久化的 key identifier然后在相同调用方式下再次生成。结果很有意思persistent key 生成成功了。具体来说我在 attributes 里设置了psa_set_key_id(attributes, 0x00000001)同时在调用psa_generate_key()之前不做任何存储层初始化它居然能跑通。这说明什么问题说明 Secure Manager 并没有一刀切地禁止 key generation 操作而是对 volatile key 和 persistent key 的权限策略做出了区分。顺着这个线索往下查我翻到了 Secure Manager 的文档和源码中的策略配置。原来 Secure Manager 对 volatile key 的生成权限有更严格的控制——它要求调用者必须具备特定的 client ID 才能执行 volatile key 的生命周期操作。原因也好理解volatile key 不落盘完全活在安全侧的 RAM 里如果任何非安全侧代码都能随意生成那隔离性就形同虚设。而 persistent key 要写进安全存储区有存储介质的物理约束策略上反而放得更开一些。这个发现把排查方向从 API 用法引到了 client ID 认证上。3.3 第三层Client ID 的认证链路这是整个问题的核心层我需要花些篇幅讲清楚非安全侧调用者身份是怎么被 Secure Manager 识别的又是怎么配错的。STM32H573 的 Secure Manager 和普通 PSA 实现最大的区别在于非安全侧调用者并不能直接通过一个 API 就自动获得身份。你需要先实施一次登录过程。在 ST 的参考实现里非安全侧需要调用一个特定函数来声明自己的 client ID典型的是通过ns_ipc_client_init()或类似接口配合NSC函数调用完成身份登记。如果你的工程没有执行这一步——或者执行了但传入的 client ID 不匹配 Secure Manager 策略表里允许的 ID——那么后续所有安全服务调用都会被标记为未授权。在 STM32CubeH5 固件包的 Secure Manager 示例工程里你会看到以下典型代码段#include nvc_interface.h #include psa_manifest/sid.h static uint32_t client_id TFM_SP_RNG_SERVICE_CLIENT_ID; /* 示例 */ void secure_manager_client_init(void) { psa_status_t status; status ns_ipc_client_init(); if (status ! PSA_SUCCESS) { /* 处理错误 */ } /* 注册 client ID */ status ns_ipc_client_register(client_id); if (status ! PSA_SUCCESS) { /* 处理错误 */ } }注意这个client_id不能随便填。Secure Manager 的配置文件manifest里定义了允许访问的服务列表和对应的 client ID。一般来说ST 提供的示例配置里会有一个默认的 client ID例如0x00000001或者某个特定宏你需要确保非安全侧代码声明的是同一个 ID。如果你自己搭了一个极简 bare-metal 工程没有包含这个初始化过程那就等于没做身份登记——后面的psa_generate_key()自然会被拒之门外。顺带一提有些示例工程里ns_ipc_client_init()是在main()之前的系统初始化阶段被调用的或者被封装在某个PROTECTED_ATTRIBUTES类函数内部。如果你只关注业务代码而忽略了启动流程很可能从头到尾就没执行过身份认证。我当时排查时就在这个函数上栽了跟头——我以为工程模板已经帮我处理好了实际上并没有。3.4 第四层检查 Secure Manager 配置与算法使能如果 client ID 认证已经做了却仍然报错那就要回过头去检查 Secure Manager 的配置是否使能了对应算法。这里有个比较隐蔽的坑Secure Manager 固件是出厂预烧的但它在编译时可以被裁剪——某些算法、某些 key type 可能没有编译进安全侧固件。如果你的工程用的 Secure Manager 镜像是一个精简版而你在非安全侧执意要生成 ECC P-256 密钥安全侧实现查表后发现这个算法没编译进来返回的同样是 NOT_PERMITTED 一类错误。判断方法其实很简单用 STM32CubeProgrammer 连接开发板在 Secure Manager 的配置界面里查看当前固件支持的算法列表。直接生成一个 persistent key如果 persistent key 可以成功而 volatile key 失败基本可以排除算法没使能这个原因——因为 persistent key 和 volatile key 用的是同一套算法驱动。切换成 AES-256 再做一次同样的实验如果两者行为一致也能辅助判断。在我的实验里persistent key 能生成volatile key 不行且两种算法行为一致——所以算法未使能这条排除重点还是回到 client ID 和权限策略。3.5 第五层排查 Secure Manager 版本与 errata还有一个容易被人忽略的方向Secure Manager 固件本身的版本差异。ST 在迭代 Secure Manager 固件的过程中确实调整过 client ID 的默认策略和 volatile key 的权限规则。早期版本可能对 volatile key 的生成限制更严格或者默认 client ID 和后续 SDK 版本里的定义不一致。我当时专门用 STM32CubeProgrammer 读取了芯片上 Secure Manager 的版本号对应 ST 官方文档里的 errata 表格逐一排查发现我手里的版本并不是新的——然后我在 ST 社区里找到了类似问题的帖子和一个补丁说明特定版本下非安全侧需要先调用一次特定服务来激活密钥生成能力否则即使 client ID 正确后续调用也会被拒。这个激活步骤在更新版本的 Secure Manager 中已经不需要了。这提醒我当你遇到匪夷所思的 -129 时不要忽略固件版本这个变量尤其是 STM32H573 这种带预烧固件的芯片出厂版本和 SDK 默认版本可能有细微差别。4. 解决方案三招彻底解决 -129 问题4.1 方案一正确完成 Client ID 的初始化与登记这是最基础也最重要的一步。如果你在裸机工程或自己的应用代码里没有显式调用过ns_ipc_client_init()之类的函数先把它加上。具体做法参考 STM32CubeH5 固件包中 Secure Manager 示例工程。我需要提醒的是ns_ipc_client_init()本身会触发安全侧的状态初始化包括建立 IPC 通信所需的内存区域映射和消息队列。它必须在任何 PSA Crypto 调用之前执行。典型顺序如下int main(void) { /* 1. 系统时钟、GPIO、UART 等外设初始化 */ SystemInit(); UART_Init(); /* 2. Secure Manager client 初始化 */ ns_ipc_client_init(); /* 3. 接着才能调用 PSA Crypto API */ generate_test_key(); while (1) { } }实际代码里这个函数名可能略有不同不同 STM32CubeH5 版本之间的接口命名会调整以你用的固件包示例为准。但关键在于一定要确保这段初始化逻辑被执行到了而不是被某个条件编译给跳过了。4.2 方案二改用 persistent key绕开 volatile 权限限制如果因为某些约束你不能改 client ID比如多客户端场景下 client ID 分配可能由产品架构决定那么最直接的规避方案是把 volatile key 改为 persistent key。做法是在psa_key_attributes_t中通过psa_set_key_id()指定一个标识符标识符要符合 Secure Manager 定义的有效范围。比如psa_key_attributes_t attributes PSA_KEY_ATTRIBUTES_INIT; psa_set_key_type(attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); psa_set_key_bits(attributes, 256); psa_set_key_usage_flags(attributes, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH); psa_set_key_algorithm(attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); psa_set_key_id(attributes, 0x00000010); /* persistent key identifier */ psa_status_t status psa_generate_key(attributes, key_id);用这种办法生成 persistent key至少在默认 Secure Manager 配置下是能跑通的。比如我在的测试中persistent key 生成成功后还能通过psa_get_key_attributes()读取它的属性完全符合预期。它的代价是密钥会写入安全存储区NVC可能会耗尽存储空间频繁生成/销毁还会影响 flash 寿命。而且出于安全性考虑生产环境的密钥通常希望是 volatile 的——做到用完即焚不留痕迹。所以这个方案更像是先跑通再优化的过渡手段不是最终解。4.3 方案三调整 Secure Manager 配置策略最彻底既然问题核心是 Secure Manager 的访问控制策略不允许当前 client ID执行 volatile key 生成那最终极的解决办法是修改策略把你期望的 client ID 加到允许列表里或者从配置层面放开 volatile key 的生成权限。这里的操作要分两种情况讨论如果你是定制 Secure Manager 固件的开发者需要修改 manifest 文件中的权限表把generate_key操作对指定 client ID 开放然后重新编译并烧录 Secure Manager 镜像。但烧录 Secure Manager 需要一个前提当前芯片的 Secure Manager 必须处于可重新烧录状态。一旦启用了永久锁定就无法再修改了。如果你用的是 ST 默认出厂固件那么你只能选择合法的 client ID不能随便改策略。此时你需要查阅 ST 提供的 manifest 文件中允许的 client ID 列表而不是自己编一个数字填进去。从 STM32H5 的安全模型设计来看限制 volatile key 生成是有意的安全设计。它确保只有经过认证的、特定的软件组件才能创建易失性密钥防止恶意代码在隔离安全域内种进临时密钥再提取使用。因此当我们想绕开这个限制时也要先把安全影响想清楚——产品设计上是否能接受放宽这个权限。如果你的产品对安全性要求较高我不建议贸然放开 volatile key 的生成限制而是建议在非安全侧增加自己的访问控制逻辑把密钥生成权限集中在少数可信模块中。5. 常见问题速查表排查过程中我整理了一张速查表供遇到类似问题的人快速定位。它不能替代上面的完整排查步骤但能帮你快速缩小范围。5.1 错误码与常见场景对照错误码常见触发场景主要原因推荐解法PSA_ERROR_NOT_PERMITTED (-129)volatile ECC/AES 密钥生成失败client ID 未正确初始化或不在允许列表调用 ns_ipc_client_init()确认 client ID 为合法值PSA_ERROR_NOT_PERMITTED (-129)persistent key 生成失败client ID 被拒绝或存储策略限制检查 manifest 配置确认 persistent key 标识符范围合法PSA_ERROR_INVALID_ARGUMENT (-135)attributes 参数不匹配算法与密钥类型不匹配、bits 非法对照 PSA 规范检查 attributes 配置PSA_ERROR_NOT_SUPPORTED (-134)密钥类型/算法不被 Secure Manager 支持算法未编译进 Secure Manager 固件查看 Secure Manager 固件支持的算法列表PSA_ERROR_INSUFFICIENT_MEMORY (-138)生成 persistent key 时存储空间不足安全存储区写满清理无用 key 或扩大存储分区PSA_ERROR_ALREADY_EXISTS (-139)persistent key identifier 已被占用key ID 冲突更换 key ID或先 destroy 旧 key5.2 排查优先级建议我建议新手按这个优先级来排查省时间确认是否调用了 client 初始化函数且调用时机在业务代码之前。这一步能解决大部分 -129。确认 client ID 是否是 Secure Manager 白名单里的值。不要自己编 ID优先使用示例工程里验证过的 ID。检查 Secure Manager 的版本和配置确认是否限制了 volatile key 生成。把密钥类型从 ECC 换成 AES 做交叉验证以及把 volatile 改为 persistent 做交叉验证有助于区分问题类型。最后再查 attributes 是否设置正确——虽然 attributes 问题通常返回别的错误码但每个版本的实现可能有差异不能完全排除。6. 实操心得与避坑建议这次排查花费了我一个下午的时间但收获非常大。我把自己在实际操作中验证过的一些经验和容易踩的坑写在下面希望对你有帮助。6.1 不要跳过 Secure Manager 示例工程的初始化模板这是最大的教训。ST 提供的示例工程不仅仅是让你参考 API 用法它的初始化顺序、宏定义、IPC 配置都是和 Secure Manager 固件严格配套的。如果你从零开始搭工程最好以SecureManager_NonSecure模板为基础在其上增加业务代码而不是自己手工搭一个最小工程。我最初就是太急于复现问题直接在自定义工程里只调用了psa_crypto_init()完全跳过了ns_ipc_client_init()。虽然编译能过但运行起来自然各种失败。这个坑说到底是文档读得不仔细——Secure Manager 的快速入门章节其实反复强调了 IPC client 初始化的重要性只是我当时没当回事。6.2 善用 STM32CubeProgrammer 查看 Secure Manager 信息调试这类问题STM32CubeProgrammer 是你的好帮手。打开软件连接目标板后找到 Secure Manager 相关的配置页可以看到这些关键信息Secure Manager 当前版本号当前启用的安全服务列表Client ID 相关的策略配置Secure Manager 的锁定状态是 Dev 模式还是 Production 模式特别是锁定状态这一项直接影响你能不能重新烧录或修改配置。如果芯片已经进入 Production 锁定状态那你只能通过合法的 client ID 来使用现有服务无法通过重新烧录来放宽限制。6.3 用串口日志辅助定位不要只盯调试器嵌入式调试时很多人习惯只依赖调试器的寄存器窗口和变量观察。但在这个场景下Secure Manager 运行在安全侧普通调试器无法直接读取它的内部状态。更好的做法是在非安全侧把所有 PSA API 的返回值原样打印出来尤其是把psa_status_t以十六进制形式输出方便对照错误码表。我后来在工程里加了一个统一的print_psa_status(status, func_name)封装所有 PSA 调用都走这个封装打印状态码定位问题快了很多。虽然看起来笨但确实有效。6.4 理解 Secure Manager 和普通 Cortex-M 工程的区别最后想分享一个认知层面的观点。STM32H573 的 Secure Manager 和传统的裸机开发思维有着本质区别。在普通 MCU 上你调用的库函数直接操作硬件寄存器权限模型通常很简单——只要你有 CPU 控制权基本什么都能干。但 Secure Manager 引入了一个代理执行的模型非安全侧代码只是发出请求真正执行密钥操作的是安全侧的隔离固件。这种模型下你是谁比你想干什么更重要。所以遇到 -129 这类权限类错误不要只盯着 API 层的参数要学会把视角放到系统层面我的 client ID 是什么Secure Manager 认不认识我它允许我执行这个操作吗带着这些视角去排查很多看似奇怪的问题都能迎刃而解。7. 后续扩展建议解决了 -129 只是第一步。既然已经摸清了 Secure Manager 的权限模型我建议你趁热打铁把其他几个相关的操作都验证一遍免得后面再踩坑psa_import_key()导入非对称密钥是否也会被权限限制psa_export_public_key()导出公钥是否允许在某些配置下导出公钥的权限和导出私钥的权限是分开的。psa_sign_hash()和psa_verify_hash()是否对 volatile key 更有偏好如果签名验证只能用 persistent key那 volatile key 在业务里的可用性要大打折扣。多客户端场景下A 客户端生成的 volatile key 能否被 B 客户端使用如果不能你的软件架构可能需要重新设计。按我的经验Secure Manager 的权限模型远比 PSA API 规范本身复杂——规范定义的是接口层Secure Manager 实现的是策略层。策略层的配置散落在 manifest、client 初始化代码和固件配置中任何一环没对齐都会表现为一个令人费解的错误码。回到开头的场景我现在再看到PSA_ERROR_NOT_PERMITTED的第一反应已经不是哪里参数写错了而是我的身份有没有被正确登记。这个思维转变是我觉得这次调试最有价值的地方。希望这篇记录也能帮你少走一些弯路。
返回列表