STM32 Bootloader开发记录 3 固件签名校验

文章目录

  • STM32 Bootloader开发记录 3 固件签名校验
    • 1. 移植mbedtls
      • 1.1 编译mbedtls
      • 1.2 修复rsa_sign的一个bug
      • 1.3 测试RSA
        • 1.3.1 **RSA加解密:**
        • 1.3.2 **RSA签名验签:**
        • 1.3.3 **生成秘钥对**
      • 1.4 移植到STM32
        • 1.4.1 移植到KEIL工程
        • 1.4.2 移植平台随机数函数
    • 2. 签名验签
      • 2.1 签名
      • 2.2 验签
      • 2.3 测试
      • 2.4 移植verify.c
        • 2.4.1 测试签名固件
        • 2.4.2 修改代码
      • 2.5 测试
        • 2.5.1 Bootloader下升级测试
        • 2.5.2 Application下升级测试

固件签名校验需要用到加解密库,在嵌入式平台,使用mbedtls库来实现加解密功能。

1. 移植mbedtls

1.1 编译mbedtls

克隆mbedtls仓库。

在linux下编译。

需要安装python,在仓库目录下执行python -m pip install -r scripts/basic.requirements.txt

mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=../libmbedtls ..
make -j12
make install

编译安装完成后,可执行文件和库被放到了,仓库目录的libmbedtls下。

我们需要测试的是rsa_decryptrsa_encryptrsa_signrsa_verify,用于固件加密和签名验签。其中源码文件是在program/pkey目录下,名称和前面的名称相对应,后续使用直接参考这些Demo文件。

在windows编译报错。后面使用了Msys2环境编译的,和linux一致。

1.2 修复rsa_sign的一个bug

使用rsa_sign进行签名时,会报如下错误:

duapple@duapple-vm:/media/data/library/mbedtls/libmbedtls/bin$ ./rsa_sign ./rsa_pub.txt . Reading private key from rsa_priv.txt. Checking the private key. Generating the RSA/SHA-256 signature failed! mbedtls_rsa_pkcs1_sign returned -0x4080

在签名时,mbedtls_rsa_pkcs1_sign直接报错了。

duapple@duapple-vm:/media/data/library/mbedtls$ findh "0x4080"
./libmbedtls/include/mbedtls/rsa.h:45:#define MBEDTLS_ERR_RSA_BAD_INPUT_DATA                    -0x4080
./include/mbedtls/rsa.h:45:#define MBEDTLS_ERR_RSA_BAD_INPUT_DATA                    -0x4080

定位到错误MBEDTLS_ERR_RSA_BAD_INPUT_DATA,出现在rsa.c中的函数mbedtls_rsa_private中:

    if( f_rng == NULL )return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );

函数指针参数为空了,导致的报错。将rsa_encrypt.c中的函数和初始化抄过来,传参即可。

