swupdate linux ota故障安全升级方案

一、简介

swupdate是一个基于嵌入式的Linux平台的升级服务框架程序,它提供了分区升级,文件升级,差分升级(补丁应用)功能,并提供了开放接口,方便用户添加自定义升级处理函数。

swupdate提供了故障安全的升级方案。支持Recovery方案和A/B系统方案来保证断电等异常情况下能保证升级正常。它提供了完整性,签名验证,固件加密等功能,保证了升级固件的安全性和私密性。

说明文档路径:https://sbabic.github.io/swupdate/overview.html

代码路径:https://github.com/sbabic/swupdate.git

swupdate 采用cpio的方式进行归档,第一文件是描述文件,默认名称为sw-description。该文件描述了升级包归档文件中的文件信息和升级信息。
第二个文件sw-description.sig该文件是对sw-description的签名文件。保证了sw-description的完整性和授权属性。下面的文件就是各个固件文件了。

二、交叉编译

如果我们要编译一个全功能的swupdate,需要依赖下列这些库。本次为技术预研,为加快进度,依赖库就不一一去移植了。直接使用buildroot去编译,让buildroot去处理依赖关系。依赖库如下:

mtd-utils: internally, mtd-utils generates libmtd and libubi. They are commonly not exported and not installed, 
		but they are linked by SWUpdate to reuse the same functions for upgrading MTD and UBI volumes.

openssl / wolfssl / mbedtls (optional) for cryptographic operations

p11-kit & wolfssl (optional) for PKCS#11 support

Lua: liblua and the development headers.

libz is always linked.

libconfig (optional) for the default parser

libarchive (optional) for archive handler

librsync (optional) for support to apply rdiff patches

libjson (optional) for JSON parser and hawkBit

libubootenv (optional) if support for U-Boot is enabled

libebgenv (optional) if support for EFI Boot Guard is enabled

libcurl used to communicate with network

在buildroot中,我们搜索swupdate,然后选中我们选中swupdate。本次我们需要验证swupdate的基础功能如下:

1)基础web升级。

2)镜像升级功能,文件升级功能,安装脚本功能。

3)升级文件完整性验证。

4)升级文件签名验证。

5)升级文件加解密功能。

6)差分升级(delta升级)功能。

7)包压缩功能。

8)硬件兼容性检测

9)swupdate ipc功能

首先使用一较新的buildroot, 至少是较新的swupdate。我当前使用的swupdate是2017年的比较旧。我使用新的buildroot里的swupdate包进行了替换,替换后的版本swupdate 2022。我们修改buildroot下swupdate的配置文件,以支持我们上述的测试需求。修改的文件为
buildroot/package/swupdate/swupdate.config

CONFIG_HW_COMPATIBILITY=y
CONFIG_SSL_IMPL_OPENSSL=y
CONFIG_HASH_VERIFY=y
CONFIG_SIGNED_IMAGES=y
CONFIG_SIGALG_RAWRSA=y
CONFIG_ENCRYPTED_IMAGES=y
CONFIG_GUNZIP=y

CONFIG_DELTA=y
CONFIG_RDIFFHANDLER=y
CONFIG_SHELLSCRIPTHANDLER=y

这里有个小技巧,下载swupdate源码,使用源码进行make menuconfig选中需要的功能,然后diff .config .config.old就能找出需要新加的配置选项了。
编译完成后,去到编译路径下,buildroot/output/rockchip_rk3568/build/swupdate-2022.12,查看一下.config文件中的配置是否符合预期。如果不符合预期多半是依赖库没有选上。

我出现了 SHELLSCRIPTHANDLER 功能被覆盖成 is not set。应该是librsync没有选上导致的。我们可以看一下buildroot/package/swupdate/Config.in文件,能发现一些线索。

当我们选上librsync和修改完成配置后,在buildroot中进行编译。

rm buildroot/output/rockchip_rk3568/build/swupdate-* -rf
make swupdate

编译完成后,在buildroot下的安装目录下(x/target/)将swupdate进行打包。打包命令如下,虽然将.h等文件也打包进去了,有点粗鲁,但是简单又快。

