安全启动CMAC计算过程
获取DFLASH存储秘钥
AesDecryptDf1Block
函数,其作用是读取HSM(High Security Module,高安全模块)数据闪存中加密的数据,然后使用AES(Advanced Encryption Standard,高级加密标准)算法进行解密,并将解密后的明文数据加载到HSM RAM结构体中。
/**
* @brief 该函数将从HSM数据闪存中读取加密数据,然后进行解密,并将明文数据加载到HSM RAM结构体中
* @param rounds: 要执行的解密循环次数
* @return 无返回值
*
*****************************************************************************/
void AesDecryptDf1Block(uint32_t rounds)
{
/* 使用密钥编号0 */
in = (uint32_t*)&hsmCfgDataRom; // 指向加密数据的指针
out = (uint32_t*)&hsmCfgData; // 指向HSM RAM结构体的指针
if (rounds > 0)
{
/* 加载第一个密文数据块 */
HSM_AES->AESIN0.U = *in++;
HSM_AES->AESIN1.U = *in++;
HSM_AES->AESIN2.U = *in++;
HSM_AES->AESIN3.U = *in++;
/* 解密数据块 */
HSM_AES->AESCTRL.U = AES_CTRL(OPC_ECB_DEC,KEYNR_0,CVNR_0); // 使用ECB模式和密钥0进行解密
while(--rounds)
{
/* 等待上一个解密操作完成 */
while (HSM_AES->AESSTAT.B.BSY == true)
;
/* 存储明文数据块 */
*out++ = HSM_AES->AESOUT0.U;
*out++ = HSM_AES->AESOUT1.U;
*out++ = HSM_AES->AESOUT2.U;
*out++ = HSM_AES->AESOUT3.U;
/* 加载下一个密文数据块 */
HSM_AES->AESIN0.U = *in++;
HSM_AES->AESIN1.U = *in++;
HSM_AES->AESIN2.U = *in++;
HSM_AES->AESIN3.U = *in++;
/* 解密数据块 */
HSM_AES->AESCTRL.U = AES_CTRL(OPC_ECB_DEC,KEYNR_0,CVNR_0);
}
/* 写入最后一个明文数据到HSM RAM结构体 */
*out++ = HSM_AES->AESOUT0.U;
*out++ = HSM_AES->AESOUT1.U;
*out++ = HSM_AES->AESOUT2.U;
*out++ = HSM_AES->AESOUT3.U;
}
}
rounds
参数指定了要执行的解密循环次数。in
指针指向一个结构体,该结构体包含了从HSM数据闪存中读取的加密数据。out
指针指向HSM RAM中的一个结构体,用于存储解密后的明文数据。HSM_AES
是HSM中AES模块的寄存器地址。AESIN0
到AESIN3
是AES模块的输入寄存器,用于加载32位的数据块。AESCTRL
是AES模块的控制寄存器,用于设置解密操作的参数。AES_CTRL
宏设置了操作模式为ECB(电子密码本模式),使用密钥编号0,和常量向量编号0。AESSTAT
是AES模块的状态寄存器,其中的BSY
位表示AES模块是否忙碌。AESOUT0
到AESOUT3
是AES模块的输出寄存器,用于存储解密后的数据。hsmCfgDataRom
按照自定义的格式存储在DF1的固定位置,只有HSM可以进行访问。
函数首先检查 rounds
是否大于0,然后开始解密循环。在每次循环中,它都会从 in
指针处读取加密的数据块,使用AES模块进行解密,并将结果写入 out
指针所指向的RAM结构体中。循环结束后,它还会处理最后一个数据块,并将结果写入RAM结构体。
判断秘钥有效性并加载秘钥
这段代码定义了一个名为 cryptoInitBootParameters
的函数,其作用是在系统启动时初始化加密参数,特别是用于CMAC(Cipher-based Message Authentication Code,基于密码的消息认证码)的AES(Advanced Encryption Standard,高级加密标准)密钥。以下是函数的中文解释和注释:
/**
* @brief 初始化启动参数的加密参数。
*
* @param None
* @return 无返回值
*
* @ingroup
*****************************************************************************/
uint8_t cryptoInitBootParameters(void)
{
uint8_t cryptoStatus;
/* 使用ECB模式和密钥槽0解密DF1数据块,然后将它复制到RAM */
AesDecryptDf1Block(HSM_CONFIG_DATA_ROUNDS);
if(hsmCfgData.BootMarker == 0xAA550000)
{
/* 加载用于CMAC的AES密钥到密钥槽2 */
HSM_AES->AESIN0.U = hsmCfgData.Cmac.Key.w[0];
HSM_AES->AESIN1.U = hsmCfgData.Cmac.Key.w[1];
HSM_AES->AESIN2.U = hsmCfgData.Cmac.Key.w[2];
HSM_AES->AESIN3.U = hsmCfgData.Cmac.Key.w[3];
/* 将CMAC密钥加载到槽2 */
HSM_AES->AESCTRL.U = AES_CTRL(OPC_WK, KEYNR_2, CVNR_0);
/* 将所有零的IV写入上下文0 */
HSM_AES->AESCTRL.U = AES_CTRL(OPC_WCV, KEYNR_2, CVNR_0);
cryptoStatus = E_CRYPTO_OK; // 设置加密状态为成功
}
else
{
/* 该设备未用有效数据初始化,或DF1中有故障 */
hsmCfgData.BootMarker = 0x0;
cryptoStatus = E_CRYPTO_NOT_OK; // 设置加密状态为失败
}
return cryptoStatus; // 返回加密状态
}
cryptoStatus
变量用于存储加密操作的状态。AesDecryptDf1Block
函数用于解密DF1数据块,并将解密后的明文数据复制到RAM中。HSM_CONFIG_DATA_ROUNDS
宏定义了要执行的解密循环次数。hsmCfgData
结构体存储了从DF1解密得到的配置数据。BootMarker
是hsmCfgData
结构体中的一个字段,用作验证解密数据是否有效的标记。- 如果
BootMarker
等于0xAA550000
,则认为数据有效,函数将加载用于CMAC的AES密钥到AES模块的密钥槽2,并设置相应的控制寄存器。 OPC_WK
宏定义了写入密钥的操作,KEYNR_2
指定了密钥槽2,CVNR_0
指定了常量向量编号0。OPC_WCV
宏定义了写入初始化向量(IV)的操作。- 如果
BootMarker
不等于0xAA550000
,则认为设备未用有效数据初始化或DF1中有故障,函数将BootMarker
设置为0并返回失败状态。
计算CMAC并输出结果
CmacOnBoot
函数,其作用是在系统启动时计算CMAC(Cipher-based Message Authentication Code,基于密码的消息认证码)。
/**
* @brief 在系统启动时计算CMAC。
*
* @param None
* @return 加密操作成功或失败的状态。
*
* @ingroup
*****************************************************************************/
uint8_t CmacOnBoot(void)
{
uint32_t rounds = 0;
uint8_t result = E_CRYPTO_NOT_OK;
#if ((DEBUG & 0x01) == 0x01)
P00_OMR.U = PORT_SET_P0; // 调试标志,设置P00.0引脚
#endif
if(hsmCfgData.BootMarker == 0xAA550000)
{
/* 从命令结构体获取输入信息 */
AES_DATA_Type *in = (AES_DATA_Type *)(hsmCfgData.BootMacStartAddress);
/* 计算最后一个块和要执行的轮数 */
rounds = AesCMAC_CalculateLastBlock(hsmCfgData.BootMacSize, in);
HSM_AES->AESCTRL.U = AES_CTRL(OPC_WK, KEYNR_0, CVNR_0); /* 清除输入值 */
HSM_AES->AESCTRL.U = AES_CTRL(OPC_WCV, KEYNR_2, CVNR_0); /* 写入IV */
/* 以128位块的形式读取闪存数据 */
if (rounds > 1)
{
AesCmacMove((uint32_t *)in, rounds-1, AES_CTRL(OPC_CBC_ENC, KEYNR_2, CVNR_0));
}
/* 总是执行最后一个块 */
HSM_AES->AESIN0.U = M_last.w[0];
HSM_AES->AESIN1.U = M_last.w[1];
HSM_AES->AESIN2.U = M_last.w[2];
HSM_AES->AESIN3.U = M_last.w[3];
HSM_AES->AESCTRL.U = AES_CTRL(OPC_CBC_ENC, KEYNR_2, CVNR_0);
while (HSM_AES->AESSTAT.B.BSY == true)
; // 等待AES模块完成操作
#if 0
/* 将计算出的CMAC与存储的值进行比较 */
AesGetResult((uint32_t *)&Cmac_boot);
for(uint32_t i=0; i < 4; i++)
{
if (Boot_MAC[i] != Cmac_boot.w[i])
{
result = E_CRYPTO_NOT_OK;
break;
}
else
{
result = E_CRYPTO_OK;
}
}
#else
/* 需要计算SAHMEM窗口,以便将数据写回主机内存 */
HSM_BRIDGE->SAHBASE.U = (uint32_t)(Boot_ResponseAddress);
out = (uint32_t*)(((uint32_t)(Boot_ResponseAddress) & 0x0000FFFFu) | 0xF0050000u);
AesGetResult(out);
out += 8;
*out++ = hsmCfgData.BootMacSize;
*out = hsmCfgData.BootMacStartAddress;
result = E_CRYPTO_OK;
#endif
}
#if ((DEBUG & 0x01) == 0x01)
P00_OMR.U = PORT_CLR_P0; // 调试标志,清除P00.0引脚
#endif
return result;
}
rounds
变量用于存储要执行的CMAC计算轮数。result
变量用于存储CMAC计算的结果状态。hsmCfgData
结构体包含了启动时的配置信息,包括启动标记(BootMarker
)、启动MAC起始地址(BootMacStartAddress
)和大小(BootMacSize
)。- 如果
BootMarker
等于0xAA550000
,则认为设备已用有效数据初始化,函数将继续执行CMAC计算。 AesCMAC_CalculateLastBlock
函数用于计算最后一个CMAC数据块和轮数。AES_DATA_Type
类型定义了要进行CMAC操作的数据指针。AesCmacMove
函数用于执行CMAC计算的中间轮。M_last
变量存储了最后一个CMAC数据块。HSM_BRIDGE
用于设置SAHMEM(Secure Access to Host Memory)基地址,以便将CMAC结果写回主机内存。AesGetResult
函数用于从AES模块获取CMAC计算的结果。
返回流程
/*!
* @fn uint8_t CheckHashApplication(void)
* @brief 计算 PFLASH 上的 HASH 值
* @param None
* @return 成功或失败的状态
*/
uint8_t CheckHashApplication(void)
{
uint8_t cryptoCheck = E_CRYPTO_NOT_OK;
/* */
Boot_ResponseAddress = Boot_ResponseAddressRom;
/* 加载 CMAC 密钥并获取子密钥 */
cryptoCheck = cryptoInitBootParameters();
/* 我们是否正确地解码了安全启动参数? */
if (E_CRYPTO_OK == cryptoCheck)
{
/* 安全启动是否应该立即释放 TriCore 还是在检查后释放? */
if ((0x01 & hsmCfgData.BootOptions) == 1)
{
/* 检查时钟控制单元是否从默认设置更改过 */
if (true == clockWasSet)
{
/* 降低使用内部 100 MHz 振荡器的 PLL0 */
CCU_RampDownSystemPll();
/* 表示我们已经重置了时钟 */
clockWasSet = false;
}
/* 释放 AURIX CPU0 */
HSM_BRIDGE->HSM2HTF.U = 1;
}
/* 对 AURIX 代码运行 CMAC */
cryptoCheck = CmacOnBoot();
}
/* 检查时钟控制单元是否从默认设置更改过 */
if (true == clockWasSet)
{
/* 降低使用内部 100 MHz 振荡器的 PLL0 */
CCU_RampDownSystemPll();
}
/* 返回结果代码,对于演示代码总是为真,对于真正的应用,这应该返回 CMAC 验证的实际状态 */
return E_CRYPTO_OK;
}
cryptoCheck
变量用于存储加密操作的状态。Boot_ResponseAddress
被设置为Boot_ResponseAddressRom
,一个用于存储启动响应的地址。cryptoInitBootParameters
函数用于加载 CMAC(Cipher-based Message Authentication Code,基于密码的消息认证码)密钥和子密钥。hsmCfgData.BootOptions
包含了启动选项,如果设置了立即释放 TriCore 标志,则会检查时钟控制单元是否更改过默认设置,并执行相应的操作。clockWasSet
变量用于指示时钟是否已经被更改过。如果是,将调用CCU_RampDownSystemPll
函数降低 PLL0 的频率。HSM_BRIDGE->HSM2HTF.U = 1;
这行代码释放了 AURIX CPU0,允许它执行用户代码。CmacOnBoot
函数用于对启动代码执行 CMAC 操作,以验证其完整性。- 最后,函数返回
E_CRYPTO_OK
表示成功,但在实际应用中,应根据cryptoCheck
的实际值返回。