diff --git a/programs/pkey/rsa_sign.c b/programs/pkey/rsa_sign.c
index f4deab029..d182f33be 100644
--- a/programs/pkey/rsa_sign.c
+++ b/programs/pkey/rsa_sign.c
@@ -35,6 +35,8 @@ int main( void )#include "mbedtls/rsa.h"#include "mbedtls/md.h"
+#include "mbedtls/ctr_drbg.h"
+#include "mbedtls/entropy.h"#include <stdio.h>#include <string.h>
@@ -131,7 +133,24 @@ int main( int argc, char *argv[] )goto exit;}-    if( ( ret = mbedtls_rsa_pkcs1_sign( &rsa, NULL, NULL, MBEDTLS_MD_SHA256,
+    mbedtls_ctr_drbg_context ctr_drbg;
+    mbedtls_entropy_context entropy;
+    const char *pers = "rsa_encrypt";
+
+    mbedtls_ctr_drbg_init( &ctr_drbg );
+    mbedtls_entropy_init( &entropy );
+
+    ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func,
+                                 &entropy, (const unsigned char *) pers,
+                                 strlen( pers ) );
+    if( ret != 0 )
+    {
+        mbedtls_printf( " failed\n  ! mbedtls_ctr_drbg_seed returned %d\n",
+                        ret );
+        goto exit;
+    }
+
+    if( ( ret = mbedtls_rsa_pkcs1_sign( &rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_MD_SHA256,32, hash, buf ) ) != 0 ){mbedtls_printf( " failed\n  ! mbedtls_rsa_pkcs1_sign returned -0x%0x\n\n", (unsigned int) -ret );
@@ -160,7 +179,8 @@ int main( int argc, char *argv[] )exit_code = MBEDTLS_EXIT_SUCCESS;exit:
-
+    mbedtls_ctr_drbg_free( &ctr_drbg );
+    mbedtls_entropy_free( &entropy );mbedtls_rsa_free( &rsa );mbedtls_mpi_free( &N ); mbedtls_mpi_free( &P ); mbedtls_mpi_free( &Q );mbedtls_mpi_free( &D ); mbedtls_mpi_free( &E ); mbedtls_mpi_free( &DP );

进入到build/program/pkey目录下,执行makemake install,重新编译和安装。

1.3 测试RSA

先复制私钥和公钥(sa_priv.txt rsa_pub.txt)到libmbedtls/bin下。

1.3.1 RSA加解密:
duapple@duapple-vm:/media/data/library/mbedtls/libmbedtls/bin$ ./rsa_encrypt "hello world". Seeding the random number generator.... Reading public key from rsa_pub.txt. Generating the RSA encrypted value. Done (created "result-enc.txt")duapple@duapple-vm:/media/data/library/mbedtls/libmbedtls/bin$ ./rsa_decrypt . Seeding the random number generator.... Reading private key from rsa_priv.txt. Decrypting the encrypted data. OKThe decrypted result is: 'hello world'
1.3.2 RSA签名验签:

解决rsa_sign.c的bug后,进行签名验签测试。

duapple@duapple-vm:/media/data/library/mbedtls/libmbedtls/bin$ ./rsa_sign rsa_pub.txt. Reading private key from rsa_priv.txt. Checking the private key. Generating the RSA/SHA-256 signature. Done (created "rsa_pub.txt.sig")duapple@duapple-vm:/media/data/library/mbedtls/libmbedtls/bin$ ./rsa_verify rsa_pub.txt. Reading public key from rsa_pub.txt. Verifying the RSA/SHA-256 signature. OK (the signature is valid)
1.3.3 生成秘钥对

mbedtls库提供的加解密和签名验签Demo测试没有问题了,我们通过openssl生成自己的公钥和私钥来进行测试。

使用openssl生成秘钥对是.pem.der格式的,需要从中提取我们需要的 N E D P Q DP DQ QP因子(ASN1格式)。

rsa_encrypt.c Demo中,我们需要将这些因子参数放到rsa_priv.txtrsa_pub.txt中。

Demo中使用秘钥对是RSA1024。这里我们使用RSA2048来测试。

参考文档。

生成RSA-2048秘钥对

openssl genrsa -out rsa_private_pkcs1_2048.pem 2048

提取RSA公钥

openssl rsa -in rsa_private_pkcs1_2048.pem  -out rsa_public_pkcs1_2048.pem -pubout -RSAPublicKey_out

(提取过程中可以转换格式,pkcs1 <=> pkcs8。)

公钥提取,得到的最后的两个十六进制表示的整型大数就是我们需要的公钥因子N E将十六进制字符串复制到rsa_pub.txt对应的参数中。

[duapple@c8016052879e bin]$ openssl asn1parse -in rsa_public_pkcs1_2048.pem 0:d=0  hl=4 l= 266 cons: SEQUENCE          4:d=1  hl=4 l= 257 prim: INTEGER           :BE29E714F51611C4F22A892658DC9A3CCF4DA5BF3F054F5EE9752901AC53BDB97BD6114C318F0C17C3EFFAE6CAB12114B98578295E18E405E1A4C151EF82794D7A9E5D171CE46BB5A5F2447175CC194D656E5A861288BD12E8235E20D932ADC7FCEF4F33342E76F7DBF5EBCA52857183485C01AD6583E773679020259A76AD7C5D13BF31D01987578DC368FDA41FDF80E88D37DA975DA7985548055A7232DD5356DE4AEF0C1BCA602943CD24654976AAC03A0577F3E04FF0BDB3680F35238267D0AA8E612155F97C0D18DED1B5B75690D5F64019BE54238BED42D31D9B742CF80EC20F54D39F582C5B26C451B3E36431B37ECE3DEA85B3BC207ADB98B226D3CD265:d=1  hl=2 l=   3 prim: INTEGER           :010001

提取RS私钥

私钥提取,得到的最后的8个十六进制表示的整形大数依次表示N E D P Q DP DQ QP将十六进制字符串复制到rsa_priv.txt对应的参数中。

[duapple@c8016052879e bin]$ openssl asn1parse -in rsa_private_pkcs1_2048.pem 0:d=0  hl=4 l=1188 cons: SEQUENCE          4:d=1  hl=2 l=   1 prim: INTEGER           :007:d=1  hl=4 l= 257 prim: INTEGER           :BE29E714F51611C4F22A892658DC9A3CCF4DA5BF3F054F5EE9752901AC53BDB97BD6114C318F0C17C3EFFAE6CAB12114B98578295E18E405E1A4C151EF82794D7A9E5D171CE46BB5A5F2447175CC194D656E5A861288BD12E8235E20D932ADC7FCEF4F33342E76F7DBF5EBCA52857183485C01AD6583E773679020259A76AD7C5D13BF31D01987578DC368FDA41FDF80E88D37DA975DA7985548055A7232DD5356DE4AEF0C1BCA602943CD24654976AAC03A0577F3E04FF0BDB3680F35238267D0AA8E612155F97C0D18DED1B5B75690D5F64019BE54238BED42D31D9B742CF80EC20F54D39F582C5B26C451B3E36431B37ECE3DEA85B3BC207ADB98B226D3CD268:d=1  hl=2 l=   3 prim: INTEGER           :010001273:d=1  hl=4 l= 256 prim: INTEGER           :6E57CA2AB2FC175B7C598A33A2247FAB016F40D9F42B949EB240B586AF03F877049076810BD0C16798DE4B6C57F8E8052A9173E42D21AC4E6D5C3880DEC58AB5968D05DF3F789BB74F8F807F85A6E497B05F944F6FB9C05C942ED193A5F46E3A395E734A76E0EF4F6B670B66DEF367A691FA314EF834CDBC7D3F7827D9B53B7C896AF0390694AAA94FCC286E3C9549D638E0E2915B37E5768A0865407F113E1236F0708CC82A728D74A912815529A6C865FC983AF68BF273233BE48F0429CD6D10AEB466C985D428BD5F7EEFBBF4418AD4AA120ABDA33CBB96D6DA6908510FE9EB911526E47DEB8C4DCCC69A5D373ACBB4A1D791BB27C4205F308CFF2387EF01533:d=1  hl=3 l= 129 prim: INTEGER           :EF7A981DE622FE9661A690FD50BA9836A70B53369C155362B4CFAABFEFCF1F95C0D8CC19112ACB4C092B28636A124471E3375D904311F97779DDDB03BA24456C80DD5D5288EEE3E67480724F8F70ABBB8A625FDFBCD847711FFB320BC50FF988230080E25A843752D2C420ABFAA979F7089D7546AE9D8EDC39FA154F20D7425D665:d=1  hl=3 l= 129 prim: INTEGER           :CB485BE8EB30B75A51CD23015799798360B8EFCCE63E635A70AE524DCA68B710F264566FB4657E56B8F2629A87F219BB2FF7A78ED689EF66BA3DB54146DC1D301A37A0FC1549D467072F67883051F09BCA43FD995BBB48FB07BFFC9FC10CFB2A5EE0C2B6B244840243D8F4C24497A64C55F7A46483B66305BCF97D49F7CDA031797:d=1  hl=3 l= 129 prim: INTEGER           :B069A9216D651E127DC10C51EF6465B8ECF944C597D985E1D2BCA056603C3E523D0BF7DE076D74F18057909B1B8A756D482A82FC40CC3EEFAA90AA44669D4D9F0E9703A647456411628534B0334496DDC2A59166EA6090D05EF4D6FB20A211FA1D7EC372FBA7267216DD700C00CD54118D0121617E4486F6E8A6B787E0F371E9929:d=1  hl=3 l= 129 prim: INTEGER           :BCA9281BB0032377A08988DE5B0565FFE7095C0CC6F6C9D08AD94E2B421A1324984144F320F23CE1917DEAC34E3E036175701BC2F61B47E3081FF31365072DD391D16507EF5DF8D1B19A6E34B7DBA43981EBBDF5D16BAC9CC5A1897C5E7784B6DB1D2FB8CD13C78C2E99B0F6872053476F5588ED7C4C0DF4AA60C5C59B44EC511061:d=1  hl=3 l= 128 prim: INTEGER           :35D3A7D1B16ED3FC6539837D5EF81E82165D07A867C1E14A81B1891CAF4E0A76AAC122D03C0058E81C394B305D04FF72B2855D2826640C2A6A1A7CDC94BD21121323B05A32C65FBF8FCE3268BC1FA9E310B9B006ED4B59119F2C06B7A8CC0D09824A703F9DF0C281A16B401D7DA651158CC779B48E0B7631D365E9FDF1157BF6

由于Demo中默认的秘钥对加密长度是1024,我们设计用的2048,可以发现,这些参数长度直接是翻了一倍。

修改完成后,重新测试。

duapple@duapple-vm:/media/linuxdata/library/mbedtls/libmbedtls/bin$ ./rsa_encrypt "hello world". Seeding the random number generator.... Reading public key from rsa_pub.txt. Generating the RSA encrypted value. Done (created "result-enc.txt")duapple@duapple-vm:/media/linuxdata/library/mbedtls/libmbedtls/bin$ ./rsa_decrypt . Seeding the random number generator.... Reading private key from rsa_priv.txt. Decrypting the encrypted data. OKThe decrypted result is: 'hello world'

此时修改D P Q 参数中的任意一个,再次测试,会发现解密不成功。

1.4 移植到STM32

1.4.1 移植到KEIL工程

参考链接: https://blog.csdn.net/qq153471503/article/details/109461794

复制mbedtls仓库中的configsincludelibrary目录到KEIL工程目录下。

并在KEIL中添加library源文件和include头文件路径。

复制configs/config-entropy.hmbedtls/include/mbedtls 目录下,并重命名为mbedtls_config.h,在这个文件中添加#define MBEDTLS_MD5_C。在main中添加测试代码,然后重新编译KEIL的Bootloader工程。

#include "mbedtls/md5.h"int main()
{int i, ret;uint8_t digest[16];char str[] = "Hello, world!";printf("\r\n MD5('%s') = ", str);if ((ret = mbedtls_md5((uint8_t *)str, 13, digest)) != 0 ){printf("MD5 caculate failed!\r\n");}else {for (i = 0; i < 16; i++) {printf("%02x", digest[i]);}}printf("\r\n");
}

烧录编译了mbedtls的固件到开发板:

1.4.2 移植平台随机数函数

参考链接:https://blog.51cto.com/u_13640625/4905753

先在CubeMX中开机RNG外设,使用STM32提供的硬件RNG生成器。

mbedtls_config.h 中配置MBEDTLS_ENTROPY_HARDWARE_ALT宏定义,使用用户实现的mbedtls_hardware_poll 函数。

int mbedtls_hardware_poll(void *Data, unsigned char *Output, size_t Len, size_t *oLen)
{uint32_t i;uint32_t random;for (i = 0; i  < Len / 4; i++){if (HAL_RNG_GenerateRandomNumber(&hrng, &random) == HAL_OK){*oLen += 4;memset(&(Output[i * 4]), (int )random, 4);}else {return -1;}}return 0;
}

使用entropyctr_drbg还需要配置其它一些宏在mbedtls_config.h中。

//#define MBEDTLS_HAVE_TIME
#define MBEDTLS_MD5_C
#define MBEDTLS_ENTROPY_HARDWARE_ALT
//#define MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES
#define MBEDTLS_NO_PLATFORM_ENTROPY
#define MBEDTLS_ENTROPY_C
#define MBEDTLS_CTR_DRBG_C
#define MBEDTLS_ENTROPY_FORCE_SHA256

需要注释掉#define MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES,否则会报错mbedtls_ctr_drbg_seed seed failed: 00000034

/** The entropy source failed. */
#define MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED        -0x0034

示例代码:

 uint8_t buffer[10];char pers[] =  "crbg_test";mbedtls_entropy_context entropy;mbedtls_ctr_drbg_context ctr_drbg;mbedtls_entropy_init(&entropy);mbedtls_ctr_drbg_init(&ctr_drbg);if ( (ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers,strlen(pers))) != 0) {printf("mbedtls_ctr_drbg_seed seed failed: %08x\r\n", -ret);}else {printf(" generate random data.\r\n");if ( (ret = mbedtls_ctr_drbg_random(&ctr_drbg, buffer, sizeof(buffer))) != 0 ){printf("mbedtls_ctr_drbg_random failed\r\n");}else {printf("random data: ");for (i = 0; i < sizeof(buffer); i++){printf("%02x ", buffer[i]);}printf("\r\n");}}mbedtls_ctr_drbg_free(&ctr_drbg);mbedtls_entropy_free(&entropy);

编译烧录,多次重启开发板,可以看出得到的随机数是不一样的。

2. 签名验签

在KEIL生成固件后,我们需要使用私钥对固件的数据摘要进行加密,俗称签名。然后,然后将签名的数据添加到固件中。在升级时,接收完升级包固件,对固件中的签名字段进行解密,再计算原始固件的数据摘要,如果解密得到的摘要信息和计算得到的摘要信息一致,则说明固件来源可信并且完整性是OK的。

在固件头部,添加一段数据来放置签名信息。我们使用mbedtls计算固件的摘要和对摘要进行加密,再将加密的摘要信息写入到固件头部。

参考代码

2.1 签名

先得到私密的ASN1格式文本:

openssl asn1parse -in rsa_private_pkcs1_2048.pem > rsa_private_pkcs1_2048.txt

再从文本rsa_private_pkcs1_2048.txt中获取私钥的N E D P Q DP DQ QP参数。将这些参数作为字符串替换到rsa_sign.c Demo中,使用mbedtls_mpi_read_string函数从字符串中获取参数。

有了私钥,就可以使用SHA256计算固件摘要了。计算得到HASH值,使用私钥进行加密,得到签名数据,存到新固件的前2k字节空间中,2k字节空间之后的空间,再存放原始固件。

RSA2048加密后的摘要信息也只有256字节,使用2048字节Flash空间来保存是为了拷贝固件的时候方便跳过整页。

2.2 验签

获取公钥的ASN1格式文本:

openssl asn1parse -in rsa_public_pkcs1_2048.pem > rsa_public_pkcs1_2048.txt

同样的方式,从这个文本中获取到公钥参数N E。由于这部分的代码最终要跑在单片机上,所以直接从文件中读取参数不适用了,这里写段代码,从rsa_public_pkcs1_2048.txt中读取参数并将参数生成.h文件。生成的.h 文件直接编译。

static int read_public_keys_arg_from_file(const char *public_file)
{FILE *f = fopen(public_file, "r");if (f == NULL) {perror(public_file);return -1;}char buffer[8192];int i;int num;char **str_list;for (i = 0; i < 1; i++) {fgets(buffer, sizeof(buffer) - 1, f);}for (i = 0; i < 2; i++){fgets(buffer, sizeof(buffer) - 1, f);str_list = strs_split(buffer, ":", &num);if (str_list != NULL && num == 4) {strs_trim(str_list[num-1], "\n");strs_trim(str_list[num-1], "\r");strncpy(key_args[i], str_list[num - 1], sizeof(key_args[i]) - 1);// printf("key_args[%d]: %s\r\n", i, key_args[i]);} else {fclose(f);strs_free2(str_list, num);return -1;}strs_free2(str_list, num);}fclose(f);keys_args_print(2);return 0;
}static char public_key_args_fmt[] = ""
"#ifndef __PUBLIC_KEY_H\r\n#define __PUBLIC_KEY_H\r\n\n"
"static char public_key_args_N[] = \"%s\";\r\n"
"static char public_key_args_E[] = \"%s\";\r\n"
"\r\n#endif\r\n";static int gen_public_key_args()
{FILE *f = fopen("public_key.h", "w+");if (f == NULL) {perror("public_key.h");return -1;}fprintf(f, public_key_args_fmt, key_args[0], key_args[1]);fclose(f);    return  0;
}

修改rsa_verify.c,直接使用上面生成public_key.h中的参数。并且从给定文件的前2k字节空间中,读取签名信息。再对2k字节后的固件进行SHA256计算,最后再对读取的签名信息和计算得到的HASH值进行校验。

2.3 测试

签名

./sign firmware6.bin rsa_private_pkcs1_2048.txt

验签

./verify firmware6_signed.bin

2.4 移植verify.c

使用签名后的固件作为OTA固件,只需要将verify.c移植到KEIL工程中,固件签名校验功能就算是完成了。最后发现,发现验签代码根本用不到随机数生成器,相当于是白移植了随机数函数。

参考代码

2.4.1 测试签名固件

修改Bootloader

修改Bootloader中的APP1的启动地址,修改为向后便宜2K字节。然后将签名后的固件烧录到APP1分区中。这里使用Bootloader升级的方式将固件下载到开发板中。测试签名固件是否能启动。

#pragma anon_unions
#define APP1_START_ADDR      (0x8010800)#define OTA_SOF (0X55)
#define OTA_EOF  (0XCC)

修改Application

需要修改Application的中断向量表的起始地址为0x8010800

并修改system_stm32l4xx.c中的中断向量表的偏移地址为0x00010800U

重新编译工程。

签名

编译Application工程得到firmware.bin固件,使用sign签名:

./sign.exe firmware.bin rsa_private_pkcs1_2048.txt

得到添加了签名信息后的固件firmware_signed.bin

下载测试

下载使用dfu_server程序进行验证。

.\dfu_server.exe G:\stm32\stm32_boot\sign_tool\firmware_signed.bin COM4

固件启动成功:

send ok
crc32/mpeg2: 478a84da
send frame: 55 01 01 00 02 da 84 8a 47 cc
receive n: 4
55 04 01 00----------------------------------
55 04 0001
aa
90ad3f6c cc
----------------------------------send firmware success
jump to application
---------------------stm32l475 bootloader start
----------------------------------------
start ota
firmware size: 15644
major: 0x01
minor: 0x01
status: OTA_RUNNING
start uart IT receive----------------------------------------stm32l475 application start
----------------------------------------
firmware version: 0x05
hardware version: 0x01
firmware size: 15644
major: 0x01
minor: 0x01
status: OTA_RUNNING
start uart IT receive

上面测试又调整了一下Flash的存储结构。将App的存储位置又向后移动了2K字节,这2K字节用来存储摘要加密数据了。Bootloader在启动时,先对签名数据进行验签,如果验签失败,则不能启动App,保证应用启动的安全性。 接下来,为bootloader添加验签功能,主要是移植verify.c

2.4.2 修改代码

添加verify.cpublic_key.h到Bootloader工程中。

修改verify.c中与文件相关的代码,直接从Flash中读取固件。

bool verify_app(uint32_t fw_size)
{int ret = 1;bool verify_ok = false;size_t i;mbedtls_rsa_context rsa;unsigned char hash[32];unsigned char buf[2048];mbedtls_md_context_t ctx;uint8_t buffer_2k[2048];int mod;printf("verify app......\r\n");mbedtls_rsa_init( &rsa );if( ( ret = mbedtls_mpi_read_string( &rsa.MBEDTLS_PRIVATE(N), 16, public_key_args_N ) ) != 0 ||( ret = mbedtls_mpi_read_string( &rsa.MBEDTLS_PRIVATE(E), 16, public_key_args_E ) ) != 0 ){printf( " failed\n  ! mbedtls_mpi_read_string returned %d\n\n", ret );goto exit;}rsa.MBEDTLS_PRIVATE(len) = ( mbedtls_mpi_bitlen( &rsa.MBEDTLS_PRIVATE(N) ) + 7 ) >> 3;/** Extract the RSA signature from the text file*/memcpy(buf, (uint32_t *)APP1_PARTITION_ADDR, sizeof(buf));fw_size -= 2048;/** Compute the SHA-256 hash of the input file and* verify the signature*/printf( "\n  . Verifying the RSA/SHA-256 signature\r\n" );fflush( stdout );mbedtls_md_init(&ctx);ret = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 0);if (ret) {printf("mbedtls_md_setup error: %d\r\n", ret);goto exit;}if ( (ret = mbedtls_md_starts(&ctx)) ){printf("mbedtsl_md_starts error: %d\r\n", ret);goto exit;}mod = fw_size % 2048;for (i = 0; i < fw_size / 2048; i++){memcpy(buffer_2k, (uint32_t *)(APP1_PARTITION_ADDR + 2048 * (i + 1)), sizeof(buffer_2k));if ( (ret = mbedtls_md_update(&ctx, buffer_2k, sizeof(buffer_2k))) ){printf("mbedtls_md_update error: %d\r\n", ret);}}if (mod) {  memcpy(buffer_2k, (uint32_t *)(APP1_PARTITION_ADDR + 2048 * (i + 1)), mod);if ( (ret = mbedtls_md_update(&ctx, buffer_2k, mod)) ){printf("mbedtls_md_update error: %d\r\n", ret);}}if ( (ret = mbedtls_md_finish(&ctx, hash))){printf("mbedtls_md_finish error: %d\r\n", ret);}if( ( ret = mbedtls_rsa_pkcs1_verify( &rsa, MBEDTLS_MD_SHA256,32, hash, buf ) ) != 0 ){printf( " failed\n  ! mbedtls_rsa_pkcs1_verify returned -0x%0x\n\n", (unsigned int) -ret );goto exit;}printf( "\n  . OK (the signature is valid)\n\n" );verify_ok = true;exit:mbedtls_rsa_free( &rsa );mbedtls_md_free(&ctx);return verify_ok;
}

另外还需要修改堆栈空间,保证足够的堆栈空间。否则在验签时,堆栈溢出会导致程序跑飞。

最后重新编译整个工程,再次使用Bootloader升级的方式下载固件到开发板中。如果验签成功,Bootloader会去启动Application,否则,不启动。

2.5 测试

2.5.1 Bootloader下升级测试

最后升级过程中,验签失败了,报如下错误。

 failed  ! mbedtls_rsa_pkcs1_verify returned -0x4380

这个错误是验签不成功。对比了签名后的固件和写入到FLASH中的固件,发现最后一个字的内容对不上,导致的验签失败。最后检查发现flash_write_fw函数有Bug,由于固件大小不是8的倍数(必须一次写入两个字,即8字节数据),导致一个字没有被写进Flash中。

ota.c中添加相关验签代码和修复固件写入不完全的Bug:

--- a/alentek_stm32l475_bootloader/user/ota.c
+++ b/alentek_stm32l475_bootloader/user/ota.c
@@ -6,6 +6,7 @@#include "crc.h"#include "usart.h"#include "gpio.h"
+#include "verify.h"uart_rx_t uart_rx = {.pos = 0};ota_info_t ota_info;
@@ -18,14 +19,17 @@ static uint16_t uart_rx_size = 0;static void ota_frame_print(ota_frame_t *frame) ;void goto_application(void)
-{
-    printf("jump to application\r\n");
+{
+    bool verify_ok = verify_app(ota_info.header.firmware_size);
+    while (!verify_ok) ;
+void (*app_reset_handler)(void) = (void *)(*((volatile uint32_t *) (APP1_START_ADDR + 4U)));HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7, GPIO_PIN_SET);HAL_UART_AbortReceive_IT(&huart1);HAL_UART_DeInit(&huart1);
-
+
+    printf("jump to application\r\n");app_reset_handler();}@@ -80,7 +84,7 @@ static void flash_erase(uint32_t bank, uint32_t page_start, uint32_t page_num)HAL_FLASH_Lock();}-static void flash_write(uint32_t addr, uint8_t *data, uint32_t size)
+void flash_write(uint32_t addr, uint8_t *data, uint32_t size){HAL_FLASH_Unlock();@@ -126,9 +130,10 @@ static void flash_write_fw(uint8_t *data, uint32_t size, uint8_t is_first)memcpy(wbuff.buffer + wbuff.index, data, size);wbuff.index += size;
+    wbuff.count += size;if (wbuff.index % 8 == 0) {
-        printf("write flash, addr: %08x, size: %d\r\n", wbuff.addr, wbuff.index);
+//        printf("write flash, addr: %08x, size: %d\r\n", wbuff.addr, wbuff.index);flash_write(wbuff.addr, wbuff.buffer, wbuff.index);wbuff.addr += wbuff.index;wbuff.index = 0;
@@ -138,6 +143,12 @@ static void flash_write_fw(uint8_t *data, uint32_t size, uint8_t is_first)wbuff.addr += (wbuff.index / 8 * 8);memcpy(wbuff.buffer, wbuff.buffer + wbuff.index / 8 * 8, wbuff.index % 8);wbuff.index = wbuff.index % 8;
+
+        // when end of firmware, not enough two words, write it to flash
+        if (wbuff.count == ota_info.header.firmware_size && wbuff.index != 0)
+        {
+            flash_write(wbuff.addr, wbuff.buffer, wbuff.index);
+        }}}

注意:Application下也要对ota.c修改 flash_write

重新编译工程,再次测试:

.\dfu_server.exe G:\stm32\stm32_boot\sign_tool\firmware_signed.bin COM4

升级成功(接收打印有点问题):

使用串口助手查看启动输出(这时需要PB12拉低,否则会进入到Bootloader升级模式),日志正常:

----------------------------------------stm32l475 bootloader start
----------------------------------------
firmware size: 15644
major: 0x01
minor: 0x01
status: OTA_RUNNING
start uart IT receive
haven't ota event
starting application
verify app....... Verifying the RSA/SHA-256 signature. OK (the signature is valid)
----------------------------------------stm32l475 application start
----------------------------------------
firmware version: 0x05
hardware version: 0x01
firmware size: 15644
major: 0x01
minor: 0x01
status: OTA_RUNNING
start uart IT receive
2.5.2 Application下升级测试

因为前面验签测试已经修改好代码了,在应用下升级应用不需要再做修改。Application工程也必须修复flash_write的Bug,否则固件复制不完全也会导致验签不通过。

前面是使用Bootloader将固件刷进去的,现在能正常启动Application了。接下来在应用中升级应用,重新编译Application成功,需要先将版本号改为6,重新编译,签名后进行测试。

./sign.exe firmware6.bin rsa_private_pkcs1_2048.txt
.\dfu_server.exe G:\stm32\stm32_boot\sign_tool\firmware6_signed.bin COM4 app_ota

升级工程。

----------------------------------------stm32l475 bootloader start
----------------------------------------
firmware size: 15644
major: 0x01
minor: 0x01
status: OTA_READY
start uart IT receive
verify app....... Verifying the RSA/SHA-256 signature. OK (the signature is valid)
----------------------------------------stm32l475 application start
----------------------------------------
firmware version: 0x06
hardware version: 0x01
firmware size: 15644
major: 0x01
minor: 0x01
status: OTA_NONE
start uart IT receive

STM32 Bootloader开发记录 3 固件签名校验相关推荐

  1. stm32学习开发记录:OLED——I2C

    [缓慢施工中] 一.STM32 HAL库的I2C API 在Keil--help中可以直接找到对应开发板所需的编程资料. 找到HAL库关于I2C函数的说明: I2C使用流程: (1)I2C参数初始化, ...

  2. STM32学习开发记录:W25Q16(FLASH)——SPI

    选用硬件:STM32F103 Nano开发板 板载有W25Q16 一.SPI协议简介 1.SPI的接线与数据 SPI:Serial Peripheral interface 串行外围设备接口 上图是S ...

  3. php开发接口,生成动态签名校验

    /*数据为josn格式 */ /* 调用接口 */ function master_api($uri,$data) { $request = 'post';  //接口请求方式 (get/post) ...

  4. NDK开发前奏 - x团参数加密和签名校验

    这次给大家分享一个关于<x团参数加密和签名校验>解决方案,主要涉及的内容是 MD5 加密算法和 JNI 基础.如果我们只是做 Android 应用开发,应当还是先把 Android 的基础 ...

  5. 华为小熊派开发记录STM32+MPU6050+WIFI8266

    华为小熊派开发记录STM32+MPU6050+WIFI8266 前言 STM32CubeMX+MDK+LiteOS MPU6050 WIFI8266 华为云使用 其它 前言 忽悠下参加了物联网开发比赛 ...

  6. Stm32 Bootloader整理

    Stm32 Bootloader整理 一.        基本概念 1.IAP IAP是In Application Programming的首字母缩写,IAP是用户自己的程序在运行过程中对User ...

  7. android ota 版本校验,OTA升级签名校验简析

    1. 概要 如果进行过OTA升级的开发者,都或多或少有这样的疑问,如何确定该OTA升级包是可以信任的呢?这其中其实涉及到一个签名验证的流程. 2. 签名生成 在生成正规的固件时,一般会运行生成新key ...

  8. iOS签名校验那些事儿

    导读:iOS签名校验机制是苹果生态安全的基础,日常工作中无论是开发阶段还是测试阶段常常会遇到很多需要通过签名机制解决的问题,了解iOS签名机制的原理有助于提高我们解决相关问题的成本和效率.本文首先介绍 ...

  9. 山东大学RISC-V公共开放平台开发记录5

    山东大学RISC-V公共开放平台开发记录 4移植Linux操作系统 4.1移植前相应知识 4.1.1特权等级与操作系统的关系 现代操作系统(如Linux.Windows等)为了权限的区分都区分为用户态 ...

  10. QQProtect驱动调用者签名校验漏洞分析

    QQProtect驱动调用者签名校验漏洞分析 源文:http://bbs.pediy.com/showthread.php?p=1230963#post1230963 上次给大家带来一个"中 ...

最新文章

  1. R语言绘制核密度图实战(Kernel Density Plot)
  2. Cygwin 下载极速源推荐
  3. java线程打水问题_Java 多线程 wait() 虚假唤醒问题
  4. 常见的MySQL命令大全
  5. 神经元模型hhmodel模型_HH神经元模型 -
  6. 纯干货,用好PPT中的异形,提高页面的设计感,建议收藏!
  7. 计算机论文目录大全,毕业论文目录范文
  8. HTML编辑器-Bluefish 2.0.1 发布
  9. HttpClient-HttpClient4.5使用代理服务器访问外网
  10. python之WeChat撩妹神奇
  11. c语言编程求pai的近似值,c语言:求π的近似值
  12. 电子电路设计需要考虑哪些方面
  13. android 奥利奥功能,一加5/5T吃上Android 8.0奥利奥 这些新功能特性你不可不知
  14. css 纵向合并_excel只能合并横向单元格
  15. xx-job任务管理平台
  16. 2021年企业十大科技趋势预测
  17. 替补者的旅行(巨长,慎入)
  18. 计算机与现代教育的英语作文,雅思写作高分范文:电脑是否现代教育所必须?...
  19. 广汽乘用车智能制造标杆工厂规划与实施
  20. IDEA 的Surround with快捷键 (例:try/catch)

热门文章

  1. 【Seedlabs】Local DNS Attack Lab
  2. 网站建设流程是什么?
  3. php 固定表头,PHPExcel如何冻结(锁定)表头
  4. cas22112-84-1/5,10,15,20-四(4-氨基苯)-21H,23H-卟啉简称:H2TAPP; Tph
  5. *TEST 10 for NOIP 迷幻测试 (230/300)
  6. win10 pip install talib一直安装失败
  7. prisma 风格设置_Prisma中的身份验证-第1部分:设置
  8. winedit使用教程_latex及winedit入门指导教程.pdf
  9. Winedit 下载第三方库
  10. WordPress云解析HTML5播放器