system分区签名校验方法

签名过程

整个system签名过程如下图所示:

system分区签名校验方法_第1张图片

  • 哈希树的生成

Dm-verity 使用加密散列树提供块设备的透明完整性检查,每个块以 4k 的大小来划分,都有一个 SHA256 的值。树中的每个节点是加密 hash,其中叶节点包含物理数据块的 hash,并且中间节点包含其子节点的 hash。因为根节点中的哈希是基于所有其他节点的值,所以只有根哈希需要被信任才能验证树的其余部分。对任何一个节点块的改动都破坏整个加密 hash。整个哈希树的结构如下图所示:

system分区签名校验方法_第2张图片

  • dm-verity table的生成

根据上面生成的哈希树,调用build_verity_metadata.py脚本生成dm-verity table。实际上dm-verity table就是描述之前生成的hash tree的一段字符串,该table中的内容是需要传递给dm-verity使用的。这个dm-verity table的组成格式需要参考内核dm-verity的文档:Documentation/device-mapper/verity.txt


 Construction Parameters

 =======================

     <version> <dev> <hash_dev>

     <data_block_size> <hash_block_size>

     <num_data_blocks> <hash_start_block>

     <algorithm> <digest> <salt>

     [<#opt_params> <opt_params>]

system分区签名校验方法_第3张图片

可以看到其中包含了hash tree的根节点,总共多少个block,每个block的大小,加盐值,算法名称等等关键数据。这个table最终是需要校验通过后传递给内核中的dm-verity驱动的。

  • RSA数字签名的生成

对dm-verity table进行签名生成数字签名,和dm-verity table一起放在system分区的后面。

  • 打包生成verity-metadata

把上面生成的dm-verity table和数字签名一起打包生成verity-metadata.img

  • system.img的组成

和上图中描述的一致,包含了system data/verity-metadata.img/verity.img,分别对应的打包数据内容是system data/dm-verity table+signature/hash tree。

校验过程

init代码中是会调用libfs_mgr库中的函数来挂载分区,所以分区校验的关键过程实现是在fs_mgr中。fs_mgr会读取该boot.img中的公钥key,高通平台的verity_key存放在ramdisk的根目录下。init中是通过do_mount/do_mount_all来实现分区挂载,对应到init.rc中的命令就是mount/mount_all。所以init.rc中需要定义mount或者mount_all来挂载对应的分区。mount_all是会查找fstab文件中的定义来挂载分区的。被验证的目标分区,有着一个包含了dm-verity table和它自身签名的元数据块,被附加到镜像的最后,通过在 fstab 文件中对挂载设备添加 verify 标签,在挂载时,当fs_mgr检测到该标签,则会使用 verity_key 公钥加载校验该分区最后附加的元数据。如果签名验证通过,则会把解析出的 dm-verity table 传递给内核,内核中的dm-verity驱动根据table包含的信息来创建虚拟的 dm-verity 块设备,然后将该虚拟块设备安装在 fstab 中指定的安装点上。因此,所有读自底层物理设备的数据都会用预先生成的hash tree散列树进行验证。对设备任何修改或添加文件,包括重新挂载为读写权限都会引起 I/O 错误。

示例如下:


fstab:

/dev/block/bootdevice/by-name/system       /system     ext4     ro,barrier=1     wait, verify

使用fstab来挂载

可执行文件:


init/fs_mgr

代码:


system/core/init

system/core/fs_mgr

Android O + avb 2.0 + A/B system

使能了A/B system之后,android O把rootfs和system data集成到一起放到system分区了(也就是说system.img会被挂载到/根目录上,其中包含了system),boot分区只存放kernel,所以校验方法如下图所示,首先bootloader需要用公钥校验vbmeta的签名,以及boot签名和system签名,签名验证通过之后才允许下一步booting,校验签名完成之后,会启动kernel,并把system分区中的rootfs相关的dm-verity数据通过cmdline的形式传递给kernel,kernel启动会挂载rootfs(注意rootfs中包含了system,两者为同一个分区),所以可以认为system的挂载是在kernel中完成的,并且使用dm-verity模式进行的挂载。

为什么要让bootloader去检验签名system分区签名并且kernel去挂载呢?我一直有这个疑问,按照以往的项目,都是fs_mgr和init中的mount来做签名校验和挂载system分区的,原因是rootfs被放置在system分区,而fs_mgr和init都都在rootfs中,如果kernel不去挂载system分区到rootfs上,我们根本是无法执行其中的init和fs_mgr程序去校验签名。

system分区签名校验方法_第4张图片


注意:

1.上表中的keystore就是vbmeta分区

2.bootloader使用的是uefi

3.A/Bsystem使能后,system.img会被挂载到/根目录上,这就意味着fs_mgr/init也存在与system.img中,因而不能被用来system自己了。

4.system分区是被bootloader使用公钥校验,并由kernel中的dm-verity挂载的,所需要的信息通过cmdline传递。

Android O + avb 2.0 + Non-A/B system

未使能A/Bsystem特性的android o,ramdisk是和kernel一起编译进boot分区的,因此我们依然采用传统的方式来校验分区,首先是bootloader校验boot分区中的签名,然后启动kernel,kernel挂载ramdisk,fs_mgr和init都在ramdisk中,所以可以由init/fs_mgr来进行后续分区的挂载。

由于Android O的特性,很多ko驱动模块被单独编译出来并放置于system分区中,所以我们需要在init的stage one第一阶段就把system分区挂载上,并insmode其中的模块,如果不如此,后续的启动流程可能会失败,因为很多关键的驱动ko都没有加载。init会在init_first_stage的第一阶段就调用fs_mgr中的相关函数把system分区校验并挂载起来,并且insmod其中的一些ko驱动。而其他分区依然在fstab中配置并在以后进行挂载,当然如果使用了AVB 2.0,那么需要调用libavb中的一些函数来实现校验的过程。

你可能感兴趣的:(system分区签名校验方法)