#Nordic52832 Secure DFU
文章目录
从nrf52832的SDK 12.0以后,提供的DFU的例子为bootloader_secure。
bootloader_secure 相对于以前版本的 dual_bank ,single bank 有如下优点:
- -对升级文件进行了
ECSDA
签名,防止误升级了未授权的程序 - -对升级的firmware版本进行了管理,防止升级回到低版本。
##ECDSA算法的实现
###签名算法DSA和ECDSA
比特币协议里使用了ECDSA(椭圆曲线签名算法),我之前以为它和基于大数分解的RSA公钥密码体系差不多。这两天看了下维基百科,才发现它们之间的差异挺大。最本质的区别是,DSA和ECDSA都只是签名算法,它用来确保信息发布人的身份和信息的完整性,不能用来做加密传输,为了实现这个功能,信息的原文(或者HASH摘要)必须随着签名一起传输和公布才能被验证。而RSA是公钥加密体系,它可以用来加密传输(即信息原文在传输中加密,到达对方后解密),它也可以实现签名验证。
DSA和ECSDA的基本架构和RSA一样,签名者持有私钥,对应公钥向全世界公开。当需要对信息签名时,签名者用私钥对信息签名,然后将签名信息和信息原文发给对方(RSA协议中,信息原文不需要发给对方,签名信息解密后就是信息原文),验证者可用签名者公开的公钥对签名信息和信息原文验证签名。由于信息长度可能比较长,在实际操作中,大家通常在信息的HASH摘要上进行签名。
- ECDSA (Elliptic Curve Digital Signature Algorithm), an algorithm to create and verify signatures for any kind of data (for example, a firmware image)
- ECDH (Elliptic Curve Diffie-Hellmann), a key agreement protocol that allows to establish a shared secret over an insecure channel
###NRF52832 实现ECDSA
NRF52832 SDK采用了micro-ecc
开源软件实现ECDSA算法
Installing micro-ecc
micro-ecc is an open source library that is required to use the cryptography library. When using micro-ecc, you must ensure compliance with the license of the library as stated in the LICENSE.txt file that is included in micro-ecc.The cryptography library expects to find the compiled micro-ecc library in InstallFolder\external\micro-ecc\micro-ecc.
To install micro-ecc, complete the following steps:
Install version 4.9-2015-q3-update of the GCC compiler toolchain for ARM. You can use ARM’s Launchpad to find the toolchain for your operating system.
Make sure that make is installed (see, for example, MinGW, GNU Make, or Xcode).
Clone the micro-ecc GitHub repository into InstallFolder\external\micro-ecc\micro-ecc.
Enter the subdirectory for the SoC and the toolchain that you are using to build your application:
InstallFolder\external\micro-ecc\nrf51_keil\armgccInstallFolder\external\micro-ecc\nrf52_keil\armgcc
InstallFolder\external\micro-ecc\nrf51_iar\armgcc
InstallFolder\external\micro-ecc\nrf52_iar\armgcc
InstallFolder\external\micro-ecc\nrf51_armgcc\armgcc
InstallFolder\external\micro-ecc\nrf52_armgcc\armgcc
Run make to compile the micro-ecc library.
Note:
If you compile the micro-ecc library without using the provided Makefiles, make sure to use the default compilation options with -Os and optimization level 3. If you change the preprocessor macros (most importantly,uECC_VLI_NATIVE_LITTLE_ENDIAN=1), the library might not work prop
由于micro-ecc
使用了汇编代码实现了fast-mult
和fast_square
等数学运算,而micro-ecc
的汇编是用arm-gcc编译的,所以必须下载arm-gcc工具。arm-gcc需要用到Makefile,这样在windows平台上需要安装gnu工具。
下载gcc-arm 和gnuarmelipse插件,设置相应的path环境变量开始编译,遇到两个问题:
- 在D:\ble\nrf52832\nRF5_SDK_12.0.0\components\toolchain\gcc目录中需要修改Makefile.common的gcc-arm路径
- 需要手动在编译目录创建_build 文件夹。编译生成
micro_ecc_lib_nrf52.lib
。
将编译生成的micro_ecc_lib_nrf52.lib
文件加到dfu_secure工程中,编译通过,但是仿真是不能看到源代码,提示加载.axf
出错。根据KEIL网站上查询得知,是对于源代码不是用keil的编译器编译的工程,不能通过源代码在MDK内进行仿真。
###PC端工具
Nordic PC端工具 nrfutil的github地址为:
nrfutil github
当前下载到的版本是v2.1.0,版本日志:
There are 2 different and incompatible DFU package formats:
- legacy: used a simple structure and no security
- modern: uses Google’s protocol buffers for serialization and can be cryptographically signed
The DFU package format transitioned from legacy to modern in SDK 12.0. Depending on the SDK version that you are using you will need to select a release of this tool compatible with it:
- Version 0.5.2 generates legacy firmware packages compatible with nRF SDK 11.0 and older
- Versions 1.5.0 and later generate modern firmware packages compatible with nRF SDK 12.0 and newer
nrfutil 是用python2.7写的,所以PC上要先安装Python2.7。如果PC上已经安装了Python3.5,可以通过修改PC的环境变量来切换3.5和2.7环境。nrfutil的包的依赖为:
six >= 1.9
pyserial >= 2.7 # 用于控制pc端串口进行升级
enum34 >= 1.0.4
click >= 6.0
ecdsa >= 0.13 # ECDSA 签名算法
behave
protobuf # protocol buffers
pc_ble_driver_py >= 0.8.0
可以通过 pyinstaller nrfutil.spec
生成nrfutil.exe
工具。
nrfutil
的用法:
生成keys
D:\ble\nrf52832\pc-nrfutil-master\dist>nrfutil keys generate priv.pem
Generated private key and stored it in: priv.pem
keys文件内容,通过base64编码
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMkpi9zq/YM18l5/QyC7W6esgh3k9Zwm9hS/wXrlrDwZoAoGCCqGSM49
AwEHoUQDQgAEFy45JuGB+tE9OiBW6SWol16M0m/M/G+vA+FrqPcHtQDbovSTV8cG
osnlpOCzHKmLQ6vYmPtO7dKNQhwzE0f8KQ==
-----END EC PRIVATE KEY-----
keys查看,公钥
D:\ble\nrf52832\pc-nrfutil-master\dist>nrfutil keys display --key pk --format he
x priv.pem
Public (verification) key pk:
e4e248152b8911627592752f44b8ccd70082981d6feb0077cb24f14c631594f3a497f8581ba69c72
4cd9173187402a2d08f62d1df67dab774c5bf44ef671d581
keys查看,私钥
D:\ble\nrf52832\pc-nrfutil-master\dist>nrfutil keys display --key sk --format he
x priv.pem
WARNING: Security risk! Do not share the private key.
Private (signing) key sk:
6bf5ba64df2a2032ebd71edd7d3d4e9e067ecbabf2b0e39e79035f474d99c2bd
生成公钥.c文件
D:\ble\nrf52832\pc-nrfutil-master\dist>nrfutil keys display --key pk --format co
de priv.pem
/* This file was automatically generated by nrfutil on 2016-12-29 (YY-MM-DD) at
13:08:48 */
#include "stdint.h"
#include "compiler_abstraction.h"
/** @brief Public key used to verify DFU images */
__ALIGN(4) const uint8_t pk[64] =
{
0xe4, 0xe2, 0x48, 0x15, 0x2b, 0x89, 0x11, 0x62, 0x75, 0x92, 0x75, 0x2f, 0x44
, 0xb8, 0xcc, 0xd7, 0x00, 0x82, 0x98, 0x1d, 0x6f, 0xeb, 0x00, 0x77, 0xcb, 0x24,
0xf1, 0x4c, 0x63, 0x15, 0x94, 0xf3,
0xa4, 0x97, 0xf8, 0x58, 0x1b, 0xa6, 0x9c, 0x72, 0x4c, 0xd9, 0x17, 0x31, 0x87
, 0x40, 0x2a, 0x2d, 0x08, 0xf6, 0x2d, 0x1d, 0xf6, 0x7d, 0xab, 0x77, 0x4c, 0x5b,
0xf4, 0x4e, 0xf6, 0x71, 0xd5, 0x81
};
将生成的pk[64]数组替换掉NRF52832工程中dfu_public_key.c中的对应数组
文件打包
D:\ble\nrf52832\pc-nrfutil-master\dist>nrfutil pkg generate --hw-version 0x52 --
sd-req 0x8c --application-version 4 --application app.hex --key-file priv.pem df
u.zip
Zip created at dfu.zip
包文件查看
D:\ble\nrf52832\pc-nrfutil-master\dist>nrfutil pkg display dfu.zip
DFU Package: <dfu.zip>:
|
|- Image count: 1
|
|- Image #0:
|- Type: application
|- Image file: app.bin
|- Init packet file: app.dat
|
|- op_code: INIT
|- signature_type: ECDSA_P256_SHA256
|- signature: 08b928a1656590802807405c37522eb2c11b67ff6b5688517d052cefcb8c
8fbbd10eaf3f5f7b6afab1c7c2bf21807f93c91dd1c9406ba036e62e9923c3c03d93
|
|- fw_version: 0x00000004 (4)
|- hw_version 0x00000052 (82)
|- sd_req: 0x8C
|- type: APPLICATION
|- sd_size: 0
|- bl_size: 0
|- app_size: 49844
|
|- hash_type: SHA256
|- hash: d7fe4842195f7b9127d4b522ee5d6b4ef3e755677508093c7f0ed29249f0c154
|
|- is_debug: False
zip包文件分析
nrfutil
生成的*.zip文件是一个普通的zip文件,可以通过winrar等解压缩工具打开,解压以后可以发现.zip*包含了三个文件。
2016/12/29 13:12 49,844 app.bin
2016/12/29 13:12 135 app.dat
2016/12/29 13:12 138 manifest.json
其中app.bin
文件为app.hex
生成的bin
文件,是需要发送给nrf52832的升级固件。
app.dta
是根据 dfu-cc.proto生成的包含init_command
的protocal buffers packet(可参考下一节内容)。
manifest.json
的内容如下,描述*.zip*文件的内容:
"manifest": {
"application": {
"bin_file": "app.bin",
"dat_file": "app.dat"
}
}
}
PC nrfutil 代码分析
待补充
##protocol Buffers的实现
###protocol Buffers简介
google protocol buffers 的使用和原理
###nano-pb介绍
nano-pb document
nano-pb
包含了protocol buffers 的decode,encode的c代码。也包含了一个用 python写generateor
工具。该工具可以和google提供的protoc.exe
配合,将*.proto
文件转换为对应的.c
和.h
文件。
Linux 环境下测试 nano-pb的example
在linux环境下测试nano-pb
附带的example中的network-server
的例子。该例子实现在网络上通过protocol buffer打包的数据来传输目录的文件列表。用generator
工具和protc
通过fileproto.proto
生成fileproto.pb.h
和fileproto.pb.c
。
protoc -ofileproto.pb fileproto.proto
python ../../generator/nanopb_generator.py fileproto.pb
Writing to fileproto.pb.h and fileproto.pb.c
编译example代码,生成sever
和client
~/nano-pb/nanopb-master/examples/network_server ubuntu@WEB
❯ make
cc -ansi -Wall -Werror -g -O0 -I/home/ubuntu/nano-pb/nanopb-master -o server server.c common.c fileproto.pb.c /home/ubuntu/nano-pb/nanopb-master/pb_encode.c /home/ubuntu/nano-pb/nanopb-master/pb_decode.c /home/ubuntu/nano-pb/nanopb-master/pb_common.c
cc -ansi -Wall -Werror -g -O0 -I/home/ubuntu/nano-pb/nanopb-master -o client client.c common.c fileproto.pb.c /home/ubuntu/nano-pb/nanopb-master/pb_encode.c /home/ubuntu/nano-pb/nanopb-master/pb_decode.c /home/ubuntu/nano-pb/nanopb-master/pb_common.c
~/nano-pb/nanopb-master/examples/network_server ubuntu@WEB
❯
运行sever
和client
进行测试
~/nano-pb/nanopb-master/examples/network_server ubuntu@WEB
❯ ./server &
[2] 1681
bind: Address already in use
[2] + 1681 exit 1 ./server
~/nano-pb/nanopb-master/examples/network_server ubuntu@WEB
❯ ./client .
Got connection.
Listing directory: .
Closing connection.
281871 Makefile
298710 server
281876 common.c
298707 fileproto.pb.c
281877 common.h
281873 client.c
281883 server.c
281872 README.txt
281878 fileproto.options
281868 .
281881 fileproto.proto
281858 ..
298708 fileproto.pb.h
298733 client
###NRF52832的nano-pb的实现
NRF52832 dfu用的.proto文件 dfu-cc.proto
package dfu;
// Version 0.1
// Definition of enums and types
enum OpCode {
RESET = 0;
INIT = 1;
}
enum FwType {
APPLICATION = 0; // default, compatible with proto3
SOFTDEVICE = 1;
BOOTLOADER = 2;
SOFTDEVICE_BOOTLOADER = 3;
}
enum HashType {
NO_HASH = 0;
CRC = 1;
SHA128 = 2;
SHA256 = 3;
SHA512 = 4;
}
message Hash
{
required HashType hash_type = 1;
required bytes hash = 2;
}
// Commands data
message InitCommand {
optional uint32 fw_version = 1;
optional uint32 hw_version = 2;
repeated uint32 sd_req = 3 [packed = true]; // packed option is default in proto3
optional FwType type = 4;
optional uint32 sd_size = 5;
optional uint32 bl_size = 6;
optional uint32 app_size = 7;
optional Hash hash = 8;
optional bool is_debug = 9 [default = false];
}
message ResetCommand
{
required uint32 timeout = 1;
}
// Command type
message Command {
optional OpCode op_code = 1;
optional InitCommand init = 2;
optional ResetCommand reset = 3;
}
// Signed command types
enum SignatureType {
ECDSA_P256_SHA256 = 0;
ED25519 = 1;
}
message SignedCommand {
required Command command = 1;
required SignatureType signature_type = 2;
required bytes signature = 3;
}
// Parent packet type
message Packet {
optional Command command = 1;
optional SignedCommand signed_command = 2;
}
分析该dfu-cc.proto
文件,Packet
中可包含普通的command
和signed_command
。signed_command
包含command
、签名类型和签名字符串。command
可以是op_code
、InitCommand
或者ResetCommand
。通过nrfutil
生成的Packet
包含的是signed_command
该command是InitCommand
。
从protobuf github下载protoc for win32工具。
用nano-pb的generateor生成.c 和.h 文件
D:\ble\nrf52832\nRF5_SDK_12.2.0\external\nano-pb\generator>protoc -odfu_cc.pb dfu-cc.proto
[libprotobuf WARNING google/protobuf/compiler/parser.cc:546] No syntax specified
for the proto file: dfu-cc.proto. Please use 'syntax = "proto2";' or 'syntax ="proto3";' to specify a syntax version. (Defaulted to proto2 syntax.)
D:\ble\nrf52832\nRF5_SDK_12.2.0\external\nano-pb\generator>nanopb_generator.py dfu_cc.pb
Writing to dfu_cc.pb.h and dfu_cc.pb.c
生成的代码如下dfu_cc.pb.h
:
/* Automatically generated nanopb header */
/* Generated by nanopb-0.3.6-dev at Thu Dec 29 18:01:42 2016. */
#ifndef PB_DFU_CC_PB_H_INCLUDED
#define PB_DFU_CC_PB_H_INCLUDED
#include <pb.h>
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Enum definitions */
typedef enum
{
DFU_OP_CODE_RESET = 0,
DFU_OP_CODE_INIT = 1
} dfu_op_code_t;
#define DFU_OP_CODE_MIN DFU_OP_CODE_RESET
#define DFU_OP_CODE_MAX DFU_OP_CODE_INIT
#define DFU_OP_CODE_ARRAYSIZE ((dfu_op_code_t)(DFU_OP_CODE_INIT+1))
typedef enum
{
DFU_FW_TYPE_APPLICATION = 0,
DFU_FW_TYPE_SOFTDEVICE = 1,
DFU_FW_TYPE_BOOTLOADER = 2,
DFU_FW_TYPE_SOFTDEVICE_BOOTLOADER = 3
} dfu_fw_type_t;
#define DFU_FW_TYPE_MIN DFU_FW_TYPE_APPLICATION
#define DFU_FW_TYPE_MAX DFU_FW_TYPE_SOFTDEVICE_BOOTLOADER
#define DFU_FW_TYPE_ARRAYSIZE ((dfu_fw_type_t)(DFU_FW_TYPE_SOFTDEVICE_BOOTLOADER+1))
typedef enum
{
DFU_HASH_TYPE_NO_HASH = 0,
DFU_HASH_TYPE_CRC = 1,
DFU_HASH_TYPE_SHA128 = 2,
DFU_HASH_TYPE_SHA256 = 3,
DFU_HASH_TYPE_SHA512 = 4
} dfu_hash_type_t;
#define DFU_HASH_TYPE_MIN DFU_HASH_TYPE_NO_HASH
#define DFU_HASH_TYPE_MAX DFU_HASH_TYPE_SHA512
#define DFU_HASH_TYPE_ARRAYSIZE ((dfu_hash_type_t)(DFU_HASH_TYPE_SHA512+1))
typedef enum
{
DFU_SIGNATURE_TYPE_ECDSA_P256_SHA256 = 0,
DFU_SIGNATURE_TYPE_ED25519 = 1
} dfu_signature_type_t;
....
自动生成的dfu_cc.pb.c
如下:
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.3.6-dev at Thu Dec 29 18:01:42 2016. */
#include "dfu_cc.pb.h"
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
const bool dfu_init_command_is_debug_default = false;
const pb_field_t dfu_hash_fields[3] = {
PB_FIELD( 1, UENUM , REQUIRED, STATIC , FIRST, dfu_hash_t, hash_type, hash_type, 0),
PB_FIELD( 2, BYTES , REQUIRED, CALLBACK, OTHER, dfu_hash_t, hash, hash_type, 0),
PB_LAST_FIELD
};
const pb_field_t dfu_init_command_fields[10] = {
PB_FIELD( 1, UINT32 , OPTIONAL, STATIC , FIRST, dfu_init_command_t, fw_version, fw_version, 0),
PB_FIELD( 2, UINT32 , OPTIONAL, STATIC , OTHER, dfu_init_command_t, hw_version, fw_version, 0),
PB_FIELD( 3, UINT32 , REPEATED, CALLBACK, OTHER, dfu_init_command_t, sd_req, hw_version, 0),
PB_FIELD( 4, UENUM , OPTIONAL, STATIC , OTHER, dfu_init_command_t, type, sd_req, 0),
PB_FIELD( 5, UINT32 , OPTIONAL, STATIC , OTHER, dfu_init_command_t, sd_size, type, 0),
PB_FIELD( 6, UINT32 , OPTIONAL, STATIC , OTHER, dfu_init_command_t, bl_size, sd_size, 0),
PB_FIELD( 7, UINT32 , OPTIONAL, STATIC , OTHER, dfu_init_command_t, app_size, bl_size, 0),
PB_FIELD( 8, MESSAGE , OPTIONAL, STATIC , OTHER, dfu_init_command_t, hash, app_size, &dfu_hash_fields),
PB_FIELD( 9, BOOL , OPTIONAL, STATIC , OTHER, dfu_init_command_t, is_debug, hash, &dfu_init_command_is_debug_default),
PB_LAST_FIELD
};
const pb_field_t dfu_reset_command_fields[2] = {
PB_FIELD( 1, UINT32 , REQUIRED, STATIC , FIRST, dfu_reset_command_t, timeout, timeout, 0),
PB_LAST_FIELD
};
const pb_field_t dfu_command_fields[4] = {
PB_FIELD( 1, UENUM , OPTIONAL, STATIC , FIRST, dfu_command_t, op_code, op_code, 0),
PB_FIELD( 2, MESSAGE , OPTIONAL, STATIC , OTHER, dfu_command_t, init, op_code, &dfu_init_command_fields),
PB_FIELD( 3, MESSAGE , OPTIONAL, STATIC , OTHER, dfu_command_t, reset, init, &dfu_reset_command_fields),
PB_LAST_FIELD
};
const pb_field_t dfu_signed_command_fields[4] = {
PB_FIELD( 1, MESSAGE , REQUIRED, STATIC , FIRST, dfu_signed_command_t, command, command, &dfu_command_fields),
PB_FIELD( 2, UENUM , REQUIRED, STATIC , OTHER, dfu_signed_command_t, signature_type, command, 0),
PB_FIELD( 3, BYTES , REQUIRED, CALLBACK, OTHER, dfu_signed_command_t, signature, signature_type, 0),
PB_LAST_FIELD
};
const pb_field_t dfu_packet_fields[3] = {
PB_FIELD( 1, MESSAGE , OPTIONAL, STATIC , FIRST, dfu_packet_t, command, command, &dfu_command_fields),
PB_FIELD( 2, MESSAGE , OPTIONAL, STATIC , OTHER, dfu_packet_t, signed_command, command, &dfu_signed_command_fields),
PB_LAST_FIELD
};
....
NRF52832 dfu 代码分析
Title: Packet decode过程
on_ble_evt->on_ctrl_pt_write:ble 接收到数据包
on_ctrl_pt_write->nrf_dfu_req_handler_on_req:处理请求
nrf_dfu_req_handler_on_req->nrf_dfu_command_req:执行命令
nrf_dfu_command_req->dfu_decode_commmand:调用nano pb解码
dfu_decode_command
函数,通过调用nano-pb的pb_decode
来解码packet。
static uint32_t dfu_decode_commmand(void)
{
stream = pb_istream_from_buffer(s_dfu_settings.init_command, s_dfu_settings.progress.command_size);
// Attach our callback to follow the field decoding
stream.decoding_callback = pb_decoding_callback;
// reset the variable where the init pointer and length will be stored.
hash_data.p_le_data = NULL;
hash_data.len = 0;
if (!pb_decode(&stream, dfu_packet_fields, &packet))
{
NRF_LOG_INFO("Handler: Invalid protocol buffer stream\r\n");
return 0;
}
return 1;
}
上述代码中stream.decoding_callback
的用途。Packet中包含了init_command
的内容也包含了init_command
的ECDSA
签名,该签名是对init_command
的hash值进行的签名。而本callback的目的是求得init_command
的起始地址和长度。然后利用起始地址和长度计算hash值。在利用该hash值进行ECDSA
签名验证。目前发现在新版本的nano-pb中已经不再支持callback这种方法。
init_command
的hash算法采用sha256。
static void pb_decoding_callback(pb_istream_t *str, uint32_t tag, pb_wire_type_t wire_type, void *iter)
{
pb_field_iter_t* p_iter = (pb_field_iter_t *) iter;
// match the beginning of the init command
if(p_iter->pos->ptr == &dfu_init_command_fields[0])
{
uint8_t *ptr = (uint8_t *) str->state;
uint32_t size = str->bytes_left;
// remove tag byte
ptr++;
size--;
// store the info in hash_data
hash_data.p_le_data = ptr;
hash_data.len = size;
NRF_LOG_INFO("PB: Init data len: %d\r\n", hash_data.len);
}
}
在stm32
平台上移植nano-pb并进行测试:
解压缩用nrfutil打包的zip
文件。
2016/12/29 13:12 49,844 app.bin
2016/12/29 13:12 135 app.dat
2016/12/29 13:12 138 manifest.json
其中app.dat
是protocol buffers格式的Packet
。用Bin2C.exe
工具将其转为.c
文件:
/*
C-file generated by Bin2C
Compiled: Jul 1 2015 at 10:51:35
Copyright (C) 2014
Segger Microcontroller GmbH & Co. KG
www.segger.com
Solutions for real time microcontroller applications
*/
static const unsigned char _acapp[135UL + 1] = {
0x12, 0x84, 0x01, 0x0A, 0x3E, 0x08, 0x01, 0x12, 0x3A, 0x08, 0x04, 0x10, 0x52, 0x1A, 0x02, 0x8C, 0x01, 0x20, 0x00, 0x28, 0x00, 0x30, 0x00, 0x38, 0xB4, 0x85, 0x03, 0x42, 0x24, 0x08, 0x03, 0x12, 0x20, 0xD7, 0xFE, 0x48, 0x42, 0x19, 0x5F, 0x7B,
0x91, 0x27, 0xD4, 0xB5, 0x22, 0xEE, 0x5D, 0x6B, 0x4E, 0xF3, 0xE7, 0x55, 0x67, 0x75, 0x08, 0x09, 0x3C, 0x7F, 0x0E, 0xD2, 0x92, 0x49, 0xF0, 0xC1, 0x54, 0x48, 0x00, 0x10, 0x00, 0x1A, 0x40, 0x08, 0xB9, 0x28, 0xA1, 0x65, 0x65, 0x90, 0x80, 0x28,
0x07, 0x40, 0x5C, 0x37, 0x52, 0x2E, 0xB2, 0xC1, 0x1B, 0x67, 0xFF, 0x6B, 0x56, 0x88, 0x51, 0x7D, 0x05, 0x2C, 0xEF, 0xCB, 0x8C, 0x8F, 0xBB, 0xD1, 0x0E, 0xAF, 0x3F, 0x5F, 0x7B, 0x6A, 0xFA, 0xB1, 0xC7, 0xC2, 0xBF, 0x21, 0x80, 0x7F, 0x93, 0xC9,
0x1D, 0xD1, 0xC9, 0x40, 0x6B, 0xA0, 0x36, 0xE6, 0x2E, 0x99, 0x23, 0xC3, 0xC0, 0x3D, 0x93, 0x00
};
/*************************** End of file ****************************/
利用STM32F746 Discovery的板子,在已有工程上添加nano-pb
所需要的文件。
在写如下测试代码:
static const unsigned char dfu_cc_packet[135UL + 1] = {
0x12, 0x84, 0x01, 0x0A, 0x3E, 0x08, 0x01, 0x12, 0x3A, 0x08, 0x04, 0x10, 0x52, 0x1A, 0x02, 0x8C, 0x01, 0x20, 0x00, 0x28, 0x00, 0x30, 0x00, 0x38, 0xB4, 0x85, 0x03, 0x42, 0x24, 0x08, 0x03, 0x12, 0x20, 0xD7, 0xFE, 0x48, 0x42, 0x19, 0x5F, 0x7B,
0x91, 0x27, 0xD4, 0xB5, 0x22, 0xEE, 0x5D, 0x6B, 0x4E, 0xF3, 0xE7, 0x55, 0x67, 0x75, 0x08, 0x09, 0x3C, 0x7F, 0x0E, 0xD2, 0x92, 0x49, 0xF0, 0xC1, 0x54, 0x48, 0x00, 0x10, 0x00, 0x1A, 0x40, 0x08, 0xB9, 0x28, 0xA1, 0x65, 0x65, 0x90, 0x80, 0x28,
0x07, 0x40, 0x5C, 0x37, 0x52, 0x2E, 0xB2, 0xC1, 0x1B, 0x67, 0xFF, 0x6B, 0x56, 0x88, 0x51, 0x7D, 0x05, 0x2C, 0xEF, 0xCB, 0x8C, 0x8F, 0xBB, 0xD1, 0x0E, 0xAF, 0x3F, 0x5F, 0x7B, 0x6A, 0xFA, 0xB1, 0xC7, 0xC2, 0xBF, 0x21, 0x80, 0x7F, 0x93, 0xC9,
0x1D, 0xD1, 0xC9, 0x40, 0x6B, 0xA0, 0x36, 0xE6, 0x2E, 0x99, 0x23, 0xC3, 0xC0, 0x3D, 0x93, 0x00
};
static void pb_decoding_callback(pb_istream_t *str, uint32_t tag, pb_wire_type_t wire_type, void *iter)
{
pb_field_iter_t* p_iter = (pb_field_iter_t *) iter;
rtt_printf("%s\r\n",__FUNCTION__);
// match the beginning of the init command
if(p_iter->pos->ptr == &dfu_init_command_fields[0])
{
uint8_t *ptr = (uint8_t *) str->state;
uint32_t size = str->bytes_left;
// remove tag byte
ptr++;
size--;
// store the info in hash_data
rtt_printf("PB: Init data len: 0x%x,%d\r\n", ptr,size);
}
}
pb_istream_t stream;
dfu_packet_t packet = DFU_PACKET_INIT_DEFAULT;
void test_pb(void)
{
stream = pb_istream_from_buffer(dfu_cc_packet, 136);
// Attach our callback to follow the field decoding
stream.decoding_callback = pb_decoding_callback;
// reset the variable where the init pointer and length will be stored.
if (!pb_decode(&stream, dfu_packet_fields, &packet))
{
rtt_printf("Handler: Invalid protocol buffer stream\r\n");
return;
}
rtt_printf("valid protocol buffer stream\r\n");
return;
}
#endif
运算后的packet
结构如下:
duf_secure代码解析
###memory map
项目 | 开始地址 | 结束地址 | 长度 |
---|---|---|---|
MBR | 0x0000 | 0x1000 | 0x1000 |
softdevice | 0x1000 | 0x1f000 | 0x1e000(120K) |
application | 0x1f000 | 0x78000 | 0x59000(356K) |
dfu bootloader | 0x78000 | 0x7e000 | 0x6000 |
bootloader setting | 0x7f000 | 0x80000 | 0x1000 |
MBR parameters | 0x7e000 | 0x7f000 | 0x1000 |
###bootloader setting
/**@brief DFU settings for application and bank data.
*/
typedef struct
{
uint32_t crc; /**< CRC for the stored DFU settings, not including the CRC itself. If 0xFFFFFFF, the CRC has never been calculated. */
uint32_t settings_version; /**< Version of the currect dfu settings struct layout.*/
uint32_t app_version; /**< Version of the last stored application. */
uint32_t bootloader_version; /**< Version of the last stored bootloader. */
uint32_t bank_layout; /**< Bank layout: single bank or dual bank. This value can change. */
uint32_t bank_current; /**< The bank that is currently used. */
nrf_dfu_bank_t bank_0; /**< Bank 0. */
nrf_dfu_bank_t bank_1; /**< Bank 1. */
uint32_t write_offset; /**< Write offset for the current operation. */
uint32_t sd_size; /**< SoftDevice size (if combined BL and SD). */
dfu_progress_t progress; /**< Current DFU progress. */
uint32_t enter_buttonless_dfu;
uint8_t init_command[INIT_COMMAND_MAX_SIZE]; /**< Buffer for storing the init command. */
} nrf_dfu_settings_t;
###bootloader 检查升级包的有效性
bootloader 通过dfu_handler_prevalidate
来检查升级包的有效性。会根据nrfutil打包升级包时输入的--hw-version
,--sd-req
,--applicatin-version
来判断升级包的有效性。同时还会验证ECDSA签名。
static nrf_dfu_res_code_t dfu_handle_prevalidate(dfu_signed_command_t const * p_command, pb_istream_t * p_stream, uint8_t * p_init_cmd, uint32_t init_cmd_len)
soft device 通过判断UICR[User information configuration registers] 写入的bootload_start_address
来判断是否有dfu bootloader。
#if defined (__CC_ARM )
#pragma push
#pragma diag_suppress 1296
uint32_t m_uicr_bootloader_start_address __attribute__((at(NRF_UICR_BOOTLOADER_START_ADDRESS)))
= BOOTLOADER_START_ADDR;
#pragma pop
#elif defined ( __GNUC__ )
volatile uint32_t m_uicr_bootloader_start_address __attribute__ ((section(".uicrBootStartAddress")))
= BOOTLOADER_START_ADDR;
#elif defined ( __ICCARM__ )
__root const uint32_t m_uicr_bootloader_start_address @ NRF_UICR_BOOTLOADER_START_ADDRESS
= BOOTLOADER_START_ADDR;
#endif