文档说要使用zchunk来作为增量升级的方式。zchunk分块压缩看起来比较复杂。。

find ./ -name "*swupdat*" -print0 \
	-o -name "libconfig*" -print0 \
	-o -name "librsync*"  -print0 \
	-o -name "libbz2*"  -print0 | tar -cvf swupdate.tar --null -T -

在目标板(aarch64)中进行解压,加压命令

tar -xvf swupdate.tar -C /

等以后有时间了,再一点一点移植库,因为我们固件不是使用builtroot构建的。

三、简单测试

3.1 命令安装测试

我们先测试一下命令是否能正常运行。测试 swupdate,swupdate-progress,swupdate-client,swupdate-ipc。当帮助信息能打印时说明命令移植正常。但不确保功能都支持了。

swupdate -h
能正常打印help信息,有 -w,-k,-K信息说明验签,加密都使能了。下面是查看版本信息

# swupdate --version
SWUpdate v2022.12 (Buildroot 2018.02-rc3-g7e95a20c-dirty)


# swupdate-progress -h 
	swupdate-progress (compiled Jun 19 2023)
	Usage swupdate-progress [OPTION]
	 -c, --color             : Use colors to show results
	 -e, --exec <script>     : call the script with the result of update
	 -r, --reboot            : reboot after a successful update
	 -w, --wait              : wait for a connection with SWUpdate
	 -p, --psplash           : send info to the psplash process
	 -s, --socket <path>     : path to progress IPC socket
	 -h, --help              : print this help and exit
	 -q, --quiet             : do not print progress bar


# swupdate-client -h
	client [OPTIONS] <image .swu to be installed>...
	With - or no swu file given, read from STDIN.
	Available OPTIONS
	-h : print help and exit
	-d : ask the server to only perform a dry run
	-e, --select <software>,<mode> : Select software images set and source
								  Ex.: stable,main
	-q : go quiet, resets verbosity
	-v : go verbose, essentially print upgrade status messages from server
	-p : ask the server to run post-update commands if upgrade succeeds



# swupdate-ipc COMMAND [OPTIONS]
	 aes <key> <ivt>
	 setversion <minversion> <maxversion> <current>
	 sendtohawkbit <action id> <status> <finished> <execution> <detail 1> <detail 2> ..
	 hawkbitcfg 
		-p, --polling-time      : Set polling time (0=from server) to ask the backend server
		-e, --enable            : Enable polling of backend server
		-d, --disable           : Disable polling of backend server
		-t, --trigger           : Enable and check for update
	 gethawkbit
	 sysrestart [OPTION]
		-w, --wait              : wait for a connection with SWUpdate
		-s, --socket <path>     : path to progress IPC socket
		-h, --help              : print this help and exit
	 monitor 
		-s, --socket <path>     : path to progress IPC socket
		-h, --help              : print this help and exit

3.2 手动测试swupdate web

新旧版本的参数不太一样,按照-h提示信息输入即可。

旧版本的测试命令
swupdate -l 15 -w '-document_root /var/www/swupdate/ -listening_ports 9090'

新版本的测试命令
swupdate -w "--document-root /var/www/swupdate/  --port 9090" -k test/key/public.pem

当运行成功后,访问设备地址+端口就看到一个web页面,可以进行reboot和上传升级包。

3.3编写一个init.d启动脚本

buildroot已经帮你编写好了, /etc/init.d/S80swupdate。里面的规则还没来得及细看。

3.4 制作一个升级包

在swupdate的的文档中有示例脚本,脚本内容也很简单。我们会以这个脚本为基础,去测试各个功能。脚本如下,文件名称和数量需要自行调整。

#!/bin/sh

set -x 

CONTAINER_VER="1.0"
PRODUCT_NAME="my-software"
FILES="sw-description sw-description.sig README print_hello encrypted.ciphertext file.rdiff.delta image.rdiff.delta p.gz oem.bin"

openssl dgst -sha256 -sign ./key/priv.pem sw-description > sw-description.sig

for i in $FILES;do
	echo $i;done | cpio -ov -H crc >  ${PRODUCT_NAME}_${CONTAINER_VER}.swu

