文章目录
前言
本篇应该是AVB系列的最后一篇博客了,前面的六篇博客基本上把AVB涉及到的技术点都过了一遍,因为平时要开发项目比较忙 所以有点时间就上来写写,文笔不是很好 大家将就着看看了~~
感兴趣的朋友可以从这里开始android AVB2.0学习总结
当然我的博客可能有更多的细节没有讲到,比如android提供的README.md文档和UBOOT中的AVB设计。我其实刚开始是有计划去把README.md翻译和解析一遍,这个文档里面基本上包含到了android设计AVB的细节,我刚开始学习AVB的时候就是啃的这个文档,包括后面自己在AIOT项目上移植AVB功能,都是参考的这篇文档,建议对AVB感兴趣的朋友认真看看README.md。
好了,言归正传,下面我讲一下我在AIOT上如何实现AVB校验分区功能,把前面博客中介绍到的技术抽离出来自己实现AVB功能。
原创不易,转载请注明出处 https://blog.csdn.net/jackone12347/article/details/122121118
本篇博客主要包括项目需求、设计思路、详细设计,内容如下。
一、移植需求
在AIOT非android项目上实现system的AVB校验功能,需要移植android AVB功能上来。
好了,需求就是这个,离开android框架,自己搭建AVB环境,实现整套的AVB校验。
二、分解需求
我们的项目是基于buid root搭建的架子,下面是我分解的需求:
· 在编译build root前,生成RSA公私钥对,并抽出公钥hash存到veritydm模块的.h头文件中;
· 对编译的system镜像打包前,预留出签名所需要的空间;
· 全部编译完成后,分别对vbmeta.img和system.img进行签名,并生成带签名的vbmeta.img和system.img;
· 在开机挂载system分区前,调用veritydm程序对system进行签名验证并使能dm verity table到kernel md驱动;
· 设计veritydm程序,移植android的libavb库,并调用libavb库完成AVB校验,
从vbmeta中获取system分区的digest和拼接参数组合成hash tree table,
调用dmsetup程序将参数通过ioctl方式传递给kernel,完成device mapper的映射功能。
三、详细设计
整个的设计流程如下:
生成RSA密钥 ==》对system.img签名 ==》生成vbmeta.img ==> Verifydm程序验证签名 ==》
进行AVB校验 ==》 组建hash tree table ==》 创建device mapper映射
1. RSA密钥生成
项目上的源码不方便直接贴出,但我会尽量将涉及的技术点讲一下。
下面是shell脚本实现的RSA相关密钥的产生。
make_key是从andorid项目的development/tools/make_key移植过来的。
#下面是制作RSA时用到的变量
export PRI_KEY_CONTENT='/C=CN/ST=PD/L=SH/O=XXX/OU=SS/CN=China/emailAddress=xxx@xxx.com'
SALT_DATA='5c83818fd6371cbe4ecae7ff169a5857a5cae2e8e7b7d4b0168ac3e4fd42176t'
#make_key是从andorid项目的development/tools/make_key移植过来的
MAKE_KEY_PATH=make_key
KEY_PK8=veritykey.pk8
KEY_VERITY=veritykey.pem
#avbtool是从android移植 external/avb/avbtoo.py
AVBTOOL_PATH=avbtool
# bin2header工具我在AVB系列博客中"AVB中将公钥转换成字符数组头文件的实现"有介绍
BIN2HEADER_PATH=bin2header
下面是制作的脚本内容
$MAKE_KEY_PATH veritykey $PRI_KEY_CONTENT
openssl pkcs8 -inform DER -nocrypt -in veritykey.pk8 -out $KEY_VERITY
python $AVBTOOL_PATH extract_public_key --key $KEY_VERITY --output vbmeta_pub_key.bin
$BIN2HEADER_PATH vbmeta_pub_key.bin vbmeta_key >> vbmeta_key_header.h
执行完成上面步骤后,产生了RSA私钥和公钥,以及公钥对应的header头文件。
2. 镜像签名和生成vbmeta镜像
有了私钥文件后,接下来就可以对system.img进行签名了
SYSTEM_IMG_PATH=system.img
export SHA_ALGORITHM=SHA256_RSA2048
export SYSTEM_ROLLBACK=1
#
调用avbtool.py脚本的calc_max_image_size函数,计算system的最大可用size
#因为要给hash tree预留空间
SIZE=`python $AVBTOOL_PATH add_hashtree_footer --do_not_generate_fec --partition_size $yourpartitionsize --calc_max_image_size`
#调用avbtool.py脚本,添加footer数据,其中$KEY_VERITY就是前面步骤产生的私钥
python $AVBTOOL_PATH add_hashtree_footer --partition_size ${SIZE} --partition_name system --image $SYSTEM_IMG_PATH\
--salt $SALT_DATA --do_not_generate_fec --key $KEY_VERITY --hash_algorithm sha256 \
--algorithm $SHA_ALGORITHM --rollback_index $SYSTEM_ROLLBACK
接下来是生成vbmeta.img,且将system.img的信息添加到vbmeta.img镜像中,主要是“–include_descriptors_from_image”这句了。
python $AVBTOOL_PATH make_vbmeta_image --output vbmeta.img --key $KEY_VERITY --algorithm $SHA_ALGORITHM --include_descriptors_from_image $SYSTEM_IMG_PATH --padding_size 4096
这样vbmeta.img和system.img就生成好了,可以使用“info_image”命令查看一下vbmeta.img和system.img的信息,查看一下他们是否匹配。
python $AVBTOOL_PATH info_image --image vbmeta.img
3. VeritySetup程序设计
签名的system.img镜像和vbmeta.img都有了,接下就是开机运行的校验了,那谁来执行这个校验呢? android中是由init的第一阶段的程序完成校验,我们这里只能自己写程序去校验了,得自己造轮子~~~
内容如下:
3.1 主程序设计
#include "libavb/libavb.h“
int load_image_and_auth()
{
...
user_data = avb_calloc(sizeof (AvbOpsUserData));
…
ops = avb_ops_new(user_data);
slot_suffix = "\0";
result = avb_slot_verify(ops, (const char *const *)requested_partition,
slot_suffix, verify_flag, verity_flag, &slot_data);
…
}
AvbOps *avb_ops_new(void *user_data);
typedef struct {
bool is_user_key;
bool is_multi_slot;
uint32_t public_key_len;
char* public_key[MAX_USER_KEY_SIZE];
} AvbOpsUserData;
AvbOps *avb_ops_new(void *user_data)
{
AvbOps *ops = avb_calloc(sizeof(AvbOps));
ops->user_data = user_data;
ops->read_from_partition = avb_read_from_partition;
ops->read_rollback_index = dummy_read_rollback_index;
ops->validate_vbmeta_public_key = avb_validate_vbmeta_public_key;
ops->read_is_device_unlocked = dummy_read_is_device_unlocked;
ops->get_size_of_partition = dummy_get_size_of_partition;
…
}
3.1 公钥比对接口设计
其中公钥比较是在avb_validate_vbmeta_public_key完成的
AvbIOResult
avb_validate_vbmeta_public_key(AvbOps *ops, const uint8_t *public_key_data,
size_t public_key_len, const uint8_t *public_key_metadata,
size_t public_key_metadata_len, bool *key_is_trusted)
{
...
if (memcmp(vbmeta_key, public_key_data, public_key_len) == 0) {
*key_is_trusted = true;
} else {
return AVB_IO_RESULT_ERROR_IO;
}
...
}
3.2 读取vbmeta分区设计
读取vbmeta分区的信息, avb_read_from_partition方法需要自己实现,
Google这几个接口默认没有实现,预留给开发者自行定义。
这里是直接read读取整个vbmeta分区。
AvbIOResult avb_read_from_partition(AvbOps *ops, const char *partition, int64_t offset, size_t num_bytes, void *buffer, size_t *out_num_read)
{
if ((fd = open(DM_VBMETA_NODE, O_RDWR)) < 0) {
return AVB_IO_RESULT_ERROR_IO;
}
size_t num_read = read(fd, buffer, num_bytes);
if (num_read != num_bytes) {
return AVB_IO_RESULT_ERROR_IO;
}
if (out_num_read != NULL) {
*out_num_read = num_read;
}
…
}
其他的接口我就不描述了,比如lock和rollback的,如果有需求请自行实现即可,难度不是很大~
3.3 DM verity设计
有了接口了后,接下来要把读出来的hash tree设置到kernel驱动侧。
至于为什么是下面这个位置添加代码逻辑,需要理解我前面博客中读到的AVB验证流程,
因为最终我们的system是采用hash tree类型的处理。
@avb_slot_verify.c
case AVB_DESCRIPTOR_TAG_HASHTREE: {
if (!avb_hashtree_descriptor_validate_and_byteswap(
…
ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
goto out;
}
...
##可以从hashtree_desc中获取到想要的信息,比如partition_name_len/image_size/data_block_size/hash_block_size/tree_offset
##然后就可以拼接hashtable了,参数如下
sprintf(hashtable, "0 %ld %s %d %s %s %ld %ld %ld %ld sha256 %s %s",
img_size / 512, "verity", dm_version, DM_SYSTEM_NODE, DM_SYSTEM_NODE, data_bsize, hash_bsize,
img_size / 4096, img_size / 4096, avb_verity.root_digest, avb_verity.part_salt);
##hashtable OK后,就可以设置参数到kernel md模块了,我这里将用了build root自带的dmsetup程序完成。当然也可以参考android的那个ioctl方式来传递,都是可以的。
sprintf(cmd_buff, "./sbin/dmsetup create verity --readonly --table \"%s\"", hashtable);
3.4 分区挂载校验
上面的主程序编译出来是verfydm,我们在开机的流程中去执行这个程序,执行完成后,会自动产生/dev/dm-0节点出来,这个和android是十分类似的。也很好理解,kernel不区分android还是linux,只要合理的ioctl成功,且底层能正常解析,就可以产生dm-x节点。
./sbin/verifydm
if [ $runResult == 0 -a -e /dev/dm-0 ]; then
/bin/mount -t ext4 -o ro /dev/dm-0 /usr
下面是整个校验过程的完整日志,看下AVB校验的过程:
[main] test veritysetup world!
[libavb] avb_slot_verify entry...
[libavb] avb_slot_verify usual mode load vbmeta and verify.
[libavb] load_and_verify_vbmeta entry ====>
[libavb] load_and_verify_vbmeta partition_name:vbmeta
[libavb] load_and_verify_vbmeta read_from_partition: vbmeta
[avbops] avb_read_from_partition vbmeta entry ===
[avbops] avb_read_from_partition start to read data ===
[avbops] readResult offset: 0 num_read: 65536 buffer: 0x2e66e6d0
[libavb] load_and_verify_vbmeta read_from_partition done.
[libavb] avb_vbmeta_image_verify handle vbmeta header.
[libavb] avb_vbmeta_image_verify algorithm type is sha256.
[libavb] avb_vbmeta_image_verify start to check hash whether is matched...
[libavb] avb_vbmeta_image_verify avb_rsa_verify done.
[libavb] avb_vbmeta_image_verify hash and signature is matched!
[libavb] avb_vbmeta_image_verify all done <=====
[libavb] load_and_verify_vbmeta start to check key whether is expected.
[avbops] avb_validate_vbmeta_public_key public_key_len:520
[avbops] avb_validate_vbmeta_public key is equal!
[libavb] load_and_verify_vbmeta start to read rollback index...
[libavb] load_and_verify_vbmeta num_descriptors inside.
[libavb] load_and_verify_vbmeta hashtree type.
[libavb] load_and_verify_vbmeta hashtree all successfully!
[libavb] load_and_verify_vbmeta all done!
[libavb] load_and_verify_vbmeta result:0
[libavb] avb_slot_verify manage dm-verity mode
[libavb] avb_slot_verify avb_sub_cmdline done
[libavb] avb_slot_verify function all done!
[main] avb_slot_verify result:0
总结
到这里AVB的移植就介绍差不多了。
为方便与大家及时交流,弄了一个微信公众号,欢迎大家留言沟通~