4. 集成RSA签名验证
任务目标
在现有Bootloader的固件跳转执行前,增加RSA-2048签名验证环节。Bootloader使用预置的公钥对固件头部附带的SHA-256摘要签名进行验签,仅当验签通过后才允许跳转到应用固件,否则进入错误处理流程。
环境与依赖
- 硬件:STM32F407系列(具有硬件CRC与充足Flash),本文以STM32F407VET6为例
- 密码库:mbedTLS 2.28.x(需启用
MBEDTLS_RSA_C、MBEDTLS_SHA256_C、MBEDTLS_PKCS1_V15) - 上位机工具:OpenSSL(用于生成密钥对与签名固件)
固件签名与验证流程
- 上位机:使用私钥对固件二进制(不含签名区)计算SHA-256摘要,再用RSA私钥签名,得到256字节签名数据。
- 固件布局:应用固件起始处存放签名结构体(固定偏移),随后才是实际代码。
- Bootloader:读取签名区与固件代码区,用内置公钥验签,通过后跳转。
关键完整代码
// bootloader_rsa_verify.h
#ifndef BOOTLOADER_RSA_VERIFY_H
#define BOOTLOADER_RSA_VERIFY_H
#include <stdint.h>
#include "mbedtls/rsa.h"
#include "mbedtls/md.h"
#include "mbedtls/pk.h"
#define APP_SIGN_OFFSET 0x0 // 签名存放在固件起始处
#define SIGN_LEN 256 // RSA-2048 签名长度
#define APP_CODE_OFFSET (SIGN_LEN + 8) // 跳过签名+长度字段
#define APP_FLASH_ADDR 0x08010000
// 固件签名结构(存放于固件起始处)
typedef struct {
uint8_t signature[SIGN_LEN]; // RSA签名
uint32_t code_len; // 实际固件代码长度
uint32_t reserved; // 保留
} app_signature_t;
// 内置RSA公钥(由私钥导出,此处为示例,实际应替换为真实公钥)
static const uint8_t rsa_pubkey_der[] = {
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
// ... 实际公钥DER数据(308字节),此处省略
0x02, 0x03, 0x01, 0x00, 0x01
};
// 验证固件签名
// 返回 0: 验证通过; -1: 参数错误; -2: 验签失败
int verify_app_signature(void) {
app_signature_t *sig = (app_signature_t *)APP_FLASH_ADDR;
uint8_t *app_code = (uint8_t *)(APP_FLASH_ADDR + APP_CODE_OFFSET);
uint32_t code_len = sig->code_len;
if (code_len == 0 || code_len > 0x40000) { // 固件最大256KB
return -1;
}
mbedtls_pk_context pk;
mbedtls_pk_init(&pk);
int ret = mbedtls_pk_parse_public_key(&pk, rsa_pubkey_der, sizeof(rsa_pubkey_der));
if (ret != 0) {
mbedtls_pk_free(&pk);
return -1;
}
// 计算固件代码区SHA-256
uint8_t hash[32];
mbedtls_md_context_t md_ctx;
mbedtls_md_init(&md_ctx);
mbedtls_md_setup(&md_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 0);
mbedtls_md_starts(&md_ctx);
mbedtls_md_update(&md_ctx, app_code, code_len);
mbedtls_md_finish(&md_ctx, hash);
mbedtls_md_free(&md_ctx);
// RSA验签
ret = mbedtls_pk_verify(&pk, MBEDTLS_MD_SHA256, hash, 0,
sig->signature, SIGN_LEN);
mbedtls_pk_free(&pk);
return (ret == 0) ? 0 : -2;
}
#endif // BOOTLOADER_RSA_VERIFY_H
// main.c 中跳转前调用
#include "bootloader_rsa_verify.h"
void jump_to_app(void) {
int ret = verify_app_signature();
if (ret != 0) {
// 验签失败,进入错误处理(如LED闪烁、等待重新烧录)
while (1) {
// 错误处理代码
}
}
// 验签通过,跳转
uint32_t app_addr = APP_FLASH_ADDR + APP_CODE_OFFSET;
void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t *)(app_addr + 4));
__set_MSP(*(volatile uint32_t *)app_addr);
app_reset();
}
上位机签名命令(OpenSSL)
# 1. 生成RSA-2048密钥对
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
# 2. 导出公钥DER格式(供Bootloader嵌入)
openssl rsa -in public.pem -pubin -outform DER -out pubkey.der
# 3. 对固件签名(假设固件为app.bin,不含签名区)
openssl dgst -sha256 -sign private.pem -out signature.bin app.bin
# 4. 生成带签名的固件镜像(签名+长度+代码)
# 使用Python脚本拼接:
python3 -c "
import struct
with open('app.bin','rb') as f:
code = f.read()
with open('signature.bin','rb') as f:
sig = f.read()
with open('app_signed.bin','wb') as f:
f.write(sig)
f.write(struct.pack('<I', len(code)))
f.write(b'\x00'*4) # reserved
f.write(code)
print('Signed image created, code length:', len(code))
"
验证方法与通过条件
- 编译烧录:将Bootloader编译烧录至0x08000000,应用固件烧录至0x08010000(使用
app_signed.bin)。 - 正常路径验证:使用正确私钥签名的固件,上电后Bootloader验签通过,LED指示跳转成功,应用正常运行。
- 篡改检测验证:修改
app_signed.bin中任意一个字节(模拟固件被篡改),重新烧录。上电后Bootloader验签失败,进入错误处理(如LED以1Hz闪烁),不跳转应用。 - 判定标准:篡改固件无法运行,且正确签名固件可正常启动,即证明RSA签名验证生效。
常见问题
- 公钥不匹配:确保
rsa_pubkey_der[]与签名所用私钥对应,否则验签必然失败。 - 内存不足:mbedTLS RSA验签需约4KB栈空间,确认Bootloader链接脚本中栈大小足够。
- 固件长度字段:
code_len必须与实际代码长度一致,否则SHA-256计算范围错误导致验签失败。