基于52x平台的安全方案
基于52x平台的安全方案主要包含安全启动, 安全隔离,安全存储,安全升级四个部分。下面对安全启动和安全隔离做详细说明。
安全启动
安全启动是从软硬件层面对产品的部分系统组件进行保护,防止攻击者对系统关键部分进行读写破坏,以达到对产品的商业保密、知识产权保护的目的。 52x安全启动任务执行在bootloader中,bootloader启动的时候会从efuse中读取rootkey、uid,对image进行解密和数字签名校验,验证通过后跳转执行 image程序,校验失败会打印失败信息,进入异常处理。
安全启动的要求
应从不可变的代码开始执行启动。
应有对可信系统组件、可信应用组件或其他安全子系统的签名验证机制。
应有对可信系统组件、可信应用组件或其他安全子系统的完整性验证机制,防止非法篡改等。
应具备安全启动过程的异常处理机制。
安全启动密钥
安全启动使用的密钥和数据有:
rootkey:保存于efuse的不可读区域BANK3中,用于加密image密钥。
uid: 芯片id,保存于efuse的可读区域BANK0,作为nonce用于image key的解密。
image key:随机产生的32字节数据,经rootkey加密后保存于ftab,用于image解密。
RSA数字签名公钥:保存于ftab中,用于验证image身份和完整性。
RSA公钥哈希值:rsa数字签名公钥的哈希值,前8字节保存于efuse可读区域BANK0,作为nonce用于image的解密以及验证RSA数字签名公钥完整性。
image 哈希值数字签名:将image明文哈希计算,用RSA密钥对哈希值进行数字签名所得,保存于ftab中,用于验证image身份和完整性。
安全启动加载流程
获取image header,通过flag判断是否是加密的image。
rsa公钥哈希校验,从ftab中读取rsa公钥,进行哈希计算得到哈希值,从efuse中读取sig_hash,两者进行比对校验,校验成功继续往下走,校验失败进入异常处理。
判断是否在flash里执行image程序。
如果在flash里运行,先从ftab中读取加密的image key,从efuse中读取rootkey和uid,对image key解密,再将解密后的imagekey配置到flash xip中。
如果在ram中执行,先从ftab中读取加密的image key,从efuse中读取rootkey和uid,对image key解密,再将加密的image拷贝到ram中,使用image key 对加密的image进行解密。
对image的数字签名和完整性进行校验,校验成功跳转到执行image程序,校验失败进入异常处理。数字签名是通过对明文的image进行哈希计算,并将计算后的哈希值 用rsa私钥进行数字签名而产生,签名后的值写入到ftab中。
数字签名和完整性验证分为以下几个步骤:
将解密后的image做哈希计算,得到image哈希值。
从ftab中读取数字签名数据值及rsa数字签名公钥。
调用mbedtls库提供的verify接口,将image哈希值、数字签名数据值、数字签名公钥传入进行校验,返回值非零校验失败,返回值零校验成功。
安全启动流程图
安全启动代码
代码路径: $SDK_ROOT\example\boot_loader\project\butterflmicro\board
secboot.c :image数字签名完整性验证,secboot异常处理。
efuse.c :efuse读取,加密的imagekey和加密的image的解密。
main.c :image拷贝加载。
rsa数字签名公钥哈希校验代码:
int sifli_hash_calculate(uint8_t *in, uint32_t in_size, uint8_t *out, uint8_t algo)
{
int last, i;
if (!in || !in_size || !out || algo > 3)
return -1;
HAL_HASH_reset();
HAL_HASH_init(NULL, algo, 0);
if (in_size > SPLIT_THRESHOLD)
{
for (i = 0; i < in_size; i += SPLIT_THRESHOLD)
{
last = (i + SPLIT_THRESHOLD >= in_size) ? 1 : 0;
if (i > 0)
{
HAL_HASH_reset();
HAL_HASH_init((uint32_t *)out, algo, last ? i : 0);
}
HAL_HASH_run(&in[i], last ? in_size - i : SPLIT_THRESHOLD, last);
HAL_HASH_result(out);
}
HAL_HASH_result(out);
}
else
{
HAL_HASH_run(in, in_size, 1);
HAL_HASH_result(out);
}
return 0;
}
int sifli_hash_verify(uint8_t *data, uint32_t data_size, uint8_t *hash, uint32_t hash_size)
{
uint8_t hash_out[32] = {0};
if (!data || !hash)
return -1;
if (sifli_hash_calculate(data, data_size, hash_out, HASH_ALGO_SHA256))
return -1;
if (memcmp(hash_out, hash, hash_size))
return -1;
return 0;
}
int sifli_sigkey_pub_verify(uint8_t *sigkey, uint32_t key_size)
{
uint32_t size = 0;
uint8_t sigkey_hash[DFU_SIG_HASH_SIZE] = {0};
size = sifli_hw_efuse_read(EFUSE_ID_SIG_HASH, sigkey_hash, DFU_SIG_HASH_SIZE);
if (size == DFU_SIG_HASH_SIZE)
return sifli_hash_verify(sigkey, key_size, sigkey_hash, DFU_SIG_HASH_SIZE);
else
return -1;
}
image key解密代码:
int sifli_hw_dec_key(uint8_t *in_data, uint8_t *out_data, int size)
{
uint8_t *uid;
uint8_t *key = NULL;
uid = &g_uid[0];
sifli_hw_efuse_read(EFUSE_UID, uid, DFU_UID_SIZE);
HAL_AES_init((uint32_t *)key, DFU_KEY_SIZE, (uint32_t *)uid, AES_MODE_CBC);
HAL_AES_run(AES_DEC, in_data, out_data, DFU_KEY_SIZE);
return 0;
}
image解密代码:
int sifli_hw_dec(uint8_t *key, uint8_t *in_data, uint8_t *out_data, int size, uint32_t init_offset)
{
uint32_t offset = 0;
static uint8_t temp[AES_BLOCK_SIZE];
memset(temp, 0, AES_BLOCK_SIZE);
while (offset < size)
{
int len = (size - offset) < AES_BLOCK_SIZE ? (size - offset) : AES_BLOCK_SIZE;
memcpy(temp, in_data + offset, len);
HAL_AES_init((uint32_t *)key, DFU_KEY_SIZE, (uint32_t *)dfu_get_counter(init_offset + offset), AES_MODE_CTR);
HAL_AES_run(AES_DEC, temp, out_data + offset, len);
offset += len;
}
return offset;
}
image哈希数字签名完整性校验代码:
int sifli_img_sig_hash_verify(uint8_t *img_hash_sig, uint8_t *sig_pub_key, uint8_t *image, uint32_t img_size)
{
uint8_t img_hash[32] = {0};
mbedtls_pk_context pk;
/*1.calculate image hash*/
if (sifli_hash_calculate(image, img_size, img_hash, HASH_ALGO_SHA256))
return -1;
/*2.verify image hash digital signature*/
mbedtls_pk_init(&pk);
if (mbedtls_pk_parse_public_key(&pk, sig_pub_key, DFU_SIG_KEY_SIZE))
return -1;
mbedtls_rsa_set_padding((mbedtls_rsa_context *)pk.pk_ctx, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_SHA256);
if (mbedtls_pk_verify(&pk, MBEDTLS_MD_SHA256, img_hash, DFU_IMG_HASH_SIZE, img_hash_sig, DFU_SIG_SIZE))
return -1;
return 0;
}
安全启动异常处理代码:
void sifli_secboot_exception(uint8_t excpt)
{
char *err = NULL;
switch (excpt)
{
case SECBOOT_SIGKEY_PUB_ERR:
err = "secboot sigkey pub err!";
boot_uart_tx(hwp_usart1, (uint8_t *)err, strlen(err));
break;
case SECBOOT_IMG_HASH_SIG_ERR:
err = "secboot img hash sig err!";
boot_uart_tx(hwp_usart1, (uint8_t *)err, strlen(err));
break;
default:
err = "secboot excpt null!";
boot_uart_tx(hwp_usart1, (uint8_t *)err, strlen(err));
break;
}
HAL_sw_breakpoint();
}
安全加密image生成
产生加密image的脚本路径:$SDK_ROOT\example\security\hcpu\project\eh-lb525\secboot
加密脚本默认使用目录secboot/sifli01下的密钥。 产生加密image使用的密钥和数据有:
rootkey:32字节的密钥,由客户提供,用于加密image key。
uid: 芯片id,芯片出厂时烧写到efuse BANK0中,在运行加密脚本之前需要先从efuse中读取uid,并保存成uid.bin文件, 作为nonce用于image key的加密。
image key:随机产生的32字节数据,加密前用于image的加密,加密后保存到ftab中。
RSA数字签名私钥:由客户提供,用于对image 哈希值进行数字签名。
RSA数字签名公钥:由客户提供,保存到ftab中。
RSA数字签名公钥哈希值:对RSA数字签名公钥做哈希计算所得,前8字节最为nonce用于image的加密。
产生加密image需要用到的文件:
build/bf0_ap.bin 编译生成的明文未加密的image。
build/ftab/ftab.bin 编译生成的ftab。
工程目录下运行加密脚本:
./secboot/gen_sec_img.bat
脚本执行成功后会在secboot目录下创建目录image_sec目录,image_sec目录下保存着加密后的image(image_sec.bin)和新生成的ftab(ftab_sec.bin)。
工程目录下执行下载脚本:
./secboot/uart_download.bat
开发板按reset键,执行脚本执行uart_download.bat
,执行成功后会将 bootloader.bin, image_sec.bin, ftab_sec.bin 下载到flash中。
安全隔离
sf52x安全隔离方案介绍
sf52x安全方案是基于secu管理模块设计,sf52x包含两个secu模块,secu1, secu2分别位于大核系统和小核系统,通过配置secu模块可以管理各个 master和slave外设的安全属性,这里的master是指具有主动发出访问请求的设备,如cpu、dma等,slave是接收访问请求的设备,如ptc、dma、efuse等。
sf52x安全方案将小核作为安全核,一直运行于安全状态,大核运行于安全和非安全两种状态,将NMI中断作为大核安全的入口,当需要运行安全任务的时候, 由大核向小核发送请求(进入安全状态),小核经过检查校验后,设置大核为安全状态,同时触发大核的NMI中断,大核进入NMI中断函数,访问安全代码区域, 安全任务运行完成后大核将自己设置为非安全状态,退出安全代码区域。当大核为非安全状态时是无法访问安全区域的代码的,也无法访问小核系统中的安全代码区域。
sf52x安全隔离流程可参考下图:
sf52x安全方案软件介绍
sf52x安全方案代码是基于sdk2.1.4开发,代码路径在 $SDK_ROOT\middleware\security\sf52x 目录下,分为hcpu和lcpu两部分,hcpu主要实现了大小核安全环境的配置, 大核安全代码的管理及安全代码的触发接口;lcpu主要负责大核是否进入安全状态的审核以及NMI中断的触发。
hcpu目录下 security.c 中实现了大小核安全环境的建立,包含了配置大核安全代码区域,配置小核系统进入安全模式,大核退出安全模式的接口, 大小核安全共享数据的写入等。大核安全代码区域包含了两个部分,第一部分就是安全代码区,存放代码及数据;另外一部分是大小核安全数据的共享区, 这部分小核随时可以访问,大核只有处于安全状态下才可以访问。大核安全代码区域的大小可以配置,目前默认配置了100kb的ram空间,位于sram2上, 其中安全数据的共享区固定占用128字节,不需要修改大小。
sec_entry.c 中实现了安全代码的中断入口,安全代码全部在NMI中断中运行,安全代码的接口也是添加到这个文件中,所有的安全代码包含此文件中的代码都会被链接到安全代码区域。
sec_task.c 实现了安全任务的触发接口sec_task_enter()
,hcpu在非安全模式下想要切换到安全模式运行安全代码需要调用此接口,此接口在多线程中调用是互斥的,
先获取到互斥量的线程优先运行安全区代码。此接口是阻塞式的调用,只有当安全代码运行完成后才会退出。
Lcpu目录下 security.c 中实现了hcpu进入安全模式的仲裁,当收到hcpu进入安全模式的请求时,首先会检查hcpu vector寄存器和中断向量表中NMI中断函数入口的值是否被篡改, 如果被篡改过则会拒绝hcpu进入安全模式的请求,如果没有被篡改则会设置hcpu为安全模式,并触发hcpu的NMI中断。
encrypt_demo目录下是安全代码样例,简单实现了几个安全接口,比如随机数的产生,aes加密,哈希计算。
sf52x安全隔离软件接口使用
1.如何添加安全代码到安全保护区域
安全代码的添加可以参考 $SDK_ROOT\security\sf52x\hcpu\sec_entry.c 中的55到57行,第一步根据安全代码接口的功能为每个接口定义SEC_ENTRY_IDX_XX,id号是枚举类型,
在sec_entry.h中定义。
第二步编写安全入口函数,如int sec_entry_xxx(void *arg),此函数里解析传递过来的参数,调用对应的安全代码接口。
第三步,修改链接文件,将添加的安全代码链接到安全保护区域。连接文件在目录_$SDK_ROOT\example\security\hcpu\project\eh-lb525\linker_scripts_ 中,打开 link_flash.sct ,
找到NMI_SEC_AREA,将安全代码添加到段中。
2.hcpu如何进入安全状态运行安全代码
Hcpu进入安全状态运行安全代码的接口在 sec_task.c 中int sec_task_enter(sec_task_list_t * sec_tls)
。接口的使用可以参考 security_demo.c 中的sec_demo1
,
首先要准备好安全接口运行所需要的参数,将安全接口的ID和对应的参数填写到sec_task_list
中,注意要填写总共需要运行的接口数量task_cnt
,sec_task_list
中最多
可以填写32个接口,自己可扩展,最后调用sec_task_enter()
,sec_task_list
中的安全接口会按照填入的顺序依次执行。
注意安全代码的执行是在NMI中断中进行的,NMI中断的优先级比较高,不会被其他的中断打断,所以NMI中断的执行时间不宜过长,中断中最好不要加打印,不要调用睡眠, 长时间延迟的函数。
触发进入安全模式运行安全代码的例子:
static struct trng_gen_arg trng;
static struct hash_encrypt_arg hash;
static struct aes_encrypt_arg aes;
static sec_task_list_t sec_tls;
/*exe sec task, contain SEC_ENTRY_ID1_T/SEC_ENTRY_ID2_T/SEC_ENTRY_ID3_T*/
static void sec_demo1(uint8_t argc, char **argv)
{
/*1.generate 1kb random num*/
rt_memset(&trng, 0, sizeof(struct trng_gen_arg));
trng.random = rt_calloc(1, 1024);
trng.random_num = 1024 / 4;
/*prepare sec task list*/
sec_tls.task[0].id = SEC_ENTRY_ID1_RTNG_GEN;
sec_tls.task[0].arg = &trng;
sec_tls.task_cnt = 1;
/*exe sec task, it will block in this function until sec task exe done*/
sec_task_enter(&sec_tls);
if (trng.complete)
{
LOG_I("trng generate complete");
for (int i = 0; i < 256; i++)
{
LOG_I("0x%x ", trng.random[i]);
}
}
/*2.aes encrypt 1kb random number*/
rt_memset(&aes, 0, sizeof(struct aes_encrypt_arg));
aes.aes_mode = AES_MODE_CTR;
aes.input = (uint8_t *)trng.random;
aes.size = 1024;
aes.output = rt_calloc(1, aes.size);
sec_tls.task[0].id = SEC_ENTRY_ID3_AES_ENC;
sec_tls.task[0].arg = &aes;
sec_tls.task_cnt = 1;
sec_task_enter(&sec_tls);
if (aes.complete)
{
LOG_I("aes encrypt complete");
for (int i = 0; i < 256; i++)
{
LOG_I("0x%x ", ((uint32_t *)aes.output)[i]);
}
}
/*3.hash 1kb aes encrypt data*/
rt_memset(&hash, 0, sizeof(struct hash_encrypt_arg));
hash.input = (uint8_t *)aes.output;
hash.in_size = 1024;
hash.output = rt_calloc(1, HASH_SHA256_SIZE);
hash.algo = HASH_ALGO_SHA256;
sec_tls.task[0].id = SEC_ENTRY_ID2_HASH_ENC;
sec_tls.task[0].arg = &hash;
sec_tls.task_cnt = 1;
sec_task_enter(&sec_tls);
if (hash.complete)
{
LOG_I("hash encrypt complete");
for (int i = 0; i < 8; i++)
{
LOG_I("0x%x ", ((uint32_t *)hash.output)[i]);
}
}
/*4.write data to nand flash, Write 2kb at most at one time*/
rt_nand_erase(FLASH_SEC_DATA_START_ADDR, 1024 * 128);
rt_nand_write_page(FLASH_SEC_DATA_START_ADDR, (uint8_t *)trng.random, 1024, NULL, 0);
rt_nand_write_page(FLASH_SEC_DATA_START_ADDR + SPI_NAND_PAGE_SIZE, aes.output, 1024, NULL, 0);
rt_nand_write_page(FLASH_SEC_DATA_START_ADDR + SPI_NAND_PAGE_SIZE * 2, hash.output, 32, NULL, 0);
/*5.read data from nand flash and check*/
uint8_t *random_data = rt_calloc(1, 1024);
rt_nand_read_page(FLASH_SEC_DATA_START_ADDR, random_data, 1024, NULL, 0);
if (rt_memcmp((void *)trng.random, (void *)random_data, 1024))
{
LOG_E("read random_data compare err");
}
else
{
LOG_E("read random_data compare succ");
}
uint8_t *aes_data = rt_calloc(1, 1024);
rt_nand_read_page(FLASH_SEC_DATA_START_ADDR + SPI_NAND_PAGE_SIZE, aes_data, 1024, NULL, 0);
if (rt_memcmp((void *)aes.output, (void *)aes_data, 1024))
{
LOG_E("read aes_data compare err");
}
else
{
LOG_E("read aes_data compare succ");
}
uint8_t *hash_data = rt_calloc(1, 32);
rt_nand_read_page(FLASH_SEC_DATA_START_ADDR + SPI_NAND_PAGE_SIZE * 2, hash_data, 32, NULL, 0);
if (rt_memcmp((void *)hash.output, (void *)hash_data, 32))
{
LOG_E("read hash_data compare err");
}
else
{
LOG_E("read hash_data compare succ");
}
/*6.free buffer*/
rt_free((void *)trng.random);
rt_free((void *)aes.output);
rt_free((void *)hash.output);
rt_free(random_data);
rt_free(aes_data);
rt_free(hash_data);
}