Mbed TLS PSA 迁移策略解析:MD Light 与 Block Cipher 的 Legacy/PSA 双路分发机制
【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware
本文基于 Flipper Zero 固件仓库中自带的 Mbed TLS 架构文档 md-cipher-dispatch.md,系统讲解 Mbed TLS 在不破坏向后兼容的前提下,如何让传统(legacy)密码学接口"暗中"调度到 PSA 驱动加速层的迁移策略:核心机制包括 MD light 哈希抽象、MBEDTLS_MD_CAN_xxx能力宏、运行时分发(dispatch)规则,以及面向 GCM/CCM 的内部块密码抽象层。读完后你将理解该仓库中md、block_cipher模块的宏依赖关系与双路分发实现原理,并能据此分析 Mbed TLS 在受限设备(如 Flipper Zero 这类嵌入式目标)上的裁剪与加速方式。
背景:为什么需要这份迁移策略
Mbed TLS 正在从传统 legacy 接口向 PSA(Platform Security Architecture)接口迁移,但迁移不能"一刀切"。文档开篇明确了本策略的适用对象:那些未受MBEDTLS_USE_PSA_CRYPTO约束、目前仍在使用 legacy 密码 API、且需要过渡到 PSA 的代码。它是总策略文档 strategy.md 的细化补充,且与旧策略存在一个关键差异:本工作中 PSA 不再被视为黑盒——可以改动实验性功能,也可以调用内部接口。
理解现状必须先理解MBEDTLS_USE_PSA_CRYPTO的局限。该选项会让库的部分模块改用 PSA API 做密码运算,但它只作用于pk.h、X.509 和 TLS,且启用后应用必须在使用这些模块前调用psa_crypto_init()。迁移工作因此要解决两个问题:
- 让未被覆盖的模块(哈希、对称密码等)也能调用 PSA——仅在确定能正常工作的前提下。这相当于让这些模块无条件地获得"部分 use-PSA"行为:可用 PSA 加速器时受益,不可用时回退。
- 让调用链穿透生效。例如 X.509 调用
pk做 PSS 验证,pk再调用 RSA,RSA 又要计算哈希——这条链上的哈希计算也应走 PSA(对应上游 issue #6497 描述的问题)。
使用 PSA 接口的收益文档概括为两点:其一是 PSA 驱动(通常是硬件加速器)往往比内置软件实现性能更好、安全性也更好;其二是很多场景下有了 PSA 驱动后,软件实现可以整体移除,从而减小代码体积。此外还能消除冗余,比如md.c与psa_crypto_mac.c中 HMAC 实现的重复、hkdf.c与psa_crypto.c中 HKDF 实现的重复。
对 Flipper Zero 固件而言,Mbed TLS 以第三方库形式整体集成在 lib/mbedtls 目录,通过 lib/mbedtls.scons 参与构建,配置调整集中在 lib/mbedtls_cfg.h。本文讨论的双路分发机制正是这类受限嵌入式构建中"用驱动替代软件实现、压缩固件体积"的基础。
需求分析:用户故事与目标
文档用五个用户故事定义了约束边界,这些是理解整套设计取舍的前提:
- 向后兼容:使用 Mbed TLS 接口(含 legacy 密码接口)的应用开发者,期望代码在新的小版本中继续工作。
- 接口设计:使用 Mbed TLS 做密码运算的库代码开发者,需要知道该调哪些函数、检查哪些 feature 宏,才能让代码在所有配置下都能工作(文档指出这与 X.509、TLS 面临的问题相同)。
- 硬件加速厂商(两个诉求):一方面希望构建 Mbed TLS 时尽量用自己的硬件加速;另一方面希望不构建与硬件功能重复的软件实现,以最小化代码体积。
- 维护者(两个诉求):需要清晰的"何时用哪个接口"规则以避免非常规配置下的 bug;需要避免代码重复。
由此得出目标:让更多非 PSA 接口在底层走 PSA 接口,同时"在不成立的情况下不破坏现有代码"。同时文档明确了非目标(Non-goals):现阶段的目标不是让更多代码直接调用psa_xxx函数,而是让更多代码在 PSA 驱动可用时调用 PSA 驱动;至于分发机制本身如何实现,是次要问题。
另一个容易被忽略的依赖规则变化是:判断某个密码机制是否可用,传统上查MBEDTLS_SHA256_C、MBEDTLS_AES_C && MBEDTLS_CIPHER_MODE_CBC这类模块宏;在 PSA 接口侧则要看PSA_WANT_xxx符号。双路分发机制正是为弥合这两套依赖判定而设计。
问题剖析:三个域与"混合域"困境
调用方分类
文档首先把实现或使用密码机制的代码分成五类:
- 基础密码机制的软件实现(哈希、AES 等)——预期不变;
- 组合型密码机制的软件实现(HMAC、CTR_DRBG、RSA 等,PSS/OAEP 需要调用哈希且 PKCS1v1.5 需要知道哈希长度)——它们必须保证:只要辅助机制的 legacy 实现可用,组合机制就能工作,无论 PSA 实现是否同时可用;
- PSA crypto 接口的实现本身——预期不变,可能向改造后的胶水代码暴露一些内部功能;
- 受
MBEDTLS_USE_PSA_CRYPTO约束的代码:pk.h、X.509、TLS(不含 TLS 1.3 特有部分); - 始终使用 PSA 的代码:TLS 1.3(除与 1.2 共用部分外)、LMS。
据此划分出三个域(domain):
- Legacy 域:不与 PSA 交互。哈希、密码原语、算术的实现。
- 混合域(mixed domain):目前不用 PSA,但"应当尽量用"。包括组合型密码原语(LMS 除外),以及
MBEDTLS_USE_PSA_CRYPTO关闭时的 pk、X.509、TLS。 - PSA 域:
MBEDTLS_USE_PSA_CRYPTO开启时的 pk、X.509、TLS,以及 TLS 1.3、LMS。
非 use-PSA 模块清单
文档逐一盘点了"调用其他模块做密码运算、但不能做任何 PSA 假设"的模块,这是改造范围的事实清单:
哈希与 HMAC 类(driver-only 哈希改造之后):
- entropy(经 MD-light 调哈希)
- ECDSA(HMAC_DRBG;
md.h暴露于 API) - ECJPAKE(经 MD-light 调哈希;
md.h暴露于 API) - MD(哈希与 HMAC)
- HKDF(经
md.h调 HMAC;md.h暴露于 API) - HMAC_DRBG(经
md.h调哈希和 HMAC;md.h暴露于 API) - PKCS12(经 MD-light 调哈希)
- PKCS5(经
md.h调 HMAC;md.h暴露于 API) - PKCS7(经 MD 调哈希)
- RSA(PSS 与 OAEP 经 MD-light 调哈希;
md.h暴露于 API) - PEM(经 MD-light 调 MD5 哈希)
对称密码与 AEAD 类(driver-only cipher 改造之前),每个模块都记录了实际使用的模式、方向与所调函数:
| 模块 | 实际使用 | 依赖 | 调用的函数 |
|---|---|---|---|
| PEM | AES/DES/3DES 的 CBC 无填充,仅解密 | 无硬依赖,AES_C/DES_C保护 | setkey_dec()+crypt_cbc()(低层 API) |
| PKCS12 | 实践中是 2DES/3DES 的 CBC + PKCS7 填充,仅解密 | check_config.h中对CIPHER_C的无条件依赖;cipher.h暴露于 API | setup、setkey、set_iv、reset、update、finish |
| PKCS5(PBES2) | 3DES/DES 的 CBC + PKCS7 填充,加密解密双向 | CIPHER_C无条件依赖 | setup、setkey、crypt |
| CTR_DRBG | AES 的 ECB,仅加密 | AES_C无条件依赖 | setkey_enc、crypt_ecb(aes.h低层 API) |
| CCM | AES/Camellia/Aria 的 ECB,仅加密 | AES_C \|\| CAMELLIA_C \|\| ARIA_C与CIPHER_C无条件依赖;也被cipher.c调用 | info、setup、setkey、update(多次,从不 finish) |
| CMAC | AES/DES 的 ECB,仅加密 | AES_C \|\| DES_C与CIPHER_C无条件依赖 | 同上 |
| GCM | AES/Camellia/Aria 的 ECB,仅加密 | 同 CCM | 同上 |
| NIST_KW | AES 的 ECB,加解密双向 | AES_C \|\| DES_C与CIPHER_C无条件依赖;cipher.h暴露于 API | 同上 |
| Cipher | 任何密码/AEAD、任何模式、任何方向 | — | — |
文档特别提醒一点:PSA cipher 构建在 Cipher 层之上,而 PSA AEAD 直接调用底层 AEAD 模块(GCM、CCM、ChachaPoly)——这正是 GCM/CCM 模块会被两条路径同时使用、因而必须谨慎改造的原因。
为什么 PSA 不总是可用
这是整个设计的核心约束。以下情形中,调用psa_xxx()做哈希或密码运算并不合适,需要回退到 legacy 软件实现:
MBEDTLS_PSA_CRYPTO_C未启用;- 存在尚未初始化(初始化发生在
psa_crypto_init()中)的 PSA 驱动; - 对 cipher 而言,keystore 尚未初始化,而 Mbed TLS 使用了自实现的 PSA ITS,此时文件系统还不可访问。文档提到一个可能的绕过:分发到 keystore 查找之后的内部函数而非 PSA API 函数,但这与
MBEDTLS_PSA_CRYPTO_CLIENT不兼容; - 某个机制在 legacy 接口启用但 PSA 接口未启用——这不是设计意图,但确实可能出现。例如为支持 PEM 的 PBKDF1 解码而启用
MBEDTLS_MD5_C,却不想启用PSA_ALG_WANT_MD5(因为 MD5 在PSA_ALG_RSA_PSS和PSA_ALG_DETERMINISTIC_ECDSA中不受支持); MBEDTLS_PSA_CRYPTO_CLIENT启用,但客户端尚未建立与服务端的连接(发生在psa_crypto_init()中);MBEDTLS_PSA_CRYPTO_CLIENT启用,但当前操作本身就是与 crypto 服务加密通信的实现的一部分,或者本地实现因避免昂贵的远程调用反而更快。
间接知识(Indirect knowledge)问题
以rsa.c中 RSA-PSS 签名需要计算哈希为例:当mbedtls_rsa_rsassa_pss_sign()被应用代码直接调用时,应调用内置实现——走 PSA 加速器属于行为变化,只有在不增加失败风险或性能下降时才允许;这一点与MBEDTLS_USE_PSA_CRYPTO的开关状态无关,因为rsa.h不在其作用范围内。而当它被X.509 代码调用时,哈希就应该走 PSA(当前没走,即 issue #6497)。
一般化之后,混合域模块的规则是:
- 被 PSA 域模块调用时,必须调用 PSA;
- 调用方不在 PSA 域、且 PSA 调用不保证成功时,不得调用 PSA(或必须有回退)。
而"最终调用方是谁"这一信息在实际执行时是拿不到的,只能靠参数、预处理符号和运行时状态推断。另外文档强调了一条"非支持保证":PSA_WANT_xxx关闭应当保证经 PSA API 尝试该机制必然失败——这一性质由测试套件test_suite_psa_crypto_not_supported(自动枚举测试用例)兜底,因此不便为个别机制开例外。
RSA-PSS 实例推演
文档用 RSA-PSS 走了一遍完整推演。RSA 属于混合域,因此:被psa_sign_hash等 PSA 函数调用时,有 PSA 哈希加速器就必须用它;被用户代码调用时,若 PSA 不可用(无论原因是MBEDTLS_PSA_CRYPTO_C关闭、该哈希的PSA_WANT_ALG_xxx关闭,还是加速器驱动尚未初始化),必须调用内置哈希实现。RSA 依据mbedtls_md_type_t参数知道要算哪个哈希(混合域模块普遍以数值类型接收算法参数,例外是 HMAC_DRBG 和 HKDF 接收const mbedtls_md_info_t*,CMAC 接收const mbedtls_cipher_info_t*)。
一种最保守的方案是双重编码:沿用MBEDTLS_MD_SHA256分发到 legacy 代码,另加MBEDTLS_MD_SHA256_USE_PSA强制走 PSA。这最大限度保住向后兼容,但代价是非 PSA 代码永远享受不到加速器,也失去了移除软件实现的空间。
按域分析"调用方如何判断哈希可用性":
- Legacy 域调用方:只要
MBEDTLS_SHA256_C启用,就要求 RSA-PSS 支持 SHA-256,且任何时刻都必须工作(例如驱动未初始化时);不关心"负支持"。 - PSA 域调用方:
PSA_WANT_ALG_SHA_256启用且psa_crypto_init()已调用,就要求支持。负支持(未启用则必须拒绝)可以在 PSA 层调用 RSA 模块之前拦截,不必压给rsa.c。 - 混合域调用方:要求取决于其调用方,RSA 决定算法可用性的机制同样适用于它。
结论:RSA 必须能在MBEDTLS_SHA256_C或PSA_WANT_ALG_SHA_256任一启用时做 SHA-256;若只有PSA_WANT_ALG_SHA_256启用(意味着 SHA-256 来自加速器驱动),则只在psa_crypto_init()调用后需要工作。
再细分编译时可用性的四种组合:
MBEDTLS_PSA_CRYPTO_CLIENT:调用 PSA 是否划算取决于性能(只在服务端有加速器时走服务端更好;反之 RPC 开销可能吃掉加速收益),且必须已成功连接服务端。其余枚举假设该选项关闭。- 无 PSA 加速器:直接调
mbedtls_sha256即可,调用链细节无关紧要。 - 有 PSA 加速器、无软件实现:可以调加速器,除非需要保证失败——文档作者表示当时想不出必须保证失败的场景。
- 两者都有:优先 PSA,但前提是驱动可用。对哈希而言"假设驱动已初始化"就够(曾考虑要求哈希驱动免初始化也能工作);对 cipher 更复杂,因为 cipher 函数依赖 keystore,且 cipher 加速器可能还需要熵源(侧信道防护),而熵源在开机早期未必可用。
文档还指出一个微妙点:当"有 PSA 加速器但没有软件实现"时,预处理符号不应表示该算法在 legacy 域可用,只能在 PSA 域可用;混合域接口不能保证算法可用,但被请求时必须尝试。
规范设计:MD Light
定义与自动启用
MD light是md.h的一个子集,实现前文描述的混合域哈希计算接口,由mbedtls_config.h中的MBEDTLS_MD_LIGHT激活。以下情形会在build_info.h中自动启用它:
- 启用了某个需要计算哈希的混合域模块;
MBEDTLS_MD_C启用。
MD light 包含的类型:mbedtls_md_type_t、mbedtls_md_info_t、mbedtls_md_context_t;包含的函数:mbedtls_md_info_from_type、mbedtls_md_init、mbedtls_md_free、mbedtls_md_setup(MBEDTLS_MD_C关闭时hmac参数必须为 0)、mbedtls_md_clone、mbedtls_md_get_size、mbedtls_md_get_type、mbedtls_md_starts、mbedtls_md_update、mbedtls_md_finish、mbedtls_md。
与完整 MD 不同,MD light不支持把mbedtls_md_context_t*作为空指针传入;但若干函数仍需支持const mbedtls_md_info_t*为空——因为尝试使用不受支持的算法时mbedtls_md_info_from_type会返回NULL。
MD 算法支持宏
对每个哈希算法,只要其可通过 MD light 使用,md.h就定义宏MBEDTLS_MD_CAN_xxx(仅在MBEDTLS_MD_LIGHT启用时定义)。判定条件是二选一:对应的MBEDTLS_xxx_C已定义;或者MBEDTLS_PSA_CRYPTO_C/MBEDTLS_PSA_CRYPTO_CLIENT之一启用且对应的PSA_WANT_ALG_xxx启用。由于 legacy 与 PSA 对部分算法的拼法不同,而 MD 是 legacy 接口,故采用 legacy 命名:
#if defined(MBEDTLS_MD_LIGHT) #if defined(MBEDTLS_SHA256_C) || \ (defined(MBEDTLS_PSA_CRYPTO_C) && PSA_WANT_ALG_SHA_256) #define MBEDTLS_MD_CAN_SHA256 #endif #endif内部支持宏
- 至少一个哈希有 PSA 驱动时定义
MBEDTLS_MD_SOME_PSA; - 至少一个哈希有 legacy 实现时定义
MBEDTLS_MD_SOME_LEGACY。
MD 上下文中的 PSA 支持
MD 上下文必须容纳二选一:某 legacy 模块的上下文(或指向它的指针),或 PSA 上下文(或指针)。文档作者倾向去掉指针间接层,但那会使 MD 上下文始终等于最大支持哈希上下文的大小;因此该规范版本保留指针,且为统一性 PSA 侧也用指针(日后可能简化):
enum { MBEDTLS_MD_ENGINE_LEGACY, MBEDTLS_MD_ENGINE_PSA, } mbedtls_md_engine_t; // private type typedef struct mbedtls_md_context_t { mbedtls_md_type_t type; #if defined(MBEDTLS_MD_SOME_PSA) mbedtls_md_engine_t engine; #endif void *md_ctx; // mbedtls_xxx_context or psa_hash_operation #if defined(MBEDTLS_MD_C) void *hmac_ctx; #endif } mbedtls_md_context_t;所有字段均为私有。engine字段看似与type冗余,但当某算法同时有 legacy 模块与 PSA 加速器时,在上下文设置时刻依据加速器的运行时可用性做选择,该选择必须记录在上下文中。
Info 结构包含准则、编码转换与运行时判定
由于 MD light 要支持"仅经 PSA 启用"的哈希,mbedtls_md_info_t结构体的包含必须以MBEDTLS_MD_CAN_xxx为准则,而不能只看 legacy 模块;mbedtls_md_info_from_type同样适用此准则。
实现需要从 legacy 类型编码转换到 PSA 编码:
static inline psa_algorithm_t psa_alg_of_md_info( const mbedtls_md_info_t *md_info );运行时支持判定由私有函数承担:
int psa_can_do_hash(psa_algorithm_t hash_alg);返回 1 表示hash_alg此刻可以经 PSA 执行,0 表示不行;只对经 PSA 启用的算法有定义,起步实现是"PSA crypto 的驱动子系统已初始化则返回 1"。使用注意:对未经 PSA 启用的算法调用它是安全的——无论返回 0 还是 1,对该算法调 PSA 哈希函数都会返回PSA_ERROR_NOT_SUPPORTED。
哈希操作的分发规则
每个执行哈希运算或上下文管理的函数都要决定走 PSA 还是 legacy:
- 拿到已建立的上下文时,用其
engine字段; - 拿到
mbedtls_md_type_t type(或const mbedtls_md_info_t *中的type)时:- 若该哈希有 PSA 加速器且
psa_can_do_hash(alg)为真,调用对应 PSA 函数,如适用则把 engine 置为MBEDTLS_MD_ENGINE_PSA(MBEDTLS_MD_SOME_PSA未定义时跳过此步); - 否则按现有方式按类型分发到 legacy 模块(
MBEDTLS_MD_SOME_LEGACY未定义时跳过); - 若两条路都不通,返回
MBEDTLS_ERR_MD_FEATURE_UNAVAILABLE。
- 若该哈希有 PSA 加速器且
这里隐含一个假设:经 PSA 启动的操作必须能完成,即mbedtls_psa_crypto_free不得在 PSA 操作进行中调用,文档要求将此写入说明。PSA 函数返回后,MD light 调用mbedtls_md_error_from_psa转换状态码。
强制"所有 legacy 算法在 PSA 中可用"
前文分析认为没有必须"legacy 有而 PSA 无"的强需求(唯一例外场景是MBEDTLS_PSA_CRYPTO_CLIENT下机制只存在于本地的情形,但没有明确需求),因此规范采用了一条简化性质:
若某算法有 legacy 实现,则它也必然经 PSA 可用。
MBEDTLS_PSA_CRYPTO_CONFIG关闭时本已如此;启用时需要修改include/mbedtls/config_psa.h使该性质成立。这条性质带来两个简化:混合域代码只要知道psa_crypto_init()已被调用,就可以调用 PSA 代码而无须逐一检查算法支持;混合域代码也可以假设 PSA 的缓冲区尺寸计算对所有它支持的算法都正确。
MD light 优化项
以下优化不是实现 MD light 所必需,但能显著缩减代码体积:
- 剥离名称:从
mbedtls_md_info_t中移除哈希名称,mbedtls_md_info_from_string和mbedtls_md_get_name改用 switch-case 或独立列表实现; - 移除 info 结构中的元数据:
mbedtls_md_get_size及需要块大小的模块改查 PSA 宏,不再从 info 结构取; - 优化类型转换:重排
mbedtls_md_type_t枚举值,使其等于 PSA 编码的低 8 位,从而转换只需一次按或:
static inline psa_algorithm_t psa_alg_of_md_info( const mbedtls_md_info_t *md_info ) { if( md_info == NULL ) return( PSA_ALG_NONE ); return( PSA_ALG_CATEGORY_HASH | md_info->type ); }- 统一 HMAC 与 PSA:PSA 有自己的 HMAC 实现;在
MBEDTLS_MD_C与PSA_WANT_ALG_HMAC同时存在且 HMAC 未完全由驱动提供的构建中,应只保留一份实现——用 PSA 驱动接口调用替换md.h中的实现,混合域模块由此还能获得直接由 PSA 驱动加速的 HMAC。
对MBEDTLS_PSA_CRYPTO_CLIENT的改进
MD light 目前只在算法经MBEDTLS_PSA_CRYPTO_C可用时分发到 PSA,而MBEDTLS_USE_PSA_CRYPTO要求MBEDTLS_PSA_CRYPTO_C,所以混合域代码实际不会经由 CLIENT 触发 PSA,现状可接受。架构扩展支持 CLIENT 的路线是:编译期依赖改为检查defined(MBEDTLS_PSA_CRYPTO_C) || defined(MBEDTLS_PSA_CRYPTO_CLIENT);MBEDTLS_PSA_CRYPTO_CLIENT实现方需随psa_crypto_init()一并提供psa_can_do_hash()(或更通用的psa_can_do),届时它成为公共接口,不能随意变更。
3.x 的取舍:范围削减与优先级
不支持无MBEDTLS_USE_PSA_CRYPTO的 PK、X.509、TLS:这些模块不需要支持 driver-only 哈希和 cipher;想充分发挥驱动的用户需自行开启该宏。注意 TLS 1.3 同样适用此限制——其中哈希的部分用法和全部 cipher 用法与 TLS 1.2 共用代码,受该宏管辖(详见 use-psa-crypto.md)。该限制在 4.0 中会自然消失(届时该宏不再是选项而是常开)。
不支持无MBEDTLS_PSA_CRYPTO_C的MBEDTLS_PSA_CRYPTO_CLIENT:事实上并不支持这种组合构建——例如MBEDTLS_USE_PSA_CRYPTO与MBEDTLS_SSL_PROTO_TLS1_3都硬性要求MBEDTLS_PSA_CRYPTO_C,尽管从原理上讲只需 CLIENT 即可。既然 4.0 前不打算解除此限制,driver-only 哈希/cipher 支持在 3.x 中带同样的限制是可接受的;但设计上仍应顾及 CLIENT,避免日后补充时更加困难。
cipher 优先面向受限设备与现代 TLS:首要目标是类似 TF-M medium profile 的配置加上仅 AEAD 密码套件的 TLS。明确排除:加密 PEM、PKCS5 与 PKCS12 加密、PK parse 中的 PKCS8 加密密钥(高度受限设备上不常用);NIST-KW(同上理由);CMAC(同上,且它可直接加速);TLS 中的 CBC 密码套件(业界早已不推荐)。
块密码原语的双路分发:设计权衡
按上述优先级,初期只支持GCM、CCM 和 CTR-DRBG,三者都只用块密码原语的加密方向,且都通过"ECB 模式"访问原语(Cipher 层和aes.h的 ECB 只支持单块,与 PSA 实现真正的 ECB 模式不同)。
GCM/CCM 目前经 Cipher 层同时支持 AES、Aria、Camellia(DES 因块尺寸小被标准排除),CTR-DRBG 则直接用aes.h低层 API。GCM 与 CCM 需求与在栈中的位置高度相似,应采用同一设计;CTR-DRBG 则不同:它只用 AES 且无扩展计划,且在栈中位置特殊——用户只关心"随机数能不能用",栈中没有任何一环会问"CTR-DRBG-AES 是否可用"(不像 AES-GCM 会被 TLS 询问)。因此结论是两套设计:
- CTR-DRBG:只需检查
AES_C是否存在,不存在则"回退"到 PSA; - GCM/CCM:需要一个统一抽象层,能统一使用 AES/Aria/Camellia 并分发到内置实现或驱动。
该抽象层可以是新内部模块,也可以是扩展现有 Cipher API 的子集。复用 Cipher 子集的理由:无需设计、实现、测试新模块;GCM/CCM 无需改代码,只改依赖;避免与仍启用CIPHER_C的构建产生代码重复;日后若支持 NIST-KW、CMAC、PKCS5、PKCS12 等其他 Cipher 用户,只需扩展分发即可。代价则是继承cipher_info_t结构的负担(它目前承担三种用途:判断是否支持、查询块大小、setup()参数)以及"上下文动态分配"这类存疑的实现决策;若为此重构 Cipher,要么导致完整 Cipher 与子集构建之间实现差异显著,要么把简化工作量摊到整个 Cipher。
两种方案的原型验证表明:新内部模块的代码体积收益更大、代码更干净,最终采用新模块路线(即文档后文的 "Internal block cipher abstraction",曾称 "Cipher light")。
内部"块密码"抽象层规范
定义
新模块由config_adjust_legacy_crypto.h自动启用:需要它的模块(即 CCM、GCM)在CIPHER_C不可用、或需要 PSA 分发时启用它。注意 CCM 和 GCM 目前硬依赖完整CIPHER_C(由check_config.h强制),此硬依赖将被上述自动启用机制取代。
对外 API:
void mbedtls_block_cipher_init(mbedtls_block_cipher_context_t *ctx); void mbedtls_block_cipher_free(mbedtls_block_cipher_context_t *ctx); int mbedtls_block_cipher_setup(mbedtls_block_cipher_context_t *ctx, mbedtls_cipher_id_t cipher_id); int mbedtls_block_cipher_setkey(mbedtls_block_cipher_context_t *ctx, const unsigned char *key, unsigned key_bitlen); int mbedtls_block_cipher_encrypt(mbedtls_block_cipher_context_t *ctx, const unsigned char input[16], unsigned char output[16]);仅支持 AES、ARIA、Camellia,由setup()中的mbedtls_cipher_id_t标识——因为调用方(GCM/CCM)就是这样标识它们的。
双路分发机制
与 MD light 高度同构。块密码上下文包含二选一:legacy 模块上下文(AES/ARIA/Camellia)或一个 PSA key identifier,另有一个字段指示当前用的是哪个,所有字段私有。engine字段的理由与 MD 相同:算法同时有 legacy 与 PSA 加速器时,在setup()时刻按加速器运行时可用性做选择并记录。
运行时支持判定:
int psa_can_do_cipher(psa_key_type_t key_type, psa_algorithm_t cipher_alg);语义同psa_can_do_hash:返回 1 表示该操作此刻可经 PSA 执行。模块内每个函数都要知道走哪条路:除setup()外全部查上下文的engine字段;setup()则根据 key 类型与psa_can_do_cipher()的返回值设定 engine。同样假设经 PSA 启动的操作必须能完成(mbedtls_psa_crypto_free不得在其进行中调用)。PSA 函数返回后,block_cipher各函数调用mbedtls_cipher_error_from_psa转换状态码。
仓库中的落地实现:从文档到源码
以上规范在当前仓库的 Mbed TLS 副本中已有相当部分落地,下面给出可核对的实现证据。
能力宏与自动启用:config_adjust_legacy_crypto.h
config_adjust_legacy_crypto.h 精确实现了 MD light 一节描述的宏体系:MBEDTLS_MD_LIGHT由混合域模块自动启用;随后对每个算法同时维护两组宏——PSA 侧的MBEDTLS_MD_CAN_xxx+MBEDTLS_MD_xxx_VIA_PSA(以MBEDTLS_PSA_ACCEL_ALG_xxx为前提,覆盖 MD5、SHA-1、SHA-224/256/384/512、RIPEMD160、SHA3-224/256/384/512)与 legacy 侧的MBEDTLS_MD_CAN_xxx+MBEDTLS_MD_SOME_LEGACY(以MBEDTLS_xxx_C为前提)。这正对应文档"info 结构包含以MBEDTLS_MD_CAN_xxx为准则"的要求。
块密码侧(第 202-266 行)定义了MBEDTLS_BLOCK_CIPHER_{AES,ARIA,CAMELLIA}_VIA_PSA(前提MBEDTLS_PSA_ACCEL_KEY_TYPE_xxx)、_VIA_LEGACY(前提MBEDTLS_xxx_C)、派生的MBEDTLS_BLOCK_CIPHER_CAN_xxx与MBEDTLS_BLOCK_CIPHER_SOME_PSA。自动启用规则与规范一致:
#if (defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CCM_C)) && \ (!defined(MBEDTLS_CIPHER_C) || defined(MBEDTLS_BLOCK_CIPHER_SOME_PSA)) #define MBEDTLS_BLOCK_CIPHER_C #endif即:无CIPHER_C时启用(等价旧方案的兜底),或存在 PSA 驱动可加速时也启用——与文档"仅当CIPHER_C不可用,或需要 PSA 分发时自动启用"逐字对应。此外还派生出MBEDTLS_CCM_GCM_CAN_{AES,ARIA,CAMELLIA},让 GCM/CCM 的能力判断同时覆盖两条路径。
上下文结构:block_cipher.h
block_cipher.h 定义了mbedtls_block_cipher_id_t(AES/CAMELLIA/ARIA)、engine 枚举(MBEDTLS_BLOCK_CIPHER_ENGINE_LEGACY/MBEDTLS_BLOCK_CIPHER_ENGINE_PSA)以及上下文结构:id字段、条件编译的engine与psa_key_id,以及存放各 legacy 上下文的 union。值得注意的实现差异:文档草稿阶段 MD 上下文"暂时保留指针",而块密码上下文最终实现直接内嵌各算法上下文的 union而非指针,省去了间接层——与 MD 侧作者"倾向去掉指针间接"的取向一致,代价是上下文尺寸等于最大算法上下文,恰好是该层仅支持三种 128 位块密码时可以接受的。
分发与密钥导入:block_cipher.c
block_cipher.c 的mbedtls_block_cipher_setup实现了规范中的setup()判定流程:先把调用方传入的mbedtls_cipher_id_t映射为内部 id,然后在MBEDTLS_BLOCK_CIPHER_SOME_PSA下查询psa_can_do_cipher(psa_key_type, PSA_ALG_ECB_NO_PADDING),成功则置engine = MBEDTLS_BLOCK_CIPHER_ENGINE_PSA直接返回,否则回落到按 id 初始化对应 legacy 上下文,未知 id 返回MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA。
block_cipher.c 的setkey在 PSA 路径上把 key 作为一次性导入的 PSA 密钥处理:设置 key type、bits、算法(PSA_ALG_ECB_NO_PADDING)与用途(PSA_KEY_USAGE_ENCRYPT),经psa_import_key得到psa_key_id,失败时走错误转换;legacy 路径按 id 调mbedtls_aes_setkey_enc/mbedtls_aria_setkey_enc/mbedtls_camellia_setkey_enc。
block_cipher.c 的encrypt按engine字段分流:PSA 路径调用psa_cipher_encrypt(ctx->psa_key_id, PSA_ALG_ECB_NO_PADDING, ...)处理 16 字节块;legacy 路径调各算法的crypt_ecb。free对应地按 engine 选择psa_destroy_key或各mbedtls_xxx_free。API 声明与文档规范完全一致,见 block_cipher_internal.h(init以 inlinememset实现,注释还特别提醒setup()参数是mbedtls_cipher_id_t而非mbedtls_block_cipher_id_t——呼应规范中"以调用方习惯的标识方式传入"的设计)。
错误码转换:psa_util_internal.h
规范中"调用 PSA 函数后转换状态码"的机制在 psa_util_internal.h 中实现:mbedtls_error_pair_t双字段表 +psa_status_to_mbedtls()逐表查找、未命中再走psa_generic_status_to_mbedtls兜底,并用PSA_TO_MBEDTLS_ERR_LIST宏缩短模块侧的调用。其中psa_to_md_errors表恰在MBEDTLS_MD_LIGHT下声明、psa_to_cipher_errors表恰在MBEDTLS_BLOCK_CIPHER_SOME_PSA下声明,与文档"MD light 调mbedtls_md_error_from_psa、block_cipher 调mbedtls_cipher_error_from_psa"的分工一一对应(block_cipher.c 中mbedtls_cipher_error_from_psa即封装了该表查询)。
小结
这套迁移策略的本质,是在不改公共 API、不破坏向后兼容的前提下,为"混合域"模块安装一个编译期判定能力(MBEDTLS_MD_CAN_xxx/MBEDTLS_BLOCK_CIPHER_CAN_xxx宏族 +MBEDTLS_BLOCK_CIPHER_C自动启用)、运行期判定路径(psa_can_do_hash/psa_can_do_cipher+ 上下文engine字段)的双路分发机制:驱动可用且就绪时走 PSA(从而可以用硬件加速替代软件实现、删减代码体积),否则透明回落到 legacy 实现。在 Flipper Zero 固件中阅读md.c、gcm.c、ccm.c、ctr_drbg.c这些模块时,凡是看到MBEDTLS_MD_SOME_PSA、MBEDTLS_BLOCK_CIPHER_SOME_PSA条件分支,其背后都是本文所述的规范在起作用;而 lib/mbedtls_cfg.h 与 lib/mbedtls.scons 则决定了这些宏在最终固件中实际取何值。
【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考