生成公钥,私钥,AES Key IV的脚本如下:

签名相关
openssl genrsa -aes256 -out priv.pem
openssl rsa -in priv.pem -out public.pem -outform PEM -pubout
openssl dgst -sha256 -sign priv.pem sw-description > sw-description.sig


对称加密相关
openssl rand -hex 32
openssl rand -hex 16

openssl enc -aes-256-cbc -in <INFILE> -out <OUTFILE> -K <KEY> -iv <IV>

我的打包环境和升级环境目录结构

打包环境
├── key
│   ├── aes_iv
│   ├── aes_key
│   ├── aes_key_iv
│   ├── priv.pem
│   └── public.pem
├── pack.sh
├── oem.bin
├── xxxx


升级环境
├── key
│   ├── aes_key_iv
│   └── public.pem

打包的AES aes_key_iv文件时相同的
cat key/aes_key_iv 
390ad54490a4a5f53722291023c19e08ffb5c4677a59e958c96ffa6e641df040 d5d601bacfe13100b149177318ebc7a4

3.5 硬件兼容性检测

如果使能了检测硬件版本兼容性,需要创建/etc/hwrevision 文件,在文件中输入于sw-description相匹配的版本号。

cat /etc/hwrevision
X900  1.2

这个文件是可以修改的。可以通过修改CONFIG_HW_COMPATIBILITY_FILE文件来改变原有的默认文件 /etc/hwrevision。举例如下:

CONFIG_HW_COMPATIBILITY_FILE="tests/etc/hwrevision"

3.6 完整性验证

因为我们编译已经开启了签名验证,每次升级检测或执行升级时,都需要进行强制进行完整性和签名检测。所以把完整性和签名验证放置在前面验证。我们使用分区镜像升级来验证完整性。

$ cat oem.bin 
hello oem partition update 2023:06:20T12:00:00Z

$ sha256sum oem.bin 
08acda10cc55ee2d5e06d9758f2602c85ca767598b11cc123872c7fb5b004162  oem.bin

images: (
	{
			filename = "oem.bin";
			device = "/dev/mmcblk0p7";
			sha256 = "08acda10cc55ee2d5e06d9758f2602c85ca767598b11cc123872c7fb5b004162";
	}
)


打包生成 my-software_1.0.swu包,拷贝到目标环境进行升级,先检测,后升级。

检测
swupdate -l 15 -c -i my-software_1.0.swu -k key/public.pem  -K key/aes_key_iv
 
升级
swupdate -l 15 -i my-software_1.0.swu -k key/public.pem  -K key/aes_key_iv

升级成功后,可以看到对应分区已经更新成我们设定的内容了。
# hexdump -C /dev/mmcblk0p7 -n 1024
00000000  68 65 6c 6c 6f 20 6f 65  6d 20 70 61 72 74 69 74  |hello oem partit|
00000010  69 6f 6e 20 75 70 64 61  74 65 20 32 30 32 33 3a  |ion update 2023:|
00000020  30 36 3a 32 30 54 31 32  3a 30 30 3a 30 30 5a 0a  |06:20T12:00:00Z.|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000400

当sha256属性不携带,或者不正确时,升级包检测不通过,升级不成功。只有携带正确的sha256值,才能正常进行升级。错误打印如下:

[ERROR] : SWUPDATE failed [0] ERROR core/cpio_utils.c : __swupdate_copy : 635 : HASH mismatch : 08acda10cc55ee2d5e06d9758f2602c85ca767598b11cc123872c7fb5b004163 <--> 08acda10cc55ee2d5e06d9758f2602c85ca767598b11cc123872c7fb5b004162
[ERROR] : SWUPDATE failed [1] Image invalid or corrupted. Not installing ...
[TRACE] : SWUPDATE running :  [network_initializer] : Main thread sleep again !
[INFO ] : No SWUPDATE running :  Waiting for requests...
[ERROR] : SWUPDATE failed [0] ERROR core/install_from_file.c : endupdate : 55 : SWUpdate *failed* !

3.7 签名验证

