HSM自学之路——阶段5安全启动专题与实践

面向汽车电子 MCU | 芯片:CCFC3008PCSN | MCAL | HSM 固件 本文档是安全启动的"一页通 + 实操手册":把操作流程、设置参数、时钟配置、其他核启动时间点、代码配置、常见坑全部串起来。 所有接口、结构体、枚举、地址、行号均来自 MCAL 源码 + HSM 固件源码原文,非臆造。

0. 一页看懂:安全启动到底在干什么

用一句话概括:芯片上电后,HSM 安全核先跑起来,在主核(Z4/Z7)真正执行应用代码之前,先用"验签密钥"检查这些代码有没有被篡改,验签通过才把主核放出去跑;不通过就按你配的策略处理(复位/降级/锁死)。

复制代码
上电
  │
  ▼
HSM 安全核先启动(固件 自己初始化时钟/看门狗/串口)
  │
  ▼
读取安全启动表(存在 D-Flash,A/B 双份冗余) getBootEntryPtr()
  │
  ├── bootType == NONE ──────────────► 不验签,直接放核
  ├── 用户调过 IgnoreSecureBoot ──────► 跳过验签,直接放核
  ├── bootType == FAST ──────────────► 先放核(边跑边验),验签失败再回卷
  └── bootType == STRICT ────────────► 先验签,通过才放核(最安全)
  │
  ▼
验签(genAuthTag_RSA / genAuthTag_AES)
  │
  ├── 通过 ──► z4_z70_z71_app_boot() 释放主核
  └── 失败 ──► operationOnFail:RESET 软复位 / LOOP 锁死 / DEGRADE 降级放行

三个"灵魂"概念,先背下来:

|------------------------|------------------------------------------------|------------------------------|
| 概念 | 含义 | 一句话 |
| 信任根(Root of Trust) | 验签用的公钥/密钥,是"信不信代码"的源头 | 密钥在 HSM 里、出不来,所以可信 |
| 验签(Verify) | HSM 用公钥对代码段算签名、比对你预置的签名 | 签名不对 = 代码被改过 |
| 放核(Release Core) | HSM 写 MC_ME.CCTLx/CADDRx + MCTL,把主核从复位态放出来 | 唯一函数 z4_z70_z71_app_boot() |

1. 背景与核心概念

1.1 为什么车规 MCU 一定要安全启动

  • 车规要求(ISO 21434 网络安全工程 / EVITA):ECU 上跑的固件必须是"可信"的。
  • 攻击场景:OTA 升级包被篡改、Flash 被物理/逻辑攻击改写、供应链植入后门。
  • 安全启动的作用:保证从"上电第一条指令"到"应用代码"整条链每一步都经过完整性 + 真实性校验,任何一环被改,系统拒绝启动或进入安全状态。

1.2 3008PCSN 的实现方式

  • HSM(HSN 安全核)独立于主核,先于主核启动,天然是"信任根"的宿主。
  • 验签密钥(RSA 公钥 / AES 对称密钥)存在 HSM 的 NVM 密钥区,主核拿不到明文。
  • 主核代码段在出厂/量产阶段由产线工具(或用 HSM 自身)算好签名,签名连同"代码段地址+长度"一起写进安全启动表。
  • 每次复位,HSM 都重跑一遍验签,保证"篡改即被发现"。

1.3 术语速查(本文高频词)

|--------------------------|------------------------------------------------|
| 术语 | 说明 |
| Secure Boot Entry(安全启动表) | 存在 D-Flash 的一张表,记录验签参数(hsmSecurebootEntry_t) |
| Code Segment(代码段) | 要验签的一段主核代码,含 address + length,最多 8 段 |
| Auth Tag(签名) | 对代码段算出来的签名值(RSA 256 字节 / AES-CMAC 16 字节) |
| DevAuth(设备认证) | 打开 HSM 受保护功能的"授权门":质询-应答 + CMAC |
| bootCore | 位掩码,标记要放哪些核(Z4_2 / Z7_0 / Z7_1) |
| A/B 双 bank | 启动表/密钥区各存两份,ping-pong 写入,防写坏 |


2. 三种启动模式:NONE / FAST / STRICT

安全启动类型枚举(Crypto_LLDriver.h:382~385 = 固件 hsm_common_type.h:352~355):

复制代码
typedef uint8_t hsmSecureBootType_t;
#define HSM_SECURE_BOOT_TYPE_NONE     ((hsmSecureBootType_t)0xFFU)  // 不启用
#define HSM_SECURE_BOOT_TYPE_FAST     ((hsmSecureBootType_t)0x00U)  // 先放核后验签
#define HSM_SECURE_BOOT_TYPE_STRICT   ((hsmSecureBootType_t)0x01U)  // 先验签后放核

2.1 三种模式时序对比(核心中的核心)

固件分流点在 HSM_SRV_SecureBootCheck()(hsm_secure_boot.c:1096~1248):

