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


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


    固件签名校验需要用到加解密库,在嵌入式平台,使用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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    编译安装完成后,可执行文件和库被放到了,仓库目录的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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在签名时,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
    
    • 1
    • 2
    • 3

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

        if( f_rng == NULL )
            return( MBEDTLS_ERR_RSA_BAD_INPUT_DATA );
    
    • 1
    • 2

    函数指针参数为空了,导致的报错。将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 
     #include 
    @@ -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 );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    进入到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
      . OK
    
    The decrypted result is: 'hello world'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    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
    
    • 1

    提取RSA公钥

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

    (提取过程中可以转换格式,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           :BE29E714F51611C4F22A892658DC9A3CCF4DA5BF3F054F5EE9752901AC53BDB97BD6114C318F0C17C3EFFAE6CAB12114B98578295E18E405E1A4C151EF82794D7A9E5D171CE46BB5A5F2447175CC194D656E5A861288BD12E8235E20D932ADC7FCEF4F33342E76F7DBF5EBCA52857183485C01AD6583E773679020259A76AD7C5D13BF31D01987578DC368FDA41FDF80E88D37DA975DA7985548055A7232DD5356DE4AEF0C1BCA602943CD24654976AAC03A0577F3E04FF0BDB3680F35238267D0AA8E612155F97C0D18DED1B5B75690D5F64019BE54238BED42D31D9B742CF80EC20F54D39F582C5B26C451B3E36431B37ECE3DEA85B3BC207ADB98B226D3CD
      265:d=1  hl=2 l=   3 prim: INTEGER           :010001
    
    • 1
    • 2
    • 3
    • 4

    提取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           :00
        7:d=1  hl=4 l= 257 prim: INTEGER           :BE29E714F51611C4F22A892658DC9A3CCF4DA5BF3F054F5EE9752901AC53BDB97BD6114C318F0C17C3EFFAE6CAB12114B98578295E18E405E1A4C151EF82794D7A9E5D171CE46BB5A5F2447175CC194D656E5A861288BD12E8235E20D932ADC7FCEF4F33342E76F7DBF5EBCA52857183485C01AD6583E773679020259A76AD7C5D13BF31D01987578DC368FDA41FDF80E88D37DA975DA7985548055A7232DD5356DE4AEF0C1BCA602943CD24654976AAC03A0577F3E04FF0BDB3680F35238267D0AA8E612155F97C0D18DED1B5B75690D5F64019BE54238BED42D31D9B742CF80EC20F54D39F582C5B26C451B3E36431B37ECE3DEA85B3BC207ADB98B226D3CD
      268:d=1  hl=2 l=   3 prim: INTEGER           :010001
      273:d=1  hl=4 l= 256 prim: INTEGER           :6E57CA2AB2FC175B7C598A33A2247FAB016F40D9F42B949EB240B586AF03F877049076810BD0C16798DE4B6C57F8E8052A9173E42D21AC4E6D5C3880DEC58AB5968D05DF3F789BB74F8F807F85A6E497B05F944F6FB9C05C942ED193A5F46E3A395E734A76E0EF4F6B670B66DEF367A691FA314EF834CDBC7D3F7827D9B53B7C896AF0390694AAA94FCC286E3C9549D638E0E2915B37E5768A0865407F113E1236F0708CC82A728D74A912815529A6C865FC983AF68BF273233BE48F0429CD6D10AEB466C985D428BD5F7EEFBBF4418AD4AA120ABDA33CBB96D6DA6908510FE9EB911526E47DEB8C4DCCC69A5D373ACBB4A1D791BB27C4205F308CFF2387EF01
      533:d=1  hl=3 l= 129 prim: INTEGER           :EF7A981DE622FE9661A690FD50BA9836A70B53369C155362B4CFAABFEFCF1F95C0D8CC19112ACB4C092B28636A124471E3375D904311F97779DDDB03BA24456C80DD5D5288EEE3E67480724F8F70ABBB8A625FDFBCD847711FFB320BC50FF988230080E25A843752D2C420ABFAA979F7089D7546AE9D8EDC39FA154F20D7425D
      665:d=1  hl=3 l= 129 prim: INTEGER           :CB485BE8EB30B75A51CD23015799798360B8EFCCE63E635A70AE524DCA68B710F264566FB4657E56B8F2629A87F219BB2FF7A78ED689EF66BA3DB54146DC1D301A37A0FC1549D467072F67883051F09BCA43FD995BBB48FB07BFFC9FC10CFB2A5EE0C2B6B244840243D8F4C24497A64C55F7A46483B66305BCF97D49F7CDA031
      797:d=1  hl=3 l= 129 prim: INTEGER           :B069A9216D651E127DC10C51EF6465B8ECF944C597D985E1D2BCA056603C3E523D0BF7DE076D74F18057909B1B8A756D482A82FC40CC3EEFAA90AA44669D4D9F0E9703A647456411628534B0334496DDC2A59166EA6090D05EF4D6FB20A211FA1D7EC372FBA7267216DD700C00CD54118D0121617E4486F6E8A6B787E0F371E9
      929:d=1  hl=3 l= 129 prim: INTEGER           :BCA9281BB0032377A08988DE5B0565FFE7095C0CC6F6C9D08AD94E2B421A1324984144F320F23CE1917DEAC34E3E036175701BC2F61B47E3081FF31365072DD391D16507EF5DF8D1B19A6E34B7DBA43981EBBDF5D16BAC9CC5A1897C5E7784B6DB1D2FB8CD13C78C2E99B0F6872053476F5588ED7C4C0DF4AA60C5C59B44EC51
     1061:d=1  hl=3 l= 128 prim: INTEGER           :35D3A7D1B16ED3FC6539837D5EF81E82165D07A867C1E14A81B1891CAF4E0A76AAC122D03C0058E81C394B305D04FF72B2855D2826640C2A6A1A7CDC94BD21121323B05A32C65FBF8FCE3268BC1FA9E310B9B006ED4B59119F2C06B7A8CC0D09824A703F9DF0C281A16B401D7DA651158CC779B48E0B7631D365E9FDF1157BF6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    由于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
      . OK
    
    The decrypted result is: 'hello world'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    1.4 移植到STM32
    1.4.1 移植到KEIL工程

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

    复制mbedtls仓库中的configsinclude library目录到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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    烧录编译了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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    使用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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    需要注释掉#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
    
    • 1
    • 2

    示例代码:

    	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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

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

    2. 签名验签

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

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

    参考代码

    2.1 签名

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

    openssl asn1parse -in rsa_private_pkcs1_2048.pem > rsa_private_pkcs1_2048.txt
    
    • 1

    再从文本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
    
    • 1

    同样的方式,从这个文本中获取到公钥参数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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

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

    2.3 测试

    签名

    ./sign firmware6.bin rsa_private_pkcs1_2048.txt
    
    • 1

    在这里插入图片描述

    验签

    ./verify firmware6_signed.bin
    
    • 1

    在这里插入图片描述

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    修改Application

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

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

    重新编译工程。

    签名

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

    ./sign.exe firmware.bin rsa_private_pkcs1_2048.txt
    
    • 1

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

    下载测试

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

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

    固件启动成功:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    上面测试又调整了一下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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

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

    在这里插入图片描述

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

    2.5 测试
    2.5.1 Bootloader下升级测试

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

     failed  ! mbedtls_rsa_pkcs1_verify returned -0x4380
    
    • 1

    这个错误是验签不成功。对比了签名后的固件和写入到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);
    +        }
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

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

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

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

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

    image-20221118182037029

    使用串口助手查看启动输出(这时需要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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    2.5.2 Application下升级测试

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

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

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

    升级工程。

    ----------------------------------------
          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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    Spring Boot(Vue3+ElementPlus+Axios+MyBatisPlus+Spring Boot 前后端分离)【一】
    SAST-数据流分析方法-理论
    v-model的各种使用状态和使用结果
    javaweb医院门诊管理系统
    c++ map/multimap
    【深度学习】pix2pix GAN理论及代码实现与理解
    Python Connect SQLServer 2008
    【npm如何发布自己的插件包】
    《Python魔法大冒险》010 魔法宝箱:列表与元组的探险
    Linux基础指令
  • 原文地址:https://blog.csdn.net/duapple/article/details/127928082