52x Platform Security Solution
The security solution based on the 52x platform mainly includes four components: secure boot, security isolation, secure storage, and secure upgrade. The following provides detailed explanations of secure boot and security isolation.
Secure Boot
Secure boot protects certain system components of the product from both software and hardware perspectives, preventing attackers from reading or writing to critical system parts, thereby achieving commercial confidentiality and intellectual property protection purposes. The 52x secure boot task executes in the bootloader. When the bootloader starts, it reads the rootkey and uid from efuse, decrypts and verifies the digital signature of the image. If verification passes, it jumps to execute the image program. If verification fails, it prints failure information and enters exception handling.
Secure Boot Requirements
Should start execution from immutable code.
Should have signature verification mechanisms for trusted system components, trusted application components, or other security subsystems.
Should have integrity verification mechanisms for trusted system components, trusted application components, or other security subsystems to prevent illegal tampering.
Should have exception handling mechanisms for the secure boot process.
Secure Boot Keys
The keys and data used in secure boot include:
rootkey: Stored in the unreadable area BANK3 of efuse, used to encrypt the image key.
uid: Chip ID, stored in the readable area BANK0 of efuse, used as nonce for image key decryption.
image key: Randomly generated 32-byte data, encrypted with rootkey and stored in ftab, used for image decryption.
RSA digital signature public key: Stored in ftab, used to verify image identity and integrity.
RSA public key hash value: Hash value of the RSA digital signature public key, with the first 8 bytes stored in efuse readable area BANK0, used as nonce for image decryption and RSA digital signature public key integrity verification.
image hash value digital signature: Digital signature obtained by calculating the hash of the image plaintext and signing the hash value with RSA private key, stored in ftab, used to verify image identity and integrity.
Secure Boot Loading Process
Get image header, determine if it’s an encrypted image through flag.
RSA public key hash verification: Read RSA public key from ftab, calculate hash to get hash value, read sig_hash from efuse, compare the two for verification. If verification succeeds, continue; if fails, enter exception handling.
Determine whether to execute image program in flash.
If running in flash, first read encrypted image key from ftab, read rootkey and uid from efuse, decrypt image key, then configure the decrypted imagekey to flash xip.
If executing in RAM, first read encrypted image key from ftab, read rootkey and uid from efuse, decrypt image key, then copy encrypted image to RAM and decrypt the encrypted image using image key.
Verify the digital signature and integrity of the image. If verification succeeds, jump to execute image program; if fails, enter exception handling. The digital signature is generated by calculating the hash of the plaintext image and digitally signing the calculated hash value with RSA private key, then writing the signed value to ftab.
Digital signature and integrity verification consists of the following steps:
Calculate hash of the decrypted image to get image hash value.
Read digital signature data value and RSA digital signature public key from ftab.
Call the verify interface provided by mbedtls library, pass in image hash value, digital signature data value, and digital signature public key for verification. Non-zero return value indicates verification failure, zero return value indicates verification success.
Secure Boot Flowcharts
Secure Boot Code
Code path: $SDK_ROOT\example\boot_loader\project\butterflmicro\board
secboot.c: Image digital signature integrity verification, secboot exception handling.
efuse.c: Efuse reading, encrypted imagekey and encrypted image decryption.
main.c: Image copy loading.
RSA digital signature public key hash verification code:
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 decryption code:
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 decryption code:
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 hash digital signature integrity verification code:
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;
}
Secure boot exception handling code:
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();
}
Secure Encrypted Image Generation
Script path for generating encrypted images: $SDK_ROOT\example\security\hcpu\project\eh-lb525\secboot
The encryption script uses keys from the default directory secboot/sifli01. Keys and data used for generating encrypted images include:
rootkey: 32-byte key provided by customer, used to encrypt image key.
uid: Chip ID, burned to efuse BANK0 during chip factory production. Before running encryption script, uid must be read from efuse and saved as uid.bin file, used as nonce for image key encryption.
image key: Randomly generated 32-byte data, used for image encryption before encryption, stored in ftab after encryption.
RSA digital signature private key: Provided by customer, used to digitally sign image hash values.
RSA digital signature public key: Provided by customer, stored in ftab.
RSA digital signature public key hash value: Hash calculated from RSA digital signature public key, with first 8 bytes used as nonce for image encryption.
Files needed for generating encrypted images:
build/bf0_ap.bin Plaintext unencrypted image generated by compilation.
build/ftab/ftab.bin Ftab generated by compilation.
Run encryption script in project directory:
./secboot/gen_sec_img.bat
After successful script execution, an image_sec directory will be created in the secboot directory, containing the encrypted image (image_sec.bin) and newly generated ftab (ftab_sec.bin).
Execute download script in project directory:
./secboot/uart_download.bat
Press the reset button on the development board, execute the script uart_download.bat
. After successful execution, bootloader.bin, image_sec.bin, ftab_sec.bin will be downloaded to flash.
Security Isolation
sf52x Security Isolation Solution Introduction
The sf52x security solution is designed based on the secu management module. sf52x contains two secu modules, secu1 and secu2, located in the big core system and small core system respectively. By configuring the secu modules, the security attributes of various master and slave peripherals can be managed. Here, master refers to devices that actively initiate access requests, such as CPU, DMA, etc., while slave refers to devices that receive access requests, such as PTC, DMA, efuse, etc.
The sf52x security solution uses the small core as the security core, always running in secure state, while the big core runs in both secure and non-secure states. The NMI interrupt serves as the entry point for big core security. When security tasks need to be executed, the big core sends a request to the small core (to enter secure state). After inspection and verification by the small core, it sets the big core to secure state and triggers the big core’s NMI interrupt. The big core enters the NMI interrupt function, accesses the secure code area, and after the security task completes, the big core sets itself to non-secure state and exits the secure code area. When the big core is in non-secure state, it cannot access code in the secure area, nor can it access the secure code area in the small core system.
The sf52x security isolation process can be referenced in the following diagram:
sf52x Security Solution Software Introduction
The sf52x security solution code is developed based on sdk2.1.4, with code path in $SDK_ROOT\middleware\security\sf52x directory, divided into hcpu and lcpu parts. hcpu mainly implements the configuration of big and small core security environments, management of big core security code, and security code trigger interfaces; lcpu is mainly responsible for auditing whether the big core enters secure state and triggering NMI interrupts.
security.c in the hcpu directory implements the establishment of big and small core security environments, including configuration of big core secure code area, configuration of small core system entering secure mode, big core exiting secure mode interfaces, and writing of big and small core security shared data. The big core secure code area contains two parts: the first part is the secure code area, storing code and data; the other part is the shared area for big and small core security data, which the small core can access at any time, and the big core can only access when in secure state. The size of the big core secure code area can be configured, currently defaulting to 100kb of RAM space located on sram2, with the security data shared area fixed at 128 bytes, no need to modify the size.
sec_entry.c implements the interrupt entry point for security code. All security code runs in NMI interrupts, and security code interfaces are also added to this file. All security code including code in this file will be linked to the secure code area.
sec_task.c implements the security task trigger interface sec_task_enter()
. When hcpu in non-secure mode wants to switch to secure mode to run security code, it needs to call this interface. This interface is mutually exclusive in multithreaded calls, with the thread that first obtains the mutex running security area code first. This interface is a blocking call, only exiting after security code execution completes.
security.c in the Lcpu directory implements arbitration for hcpu entering secure mode. When receiving a request for hcpu to enter secure mode, it first checks whether the hcpu vector register and NMI interrupt function entry value in the interrupt vector table have been tampered with. If tampered, it will reject the hcpu’s request to enter secure mode; if not tampered, it will set hcpu to secure mode and trigger hcpu’s NMI interrupt.
The encrypt_demo directory contains security code samples, simply implementing several security interfaces such as random number generation, AES encryption, and hash calculation.
sf52x Security Isolation Software Interface Usage
1. How to Add Security Code to Security Protection Area
Adding security code can refer to lines 55 to 57 in $SDK_ROOT\security\sf52x\hcpu\sec_entry.c. First, define SEC_ENTRY_IDX_XX for each interface according to the security code interface functionality. The ID number is an enumeration type, defined in sec_entry.h.
Second, write security entry functions, such as int sec_entry_xxx(void *arg). This function parses passed parameters and calls corresponding security code interfaces.
Third, modify the linker file to link added security code to the security protection area. The linker file is in directory $SDK_ROOT\example\security\hcpu\project\eh-lb525\linker_scripts. Open link_flash.sct, find NMI_SEC_AREA, and add security code to the section.
2. How hcpu Enters Secure State to Run Security Code
The interface for hcpu to enter secure state and run security code is in sec_task.c: int sec_task_enter(sec_task_list_t * sec_tls)
. Interface usage can refer to sec_demo1
in security_demo.c. First, prepare parameters required for security interface execution, fill the security interface ID and corresponding parameters into sec_task_list
, note to fill in the total number of interfaces to run task_cnt
. sec_task_list
can fill up to 32 interfaces, which can be extended. Finally, call sec_task_enter()
, and security interfaces in sec_task_list
will execute sequentially according to the filled order.
Note that security code execution occurs in NMI interrupts. NMI interrupts have relatively high priority and will not be interrupted by other interrupts, so NMI interrupt execution time should not be too long. It’s best not to add printing in interrupts, and not to call sleep or long-delay functions.
Example of triggering entry into secure mode to run security code:
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);
}