|-------------------|--------------------------------------|--------|-----|------------|-------------------|
| 模式 | 放核时机 | 验签时机 | 安全性 | 启动速度 | 适用 |
| NONE (0xFF) | 立即(boot(TRUE,FALSE),L1125) | 不验签 | 无 | 最快 | 开发调试、无安全需求 |
| FAST (0x00) | 验签前 先放核(boot(TRUE,TRUE),L1137) | 放核后并行验 | 中 | 快(用户感知零等待) | 快速启动场景,验签失败后回卷/复位 |
| STRICT (0x01) | 验签后 (boot(TRUE,TRUE),L1243) | 放核前先验 | 高 | 慢(等验签完成) | 高安全场景(网关/域控/VCU) |

关键理解点:

  • FAST 的"快"是相对的------主核先跑起来,HSM 在后台验签,验签失败再处理(但此时主核可能已经跑了一段,要靠 operationOnFail 兜底)。
  • STRICT 的"慢"是绝对的------主核必须等 HSM 把每个代码段都验完、签名匹配,才被放出去。
  • NONE 和 IgnoreSecureBoot 都是 boot(TRUE,FALSE),第二个参数 addrcheck=FALSE 表示"不做地址/校验检查",纯粹放核。

2.2 失败动作 operationOnFail(三个选项)

枚举(Crypto_LLDriver.h:387~390):

复制代码
typedef uint8_t hsmSecureBootFailOp_t;
#define HSM_SECURE_BOOT_ONFAIL_DEGRADE  ((hsmSecureBootFailOp_t)0x00U)  // 降级:照常放行,但不能用认证功能
#define HSM_SECURE_BOOT_ONFAIL_RESET    ((hsmSecureBootFailOp_t)0x01U)  // 软复位
#define HSM_SECURE_BOOT_ONFAIL_LOOP     ((hsmSecureBootFailOp_t)0x02U)  // 停核死循环(最狠)

固件落点(hsm_secure_boot.c:1212~1226):

复制代码
if (g_boot_status == HSM_SECURE_BOOT_FAIL){
    if (operationOnFail == HSM_SECURE_BOOT_ONFAIL_LOOP){
        Sysclk_Init_Reback(); stopCore(); while(1){ watchDog_service(); }   // 锁死
    } else if (operationOnFail == HSM_SECURE_BOOT_ONFAIL_RESET){
        Sysclk_Init_Reback(); softreset();                                  // 复位
    } else { // DEGRADE
        srvResp = HSM_SRV_RESP_OK;                                          // 降级放行
    }
}

注意:LOOP 和 RESET 前都会先调 Sysclk_Init_Reback() 把时钟回退(因为验签阶段 HSM 可能临时升频/降频了,失败要还原)。


3. 安全启动表与 A/B 双 bank 冗余

3.1 安全启动表结构 hsmSecurebootEntry_t

固件侧(hsm_common_type.h:897~920,NEW_SECURITY_BOOT 分支):

复制代码
typedef struct {
    uint32_t            validFlag;                    // 有效性标志 = 0x87654321
    hsmSecureBootType_t bootType;                     // NONE/FAST/STRICT
    hsmSecureBootFailOp_t operationOnFail;            // DEGRADE/RESET/LOOP
    uint8_t             segCount;                     // 代码段数量 1~8
    uint8_t             bootCore;                     // 位掩码:bit2=Z4_2, bit0=Z7_0, bit1=Z7_1
    xoscClkCofigData_t  xoscClkConfig;                // 时钟快照(复位后验签用)
    struct _verify_code_segment codeSeg[8];           // 8 段 {address, length}
    hsmSecureBootTag_t  authTags[8];                  // 8 段签名(RSA 256B / AES-CMAC 16B)
    HOST_ADDR           authKeyHandle;                // 验签密钥句柄
    hsmKeyType_t        authKeyType;                  // AES / RSA_PUB / RSA_PAIR
    hsmSmrInstall_t     smrInstall;                   // SMR 安全内存区安装
    uint64_t            reversed;
} hsmSecurebootEntry_t;

两个 validFlag 别搞混(§19.7 结论):

|----------------------------------|---------------------------------------------|-----------------|--------------------------------|
| 标志 | 位置 | 含义 | 值 |
| BOOT_VALID_FLAG | bank 末尾的 HSM_DATA_x_VALID_ADDR(8 字节 + 取反) | 判"这张启动表写完整了没有" | 库头文件定义(开源未见,语义同 0x87654321 套路) |
| hsmSecurebootEntry_t.validFlag | 表结构体第一个字段 | 判"这张表内容是否是有效配置" | 0x87654321 |

3.2 参数区 A/B 双 bank + ping-pong

地址布局(V229 PCSN,hsm_reg_define.h:100~123):