经过采用3.6的测试,已经正向验证签名正确时,可进行升级。这里错误场景测试一下。修改sw-description.sig文件,然后打包进行测试,升级。
结果就是验签不通过。

[TRACE] : SWUPDATE running :  [swupdate_verify_file] : Verify signed image: Read 1853 bytes
[ERROR] : SWUPDATE failed [0] ERROR corelib/swupdate_rsa_verify.c : verify_final : 99 : EVP_DigestVerifyFinal failed, error 0x407008a 0
[TRACE] : SWUPDATE running :  [swupdate_verify_file] : Error Verifying Data

3.8 分区镜像安装

sw-description配置文件示例如下。我们随便创建一个oem.bin文件,就可以拿一个目前不使用的分区做测试。我们使用oem分区做测试。
文件内容为hello oem partition update 2023:06:20T12:00:00Z

参考3.6测试即可。

3.9 文件安装

我们将oem区格式化成ext4文件系统,方便进行文件安装测试。

 mkfs.ext4 /dev/mmcblk0p7

当你不写type时,默认为raw类型,swupdate会将升级包中的README复制到/root/yp/README"中。device是指定的分区,这里的路径是以挂载点为根目录的。如果/dev/mmcblk0p7挂载在/tmp/oem下,那么key会被安装到/tmp/oem/test/miss/key中。

files: (
	{
		filename = "README";
		path = "/root/yp/README";
		device = "/dev/mmcblk0p6";
		filesystem = "ext4";
		sha256 = "16e3c135a4214781e94cf34a2b5879d49d57ecfb4bc55eeb4f9de58d4057cb93";
	},
	{
			filename = "key";
			path = "/test/miss/key";
			device = "/dev/mmcblk0p7";
			filesystem = "ext4";
			sha256 = "48f2ac99f953c773d833de83880517846aa8ea5473ea725dd1b42d19c17ee086";
	}
)

3.10 脚本执行

type可以是lua,shellscript,preinstall,postinstall。shellscript在装前装后都执行,传入"preinst"或"postinst"作为第一个参数传递给脚本。

scripts: (
	{
		filename = "print_hello";
		type = "shellscript";
		sha256 = "d4e41ec55b6a4f87e521791d581800b6c5dbfb1a4c27e0cc6e994bf33aa00ea7";
	}
);

执行脚本内容
#!/bin/sh

echo "print hello $1 $@" > /tmp/swxxxxx.$1
date >> /tmp/swxxxxx.$1

测试是正常的,执行结果为:

# cat /tmp/swxxxxx.preinst 
print hello preinst preinst
Tue Jun 20 20:15:05 CST 2023

 
# cat /tmp/swxxxxx.postinst 
print hello postinst postinst
Tue Jun 20 20:15:05 CST 2023

3.11 加密验证

第一步在打包环境下生成加密镜像,操作如下,记得把key和IV记录下来。

openssl rand -hex 32
# key, for example 390ad54490a4a5f53722291023c19e08ffb5c4677a59e958c96ffa6e641df040
openssl rand -hex 16
# IV, for example d5d601bacfe13100b149177318ebc7a4


生成加密镜像
openssl enc -aes-256-cbc -in <INFILE> -out <OUTFILE> -K <KEY> -iv <IV>
openssl enc -aes-256-cbc -in README -out README.ciphertext -K  390ad54490a4a5f53722291023c19e08ffb5c4677a59e958c96ffa6e641df040 -iv d5d601bacfe13100b149177318ebc7a4
openssl enc -aes-256-cbc -in encrypted.plain -out encrypted.ciphertext -K  390ad54490a4a5f53722291023c19e08ffb5c4677a59e958c96ffa6e641df040 -iv d5d601bacfe13100b149177318ebc7a4

第二步将AES Key和IV使用空格拼接,保存在升级环境中。

cat key/aes_key_iv 

390ad54490a4a5f53722291023c19e08ffb5c4677a59e958c96ffa6e641df040  d5d601bacfe13100b149177318ebc7a4

第三步按照下面配置,打包加密固件。