|-----|------------|----------|----------|----------|
| 区域 | 起始 | entry | COUNTER | VALID |
| A 区 | 0x00610000 | 0x610100 | 0x61FFF0 | 0x61FFF8 |
| B 区 | 0x00620000 | 0x620100 | 0x62FFF0 | 0x62FFF8 |

  • VALID_ADDR = BOOT_VALID_FLAG + 按位取反(8 字节),两份对得上才认为"这张表写完整、有效"。
  • COUNTER_ADDR = 单调递增 counter + 取反(8 字节),当 A/B 两份都有效时,用 counter 判"谁更新"。
  • ping-pong 写入 (HSM_Util_FlsSetDataWithBackUp,hsm_util.c:614~795):先擦备份 bank → 搬数据 → counter+1 写备份 counter → 写备份 valid → 最后擦旧 bank。这样任何时刻至少有一份完整的有效数据,断电/写一半也不会丢。

3.3 getBootEntryPtr() 两级仲裁(hsm_secure_boot.c:60~118)

选 bank 逻辑:

  1. 第一级(VALID):读 A、B 的 VALID 标志。若 A 无效 → 取 B;若 B 无效 → 取 A。
  1. 第二级(COUNTER) :若 A、B 都有效 ,比较 counter,flag_b[0]-1 == flag_a[0] 说明 B 更新 → 取 B,否则取 A。
  1. 兜底 :若 A、B 都无效 ,先 A 后 B(最终返回 B 地址),此时 B 里可能是全 0xFF(出厂未配置)或半写坏残留,无正确性保证。

⚠️ 关键澄清(此前踩过的坑):getBootEntryPtr 的两个 if 判的是"无效 ",所以 A/B 同时有效时两个 if 都不进,落到 counter 仲裁,不会互相覆盖。

3.4 第二道闸 isBootEntryValid()(hsm_secure_boot.c:667~673)

getBootEntryPtr 只返回地址指针、不校验内容。内容是否可用由第二道闸判断:

  • 用表结构体自身的 validFlag 字段判断(= 0x87654321 才有效)。
  • 特判 :validFlag == 0xFFFFFFFF && bootType == 0xFF → 视为"从未配置"(全擦除态),等效 NONE,直接放行。
  • 调用点 HSM_SRV_PreSecureBootCheck(hsm_secure_boot.c:1038):isBootEntryValid()==0 → 返回 VERIFY_FAILED。

结论:全 0xFF 放行(等效 NONE),半写坏/残留垃圾 → 验签失败。


4. 端到端操作流程(8 大步)

下面是一条"从零到安全启动跑起来"的完整链路。每一步都给出:做什么 + 调什么接口 + 关键参数。