files: (
	{
		filename = "encrypted.ciphertext";
		path = "/root/yp/encrypted.plain";
		device = "/dev/mmcblk0p6";
		filesystem = "ext4";
		encrypted = true;
		ivt = "d5d601bacfe13100b149177318ebc7a4";
		sha256 = "fbf59b024264c24534f17afb15bf12630c774af9b4a09f54aacd349e0dd12e6b";
	}
);
验证加密包
swupdate -l 15 -c -i my-software_1.0.swu -k key/public.pem  -K key/aes_key_iv


执行本地包升级
swupdate -l 15 -i my-software_1.0.swu -k key/public.pem  -K key/aes_key_iv


# cat /root/yp/encrypted.plain
Mon 19 Jun 2023 07:40:50 PM CST

This is a encrypted test file on swupdate!!!

从测试来看swupdate并没有保持整个升级过程的原子性。当有固件失败时,无法进行回退。

3.12 delta升级(差分升级)

SWUpdate 通过 rdiff 处理程序支持librsync作为增量编码器。但是新文档说要使用zchunk来作为增量升级的方法。zchunk分块压缩看起来比较复杂。
zck的命令在ubuntu也下不到(可能是下载源的问题),所以就先不研究。

先研究一下简单的差分升级,我们使用rdiff工具,进行一些基本的流程操作。参考连接https://gist.github.com/jpillora/edb83dc2d511c410cf46485e53569fdf
文件操作如下:

rdiff的基础使用
# create signature for old file
rdiff signature old-file signature-file

# create delta using signature file and new file
rdiff delta signature-file new-file delta-file

# generate new file using old file and delta
rdiff patch old-file delta-file gen-file

# test
diff -s gen-file new-file
# Files gen-file and new-file are identical

对目录的操作使用的是rdiffdir命令,语法与rdiff很相识。操作如下:

rdiffdir [options] sig[nature] basis_dir signature_file

rdiffdir [options] delta full_sigtar {incr_sigtar} new_dir delta_file

rdiffdir [options] patch basis_dir delta_file

rdiffdir [options] tar basis_dir tar_file


rdiffdir signature old-dir  signature-file
rdiffdir delta     signature-file  new-dir delta-file
rdiffdir patch     basis_dir  delta-file


采用gzib进行压缩,delta文件小很多。
rdiffdir -z  sig key sign
rdiffdir -z delta sign key-back/ delta-dir-z
rdiffdir -z patch basis_dir  delta-dir-z
rdiffdir -z patch basis_dir  delta-dir-z

在swupdate中我们找到了delta的配置CONFIG_DELTA=y和CONFIG_RDIFFHANDLER=y,将这2个配置添加到buildroot 的swupdate的配置文件中,重新编译。
在sw-description文件中加入下面描述

files: (
	{
		type = "rdiff_file"
		filename = "file.rdiff.delta";
		path = "/usr/bin/file";
	}
);

只适用于单个文件,而且这个文件还需要存在,rdiffdir生成的对目录的delta是不支持的。

对分区的delta升级

images: (
	{
		type = "rdiff_image";
		filename = "image.rdiff.delta";
		device = "/dev/mmcblk0p2";
		properties: {
			rdiffbase = ["/dev/mmcblk0p1"];
		};
	}
);

没有看太懂,将分区1的内容增量处理delta文件后保存在分区2,可以是同一个分区吗?我测试一下。测试的结果是可以,但就是纯为二进制的,从偏移为0的地方进行patch。在测试中,出现了一次擦除错误,导致kernel painc,根文件系统ls等基础命令无法使用,不知到是否是这个导致的。

感觉批量对指定目录和文件进行patch,还是要靠定义使用diff来实现。

3.13 与debin包结合使用

这个完全是自定义处理函数处理。

3.14 压缩功能测试

{
	filename = "p.gz";
	path = "/test/miss/key";
	device = "/dev/mmcblk0p7";
	filesystem = "ext4";
	compressed = "zlib";
	properties = {create-destination = "true";}
	sha256 = "48f2ac99f953c773d833de83880517846aa8ea5473ea725dd1b42d19c17ee086";
}

按照相同方法进行测试,可以达到目标。

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值