复制代码
第 1 步  导入 DevAuth 密钥(AES-128,设备认证用)
第 2 步  打开 DevAuth 权限(质询-应答,拿到 SECURE_BOOT 权限)
第 3 步  导入验签密钥(RSA 公钥 或 AES 对称密钥)
第 4 步  对主核代码段算签名(RSA PKCS#1 v1.5 + SHA-256,或 AES-CMAC)
第 5 步  配置安全启动表(ConfigSecureBoot,含时钟快照)
第 6 步  查询/自检(PreSecureBootCheck / GetSecureBootStatus)
第 7 步  复位(安全启动表复位后由 HSM 消费)
第 8 步  HSM 验签 → 放核(自动完成,主核此时才真正跑起来)

第 1 步:导入 DevAuth 密钥

DevAuth 密钥是一把 AES-128 密钥,HSM 用它做设备认证(质询-应答)。测试代码默认值(tester_crypto.c:11):

复制代码
uint8 App_DevAuthKey[16] = {
    0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,
    0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88
};

密钥句柄(三级寻址,GET_KEY_HANDLE(catalog, group, slot) = (catalog<<16)|(group<<8)|slot):

复制代码
#define APP_AES128_DEVAUTH_KEY_HANDLE  GET_KEY_HANDLE(HSM_KEY_CATALOG_ID_NVM, HSM_KEY_GROUP_SYM, 0)

量产注意:DevAuth 密钥是"设备认证"的信任根,量产后一旦设为不可导出/不可更新就改不了,烧录流程要一次性做对。

第 2 步:打开 DevAuth 权限

App_OpenDevAuth_New()(tester_crypto.c:83~137)完整流程:

复制代码
AppStatus_t App_OpenDevAuth_New(const uint8_t *pKey, uint16_t keyLen, uint8_t devAuthOption)
{
    // 2.1 检查 DevAuth key 是否已存在,不存在则导入
    status = HSM_LLD_GetKeyInfo(APP_AES128_DEVAUTH_KEY_HANDLE, &keyInfo);
    if (STATUS_HSM_KEY_EMPTY == status) {
        HSM_LLD_ImportPlainSymKey(APP_AES128_DEVAUTH_KEY_HANDLE,
            HSM_KEY_TYPE_AES,
            HSM_KEY_FLAG_USAGE_SIGN | HSM_KEY_FLAG_USAGE_VERIFY | HSM_KEY_FLAG_USAGE_AUTH,
            keyLen, pKey);
    }

    // 2.2 发起质询(DevAuthReq):HSM 返回 32 字节 challenge
    hsmAuthScheme.macScheme.macAlgo = HSM_MAC_ALGO_CMAC;
    Crypto_AutoSar_Ext_DevAuthReq(APP_AES128_DEVAUTH_KEY_HANDLE,
                                   devAuthOption, &hsmAuthScheme, challenge);

    // 2.3 本地用同一把 key 对 challenge 算 CMAC(应答)
    HSM_LLD_AesCmacGenerate(HSM_OPERATION_MODE_SINGLE_CALL,
                            APP_AES128_DEVAUTH_KEY_HANDLE,
                            challenge, sizeof(challenge), cmac, &cmacLen);

    // 2.4 回送应答(DevAuthResp):HSM 比对 CMAC,一致则开放权限
    Crypto_AutoSar_Ext_DevAuthResp(cmac, cmacLen, NULL, 0);
    return 0;
}

DevAuth 权限位 (Crypto_LLDriver.h:326~332,这是"门禁"能开哪些能力):

复制代码
#define HSM_DEV_AUTH_KEY_MGMT         (1U << 0U)  // 密钥管理
#define HSM_DEV_AUTH_USERDATA_MGMT    (1U << 1U)  // 用户数据安全读写
#define HSM_DEV_AUTH_FW_UPDATE        (1U << 2U)  // 固件升级
#define HSM_DEV_AUTH_FLASH_OPERATION  (1U << 3U)  // Flash 操作
#define HSM_DEV_AUTH_SECURE_BOOT      (1U << 4U)  // 安全启动(★本文核心)
#define HSM_DEV_AUTH_ALL  (HSM_DEV_AUTH_SECURE_BOOT | HSM_DEV_AUTH_KEY_MGMT | ...)

⚠️ 配置安全启动(ConfigSecureBoot)前,必须先拿到 HSM_DEV_AUTH_SECURE_BOOT 权限 ,否则固件第一行就返回 HSM_SRV_RESP_DEV_NOT_AUTHED(hsm_secure_boot.c:696~699)。

第 3 步:导入验签密钥(RSA 公钥)

以 RSA-2048 为例。密钥句柄(tester_crypto_SecureBoot.c:30~36,非 EB 分支用 slot 3/4):

复制代码
#define APP_RSA2048_DEVAUTH_PUKKEY_HANDLE  GET_KEY_HANDLE(HSM_KEY_CATALOG_ID_NVM, HSM_KEY_GROUP_ASYM, 3)  // 公钥
#define APP_RSA2048_DEVAUTH_PRIKEY_HANDLE  GET_KEY_HANDLE(HSM_KEY_CATALOG_ID_NVM, HSM_KEY_GROUP_ASYM, 4)  // 私钥

导入公钥(只需 modulus + publicExponent 两段,不需要私钥,私钥只在"算签名"时用,放在产线):

复制代码
Crypto_HSM_KeyImportExportType App_Rsa2048Pubkey;
App_Rsa2048Pubkey.pKey[0] = (uint8 *)rsa2048key_modulus;          // 模数 N(2048 位)
App_Rsa2048Pubkey.u16KeyLength[0] = 2048;
App_Rsa2048Pubkey.pKey[1] = (uint8 *)rsa2048key_publicExponent;   // 公钥指数 e
App_Rsa2048Pubkey.u16KeyLength[1] = sizeof(rsa2048key_publicExponent);

Crypto_KeyElementSet(APP_RSA2048_PUB_KEY_ID, CRYPTO_KE_CIPHER_KEY,
                     (uint8_t *)&App_Rsa2048Pubkey, sizeof(App_Rsa2048Pubkey));
Crypto_KeySetValid(APP_RSA2048_PUB_KEY_ID);   // 置有效

对称方案(AES)则导入一把 AES 密钥(APP_AES_DEVAUTH_KEY_HANDLE = GET_KEY_HANDLE(NVM, SYM, 12)),验签用 CMAC。RSA 方案验签用 RSASSA-PKCS1-v1.5 + SHA-256,签名 256 字节;AES 方案签名 16 字节。

第 4 步:对主核代码段算签名

代码段地址宏 (tester_crypto.h:137~216,这是理解"要验哪些代码"的关键):

复制代码
#if (RESOURCE_CHIP_CORE_NUM > 1U)   // 多核
#define CODE_SEG_0_ADDR_Z4   (0x1100000)   // Z4 代码从 0x1100000 起
#else                               // 单核
#define CODE_SEG_0_ADDR_Z4   (0x1000000)
#endif
#define CODE_SEG_0_LEN_Z4    (0x00020000)  // 每段 128KB
#define CODE_SEG_1_ADDR_Z4   (CODE_SEG_0_ADDR_Z4 + CODE_SEG_0_LEN_Z4)
// ... 依此类推,最多 8 段
#define CODE_SEG_0_ADDR_Z7_0 (0x1000000)   // Z7_0 代码从 0x1000000 起
#define CODE_SEG_0_ADDR_Z7_1 (0x1080000)   // Z7_1 代码从 0x1080000 起

填充段表(initVerifyCodeSegments_Z4_8Segs,tester_crypto_SecureBoot.c:276~303):

复制代码
verifyCodeSegments appCodeSeg;
initVerifyCodeSegments_Z4_8Segs(&appCodeSeg);  // 填 8 段 {address, length}

算签名(tester_crypto_SecureBoot.c:624~635):

复制代码
for (index = 0; index < HSM_SECURE_BOOT_MAX_CODE_SEG; index++) {
    if (appCodeSeg.seg[index].address > 0 && appCodeSeg.seg[index].length != 0) {
        HSM_LLD_RsaPkcs1V15GenerateSignature(
            APP_RSA2048_DEVAUTH_PRIKEY_HANDLE,          // 用私钥签
            HSM_HASH_ALGO_SHA2_256,                     // 先 SHA-256 再 RSA
            (uint8 *)appCodeSeg.seg[index].address,
            appCodeSeg.seg[index].length,
            FALSE, g_CodeSignArray.sign[index].authTag0, &signatureLength);
        segNum++;
    }
}

签名落地:把每个段的签名写到 Flash 固定区(SECURE_BOOT_CODE_SIGN_ADDR,测试代码 tester_crypto_SecureBoot.c:637~665 用 Fls_Erase + Fls_Write 每段写 512 字节)。

第 5 步:配置安全启动表 ConfigSecureBoot

MCAL 侧接口 Crypto_AutoSar_Ext_ConfigSecureBoot()(10 个入参),对应固件 HSM_SRV_ConfigSecureBoot(hsm_secure_boot.c:677),下发命令 HSM_SRV_CMD_CONFIG_SECURE_BOOT。

复制代码
retVal = Crypto_AutoSar_Ext_ConfigSecureBoot(
    HSM_SECURE_BOOT_TYPE_STRICT,              // ① bootType:NONE/FAST/STRICT
    APP_RSA2048_DEVAUTH_PUKKEY_HANDLE,        // ② authKeyHandle:验签公钥句柄
    HSM_SECURE_BOOT_ONFAIL_RESET,             // ③ operationOnFail:DEGRADE/RESET/LOOP
    &xoscConfigData,                          // ④ xoscConfig:时钟快照(可为 NULL)
    &appCodeSeg,                              // ⑤ verifySegs:代码段表 {address,length}[8]
    (codeSigns0 *)signs,                      // ⑥ signs:各段签名指针数组
    segNum,                                   // ⑦ signlens/segcnt:段数量 1~8
    TRUE,                                     // ⑧ bootZ4:是否放 Z4_2 核
    FALSE,                                    // ⑨ bootZ7_0:是否放 Z7_0 核
    FALSE);                                   // ⑩ bootZ7_1:是否放 Z7_1 核

固件侧校验顺序 (hsm_secure_boot.c:696~900,务必记牢,排查参数错误按这个顺序):

  1. 检查 HSM_DEV_AUTH_SECURE_BOOT 权限(没开 → DEV_NOT_AUTHED)
  1. bootType 三选一(NONE/FAST/STRICT,其他 → INVALID_PARAM)
  1. bootZ4/bootZ7_0/bootZ7_1 至少一个 TRUE(bootCore==0 → INVALID_PARAM)
  1. operationOnFail 三选一
  1. verifySegs 非空(NONE 模式可空)
  1. segCount 1~8(0 或 >8 → INVALID_PARAM)
  1. 读验签密钥(HSM_Util_ReadKey,读不到 → 验签失败)
  1. genAuthTag_RSA / genAuthTag_AES 验签
  1. 比对预置签名 vs 重算签名(不一致 → VERIFY_FAILED)
  1. updateBootEntryInFlash 写启动表到 D-Flash(A/B ping-pong)

第 6 步:查询 / 自检

复制代码
Crypto_AutoSar_Ext_GetSecureBootStatus(&secureBootStatus);  // 读回启动状态
Crypto_AutoSar_Ext_PreSecureBootCheck(...);                 // 复位前预检

启动状态值 (Crypto_LLDriver.h:392~396):

|------------------|------------------|
| 值 | 含义 |
| 0x00 NOT_START | 尚未开始验签 |
| 0x11 OK | 验签通过 |
| 0x22 FAIL | 验签失败 |
| 0x33 NONE | 未配置安全启动(等效 NONE) |

第 7 步:复位

ConfigSecureBoot 只是把表写进 D-Flash,当下不生效。真正生效要等下一次复位,HSM 启动后读启动表、验签、放核。

第 8 步:HSM 自动验签 → 放核

复位后 HSM 固件自动走 HSM_SRV_SecureBootCheck()(§2.1 已详述),验签通过后调 z4_z70_z71_app_boot() 释放主核,主核从代码段首地址开始执行。


5. 时钟配置:两条路径,别混

安全启动涉及时钟,最容易搞混。分清两条链:

|-------------|-------------------------------------------------------------|-------------------------------------------|------|-----------------------|
| 路径 | 接口 | 作用 | 生效时机 | 存哪 |
| ① 时钟快照 | ConfigSecureBoot(xoscConfig) | 把主核完整时钟树镜像给 HSM,复位后 HSM 用它初始化自己的时钟(验签阶段用) | 复位后 | 安全启动表 xoscClkConfig |
| ② 立即配分频 | MCU_HSM_SetClkDiv(divider) / HSM_LLD_SetClkDiv(divider) | 立即写 HSM_CLKDIV,改 HSM 当前分频 | 立即 | 寄存器 HSM_CLKDIV |

5.1 时钟快照 xoscClkCofigData_t

convXOSCConfig()(tester_crypto_SecureBoot.c:371~507)从 Mcu_ClockConfigType 提取完整时钟树,逐个填寄存器值,末尾置 validFlag = 0x87654321。包含:MC_CGM 各 SC_DC/AC 分频、XOSC.CTL、IRCOSC.CTL、PLLDIG.PLL0DV/PLL1DV/PLL1FM/PLL1FD 等。

复制代码
void convXOSCConfig(xoscClkCofigData_t* xoscClkConfig, Mcu_ClockConfigType Mcu_ClockConfig) {
    xoscClkConfig->MC_CGM_SC_DC0_Value = (1U << 31U) | Mcu_ClockConfig.cgmConfig.sc_dc0;
    xoscClkConfig->MC_CGM_SC_DC1_Value = (1U << 31U) | Mcu_ClockConfig.cgmConfig.sc_dc1;
    // ...(几十个寄存器字段)
    xoscClkConfig->validFlag = HSM_SECURE_BOOT_ENTRY_VALID_FLAG;  // 0x87654321
}

关键结论(§17/§18 已核实):

  • xoscConfig 非必须 :固件里 if (srvReq->xoscConfig != 0) 才 memcpy(hsm_secure_boot.c:764~766),传 NULL 则不更新时钟字段。
  • 它不是当下生效 ,而是随启动表写进 D-Flash,复位后由 HSM 消费。
  • HSM 时钟 ≠ 主核时钟 :xoscClkConfig 是"镜像给 HSM 验签用"的,主核自己的时钟仍由应用层 Mcu_Init 配置。
  • ⚠️ 待闭环点:源码里只"保存"未见"复位后写回 MC_CGM/PLLDIG/XOSC 寄存器"的显式消费点,复位后动时钟靠 checkAndWaitLowDownClock(hsm_secure_boot.c:182)+ Sysclk_Init_Reback(仅 extern 声明,定义在链接库)+ SET_CLK_DIV 写 HSM_CLKDIV。

5.2 立即配分频的两条接口链

主核想立即给 HSM 配时钟分频,有两条现成链(§18 已核实):

  1. 标准 MCAL 入口 MCU_HSM_SetClkDiv(divider)(Mcu_LLDriver.c:4195~4219)→ 发 SET_ATTR(attrId=HSM_ATTR_ID_CORE_CLK_DIVIDER=5) → 固件 hsm_attr.c:274~284 写 HSM_CLKDIV = 0x80000000UL | (val<<16)。
  1. 底层 Crypto 专用 HSM_LLD_SetClkDiv(divider)(Crypto_LLDriver.c:466~488)→ 写 HSM.ALG_PARAM[1] + HOST_FLAG=HSM_SRV_CMD_SET_CLK_DIV → 固件 HSM_IRQHandler 写 HSM_CLKDIV。

主核已经自动调用 :Mcu_LLD_ConfigureSystemClockDividers(Mcu_LLDriver.c:614)在 HSM 使能时调 MCU_HSM_SetClkDiv(sc_dc1>>16)(Mcu_LLDriver.c:631),无需客户额外写代码。

两条路径边界 :ConfigSecureBoot 的 xoscConfig = 完整时钟快照(复位后验签用);SetClkDiv = 单个分频值(立即改 HSM_CLKDIV)。

5.3 HSM 时钟上限

⚠️ 代码注释明确 HSM 时钟 MAX 100MHz,配置时钟别超,否则验签/运算可能异常。


6. 其他核的启动时间点(FAST/STRICT/NONE 逐一定位)

结论先行:启动其他核的是 HSM ,唯一放核函数是 z4_z70_z71_app_boot(needVerify, addrcheck)(hsm_util.c:4202~4629)。

6.1 三种模式的放核调用时机(hsm_secure_boot.c:1119~1244)

|------------------|------------------------------------|-------|----------------------------------|------------|
| 模式 | 放核调用 | 行号 | 参数 | 说明 |
| NONE | z4_z70_z71_app_boot(TRUE, FALSE) | L1125 | needVerify=TRUE, addrcheck=FALSE | 不验签,直接放核 |
| IgnoreSecureBoot | z4_z70_z71_app_boot(TRUE, FALSE) | L1131 | 同上 | 用户主动跳过验签 |
| FAST | z4_z70_z71_app_boot(TRUE, TRUE) | L1137 | needVerify=TRUE, addrcheck=TRUE | 验签前先放核 |
| STRICT | z4_z70_z71_app_boot(TRUE, TRUE) | L1243 | needVerify=TRUE, addrcheck=TRUE | 验签后才放核 |

6.2 z4_z70_z71_app_boot() 内部做了什么(hsm_util.c:4202~4629)

复制代码
// 伪代码还原(按源码行号注释)
z4_z70_z71_app_boot(needVerify, addrcheck)
{
    if (z4z7_already_boot) return;            // 防重入(L4216/4628)
    // MBIST 检查:Z70_MBIST_RESULT == 0x5A 才继续(L4237~4239)
    // 在 app_located_addr[8] 里搜 boot header 的 isValidVector(L44/4176)
    // PCSN:写 MC_ME.CCTL1 / CADDR1(L4592~4594)
    //       写 MC_ME.MCTL 触发 DRUN 模式切换(L4613~4614)
}

核释放机制的本质 :HSM 直接写 MC_ME.CCTLx/CADDRx(配好各核的启动地址)+ MC_ME.MCTL(触发 DRUN 转换),不是主核自跳。也就是说,主核在被放出来之前一直处于复位/停机态,由 HSM 决定何时、从哪个地址让它起跑。

6.3 多核 bootCore 位掩码

bootCore 是位掩码(hsm_secure_boot.c:728~738):

复制代码
if (srvReq->bootZ4)   secureBootEntry.bootCore |= 0x01 << CORE_ID_Z4_2;   // Z4_2
if (srvReq->bootZ7_0) secureBootEntry.bootCore |= 0x01 << CORE_ID_Z7_0;   // Z7_0
if (srvReq->bootZ7_1) secureBootEntry.bootCore |= 0x01 << CORE_ID_Z7_1;   // Z7_1

核心 ID 枚举(hsm_core.h):Z7_0=0、Z7_1=1、Z4_2=2、Z7_3_Check=3、Z0_4_HSM=4。

对应关系:bootZ4=TRUE → bit2(Z4_2);bootZ7_0=TRUE → bit0;bootZ7_1=TRUE → bit1。至少一个为 TRUE,否则 bootCore==0 → 参数错误。

6.4 三种模式的时间轴(可视化)

复制代码
【NONE】    上电 → HSM 初始化 → 直接放核 → 主核跑(永不验签)
【FAST】    上电 → HSM 初始化 → 放核(主核先跑) → HSM 后台验签 → 失败则 operationOnFail 兜底
【STRICT】  上电 → HSM 初始化 → HSM 验签(主核等待) → 通过才放核 → 主核跑
             ^                                    ^
             └── 主核处于复位/停机态 ──────────────┘

7. 完整代码配置示例(RSA-2048 STRICT 单核)

把 §4 的 8 步串成一段可直接参照的代码(精简自 tester_crypto_SecureBoot.c:510~689):

复制代码
略

8. 参数速查总表(设置参数一页尽览)

8.1 ConfigSecureBoot 十参

|---|-----------------|-------------------------------------|----------|------------|
| # | 参数 | 类型/取值 | 必填 | 说明 |
| ① | bootType | NONE=0xFF / FAST=0x00 / STRICT=0x01 | 是 | 启动模式 |
| ② | authKeyHandle | RSA 公钥 / AES 密钥句柄 | 是(非NONE) | 验签密钥 |
| ③ | operationOnFail | DEGRADE=0 / RESET=1 / LOOP=2 | 是(非NONE) | 失败动作 |
| ④ | xoscConfig | 时钟快照指针,可 NULL | 否 | 复位后 HSM 时钟 |
| ⑤ | verifySegs | {address,length}8 指针 | 是(非NONE) | 代码段表 |
| ⑥ | signs | 各段签名指针数组 | 是(非NONE) | 签名 |
| ⑦ | segcnt | 1~8 | 是(非NONE) | 段数量 |
| ⑧ | bootZ4 | TRUE/FALSE | 至少一个TRUE | 放 Z4_2 |
| ⑨ | bootZ7_0 | TRUE/FALSE | 至少一个TRUE | 放 Z7_0 |
| ⑩ | bootZ7_1 | TRUE/FALSE | 至少一个TRUE | 放 Z7_1 |

8.2 服务侧结构体对照(hsm_srv_type.h:709~726)

复制代码
typedef struct {
    hsmSecureBootType_t   bootType;
    hsmKeyHandle_t        authkeyHandle;
    hsmSecureBootFailOp_t operationOnFail;
    HOST_ADDR             xoscConfig;
    HOST_ADDR             verifySegs;
    HOST_ADDR             signs;
    uint32_t              signlens;
    boolean               bootZ4;
    boolean               bootZ7_0;
    boolean               bootZ7_1;
} hsmSecureBootConfigSrv_t;

8.3 签名类型对照(hsm_common_type.h:832~849)

复制代码
// 主核侧:uint8_t authTag0[512](RSA 最大 512 字节,AES-CMAC 16 字节)
typedef struct { uint8_t authTag0[512]; } hostSecureBootTag0_t;

// 认证算法(SMR/扩展用)
#define HSM_AUTH_ALGO_CMAC           0x00  // AES-CMAC
#define HSM_AUTH_ALGO_GMAC           0x01
#define HSM_AUTH_ALGO_HMAC           0x02
#define HSM_AUTH_ALGO_SM2            0x03
#define HSM_AUTH_ALGO_ECDSA          0x04
#define HSM_AUTH_ALGO_RSASSA_PKCS1V15 0x05 // ★RSA 安全启动默认
#define HSM_AUTH_ALGO_RSASSA_PSS     0x06

8.4 验签密钥类型判断(hsm_secure_boot.c:1180~1201)

|-------------------------------------|------------------|----------------|
| authKeyType | 验签函数 | 签名长度 |
| HSM_KEY_TYPE_AES | genAuthTag_AES | 16 字节(CMAC) |
| HSM_KEY_TYPE_RSA_PAIR / RSA_PUB | genAuthTag_RSA | 256 字节(2048 位) |


9. 常见坑与排查清单(FAE 实战)

9.1 配置阶段常见错误

|----------------------------------------------|----------------------------|-------------------------------------------------------------|
| 现象 | 根因 | 排查 |
| 返回 DEV_NOT_AUTHED | 没先开 DevAuth SECURE_BOOT 权限 | 确认 App_OpenDevAuth_New(..., HSM_DEV_AUTH_SECURE_BOOT) 已成功 |
| 返回 INVALID_PARAM + "bootType error" | bootType 不是 0/1/0xFF | 核对枚举值 |
| 返回 INVALID_PARAM + "no core is set boot" | 三个 bootZx 全 FALSE | 至少放一个核 |
| 返回 INVALID_PARAM + "no valid code segment" | segcnt 为 0 或 >8 | 段数量 1~8 |
| 返回 VERIFY_FAILED | 签名不匹配 | 检查私钥/公钥是否配对、段地址长度是否与签名时一致 |
| 返回 KEY_INVALID | 验签密钥类型不对 | 确认是 AES 或 RSA_PUB/RSA_PAIR |

9.2 复位后验签阶段常见错误

|-----------------|---------------------|-------------------------------------|
| 现象 | 根因 | 排查 |
| 主核起不来、HSM 复位循环 | 验签失败 + ONFAIL_RESET | 抓 HSM 串口日志,看 HSM_SECURE_BOOT_FAIL |
| 全 0xFF 却直接放行 | 启动表从未配置(等效 NONE) | 确认 ConfigSecureBoot 真的写成功了 |
| 半写坏/残留垃圾 → 验签失败 | A/B 都无效,兜底取了坏数据 | 检查 D-Flash 启动表区域是否被误擦 |
| 主核时钟不对 | xoscConfig 没传或没生效 | 分清"快照(复位后)" vs "立即分频"两条链 |

9.3 高频踩坑点(速记)

  1. 密钥一次性:DevAuth/验签密钥导入后设不可导出不可更新,就再也改不了,烧录流程一次性做对。
  1. 生命周期收窄:切生命周期后权限收窄、调试口可能被锁,量产前规划。
  1. HSM 时钟 ≤ 100MHz:别超。
  1. 失败动作提前定义:RESET/LOOP/DEGRADE 三者对客户体验差异巨大,别让主核带病运行。
  1. A/B 双份别只看一份:排查启动表问题要看 A、B 两个 bank + VALID/COUNTER。
  1. FAST 不是"不验签":FAST 是"先放后验",失败仍会触发 operationOnFail,只是主核已经跑了一段。

一句话总结:安全启动 = 上电 HSM 先跑 → 读 A/B 启动表 → 按 NONE/FAST/STRICT 决定"先放核还是先验签" → RSA/AES 验签 → 通过由 z4_z70_z71_app_boot 释放主核;时钟走"快照(复位后)"+"立即分频"两条链;密钥与权限是整条链的信任根。

相关推荐
三8441 小时前
Fastjson 漏洞· 06 · 防御、审计与检测
安全·web安全·fastjson
Jason_zhao_MR2 小时前
新一代电能数据采集终端方案
linux·人工智能·嵌入式硬件·fpga开发·嵌入式
qetfw2 小时前
Debian SSH 安全配置:端口、来源限制与免密登录
linux·安全·debian·ssh
砚凝霜3 小时前
【软考信息安全】第十二章 网络安全审计概述与分类
网络·安全·web安全
大鹏的NLP博客3 小时前
Rust 的本质:用数学约束实现编译期内存安全
安全·rust
迪康妍妍3 小时前
终端安全实战:用迪康终端安全管理系统实现U盘四分档管控与全量审计
android·运维·网络·安全·电脑
YOLO数据集集合3 小时前
空中鸟类检测数据集 | 单目标 鸟类检测 生态监测 鸟击防范 目标检测9174期
安全·yolo·目标检测·飞鸟数据集·飞鸟
每天题库3 小时前
登高架设作业考试题库大全 核心考点真题刷题备考通关秘籍
学习·安全·考试·题库·考证
小蒋观天下15 小时前
专项方案:大场景港口AI安防、多干扰环境下的算法调优与落地实操
人工智能·深度学习·算法·安全·机器学习·计算机视觉·ai大模型